12057 lines
612 KiB
PowerShell
12057 lines
612 KiB
PowerShell
|
<#
|
|||
|
|
|||
|
.SYNOPSIS
|
|||
|
|
|||
|
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.
|
|||
|
|
|||
|
.DESCRIPTION
|
|||
|
|
|||
|
ADRecon is a tool which extracts and combines various artefacts (as highlighted below) out of an AD environment. The information can be presented in a specially formatted Microsoft Excel report that includes summary views with metrics to facilitate analysis and provide a holistic picture of the current state of the target AD environment.
|
|||
|
The tool is useful to various classes of security professionals like auditors, DFIR, students, administrators, etc. It can also be an invaluable post-exploitation tool for a penetration tester.
|
|||
|
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) account.
|
|||
|
Fine Grained Password Policy, LAPS and BitLocker may require Privileged user accounts.
|
|||
|
The tool will use Microsoft Remote Server Administration Tools (RSAT) if available, otherwise it will communicate with the Domain Controller using LDAP.
|
|||
|
The following information is gathered by the tool:
|
|||
|
- Forest;
|
|||
|
- Domain;
|
|||
|
- Trusts;
|
|||
|
- Sites;
|
|||
|
- Subnets;
|
|||
|
- Default and Fine Grained Password Policy (if implemented);
|
|||
|
- Domain Controllers, SMB versions, whether SMB Signing is supported and FSMO roles;
|
|||
|
- Users and their attributes;
|
|||
|
- Service Principal Names (SPNs);
|
|||
|
- Groups and memberships;
|
|||
|
- Organizational Units (OUs);
|
|||
|
- Group Policy Object and gPLink details;
|
|||
|
- DNS Zones and Records;
|
|||
|
- Printers;
|
|||
|
- Computers and their attributes;
|
|||
|
- PasswordAttributes (Experimental);
|
|||
|
- LAPS passwords (if implemented);
|
|||
|
- BitLocker Recovery Keys (if implemented);
|
|||
|
- ACLs (DACLs and SACLs) for the Domain, OUs, Root Containers, GPO, Users, Computers and Groups objects;
|
|||
|
- GPOReport (requires RSAT);
|
|||
|
- Kerberoast (not included in the default collection method); and
|
|||
|
- Domain accounts used for service accounts (requires privileged account and not included in the default collection method).
|
|||
|
|
|||
|
Author : Prashant Mahajan
|
|||
|
Company : https://www.senseofsecurity.com.au
|
|||
|
|
|||
|
.NOTES
|
|||
|
|
|||
|
The following commands can be used to turn off ExecutionPolicy: (Requires Admin Privs)
|
|||
|
|
|||
|
PS > $ExecPolicy = Get-ExecutionPolicy
|
|||
|
PS > Set-ExecutionPolicy bypass
|
|||
|
PS > .\ADRecon.ps1
|
|||
|
PS > Set-ExecutionPolicy $ExecPolicy
|
|||
|
|
|||
|
OR
|
|||
|
|
|||
|
Start the PowerShell as follows:
|
|||
|
powershell.exe -ep bypass
|
|||
|
|
|||
|
OR
|
|||
|
|
|||
|
Already have a PowerShell open ?
|
|||
|
PS > $Env:PSExecutionPolicyPreference = 'Bypass'
|
|||
|
|
|||
|
OR
|
|||
|
|
|||
|
powershell.exe -nologo -executionpolicy bypass -noprofile -file ADRecon.ps1
|
|||
|
|
|||
|
.PARAMETER Protocol
|
|||
|
Which protocol to use; ADWS (default) or LDAP
|
|||
|
|
|||
|
.PARAMETER DomainController
|
|||
|
Domain Controller IP Address or Domain FQDN.
|
|||
|
|
|||
|
.PARAMETER Credential
|
|||
|
Domain Credentials.
|
|||
|
|
|||
|
.PARAMETER GenExcel
|
|||
|
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.
|
|||
|
|
|||
|
.PARAMETER OutputDir
|
|||
|
Path for ADRecon output folder to save the files and the ADRecon-Report.xlsx. (The folder specified will be created if it doesn't exist)
|
|||
|
|
|||
|
.PARAMETER Collect
|
|||
|
Which modules to run; Comma separated; e.g Forest,Domain (Default all except Kerberoast, DomainAccountsusedforServiceLogon)
|
|||
|
Valid values include: Forest, Domain, Trusts, Sites, Subnets, PasswordPolicy, FineGrainedPasswordPolicy, DomainControllers, Users, UserSPNs, PasswordAttributes, Groups, GroupMembers, OUs, GPOs, gPLinks, DNSZones, Printers, Computers, ComputerSPNs, LAPS, BitLocker, ACLs, GPOReport, Kerberoast, DomainAccountsusedforServiceLogon.
|
|||
|
|
|||
|
.PARAMETER OutputType
|
|||
|
Output Type; Comma seperated; e.g STDOUT,CSV,XML,JSON,HTML,Excel (Default STDOUT with -Collect parameter, else CSV and Excel).
|
|||
|
Valid values include: STDOUT, CSV, XML, JSON, HTML, Excel, All (excludes STDOUT).
|
|||
|
|
|||
|
.PARAMETER DormantTimeSpan
|
|||
|
Timespan for Dormant accounts. (Default 90 days)
|
|||
|
|
|||
|
.PARAMETER PassMaxAge
|
|||
|
Maximum machine account password age. (Default 30 days)
|
|||
|
|
|||
|
.PARAMETER PageSize
|
|||
|
The PageSize to set for the LDAP searcher object.
|
|||
|
|
|||
|
.PARAMETER Threads
|
|||
|
The number of threads to use during processing objects. (Default 10)
|
|||
|
|
|||
|
.PARAMETER Log
|
|||
|
Create ADRecon Log using Start-Transcript
|
|||
|
|
|||
|
.EXAMPLE
|
|||
|
|
|||
|
.\ADRecon.ps1 -GenExcel C:\ADRecon-Report-<timestamp>
|
|||
|
[*] ADRecon <version> by Prashant Mahajan (@prashant3535) from Sense of Security.
|
|||
|
[*] Generating ADRecon-Report.xlsx
|
|||
|
[+] Excelsheet Saved to: C:\ADRecon-Report-<timestamp>\<domain>-ADRecon-Report.xlsx
|
|||
|
|
|||
|
.EXAMPLE
|
|||
|
|
|||
|
.\ADRecon.ps1 -DomainController <IP or FQDN> -Credential <domain\username>
|
|||
|
[*] ADRecon <version> by Prashant Mahajan (@prashant3535) from Sense of Security.
|
|||
|
[*] Running on <domain>\<hostname> - Member Workstation
|
|||
|
<snip>
|
|||
|
|
|||
|
Example output from Domain Member with Alternate Credentials.
|
|||
|
|
|||
|
.EXAMPLE
|
|||
|
|
|||
|
.\ADRecon.ps1 -DomainController <IP or FQDN> -Credential <domain\username> -Collect DomainControllers -OutputType Excel
|
|||
|
[*] ADRecon <version> by Prashant Mahajan (@prashant3535) from Sense of Security.
|
|||
|
[*] Running on WORKGROUP\<hostname> - Standalone Workstation
|
|||
|
[*] Commencing - <timestamp>
|
|||
|
[-] Domain Controllers
|
|||
|
[*] Total Execution Time (mins): <minutes>
|
|||
|
[*] Generating ADRecon-Report.xlsx
|
|||
|
[+] Excelsheet Saved to: C:\ADRecon-Report-<timestamp>\<domain>-ADRecon-Report.xlsx
|
|||
|
[*] Completed.
|
|||
|
[*] Output Directory: C:\ADRecon-Report-<timestamp>
|
|||
|
|
|||
|
Example output from from a Non-Member using RSAT to only enumerate Domain Controllers.
|
|||
|
|
|||
|
.EXAMPLE
|
|||
|
|
|||
|
.\ADRecon.ps1 -Protocol ADWS -DomainController <IP or FQDN> -Credential <domain\username>
|
|||
|
[*] ADRecon <version> by Prashant Mahajan (@prashant3535) from Sense of Security.
|
|||
|
[*] Running on WORKGROUP\<hostname> - Standalone Workstation
|
|||
|
[*] Commencing - <timestamp>
|
|||
|
[-] Domain
|
|||
|
[-] Forest
|
|||
|
[-] Trusts
|
|||
|
[-] Sites
|
|||
|
[-] Subnets
|
|||
|
[-] Default Password Policy
|
|||
|
[-] Fine Grained Password Policy - May need a Privileged Account
|
|||
|
[-] Domain Controllers
|
|||
|
[-] Users - May take some time
|
|||
|
[-] User SPNs
|
|||
|
[-] PasswordAttributes - Experimental
|
|||
|
[-] Groups - May take some time
|
|||
|
[-] Group Memberships - May take some time
|
|||
|
[-] OrganizationalUnits (OUs)
|
|||
|
[-] GPOs
|
|||
|
[-] gPLinks - Scope of Management (SOM)
|
|||
|
[-] DNS Zones and Records
|
|||
|
[-] Printers
|
|||
|
[-] Computers - May take some time
|
|||
|
[-] Computer SPNs
|
|||
|
[-] LAPS - Needs Privileged Account
|
|||
|
WARNING: [*] LAPS is not implemented.
|
|||
|
[-] BitLocker Recovery Keys - Needs Privileged Account
|
|||
|
[-] ACLs - May take some time
|
|||
|
WARNING: [*] SACLs - Currently, the module is only supported with LDAP.
|
|||
|
[-] GPOReport - May take some time
|
|||
|
WARNING: [EXCEPTION] Current security context is not associated with an Active Directory domain or forest.
|
|||
|
WARNING: [*] Run the tool using RUNAS.
|
|||
|
WARNING: [*] runas /user:<Domain FQDN>\<Username> /netonly powershell.exe
|
|||
|
[*] Total Execution Time (mins): <minutes>
|
|||
|
[*] Output Directory: C:\ADRecon-Report-<timestamp>
|
|||
|
[*] Generating ADRecon-Report.xlsx
|
|||
|
[+] Excelsheet Saved to: C:\ADRecon-Report-<timestamp>\<domain>-ADRecon-Report.xlsx
|
|||
|
|
|||
|
Example output from a Non-Member using RSAT.
|
|||
|
|
|||
|
.EXAMPLE
|
|||
|
|
|||
|
.\ADRecon.ps1 -Protocol LDAP -DomainController <IP or FQDN> -Credential <domain\username>
|
|||
|
[*] ADRecon <version> by Prashant Mahajan (@prashant3535) from Sense of Security.
|
|||
|
[*] Running on WORKGROUP\<hostname> - Standalone Workstation
|
|||
|
[*] LDAP bind Successful
|
|||
|
[*] Commencing - <timestamp>
|
|||
|
[-] Domain
|
|||
|
[-] Forest
|
|||
|
[-] Trusts
|
|||
|
[-] Sites
|
|||
|
[-] Subnets
|
|||
|
[-] Default Password Policy
|
|||
|
[-] Fine Grained Password Policy - May need a Privileged Account
|
|||
|
[-] Domain Controllers
|
|||
|
[-] Users - May take some time
|
|||
|
[-] User SPNs
|
|||
|
[-] PasswordAttributes - Experimental
|
|||
|
[-] Groups - May take some time
|
|||
|
[-] Group Memberships - May take some time
|
|||
|
[-] OrganizationalUnits (OUs)
|
|||
|
[-] GPOs
|
|||
|
[-] gPLinks - Scope of Management (SOM)
|
|||
|
[-] DNS Zones and Records
|
|||
|
[-] Printers
|
|||
|
[-] Computers - May take some time
|
|||
|
[-] Computer SPNs
|
|||
|
[-] LAPS - Needs Privileged Account
|
|||
|
WARNING: [*] LAPS is not implemented.
|
|||
|
[-] BitLocker Recovery Keys - Needs Privileged Account
|
|||
|
[-] ACLs - May take some time
|
|||
|
[-] GPOReport - May take some time
|
|||
|
WARNING: [*] Currently, the module is only supported with ADWS.
|
|||
|
[*] Total Execution Time (mins): <minutes>
|
|||
|
[*] Output Directory: C:\ADRecon-Report-<timestamp>
|
|||
|
[*] Generating ADRecon-Report.xlsx
|
|||
|
[+] Excelsheet Saved to: C:\ADRecon-Report-<timestamp>\<domain>-ADRecon-Report.xlsx
|
|||
|
|
|||
|
Example output from a Non-Member using LDAP.
|
|||
|
|
|||
|
.LINK
|
|||
|
|
|||
|
https://github.com/sense-of-security/ADRecon
|
|||
|
#>
|
|||
|
|
|||
|
[CmdletBinding()]
|
|||
|
param
|
|||
|
(
|
|||
|
[Parameter(Mandatory = $false, HelpMessage = "Which protocol to use; ADWS (default) or LDAP.")]
|
|||
|
[ValidateSet('ADWS', 'LDAP')]
|
|||
|
[string] $Protocol = 'ADWS',
|
|||
|
|
|||
|
[Parameter(Mandatory = $false, HelpMessage = "Domain Controller IP Address or Domain FQDN.")]
|
|||
|
[string] $DomainController = '',
|
|||
|
|
|||
|
[Parameter(Mandatory = $false, HelpMessage = "Domain Credentials.")]
|
|||
|
[Management.Automation.PSCredential] $Credential = [Management.Automation.PSCredential]::Empty,
|
|||
|
|
|||
|
[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.")]
|
|||
|
[string] $GenExcel,
|
|||
|
|
|||
|
[Parameter(Mandatory = $false, HelpMessage = "Path for ADRecon output folder to save the CSV/XML/JSON/HTML files and the ADRecon-Report.xlsx. (The folder specified will be created if it doesn't exist)")]
|
|||
|
[string] $OutputDir,
|
|||
|
|
|||
|
[Parameter(Mandatory = $false, HelpMessage = "Which modules to run; Comma separated; e.g Forest,Domain (Default all except Kerberoast, DomainAccountsusedforServiceLogon) Valid values include: Forest, Domain, Trusts, Sites, Subnets, PasswordPolicy, FineGrainedPasswordPolicy, DomainControllers, Users, UserSPNs, PasswordAttributes, Groups, GroupMembers, OUs, GPOs, gPLinks, DNSZones, Printers, Computers, ComputerSPNs, LAPS, BitLocker, ACLs, GPOReport, Kerberoast, DomainAccountsusedforServiceLogon")]
|
|||
|
[ValidateSet('Forest', 'Domain', 'Trusts', 'Sites', 'Subnets', 'PasswordPolicy', 'FineGrainedPasswordPolicy', 'DomainControllers', 'Users', 'UserSPNs', 'PasswordAttributes', 'Groups', 'GroupMembers', 'OUs', 'GPOs', 'gPLinks', 'DNSZones', 'Printers', 'Computers', 'ComputerSPNs', 'LAPS', 'BitLocker', 'ACLs', 'GPOReport', 'Kerberoast', 'DomainAccountsusedforServiceLogon', 'Default')]
|
|||
|
[array] $Collect = 'Default',
|
|||
|
|
|||
|
[Parameter(Mandatory = $false, HelpMessage = "Output type; Comma seperated; e.g STDOUT,CSV,XML,JSON,HTML,Excel (Default STDOUT with -Collect parameter, else CSV and Excel)")]
|
|||
|
[ValidateSet('STDOUT', 'CSV', 'XML', 'JSON', 'EXCEL', 'HTML', 'All', 'Default')]
|
|||
|
[array] $OutputType = 'Default',
|
|||
|
|
|||
|
[Parameter(Mandatory = $false, HelpMessage = "Timespan for Dormant accounts. Default 90 days")]
|
|||
|
[ValidateRange(1,1000)]
|
|||
|
[int] $DormantTimeSpan = 90,
|
|||
|
|
|||
|
[Parameter(Mandatory = $false, HelpMessage = "Maximum machine account password age. Default 30 days")]
|
|||
|
[ValidateRange(1,1000)]
|
|||
|
[int] $PassMaxAge = 30,
|
|||
|
|
|||
|
[Parameter(Mandatory = $false, HelpMessage = "The PageSize to set for the LDAP searcher object. Default 200")]
|
|||
|
[ValidateRange(1,10000)]
|
|||
|
[int] $PageSize = 200,
|
|||
|
|
|||
|
[Parameter(Mandatory = $false, HelpMessage = "The number of threads to use during processing of objects. Default 10")]
|
|||
|
[ValidateRange(1,100)]
|
|||
|
[int] $Threads = 10,
|
|||
|
|
|||
|
[Parameter(Mandatory = $false, HelpMessage = "Create ADRecon Log using Start-Transcript")]
|
|||
|
[switch] $Log
|
|||
|
)
|
|||
|
|
|||
|
$ADWSSource = @"
|
|||
|
// Thanks Dennis Albuquerque for the C# multithreading code
|
|||
|
using System;
|
|||
|
using System.Collections;
|
|||
|
using System.Collections.Generic;
|
|||
|
using System.Linq;
|
|||
|
using System.Threading;
|
|||
|
using System.DirectoryServices;
|
|||
|
using System.Security.Principal;
|
|||
|
using System.Security.AccessControl;
|
|||
|
using System.Management.Automation;
|
|||
|
|
|||
|
namespace ADRecon
|
|||
|
{
|
|||
|
public static class ADWSClass
|
|||
|
{
|
|||
|
private static DateTime Date1;
|
|||
|
private static int PassMaxAge;
|
|||
|
private static int DormantTimeSpan;
|
|||
|
private static Dictionary<String, String> AdGroupDictionary = new Dictionary<String, String>();
|
|||
|
private static String DomainSID;
|
|||
|
private static Dictionary<String, String> AdGPODictionary = new Dictionary<String, String>();
|
|||
|
private static Hashtable GUIDs = new Hashtable();
|
|||
|
private static Dictionary<String, String> AdSIDDictionary = new Dictionary<String, String>();
|
|||
|
private static readonly HashSet<string> Groups = new HashSet<string> ( new String[] {"268435456", "268435457", "536870912", "536870913"} );
|
|||
|
private static readonly HashSet<string> Users = new HashSet<string> ( new String[] { "805306368" } );
|
|||
|
private static readonly HashSet<string> Computers = new HashSet<string> ( new String[] { "805306369" }) ;
|
|||
|
private static readonly HashSet<string> TrustAccounts = new HashSet<string> ( new String[] { "805306370" } );
|
|||
|
|
|||
|
[Flags]
|
|||
|
//Values taken from https://support.microsoft.com/en-au/kb/305144
|
|||
|
public enum UACFlags
|
|||
|
{
|
|||
|
SCRIPT = 1, // 0x1
|
|||
|
ACCOUNTDISABLE = 2, // 0x2
|
|||
|
HOMEDIR_REQUIRED = 8, // 0x8
|
|||
|
LOCKOUT = 16, // 0x10
|
|||
|
PASSWD_NOTREQD = 32, // 0x20
|
|||
|
PASSWD_CANT_CHANGE = 64, // 0x40
|
|||
|
ENCRYPTED_TEXT_PASSWORD_ALLOWED = 128, // 0x80
|
|||
|
TEMP_DUPLICATE_ACCOUNT = 256, // 0x100
|
|||
|
NORMAL_ACCOUNT = 512, // 0x200
|
|||
|
INTERDOMAIN_TRUST_ACCOUNT = 2048, // 0x800
|
|||
|
WORKSTATION_TRUST_ACCOUNT = 4096, // 0x1000
|
|||
|
SERVER_TRUST_ACCOUNT = 8192, // 0x2000
|
|||
|
DONT_EXPIRE_PASSWD = 65536, // 0x10000
|
|||
|
MNS_LOGON_ACCOUNT = 131072, // 0x20000
|
|||
|
SMARTCARD_REQUIRED = 262144, // 0x40000
|
|||
|
TRUSTED_FOR_DELEGATION = 524288, // 0x80000
|
|||
|
NOT_DELEGATED = 1048576, // 0x100000
|
|||
|
USE_DES_KEY_ONLY = 2097152, // 0x200000
|
|||
|
DONT_REQUIRE_PREAUTH = 4194304, // 0x400000
|
|||
|
PASSWORD_EXPIRED = 8388608, // 0x800000
|
|||
|
TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION = 16777216, // 0x1000000
|
|||
|
PARTIAL_SECRETS_ACCOUNT = 67108864 // 0x04000000
|
|||
|
}
|
|||
|
|
|||
|
[Flags]
|
|||
|
//Values taken from https://blogs.msdn.microsoft.com/openspecification/2011/05/30/windows-configurations-for-kerberos-supported-encryption-type/
|
|||
|
public enum KerbEncFlags
|
|||
|
{
|
|||
|
ZERO = 0,
|
|||
|
DES_CBC_CRC = 1, // 0x1
|
|||
|
DES_CBC_MD5 = 2, // 0x2
|
|||
|
RC4_HMAC = 4, // 0x4
|
|||
|
AES128_CTS_HMAC_SHA1_96 = 8, // 0x18
|
|||
|
AES256_CTS_HMAC_SHA1_96 = 16 // 0x10
|
|||
|
}
|
|||
|
|
|||
|
private static readonly Dictionary<String, String> Replacements = new Dictionary<String, String>()
|
|||
|
{
|
|||
|
//{System.Environment.NewLine, ""},
|
|||
|
//{",", ";"},
|
|||
|
{"\"", "'"}
|
|||
|
};
|
|||
|
|
|||
|
public static String CleanString(Object StringtoClean)
|
|||
|
{
|
|||
|
// Remove extra spaces and new lines
|
|||
|
String CleanedString = String.Join(" ", ((Convert.ToString(StringtoClean)).Split((string[]) null, StringSplitOptions.RemoveEmptyEntries)));
|
|||
|
foreach (String Replacement in Replacements.Keys)
|
|||
|
{
|
|||
|
CleanedString = CleanedString.Replace(Replacement, Replacements[Replacement]);
|
|||
|
}
|
|||
|
return CleanedString;
|
|||
|
}
|
|||
|
|
|||
|
public static int ObjectCount(Object[] ADRObject)
|
|||
|
{
|
|||
|
return ADRObject.Length;
|
|||
|
}
|
|||
|
|
|||
|
public static Object[] UserParser(Object[] AdUsers, DateTime Date1, int DormantTimeSpan, int PassMaxAge, int numOfThreads)
|
|||
|
{
|
|||
|
ADWSClass.Date1 = Date1;
|
|||
|
ADWSClass.DormantTimeSpan = DormantTimeSpan;
|
|||
|
ADWSClass.PassMaxAge = PassMaxAge;
|
|||
|
|
|||
|
Object[] ADRObj = runProcessor(AdUsers, numOfThreads, "Users");
|
|||
|
return ADRObj;
|
|||
|
}
|
|||
|
|
|||
|
public static Object[] UserSPNParser(Object[] AdUsers, int numOfThreads)
|
|||
|
{
|
|||
|
Object[] ADRObj = runProcessor(AdUsers, numOfThreads, "UserSPNs");
|
|||
|
return ADRObj;
|
|||
|
}
|
|||
|
|
|||
|
public static Object[] GroupParser(Object[] AdGroups, int numOfThreads)
|
|||
|
{
|
|||
|
Object[] ADRObj = runProcessor(AdGroups, numOfThreads, "Groups");
|
|||
|
return ADRObj;
|
|||
|
}
|
|||
|
|
|||
|
public static Object[] GroupMemberParser(Object[] AdGroups, Object[] AdGroupMembers, String DomainSID, int numOfThreads)
|
|||
|
{
|
|||
|
ADWSClass.AdGroupDictionary = new Dictionary<String, String>();
|
|||
|
runProcessor(AdGroups, numOfThreads, "GroupsDictionary");
|
|||
|
ADWSClass.DomainSID = DomainSID;
|
|||
|
Object[] ADRObj = runProcessor(AdGroupMembers, numOfThreads, "GroupMembers");
|
|||
|
return ADRObj;
|
|||
|
}
|
|||
|
|
|||
|
public static Object[] OUParser(Object[] AdOUs, int numOfThreads)
|
|||
|
{
|
|||
|
Object[] ADRObj = runProcessor(AdOUs, numOfThreads, "OUs");
|
|||
|
return ADRObj;
|
|||
|
}
|
|||
|
|
|||
|
public static Object[] GPOParser(Object[] AdGPOs, int numOfThreads)
|
|||
|
{
|
|||
|
Object[] ADRObj = runProcessor(AdGPOs, numOfThreads, "GPOs");
|
|||
|
return ADRObj;
|
|||
|
}
|
|||
|
|
|||
|
public static Object[] SOMParser(Object[] AdGPOs, Object[] AdSOMs, int numOfThreads)
|
|||
|
{
|
|||
|
ADWSClass.AdGPODictionary = new Dictionary<String, String>();
|
|||
|
runProcessor(AdGPOs, numOfThreads, "GPOsDictionary");
|
|||
|
Object[] ADRObj = runProcessor(AdSOMs, numOfThreads, "SOMs");
|
|||
|
return ADRObj;
|
|||
|
}
|
|||
|
|
|||
|
public static Object[] PrinterParser(Object[] ADPrinters, int numOfThreads)
|
|||
|
{
|
|||
|
Object[] ADRObj = runProcessor(ADPrinters, numOfThreads, "Printers");
|
|||
|
return ADRObj;
|
|||
|
}
|
|||
|
|
|||
|
public static Object[] ComputerParser(Object[] AdComputers, DateTime Date1, int DormantTimeSpan, int PassMaxAge, int numOfThreads)
|
|||
|
{
|
|||
|
ADWSClass.Date1 = Date1;
|
|||
|
ADWSClass.DormantTimeSpan = DormantTimeSpan;
|
|||
|
ADWSClass.PassMaxAge = PassMaxAge;
|
|||
|
|
|||
|
Object[] ADRObj = runProcessor(AdComputers, numOfThreads, "Computers");
|
|||
|
return ADRObj;
|
|||
|
}
|
|||
|
|
|||
|
public static Object[] ComputerSPNParser(Object[] AdComputers, int numOfThreads)
|
|||
|
{
|
|||
|
Object[] ADRObj = runProcessor(AdComputers, numOfThreads, "ComputerSPNs");
|
|||
|
return ADRObj;
|
|||
|
}
|
|||
|
|
|||
|
public static Object[] LAPSParser(Object[] AdComputers, int numOfThreads)
|
|||
|
{
|
|||
|
Object[] ADRObj = runProcessor(AdComputers, numOfThreads, "LAPS");
|
|||
|
return ADRObj;
|
|||
|
}
|
|||
|
|
|||
|
public static Object[] DACLParser(Object[] ADObjects, Object PSGUIDs, int numOfThreads)
|
|||
|
{
|
|||
|
ADWSClass.AdSIDDictionary = new Dictionary<String, String>();
|
|||
|
runProcessor(ADObjects, numOfThreads, "SIDDictionary");
|
|||
|
ADWSClass.GUIDs = (Hashtable) PSGUIDs;
|
|||
|
Object[] ADRObj = runProcessor(ADObjects, numOfThreads, "DACLs");
|
|||
|
return ADRObj;
|
|||
|
}
|
|||
|
|
|||
|
public static Object[] SACLParser(Object[] ADObjects, Object PSGUIDs, int numOfThreads)
|
|||
|
{
|
|||
|
ADWSClass.GUIDs = (Hashtable) PSGUIDs;
|
|||
|
Object[] ADRObj = runProcessor(ADObjects, numOfThreads, "SACLs");
|
|||
|
return ADRObj;
|
|||
|
}
|
|||
|
|
|||
|
static Object[] runProcessor(Object[] arrayToProcess, int numOfThreads, string processorType)
|
|||
|
{
|
|||
|
int totalRecords = arrayToProcess.Length;
|
|||
|
IRecordProcessor recordProcessor = recordProcessorFactory(processorType);
|
|||
|
IResultsHandler resultsHandler = new SimpleResultsHandler ();
|
|||
|
int numberOfRecordsPerThread = totalRecords / numOfThreads;
|
|||
|
int remainders = totalRecords % numOfThreads;
|
|||
|
|
|||
|
Thread[] threads = new Thread[numOfThreads];
|
|||
|
for (int i = 0; i < numOfThreads; i++)
|
|||
|
{
|
|||
|
int numberOfRecordsToProcess = numberOfRecordsPerThread;
|
|||
|
if (i == (numOfThreads - 1))
|
|||
|
{
|
|||
|
//last thread, do the remaining records
|
|||
|
numberOfRecordsToProcess += remainders;
|
|||
|
}
|
|||
|
|
|||
|
//split the full array into chunks to be given to different threads
|
|||
|
Object[] sliceToProcess = new Object[numberOfRecordsToProcess];
|
|||
|
Array.Copy(arrayToProcess, i * numberOfRecordsPerThread, sliceToProcess, 0, numberOfRecordsToProcess);
|
|||
|
ProcessorThread processorThread = new ProcessorThread(i, recordProcessor, resultsHandler, sliceToProcess);
|
|||
|
threads[i] = new Thread(processorThread.processThreadRecords);
|
|||
|
threads[i].Start();
|
|||
|
}
|
|||
|
foreach (Thread t in threads)
|
|||
|
{
|
|||
|
t.Join();
|
|||
|
}
|
|||
|
|
|||
|
return resultsHandler.finalise();
|
|||
|
}
|
|||
|
|
|||
|
static IRecordProcessor recordProcessorFactory(String name)
|
|||
|
{
|
|||
|
switch (name)
|
|||
|
{
|
|||
|
case "Users":
|
|||
|
return new UserRecordProcessor();
|
|||
|
case "UserSPNs":
|
|||
|
return new UserSPNRecordProcessor();
|
|||
|
case "Groups":
|
|||
|
return new GroupRecordProcessor();
|
|||
|
case "GroupsDictionary":
|
|||
|
return new GroupRecordDictionaryProcessor();
|
|||
|
case "GroupMembers":
|
|||
|
return new GroupMemberRecordProcessor();
|
|||
|
case "OUs":
|
|||
|
return new OURecordProcessor();
|
|||
|
case "GPOs":
|
|||
|
return new GPORecordProcessor();
|
|||
|
case "GPOsDictionary":
|
|||
|
return new GPORecordDictionaryProcessor();
|
|||
|
case "SOMs":
|
|||
|
return new SOMRecordProcessor();
|
|||
|
case "Printers":
|
|||
|
return new PrinterRecordProcessor();
|
|||
|
case "Computers":
|
|||
|
return new ComputerRecordProcessor();
|
|||
|
case "ComputerSPNs":
|
|||
|
return new ComputerSPNRecordProcessor();
|
|||
|
case "LAPS":
|
|||
|
return new LAPSRecordProcessor();
|
|||
|
case "SIDDictionary":
|
|||
|
return new SIDRecordDictionaryProcessor();
|
|||
|
case "DACLs":
|
|||
|
return new DACLRecordProcessor();
|
|||
|
case "SACLs":
|
|||
|
return new SACLRecordProcessor();
|
|||
|
}
|
|||
|
throw new ArgumentException("Invalid processor type " + name);
|
|||
|
}
|
|||
|
|
|||
|
class ProcessorThread
|
|||
|
{
|
|||
|
readonly int id;
|
|||
|
readonly IRecordProcessor recordProcessor;
|
|||
|
readonly IResultsHandler resultsHandler;
|
|||
|
readonly Object[] objectsToBeProcessed;
|
|||
|
|
|||
|
public ProcessorThread(int id, IRecordProcessor recordProcessor, IResultsHandler resultsHandler, Object[] objectsToBeProcessed)
|
|||
|
{
|
|||
|
this.recordProcessor = recordProcessor;
|
|||
|
this.id = id;
|
|||
|
this.resultsHandler = resultsHandler;
|
|||
|
this.objectsToBeProcessed = objectsToBeProcessed;
|
|||
|
}
|
|||
|
|
|||
|
public void processThreadRecords()
|
|||
|
{
|
|||
|
for (int i = 0; i < objectsToBeProcessed.Length; i++)
|
|||
|
{
|
|||
|
Object[] result = recordProcessor.processRecord(objectsToBeProcessed[i]);
|
|||
|
resultsHandler.processResults(result); //this is a thread safe operation
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
//The interface and implmentation class used to process a record (this implemmentation just returns a log type string)
|
|||
|
|
|||
|
interface IRecordProcessor
|
|||
|
{
|
|||
|
PSObject[] processRecord(Object record);
|
|||
|
}
|
|||
|
|
|||
|
class UserRecordProcessor : IRecordProcessor
|
|||
|
{
|
|||
|
public PSObject[] processRecord(Object record)
|
|||
|
{
|
|||
|
try
|
|||
|
{
|
|||
|
PSObject AdUser = (PSObject) record;
|
|||
|
bool? Enabled = null;
|
|||
|
bool MustChangePasswordatLogon = false;
|
|||
|
bool PasswordNotChangedafterMaxAge = false;
|
|||
|
bool NeverLoggedIn = false;
|
|||
|
int? DaysSinceLastLogon = null;
|
|||
|
int? DaysSinceLastPasswordChange = null;
|
|||
|
int? AccountExpirationNumofDays = null;
|
|||
|
bool Dormant = false;
|
|||
|
String SIDHistory = "";
|
|||
|
bool? KerberosRC4 = null;
|
|||
|
bool? KerberosAES128 = null;
|
|||
|
bool? KerberosAES256 = null;
|
|||
|
String DelegationType = null;
|
|||
|
String DelegationProtocol = null;
|
|||
|
String DelegationServices = null;
|
|||
|
DateTime? LastLogonDate = null;
|
|||
|
DateTime? PasswordLastSet = null;
|
|||
|
DateTime? AccountExpires = null;
|
|||
|
|
|||
|
try
|
|||
|
{
|
|||
|
// The Enabled field can be blank which raises an exception. This may occur when the user is not allowed to query the UserAccountControl attribute.
|
|||
|
Enabled = (bool) AdUser.Members["Enabled"].Value;
|
|||
|
}
|
|||
|
catch //(Exception e)
|
|||
|
{
|
|||
|
//Console.WriteLine("{0} Exception caught.", e);
|
|||
|
}
|
|||
|
if (AdUser.Members["lastLogonTimeStamp"].Value != null)
|
|||
|
{
|
|||
|
//LastLogonDate = DateTime.FromFileTime((long)(AdUser.Members["lastLogonTimeStamp"].Value));
|
|||
|
// LastLogonDate is lastLogonTimeStamp converted to local time
|
|||
|
LastLogonDate = Convert.ToDateTime(AdUser.Members["LastLogonDate"].Value);
|
|||
|
DaysSinceLastLogon = Math.Abs((Date1 - (DateTime)LastLogonDate).Days);
|
|||
|
if (DaysSinceLastLogon > DormantTimeSpan)
|
|||
|
{
|
|||
|
Dormant = true;
|
|||
|
}
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
NeverLoggedIn = true;
|
|||
|
}
|
|||
|
if (Convert.ToString(AdUser.Members["pwdLastSet"].Value) == "0")
|
|||
|
{
|
|||
|
if ((bool) AdUser.Members["PasswordNeverExpires"].Value == false)
|
|||
|
{
|
|||
|
MustChangePasswordatLogon = true;
|
|||
|
}
|
|||
|
}
|
|||
|
if (AdUser.Members["PasswordLastSet"].Value != null)
|
|||
|
{
|
|||
|
//PasswordLastSet = DateTime.FromFileTime((long)(AdUser.Members["pwdLastSet"].Value));
|
|||
|
// PasswordLastSet is pwdLastSet converted to local time
|
|||
|
PasswordLastSet = Convert.ToDateTime(AdUser.Members["PasswordLastSet"].Value);
|
|||
|
DaysSinceLastPasswordChange = Math.Abs((Date1 - (DateTime)PasswordLastSet).Days);
|
|||
|
if (DaysSinceLastPasswordChange > PassMaxAge)
|
|||
|
{
|
|||
|
PasswordNotChangedafterMaxAge = true;
|
|||
|
}
|
|||
|
}
|
|||
|
//https://msdn.microsoft.com/en-us/library/ms675098(v=vs.85).aspx
|
|||
|
//if ((Int64) AdUser.Members["accountExpires"].Value != (Int64) 9223372036854775807)
|
|||
|
//{
|
|||
|
//if ((Int64) AdUser.Members["accountExpires"].Value != (Int64) 0)
|
|||
|
if (AdUser.Members["AccountExpirationDate"].Value != null)
|
|||
|
{
|
|||
|
try
|
|||
|
{
|
|||
|
//AccountExpires = DateTime.FromFileTime((long)(AdUser.Members["accountExpires"].Value));
|
|||
|
// AccountExpirationDate is accountExpires converted to local time
|
|||
|
AccountExpires = Convert.ToDateTime(AdUser.Members["AccountExpirationDate"].Value);
|
|||
|
AccountExpirationNumofDays = ((int)((DateTime)AccountExpires - Date1).Days);
|
|||
|
|
|||
|
}
|
|||
|
catch //(Exception e)
|
|||
|
{
|
|||
|
//Console.WriteLine("{0} Exception caught.", e);
|
|||
|
}
|
|||
|
}
|
|||
|
//}
|
|||
|
Microsoft.ActiveDirectory.Management.ADPropertyValueCollection history = (Microsoft.ActiveDirectory.Management.ADPropertyValueCollection) AdUser.Members["SIDHistory"].Value;
|
|||
|
if (history.Value is System.Security.Principal.SecurityIdentifier[])
|
|||
|
{
|
|||
|
string sids = "";
|
|||
|
foreach (var value in (SecurityIdentifier[]) history.Value)
|
|||
|
{
|
|||
|
sids = sids + "," + Convert.ToString(value);
|
|||
|
}
|
|||
|
SIDHistory = sids.TrimStart(',');
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
SIDHistory = history != null ? Convert.ToString(history.Value) : "";
|
|||
|
}
|
|||
|
if (AdUser.Members["msDS-SupportedEncryptionTypes"].Value != null)
|
|||
|
{
|
|||
|
var userKerbEncFlags = (KerbEncFlags) AdUser.Members["msDS-SupportedEncryptionTypes"].Value;
|
|||
|
if (userKerbEncFlags != KerbEncFlags.ZERO)
|
|||
|
{
|
|||
|
KerberosRC4 = (userKerbEncFlags & KerbEncFlags.RC4_HMAC) == KerbEncFlags.RC4_HMAC;
|
|||
|
KerberosAES128 = (userKerbEncFlags & KerbEncFlags.AES128_CTS_HMAC_SHA1_96) == KerbEncFlags.AES128_CTS_HMAC_SHA1_96;
|
|||
|
KerberosAES256 = (userKerbEncFlags & KerbEncFlags.AES256_CTS_HMAC_SHA1_96) == KerbEncFlags.AES256_CTS_HMAC_SHA1_96;
|
|||
|
}
|
|||
|
}
|
|||
|
if ((bool) AdUser.Members["TrustedForDelegation"].Value)
|
|||
|
{
|
|||
|
DelegationType = "Unconstrained";
|
|||
|
DelegationServices = "Any";
|
|||
|
}
|
|||
|
if (AdUser.Members["msDS-AllowedToDelegateTo"] != null)
|
|||
|
{
|
|||
|
Microsoft.ActiveDirectory.Management.ADPropertyValueCollection delegateto = (Microsoft.ActiveDirectory.Management.ADPropertyValueCollection) AdUser.Members["msDS-AllowedToDelegateTo"].Value;
|
|||
|
if (delegateto.Value != null)
|
|||
|
{
|
|||
|
DelegationType = "Constrained";
|
|||
|
if (delegateto.Value is System.String[])
|
|||
|
{
|
|||
|
foreach (var value in (String[]) delegateto.Value)
|
|||
|
{
|
|||
|
DelegationServices = DelegationServices + "," + Convert.ToString(value);
|
|||
|
}
|
|||
|
DelegationServices = DelegationServices.TrimStart(',');
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
DelegationServices = Convert.ToString(delegateto.Value);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
if ((bool) AdUser.Members["TrustedToAuthForDelegation"].Value == true)
|
|||
|
{
|
|||
|
DelegationProtocol = "Any";
|
|||
|
}
|
|||
|
else if (DelegationType != null)
|
|||
|
{
|
|||
|
DelegationProtocol = "Kerberos";
|
|||
|
}
|
|||
|
|
|||
|
PSObject UserObj = new PSObject();
|
|||
|
UserObj.Members.Add(new PSNoteProperty("UserName", AdUser.Members["SamAccountName"].Value));
|
|||
|
UserObj.Members.Add(new PSNoteProperty("Name", CleanString(AdUser.Members["Name"].Value)));
|
|||
|
UserObj.Members.Add(new PSNoteProperty("Enabled", Enabled));
|
|||
|
UserObj.Members.Add(new PSNoteProperty("Must Change Password at Logon", MustChangePasswordatLogon));
|
|||
|
UserObj.Members.Add(new PSNoteProperty("Cannot Change Password", AdUser.Members["CannotChangePassword"].Value));
|
|||
|
UserObj.Members.Add(new PSNoteProperty("Password Never Expires", AdUser.Members["PasswordNeverExpires"].Value));
|
|||
|
UserObj.Members.Add(new PSNoteProperty("Reversible Password Encryption", AdUser.Members["AllowReversiblePasswordEncryption"].Value));
|
|||
|
UserObj.Members.Add(new PSNoteProperty("Smartcard Logon Required", AdUser.Members["SmartcardLogonRequired"].Value));
|
|||
|
UserObj.Members.Add(new PSNoteProperty("Delegation Permitted", !((bool) AdUser.Members["AccountNotDelegated"].Value)));
|
|||
|
UserObj.Members.Add(new PSNoteProperty("Kerberos DES Only", AdUser.Members["UseDESKeyOnly"].Value));
|
|||
|
UserObj.Members.Add(new PSNoteProperty("Kerberos RC4", KerberosRC4));
|
|||
|
UserObj.Members.Add(new PSNoteProperty("Kerberos AES-128bit", KerberosAES128));
|
|||
|
UserObj.Members.Add(new PSNoteProperty("Kerberos AES-256bit", KerberosAES256));
|
|||
|
UserObj.Members.Add(new PSNoteProperty("Does Not Require Pre Auth", AdUser.Members["DoesNotRequirePreAuth"].Value));
|
|||
|
UserObj.Members.Add(new PSNoteProperty("Never Logged in", NeverLoggedIn));
|
|||
|
UserObj.Members.Add(new PSNoteProperty("Logon Age (days)", DaysSinceLastLogon));
|
|||
|
UserObj.Members.Add(new PSNoteProperty("Password Age (days)", DaysSinceLastPasswordChange));
|
|||
|
UserObj.Members.Add(new PSNoteProperty("Dormant (> " + DormantTimeSpan + " days)", Dormant));
|
|||
|
UserObj.Members.Add(new PSNoteProperty("Password Age (> " + PassMaxAge + " days)", PasswordNotChangedafterMaxAge));
|
|||
|
UserObj.Members.Add(new PSNoteProperty("Account Locked Out", AdUser.Members["LockedOut"].Value));
|
|||
|
UserObj.Members.Add(new PSNoteProperty("Password Expired", AdUser.Members["PasswordExpired"].Value));
|
|||
|
UserObj.Members.Add(new PSNoteProperty("Password Not Required", AdUser.Members["PasswordNotRequired"].Value));
|
|||
|
UserObj.Members.Add(new PSNoteProperty("Delegation Type", DelegationType));
|
|||
|
UserObj.Members.Add(new PSNoteProperty("Delegation Protocol", DelegationProtocol));
|
|||
|
UserObj.Members.Add(new PSNoteProperty("Delegation Services", DelegationServices));
|
|||
|
UserObj.Members.Add(new PSNoteProperty("Logon Workstations", AdUser.Members["LogonWorkstations"].Value));
|
|||
|
UserObj.Members.Add(new PSNoteProperty("AdminCount", AdUser.Members["AdminCount"].Value));
|
|||
|
UserObj.Members.Add(new PSNoteProperty("Primary GroupID", AdUser.Members["primaryGroupID"].Value));
|
|||
|
UserObj.Members.Add(new PSNoteProperty("SID", AdUser.Members["SID"].Value));
|
|||
|
UserObj.Members.Add(new PSNoteProperty("SIDHistory", SIDHistory));
|
|||
|
UserObj.Members.Add(new PSNoteProperty("Description", CleanString(AdUser.Members["Description"].Value)));
|
|||
|
UserObj.Members.Add(new PSNoteProperty("Title", CleanString(AdUser.Members["Title"].Value)));
|
|||
|
UserObj.Members.Add(new PSNoteProperty("Department", CleanString(AdUser.Members["Department"].Value)));
|
|||
|
UserObj.Members.Add(new PSNoteProperty("Company", CleanString(AdUser.Members["Company"].Value)));
|
|||
|
UserObj.Members.Add(new PSNoteProperty("Manager", CleanString(AdUser.Members["Manager"].Value)));
|
|||
|
UserObj.Members.Add(new PSNoteProperty("Info", CleanString(AdUser.Members["Info"].Value)));
|
|||
|
UserObj.Members.Add(new PSNoteProperty("Last Logon Date", LastLogonDate));
|
|||
|
UserObj.Members.Add(new PSNoteProperty("Password LastSet", PasswordLastSet));
|
|||
|
UserObj.Members.Add(new PSNoteProperty("Account Expiration Date", AccountExpires));
|
|||
|
UserObj.Members.Add(new PSNoteProperty("Account Expiration (days)", AccountExpirationNumofDays));
|
|||
|
UserObj.Members.Add(new PSNoteProperty("Mobile", CleanString(AdUser.Members["Mobile"].Value)));
|
|||
|
UserObj.Members.Add(new PSNoteProperty("Email", CleanString(AdUser.Members["mail"].Value)));
|
|||
|
UserObj.Members.Add(new PSNoteProperty("HomeDirectory", AdUser.Members["homeDirectory"].Value));
|
|||
|
UserObj.Members.Add(new PSNoteProperty("ProfilePath", AdUser.Members["profilePath"].Value));
|
|||
|
UserObj.Members.Add(new PSNoteProperty("ScriptPath", AdUser.Members["ScriptPath"].Value));
|
|||
|
UserObj.Members.Add(new PSNoteProperty("UserAccountControl", AdUser.Members["UserAccountControl"].Value));
|
|||
|
UserObj.Members.Add(new PSNoteProperty("First Name", CleanString(AdUser.Members["givenName"].Value)));
|
|||
|
UserObj.Members.Add(new PSNoteProperty("Middle Name", CleanString(AdUser.Members["middleName"].Value)));
|
|||
|
UserObj.Members.Add(new PSNoteProperty("Last Name", CleanString(AdUser.Members["sn"].Value)));
|
|||
|
UserObj.Members.Add(new PSNoteProperty("Country", CleanString(AdUser.Members["c"].Value)));
|
|||
|
UserObj.Members.Add(new PSNoteProperty("whenCreated", AdUser.Members["whenCreated"].Value));
|
|||
|
UserObj.Members.Add(new PSNoteProperty("whenChanged", AdUser.Members["whenChanged"].Value));
|
|||
|
UserObj.Members.Add(new PSNoteProperty("DistinguishedName", CleanString(AdUser.Members["DistinguishedName"].Value)));
|
|||
|
UserObj.Members.Add(new PSNoteProperty("CanonicalName", AdUser.Members["CanonicalName"].Value));
|
|||
|
return new PSObject[] { UserObj };
|
|||
|
}
|
|||
|
catch (Exception e)
|
|||
|
{
|
|||
|
Console.WriteLine("{0} Exception caught.", e);
|
|||
|
return new PSObject[] { };
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
class UserSPNRecordProcessor : IRecordProcessor
|
|||
|
{
|
|||
|
public PSObject[] processRecord(Object record)
|
|||
|
{
|
|||
|
try
|
|||
|
{
|
|||
|
PSObject AdUser = (PSObject) record;
|
|||
|
List<PSObject> SPNList = new List<PSObject>();
|
|||
|
bool? Enabled = null;
|
|||
|
String Memberof = null;
|
|||
|
DateTime? PasswordLastSet = null;
|
|||
|
|
|||
|
// When the user is not allowed to query the UserAccountControl attribute.
|
|||
|
if (AdUser.Members["userAccountControl"].Value != null)
|
|||
|
{
|
|||
|
var userFlags = (UACFlags) AdUser.Members["userAccountControl"].Value;
|
|||
|
Enabled = !((userFlags & UACFlags.ACCOUNTDISABLE) == UACFlags.ACCOUNTDISABLE);
|
|||
|
}
|
|||
|
if (Convert.ToString(AdUser.Members["pwdLastSet"].Value) != "0")
|
|||
|
{
|
|||
|
PasswordLastSet = DateTime.FromFileTime((long)AdUser.Members["pwdLastSet"].Value);
|
|||
|
}
|
|||
|
Microsoft.ActiveDirectory.Management.ADPropertyValueCollection SPNs = (Microsoft.ActiveDirectory.Management.ADPropertyValueCollection)AdUser.Members["servicePrincipalName"].Value;
|
|||
|
Microsoft.ActiveDirectory.Management.ADPropertyValueCollection MemberOfAttribute = (Microsoft.ActiveDirectory.Management.ADPropertyValueCollection)AdUser.Members["memberof"].Value;
|
|||
|
if (MemberOfAttribute.Value is System.String[])
|
|||
|
{
|
|||
|
foreach (String Member in (System.String[])MemberOfAttribute.Value)
|
|||
|
{
|
|||
|
Memberof = Memberof + "," + ((Convert.ToString(Member)).Split(',')[0]).Split('=')[1];
|
|||
|
}
|
|||
|
Memberof = Memberof.TrimStart(',');
|
|||
|
}
|
|||
|
else if (Memberof != null)
|
|||
|
{
|
|||
|
Memberof = ((Convert.ToString(MemberOfAttribute.Value)).Split(',')[0]).Split('=')[1];
|
|||
|
}
|
|||
|
String Description = CleanString(AdUser.Members["Description"].Value);
|
|||
|
String PrimaryGroupID = Convert.ToString(AdUser.Members["primaryGroupID"].Value);
|
|||
|
if (SPNs.Value is System.String[])
|
|||
|
{
|
|||
|
foreach (String SPN in (System.String[])SPNs.Value)
|
|||
|
{
|
|||
|
String[] SPNArray = SPN.Split('/');
|
|||
|
PSObject UserSPNObj = new PSObject();
|
|||
|
UserSPNObj.Members.Add(new PSNoteProperty("Name", AdUser.Members["Name"].Value));
|
|||
|
UserSPNObj.Members.Add(new PSNoteProperty("Username", AdUser.Members["SamAccountName"].Value));
|
|||
|
UserSPNObj.Members.Add(new PSNoteProperty("Enabled", Enabled));
|
|||
|
UserSPNObj.Members.Add(new PSNoteProperty("Service", SPNArray[0]));
|
|||
|
UserSPNObj.Members.Add(new PSNoteProperty("Host", SPNArray[1]));
|
|||
|
UserSPNObj.Members.Add(new PSNoteProperty("Password Last Set", PasswordLastSet));
|
|||
|
UserSPNObj.Members.Add(new PSNoteProperty("Description", Description));
|
|||
|
UserSPNObj.Members.Add(new PSNoteProperty("Primary GroupID", PrimaryGroupID));
|
|||
|
UserSPNObj.Members.Add(new PSNoteProperty("Memberof", Memberof));
|
|||
|
SPNList.Add( UserSPNObj );
|
|||
|
}
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
String[] SPNArray = Convert.ToString(SPNs.Value).Split('/');
|
|||
|
PSObject UserSPNObj = new PSObject();
|
|||
|
UserSPNObj.Members.Add(new PSNoteProperty("Name", AdUser.Members["Name"].Value));
|
|||
|
UserSPNObj.Members.Add(new PSNoteProperty("Username", AdUser.Members["SamAccountName"].Value));
|
|||
|
UserSPNObj.Members.Add(new PSNoteProperty("Enabled", Enabled));
|
|||
|
UserSPNObj.Members.Add(new PSNoteProperty("Service", SPNArray[0]));
|
|||
|
UserSPNObj.Members.Add(new PSNoteProperty("Host", SPNArray[1]));
|
|||
|
UserSPNObj.Members.Add(new PSNoteProperty("Password Last Set", PasswordLastSet));
|
|||
|
UserSPNObj.Members.Add(new PSNoteProperty("Description", Description));
|
|||
|
UserSPNObj.Members.Add(new PSNoteProperty("Primary GroupID", PrimaryGroupID));
|
|||
|
UserSPNObj.Members.Add(new PSNoteProperty("Memberof", Memberof));
|
|||
|
SPNList.Add( UserSPNObj );
|
|||
|
}
|
|||
|
return SPNList.ToArray();
|
|||
|
}
|
|||
|
catch (Exception e)
|
|||
|
{
|
|||
|
Console.WriteLine("{0} Exception caught.", e);
|
|||
|
return new PSObject[] { };
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
class GroupRecordProcessor : IRecordProcessor
|
|||
|
{
|
|||
|
public PSObject[] processRecord(Object record)
|
|||
|
{
|
|||
|
try
|
|||
|
{
|
|||
|
PSObject AdGroup = (PSObject) record;
|
|||
|
string ManagedByValue = Convert.ToString(AdGroup.Members["managedBy"].Value);
|
|||
|
string ManagedBy = "";
|
|||
|
String SIDHistory = "";
|
|||
|
|
|||
|
if (AdGroup.Members["managedBy"].Value != null)
|
|||
|
{
|
|||
|
ManagedBy = (ManagedByValue.Split(',')[0]).Split('=')[1];
|
|||
|
}
|
|||
|
Microsoft.ActiveDirectory.Management.ADPropertyValueCollection history = (Microsoft.ActiveDirectory.Management.ADPropertyValueCollection) AdGroup.Members["SIDHistory"].Value;
|
|||
|
if (history.Value is System.Security.Principal.SecurityIdentifier[])
|
|||
|
{
|
|||
|
string sids = "";
|
|||
|
foreach (var value in (SecurityIdentifier[]) history.Value)
|
|||
|
{
|
|||
|
sids = sids + "," + Convert.ToString(value);
|
|||
|
}
|
|||
|
SIDHistory = sids.TrimStart(',');
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
SIDHistory = history != null ? Convert.ToString(history.Value) : "";
|
|||
|
}
|
|||
|
|
|||
|
PSObject GroupObj = new PSObject();
|
|||
|
GroupObj.Members.Add(new PSNoteProperty("Name", AdGroup.Members["SamAccountName"].Value));
|
|||
|
GroupObj.Members.Add(new PSNoteProperty("AdminCount", AdGroup.Members["AdminCount"].Value));
|
|||
|
GroupObj.Members.Add(new PSNoteProperty("GroupCategory", AdGroup.Members["GroupCategory"].Value));
|
|||
|
GroupObj.Members.Add(new PSNoteProperty("GroupScope", AdGroup.Members["GroupScope"].Value));
|
|||
|
GroupObj.Members.Add(new PSNoteProperty("ManagedBy", ManagedBy));
|
|||
|
GroupObj.Members.Add(new PSNoteProperty("SID", AdGroup.Members["sid"].Value));
|
|||
|
GroupObj.Members.Add(new PSNoteProperty("SIDHistory", SIDHistory));
|
|||
|
GroupObj.Members.Add(new PSNoteProperty("Description", CleanString(AdGroup.Members["Description"].Value)));
|
|||
|
GroupObj.Members.Add(new PSNoteProperty("whenCreated", AdGroup.Members["whenCreated"].Value));
|
|||
|
GroupObj.Members.Add(new PSNoteProperty("whenChanged", AdGroup.Members["whenChanged"].Value));
|
|||
|
GroupObj.Members.Add(new PSNoteProperty("DistinguishedName", CleanString(AdGroup.Members["DistinguishedName"].Value)));
|
|||
|
GroupObj.Members.Add(new PSNoteProperty("CanonicalName", AdGroup.Members["CanonicalName"].Value));
|
|||
|
return new PSObject[] { GroupObj };
|
|||
|
}
|
|||
|
catch (Exception e)
|
|||
|
{
|
|||
|
Console.WriteLine("{0} Exception caught.", e);
|
|||
|
return new PSObject[] { };
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
class GroupRecordDictionaryProcessor : IRecordProcessor
|
|||
|
{
|
|||
|
public PSObject[] processRecord(Object record)
|
|||
|
{
|
|||
|
try
|
|||
|
{
|
|||
|
PSObject AdGroup = (PSObject) record;
|
|||
|
ADWSClass.AdGroupDictionary.Add((Convert.ToString(AdGroup.Properties["SID"].Value)), (Convert.ToString(AdGroup.Members["SamAccountName"].Value)));
|
|||
|
return new PSObject[] { };
|
|||
|
}
|
|||
|
catch (Exception e)
|
|||
|
{
|
|||
|
Console.WriteLine("{0} Exception caught.", e);
|
|||
|
return new PSObject[] { };
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
class GroupMemberRecordProcessor : IRecordProcessor
|
|||
|
{
|
|||
|
public PSObject[] processRecord(Object record)
|
|||
|
{
|
|||
|
try
|
|||
|
{
|
|||
|
// based on https://github.com/BloodHoundAD/BloodHound/blob/master/PowerShell/BloodHound.ps1
|
|||
|
PSObject AdGroup = (PSObject) record;
|
|||
|
List<PSObject> GroupsList = new List<PSObject>();
|
|||
|
string SamAccountType = Convert.ToString(AdGroup.Members["samaccounttype"].Value);
|
|||
|
string AccountType = "";
|
|||
|
string GroupName = "";
|
|||
|
string MemberUserName = "-";
|
|||
|
string MemberName = "";
|
|||
|
|
|||
|
if (Groups.Contains(SamAccountType))
|
|||
|
{
|
|||
|
AccountType = "group";
|
|||
|
MemberName = ((Convert.ToString(AdGroup.Members["DistinguishedName"].Value)).Split(',')[0]).Split('=')[1];
|
|||
|
Microsoft.ActiveDirectory.Management.ADPropertyValueCollection MemberGroups = (Microsoft.ActiveDirectory.Management.ADPropertyValueCollection)AdGroup.Members["memberof"].Value;
|
|||
|
if (MemberGroups.Value != null)
|
|||
|
{
|
|||
|
if (MemberGroups.Value is System.String[])
|
|||
|
{
|
|||
|
foreach (String GroupMember in (System.String[])MemberGroups.Value)
|
|||
|
{
|
|||
|
GroupName = ((Convert.ToString(GroupMember)).Split(',')[0]).Split('=')[1];
|
|||
|
PSObject GroupMemberObj = new PSObject();
|
|||
|
GroupMemberObj.Members.Add(new PSNoteProperty("Group Name", GroupName));
|
|||
|
GroupMemberObj.Members.Add(new PSNoteProperty("Member UserName", MemberUserName));
|
|||
|
GroupMemberObj.Members.Add(new PSNoteProperty("Member Name", MemberName));
|
|||
|
GroupMemberObj.Members.Add(new PSNoteProperty("AccountType", AccountType));
|
|||
|
GroupsList.Add( GroupMemberObj );
|
|||
|
}
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
GroupName = (Convert.ToString(MemberGroups.Value).Split(',')[0]).Split('=')[1];
|
|||
|
PSObject GroupMemberObj = new PSObject();
|
|||
|
GroupMemberObj.Members.Add(new PSNoteProperty("Group Name", GroupName));
|
|||
|
GroupMemberObj.Members.Add(new PSNoteProperty("Member UserName", MemberUserName));
|
|||
|
GroupMemberObj.Members.Add(new PSNoteProperty("Member Name", MemberName));
|
|||
|
GroupMemberObj.Members.Add(new PSNoteProperty("AccountType", AccountType));
|
|||
|
GroupsList.Add( GroupMemberObj );
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
if (Users.Contains(SamAccountType))
|
|||
|
{
|
|||
|
AccountType = "user";
|
|||
|
MemberName = ((Convert.ToString(AdGroup.Members["DistinguishedName"].Value)).Split(',')[0]).Split('=')[1];
|
|||
|
MemberUserName = Convert.ToString(AdGroup.Members["sAMAccountName"].Value);
|
|||
|
String PrimaryGroupID = Convert.ToString(AdGroup.Members["primaryGroupID"].Value);
|
|||
|
try
|
|||
|
{
|
|||
|
GroupName = ADWSClass.AdGroupDictionary[ADWSClass.DomainSID + "-" + PrimaryGroupID];
|
|||
|
}
|
|||
|
catch //(Exception e)
|
|||
|
{
|
|||
|
//Console.WriteLine("{0} Exception caught.", e);
|
|||
|
GroupName = PrimaryGroupID;
|
|||
|
}
|
|||
|
|
|||
|
{
|
|||
|
PSObject GroupMemberObj = new PSObject();
|
|||
|
GroupMemberObj.Members.Add(new PSNoteProperty("Group Name", GroupName));
|
|||
|
GroupMemberObj.Members.Add(new PSNoteProperty("Member UserName", MemberUserName));
|
|||
|
GroupMemberObj.Members.Add(new PSNoteProperty("Member Name", MemberName));
|
|||
|
GroupMemberObj.Members.Add(new PSNoteProperty("AccountType", AccountType));
|
|||
|
GroupsList.Add( GroupMemberObj );
|
|||
|
}
|
|||
|
|
|||
|
Microsoft.ActiveDirectory.Management.ADPropertyValueCollection MemberGroups = (Microsoft.ActiveDirectory.Management.ADPropertyValueCollection)AdGroup.Members["memberof"].Value;
|
|||
|
if (MemberGroups.Value != null)
|
|||
|
{
|
|||
|
if (MemberGroups.Value is System.String[])
|
|||
|
{
|
|||
|
foreach (String GroupMember in (System.String[])MemberGroups.Value)
|
|||
|
{
|
|||
|
GroupName = ((Convert.ToString(GroupMember)).Split(',')[0]).Split('=')[1];
|
|||
|
PSObject GroupMemberObj = new PSObject();
|
|||
|
GroupMemberObj.Members.Add(new PSNoteProperty("Group Name", GroupName));
|
|||
|
GroupMemberObj.Members.Add(new PSNoteProperty("Member UserName", MemberUserName));
|
|||
|
GroupMemberObj.Members.Add(new PSNoteProperty("Member Name", MemberName));
|
|||
|
GroupMemberObj.Members.Add(new PSNoteProperty("AccountType", AccountType));
|
|||
|
GroupsList.Add( GroupMemberObj );
|
|||
|
}
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
GroupName = (Convert.ToString(MemberGroups.Value).Split(',')[0]).Split('=')[1];
|
|||
|
PSObject GroupMemberObj = new PSObject();
|
|||
|
GroupMemberObj.Members.Add(new PSNoteProperty("Group Name", GroupName));
|
|||
|
GroupMemberObj.Members.Add(new PSNoteProperty("Member UserName", MemberUserName));
|
|||
|
GroupMemberObj.Members.Add(new PSNoteProperty("Member Name", MemberName));
|
|||
|
GroupMemberObj.Members.Add(new PSNoteProperty("AccountType", AccountType));
|
|||
|
GroupsList.Add( GroupMemberObj );
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
if (Computers.Contains(SamAccountType))
|
|||
|
{
|
|||
|
AccountType = "computer";
|
|||
|
MemberName = ((Convert.ToString(AdGroup.Members["DistinguishedName"].Value)).Split(',')[0]).Split('=')[1];
|
|||
|
MemberUserName = Convert.ToString(AdGroup.Members["sAMAccountName"].Value);
|
|||
|
String PrimaryGroupID = Convert.ToString(AdGroup.Members["primaryGroupID"].Value);
|
|||
|
try
|
|||
|
{
|
|||
|
GroupName = ADWSClass.AdGroupDictionary[ADWSClass.DomainSID + "-" + PrimaryGroupID];
|
|||
|
}
|
|||
|
catch //(Exception e)
|
|||
|
{
|
|||
|
//Console.WriteLine("{0} Exception caught.", e);
|
|||
|
GroupName = PrimaryGroupID;
|
|||
|
}
|
|||
|
|
|||
|
{
|
|||
|
PSObject GroupMemberObj = new PSObject();
|
|||
|
GroupMemberObj.Members.Add(new PSNoteProperty("Group Name", GroupName));
|
|||
|
GroupMemberObj.Members.Add(new PSNoteProperty("Member UserName", MemberUserName));
|
|||
|
GroupMemberObj.Members.Add(new PSNoteProperty("Member Name", MemberName));
|
|||
|
GroupMemberObj.Members.Add(new PSNoteProperty("AccountType", AccountType));
|
|||
|
GroupsList.Add( GroupMemberObj );
|
|||
|
}
|
|||
|
|
|||
|
Microsoft.ActiveDirectory.Management.ADPropertyValueCollection MemberGroups = (Microsoft.ActiveDirectory.Management.ADPropertyValueCollection)AdGroup.Members["memberof"].Value;
|
|||
|
if (MemberGroups.Value != null)
|
|||
|
{
|
|||
|
if (MemberGroups.Value is System.String[])
|
|||
|
{
|
|||
|
foreach (String GroupMember in (System.String[])MemberGroups.Value)
|
|||
|
{
|
|||
|
GroupName = ((Convert.ToString(GroupMember)).Split(',')[0]).Split('=')[1];
|
|||
|
PSObject GroupMemberObj = new PSObject();
|
|||
|
GroupMemberObj.Members.Add(new PSNoteProperty("Group Name", GroupName));
|
|||
|
GroupMemberObj.Members.Add(new PSNoteProperty("Member UserName", MemberUserName));
|
|||
|
GroupMemberObj.Members.Add(new PSNoteProperty("Member Name", MemberName));
|
|||
|
GroupMemberObj.Members.Add(new PSNoteProperty("AccountType", AccountType));
|
|||
|
GroupsList.Add( GroupMemberObj );
|
|||
|
}
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
GroupName = (Convert.ToString(MemberGroups.Value).Split(',')[0]).Split('=')[1];
|
|||
|
PSObject GroupMemberObj = new PSObject();
|
|||
|
GroupMemberObj.Members.Add(new PSNoteProperty("Group Name", GroupName));
|
|||
|
GroupMemberObj.Members.Add(new PSNoteProperty("Member UserName", MemberUserName));
|
|||
|
GroupMemberObj.Members.Add(new PSNoteProperty("Member Name", MemberName));
|
|||
|
GroupMemberObj.Members.Add(new PSNoteProperty("AccountType", AccountType));
|
|||
|
GroupsList.Add( GroupMemberObj );
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
if (TrustAccounts.Contains(SamAccountType))
|
|||
|
{
|
|||
|
// TO DO
|
|||
|
}
|
|||
|
return GroupsList.ToArray();
|
|||
|
}
|
|||
|
catch (Exception e)
|
|||
|
{
|
|||
|
Console.WriteLine("{0} Exception caught.", e);
|
|||
|
return new PSObject[] { };
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
class OURecordProcessor : IRecordProcessor
|
|||
|
{
|
|||
|
public PSObject[] processRecord(Object record)
|
|||
|
{
|
|||
|
try
|
|||
|
{
|
|||
|
PSObject AdOU = (PSObject) record;
|
|||
|
PSObject OUObj = new PSObject();
|
|||
|
OUObj.Members.Add(new PSNoteProperty("Name", AdOU.Members["Name"].Value));
|
|||
|
OUObj.Members.Add(new PSNoteProperty("Depth", ((Convert.ToString(AdOU.Members["DistinguishedName"].Value).Split(new string[] { "OU=" }, StringSplitOptions.None)).Length -1)));
|
|||
|
OUObj.Members.Add(new PSNoteProperty("Description", AdOU.Members["Description"].Value));
|
|||
|
OUObj.Members.Add(new PSNoteProperty("whenCreated", AdOU.Members["whenCreated"].Value));
|
|||
|
OUObj.Members.Add(new PSNoteProperty("whenChanged", AdOU.Members["whenChanged"].Value));
|
|||
|
OUObj.Members.Add(new PSNoteProperty("DistinguishedName", AdOU.Members["DistinguishedName"].Value));
|
|||
|
return new PSObject[] { OUObj };
|
|||
|
}
|
|||
|
catch (Exception e)
|
|||
|
{
|
|||
|
Console.WriteLine("{0} Exception caught.", e);
|
|||
|
return new PSObject[] { };
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
class GPORecordProcessor : IRecordProcessor
|
|||
|
{
|
|||
|
public PSObject[] processRecord(Object record)
|
|||
|
{
|
|||
|
try
|
|||
|
{
|
|||
|
PSObject AdGPO = (PSObject) record;
|
|||
|
|
|||
|
PSObject GPOObj = new PSObject();
|
|||
|
GPOObj.Members.Add(new PSNoteProperty("DisplayName", CleanString(AdGPO.Members["DisplayName"].Value)));
|
|||
|
GPOObj.Members.Add(new PSNoteProperty("GUID", CleanString(AdGPO.Members["Name"].Value)));
|
|||
|
GPOObj.Members.Add(new PSNoteProperty("whenCreated", AdGPO.Members["whenCreated"].Value));
|
|||
|
GPOObj.Members.Add(new PSNoteProperty("whenChanged", AdGPO.Members["whenChanged"].Value));
|
|||
|
GPOObj.Members.Add(new PSNoteProperty("DistinguishedName", CleanString(AdGPO.Members["DistinguishedName"].Value)));
|
|||
|
GPOObj.Members.Add(new PSNoteProperty("FilePath", AdGPO.Members["gPCFileSysPath"].Value));
|
|||
|
return new PSObject[] { GPOObj };
|
|||
|
}
|
|||
|
catch (Exception e)
|
|||
|
{
|
|||
|
Console.WriteLine("{0} Exception caught.", e);
|
|||
|
return new PSObject[] { };
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
class GPORecordDictionaryProcessor : IRecordProcessor
|
|||
|
{
|
|||
|
public PSObject[] processRecord(Object record)
|
|||
|
{
|
|||
|
try
|
|||
|
{
|
|||
|
PSObject AdGPO = (PSObject) record;
|
|||
|
ADWSClass.AdGPODictionary.Add((Convert.ToString(AdGPO.Members["DistinguishedName"].Value).ToUpper()), (Convert.ToString(AdGPO.Members["DisplayName"].Value)));
|
|||
|
return new PSObject[] { };
|
|||
|
}
|
|||
|
catch (Exception e)
|
|||
|
{
|
|||
|
Console.WriteLine("{0} Exception caught.", e);
|
|||
|
return new PSObject[] { };
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
class SOMRecordProcessor : IRecordProcessor
|
|||
|
{
|
|||
|
public PSObject[] processRecord(Object record)
|
|||
|
{
|
|||
|
try
|
|||
|
{
|
|||
|
PSObject AdSOM = (PSObject) record;
|
|||
|
List<PSObject> SOMsList = new List<PSObject>();
|
|||
|
int Depth = 0;
|
|||
|
bool BlockInheritance = false;
|
|||
|
bool? LinkEnabled = null;
|
|||
|
bool? Enforced = null;
|
|||
|
String gPLink = Convert.ToString(AdSOM.Members["gPLink"].Value);
|
|||
|
String GPOName = null;
|
|||
|
|
|||
|
Depth = (Convert.ToString(AdSOM.Members["DistinguishedName"].Value).Split(new string[] { "OU=" }, StringSplitOptions.None)).Length -1;
|
|||
|
if (AdSOM.Members["gPOptions"].Value != null && (int) AdSOM.Members["gPOptions"].Value == 1)
|
|||
|
{
|
|||
|
BlockInheritance = true;
|
|||
|
}
|
|||
|
var GPLinks = gPLink.Split(']', '[').Where(x => x.StartsWith("LDAP"));
|
|||
|
int Order = (GPLinks.ToArray()).Length;
|
|||
|
if (Order == 0)
|
|||
|
{
|
|||
|
PSObject SOMObj = new PSObject();
|
|||
|
SOMObj.Members.Add(new PSNoteProperty("Name", AdSOM.Members["Name"].Value));
|
|||
|
SOMObj.Members.Add(new PSNoteProperty("Depth", Depth));
|
|||
|
SOMObj.Members.Add(new PSNoteProperty("DistinguishedName", AdSOM.Members["DistinguishedName"].Value));
|
|||
|
SOMObj.Members.Add(new PSNoteProperty("Link Order", null));
|
|||
|
SOMObj.Members.Add(new PSNoteProperty("GPO", GPOName));
|
|||
|
SOMObj.Members.Add(new PSNoteProperty("Enforced", Enforced));
|
|||
|
SOMObj.Members.Add(new PSNoteProperty("Link Enabled", LinkEnabled));
|
|||
|
SOMObj.Members.Add(new PSNoteProperty("BlockInheritance", BlockInheritance));
|
|||
|
SOMObj.Members.Add(new PSNoteProperty("gPLink", gPLink));
|
|||
|
SOMObj.Members.Add(new PSNoteProperty("gPOptions", AdSOM.Members["gPOptions"].Value));
|
|||
|
SOMsList.Add( SOMObj );
|
|||
|
}
|
|||
|
foreach (String link in GPLinks)
|
|||
|
{
|
|||
|
String[] linksplit = link.Split('/', ';');
|
|||
|
if (!Convert.ToBoolean((Convert.ToInt32(linksplit[3]) & 1)))
|
|||
|
{
|
|||
|
LinkEnabled = true;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
LinkEnabled = false;
|
|||
|
}
|
|||
|
if (Convert.ToBoolean((Convert.ToInt32(linksplit[3]) & 2)))
|
|||
|
{
|
|||
|
Enforced = true;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
Enforced = false;
|
|||
|
}
|
|||
|
GPOName = ADWSClass.AdGPODictionary.ContainsKey(linksplit[2].ToUpper()) ? ADWSClass.AdGPODictionary[linksplit[2].ToUpper()] : linksplit[2].Split('=',',')[1];
|
|||
|
PSObject SOMObj = new PSObject();
|
|||
|
SOMObj.Members.Add(new PSNoteProperty("Name", AdSOM.Members["Name"].Value));
|
|||
|
SOMObj.Members.Add(new PSNoteProperty("Depth", Depth));
|
|||
|
SOMObj.Members.Add(new PSNoteProperty("DistinguishedName", AdSOM.Members["DistinguishedName"].Value));
|
|||
|
SOMObj.Members.Add(new PSNoteProperty("Link Order", Order));
|
|||
|
SOMObj.Members.Add(new PSNoteProperty("GPO", GPOName));
|
|||
|
SOMObj.Members.Add(new PSNoteProperty("Enforced", Enforced));
|
|||
|
SOMObj.Members.Add(new PSNoteProperty("Link Enabled", LinkEnabled));
|
|||
|
SOMObj.Members.Add(new PSNoteProperty("BlockInheritance", BlockInheritance));
|
|||
|
SOMObj.Members.Add(new PSNoteProperty("gPLink", gPLink));
|
|||
|
SOMObj.Members.Add(new PSNoteProperty("gPOptions", AdSOM.Members["gPOptions"].Value));
|
|||
|
SOMsList.Add( SOMObj );
|
|||
|
Order--;
|
|||
|
}
|
|||
|
return SOMsList.ToArray();
|
|||
|
}
|
|||
|
catch (Exception e)
|
|||
|
{
|
|||
|
Console.WriteLine("{0} Exception caught.", e);
|
|||
|
return new PSObject[] { };
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
class PrinterRecordProcessor : IRecordProcessor
|
|||
|
{
|
|||
|
public PSObject[] processRecord(Object record)
|
|||
|
{
|
|||
|
try
|
|||
|
{
|
|||
|
PSObject AdPrinter = (PSObject) record;
|
|||
|
|
|||
|
PSObject PrinterObj = new PSObject();
|
|||
|
PrinterObj.Members.Add(new PSNoteProperty("Name", AdPrinter.Members["Name"].Value));
|
|||
|
PrinterObj.Members.Add(new PSNoteProperty("ServerName", AdPrinter.Members["serverName"].Value));
|
|||
|
PrinterObj.Members.Add(new PSNoteProperty("ShareName", ((Microsoft.ActiveDirectory.Management.ADPropertyValueCollection) (AdPrinter.Members["printShareName"].Value)).Value));
|
|||
|
PrinterObj.Members.Add(new PSNoteProperty("DriverName", AdPrinter.Members["driverName"].Value));
|
|||
|
PrinterObj.Members.Add(new PSNoteProperty("DriverVersion", AdPrinter.Members["driverVersion"].Value));
|
|||
|
PrinterObj.Members.Add(new PSNoteProperty("PortName", ((Microsoft.ActiveDirectory.Management.ADPropertyValueCollection) (AdPrinter.Members["portName"].Value)).Value));
|
|||
|
PrinterObj.Members.Add(new PSNoteProperty("URL", ((Microsoft.ActiveDirectory.Management.ADPropertyValueCollection) (AdPrinter.Members["url"].Value)).Value));
|
|||
|
PrinterObj.Members.Add(new PSNoteProperty("whenCreated", AdPrinter.Members["whenCreated"].Value));
|
|||
|
PrinterObj.Members.Add(new PSNoteProperty("whenChanged", AdPrinter.Members["whenChanged"].Value));
|
|||
|
return new PSObject[] { PrinterObj };
|
|||
|
}
|
|||
|
catch (Exception e)
|
|||
|
{
|
|||
|
Console.WriteLine("{0} Exception caught.", e);
|
|||
|
return new PSObject[] { };
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
class ComputerRecordProcessor : IRecordProcessor
|
|||
|
{
|
|||
|
public PSObject[] processRecord(Object record)
|
|||
|
{
|
|||
|
try
|
|||
|
{
|
|||
|
PSObject AdComputer = (PSObject) record;
|
|||
|
int? DaysSinceLastLogon = null;
|
|||
|
int? DaysSinceLastPasswordChange = null;
|
|||
|
bool Dormant = false;
|
|||
|
bool PasswordNotChangedafterMaxAge = false;
|
|||
|
String SIDHistory = "";
|
|||
|
String DelegationType = null;
|
|||
|
String DelegationProtocol = null;
|
|||
|
String DelegationServices = null;
|
|||
|
DateTime? LastLogonDate = null;
|
|||
|
DateTime? PasswordLastSet = null;
|
|||
|
|
|||
|
if (AdComputer.Members["LastLogonDate"].Value != null)
|
|||
|
{
|
|||
|
//LastLogonDate = DateTime.FromFileTime((long)(AdComputer.Members["lastLogonTimeStamp"].Value));
|
|||
|
// LastLogonDate is lastLogonTimeStamp converted to local time
|
|||
|
LastLogonDate = Convert.ToDateTime(AdComputer.Members["LastLogonDate"].Value);
|
|||
|
DaysSinceLastLogon = Math.Abs((Date1 - (DateTime)LastLogonDate).Days);
|
|||
|
if (DaysSinceLastLogon > DormantTimeSpan)
|
|||
|
{
|
|||
|
Dormant = true;
|
|||
|
}
|
|||
|
}
|
|||
|
if (AdComputer.Members["PasswordLastSet"].Value != null)
|
|||
|
{
|
|||
|
//PasswordLastSet = DateTime.FromFileTime((long)(AdComputer.Members["pwdLastSet"].Value));
|
|||
|
// PasswordLastSet is pwdLastSet converted to local time
|
|||
|
PasswordLastSet = Convert.ToDateTime(AdComputer.Members["PasswordLastSet"].Value);
|
|||
|
DaysSinceLastPasswordChange = Math.Abs((Date1 - (DateTime)PasswordLastSet).Days);
|
|||
|
if (DaysSinceLastPasswordChange > PassMaxAge)
|
|||
|
{
|
|||
|
PasswordNotChangedafterMaxAge = true;
|
|||
|
}
|
|||
|
}
|
|||
|
if ( ((bool) AdComputer.Members["TrustedForDelegation"].Value) && ((int) AdComputer.Members["primaryGroupID"].Value == 515) )
|
|||
|
{
|
|||
|
DelegationType = "Unconstrained";
|
|||
|
DelegationServices = "Any";
|
|||
|
}
|
|||
|
if (AdComputer.Members["msDS-AllowedToDelegateTo"] != null)
|
|||
|
{
|
|||
|
Microsoft.ActiveDirectory.Management.ADPropertyValueCollection delegateto = (Microsoft.ActiveDirectory.Management.ADPropertyValueCollection) AdComputer.Members["msDS-AllowedToDelegateTo"].Value;
|
|||
|
if (delegateto.Value != null)
|
|||
|
{
|
|||
|
DelegationType = "Constrained";
|
|||
|
if (delegateto.Value is System.String[])
|
|||
|
{
|
|||
|
foreach (var value in (String[]) delegateto.Value)
|
|||
|
{
|
|||
|
DelegationServices = DelegationServices + "," + Convert.ToString(value);
|
|||
|
}
|
|||
|
DelegationServices = DelegationServices.TrimStart(',');
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
DelegationServices = Convert.ToString(delegateto.Value);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
if ((bool) AdComputer.Members["TrustedToAuthForDelegation"].Value)
|
|||
|
{
|
|||
|
DelegationProtocol = "Any";
|
|||
|
}
|
|||
|
else if (DelegationType != null)
|
|||
|
{
|
|||
|
DelegationProtocol = "Kerberos";
|
|||
|
}
|
|||
|
Microsoft.ActiveDirectory.Management.ADPropertyValueCollection history = (Microsoft.ActiveDirectory.Management.ADPropertyValueCollection) AdComputer.Members["SIDHistory"].Value;
|
|||
|
if (history.Value is System.Security.Principal.SecurityIdentifier[])
|
|||
|
{
|
|||
|
string sids = "";
|
|||
|
foreach (var value in (SecurityIdentifier[]) history.Value)
|
|||
|
{
|
|||
|
sids = sids + "," + Convert.ToString(value);
|
|||
|
}
|
|||
|
SIDHistory = sids.TrimStart(',');
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
SIDHistory = history != null ? Convert.ToString(history.Value) : "";
|
|||
|
}
|
|||
|
String OperatingSystem = CleanString((AdComputer.Members["OperatingSystem"].Value != null ? AdComputer.Members["OperatingSystem"].Value : "-") + " " + AdComputer.Members["OperatingSystemHotfix"].Value + " " + AdComputer.Members["OperatingSystemServicePack"].Value + " " + AdComputer.Members["OperatingSystemVersion"].Value);
|
|||
|
|
|||
|
PSObject ComputerObj = new PSObject();
|
|||
|
ComputerObj.Members.Add(new PSNoteProperty("Name", AdComputer.Members["Name"].Value));
|
|||
|
ComputerObj.Members.Add(new PSNoteProperty("DNSHostName", AdComputer.Members["DNSHostName"].Value));
|
|||
|
ComputerObj.Members.Add(new PSNoteProperty("Enabled", AdComputer.Members["Enabled"].Value));
|
|||
|
ComputerObj.Members.Add(new PSNoteProperty("IPv4Address", AdComputer.Members["IPv4Address"].Value));
|
|||
|
ComputerObj.Members.Add(new PSNoteProperty("Operating System", OperatingSystem));
|
|||
|
ComputerObj.Members.Add(new PSNoteProperty("Logon Age (days)", DaysSinceLastLogon));
|
|||
|
ComputerObj.Members.Add(new PSNoteProperty("Password Age (days)", DaysSinceLastPasswordChange));
|
|||
|
ComputerObj.Members.Add(new PSNoteProperty("Dormant (> " + DormantTimeSpan + " days)", Dormant));
|
|||
|
ComputerObj.Members.Add(new PSNoteProperty("Password Age (> " + PassMaxAge + " days)", PasswordNotChangedafterMaxAge));
|
|||
|
ComputerObj.Members.Add(new PSNoteProperty("Delegation Type", DelegationType));
|
|||
|
ComputerObj.Members.Add(new PSNoteProperty("Delegation Protocol", DelegationProtocol));
|
|||
|
ComputerObj.Members.Add(new PSNoteProperty("Delegation Services", DelegationServices));
|
|||
|
ComputerObj.Members.Add(new PSNoteProperty("UserName", AdComputer.Members["SamAccountName"].Value));
|
|||
|
ComputerObj.Members.Add(new PSNoteProperty("Primary Group ID", AdComputer.Members["primaryGroupID"].Value));
|
|||
|
ComputerObj.Members.Add(new PSNoteProperty("SID", AdComputer.Members["SID"].Value));
|
|||
|
ComputerObj.Members.Add(new PSNoteProperty("SIDHistory", SIDHistory));
|
|||
|
ComputerObj.Members.Add(new PSNoteProperty("Description", AdComputer.Members["Description"].Value));
|
|||
|
ComputerObj.Members.Add(new PSNoteProperty("ms-ds-CreatorSid", AdComputer.Members["ms-ds-CreatorSid"].Value));
|
|||
|
ComputerObj.Members.Add(new PSNoteProperty("Last Logon Date", LastLogonDate));
|
|||
|
ComputerObj.Members.Add(new PSNoteProperty("Password LastSet", PasswordLastSet));
|
|||
|
ComputerObj.Members.Add(new PSNoteProperty("UserAccountControl", AdComputer.Members["UserAccountControl"].Value));
|
|||
|
ComputerObj.Members.Add(new PSNoteProperty("whenCreated", AdComputer.Members["whenCreated"].Value));
|
|||
|
ComputerObj.Members.Add(new PSNoteProperty("whenChanged", AdComputer.Members["whenChanged"].Value));
|
|||
|
ComputerObj.Members.Add(new PSNoteProperty("Distinguished Name", AdComputer.Members["DistinguishedName"].Value));
|
|||
|
return new PSObject[] { ComputerObj };
|
|||
|
}
|
|||
|
catch (Exception e)
|
|||
|
{
|
|||
|
Console.WriteLine("{0} Exception caught.", e);
|
|||
|
return new PSObject[] { };
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
class ComputerSPNRecordProcessor : IRecordProcessor
|
|||
|
{
|
|||
|
public PSObject[] processRecord(Object record)
|
|||
|
{
|
|||
|
try
|
|||
|
{
|
|||
|
PSObject AdComputer = (PSObject) record;
|
|||
|
List<PSObject> SPNList = new List<PSObject>();
|
|||
|
|
|||
|
Microsoft.ActiveDirectory.Management.ADPropertyValueCollection SPNs = (Microsoft.ActiveDirectory.Management.ADPropertyValueCollection)AdComputer.Members["servicePrincipalName"].Value;
|
|||
|
if (SPNs.Value is System.String[])
|
|||
|
{
|
|||
|
foreach (String SPN in (System.String[])SPNs.Value)
|
|||
|
{
|
|||
|
bool flag = true;
|
|||
|
String[] SPNArray = SPN.Split('/');
|
|||
|
foreach (PSObject Obj in SPNList)
|
|||
|
{
|
|||
|
if ( (String) Obj.Members["Service"].Value == SPNArray[0] )
|
|||
|
{
|
|||
|
Obj.Members["Host"].Value = String.Join(",", (Obj.Members["Host"].Value + "," + SPNArray[1]).Split(',').Distinct().ToArray());
|
|||
|
flag = false;
|
|||
|
}
|
|||
|
}
|
|||
|
if (flag)
|
|||
|
{
|
|||
|
PSObject ComputerSPNObj = new PSObject();
|
|||
|
ComputerSPNObj.Members.Add(new PSNoteProperty("Name", AdComputer.Members["Name"].Value));
|
|||
|
ComputerSPNObj.Members.Add(new PSNoteProperty("Service", SPNArray[0]));
|
|||
|
ComputerSPNObj.Members.Add(new PSNoteProperty("Host", SPNArray[1]));
|
|||
|
SPNList.Add( ComputerSPNObj );
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
String[] SPNArray = Convert.ToString(SPNs.Value).Split('/');
|
|||
|
PSObject ComputerSPNObj = new PSObject();
|
|||
|
ComputerSPNObj.Members.Add(new PSNoteProperty("Name", AdComputer.Members["Name"].Value));
|
|||
|
ComputerSPNObj.Members.Add(new PSNoteProperty("Service", SPNArray[0]));
|
|||
|
ComputerSPNObj.Members.Add(new PSNoteProperty("Host", SPNArray[1]));
|
|||
|
SPNList.Add( ComputerSPNObj );
|
|||
|
}
|
|||
|
return SPNList.ToArray();
|
|||
|
}
|
|||
|
catch (Exception e)
|
|||
|
{
|
|||
|
Console.WriteLine("{0} Exception caught.", e);
|
|||
|
return new PSObject[] { };
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
class LAPSRecordProcessor : IRecordProcessor
|
|||
|
{
|
|||
|
public PSObject[] processRecord(Object record)
|
|||
|
{
|
|||
|
try
|
|||
|
{
|
|||
|
PSObject AdComputer = (PSObject) record;
|
|||
|
bool PasswordStored = false;
|
|||
|
DateTime? CurrentExpiration = null;
|
|||
|
try
|
|||
|
{
|
|||
|
CurrentExpiration = DateTime.FromFileTime((long)(AdComputer.Members["ms-Mcs-AdmPwdExpirationTime"].Value));
|
|||
|
PasswordStored = true;
|
|||
|
}
|
|||
|
catch //(Exception e)
|
|||
|
{
|
|||
|
//Console.WriteLine("{0} Exception caught.", e);
|
|||
|
}
|
|||
|
PSObject LAPSObj = new PSObject();
|
|||
|
LAPSObj.Members.Add(new PSNoteProperty("Hostname", (AdComputer.Members["DNSHostName"].Value != null ? AdComputer.Members["DNSHostName"].Value : AdComputer.Members["CN"].Value )));
|
|||
|
LAPSObj.Members.Add(new PSNoteProperty("Stored", PasswordStored));
|
|||
|
LAPSObj.Members.Add(new PSNoteProperty("Readable", (AdComputer.Members["ms-Mcs-AdmPwd"].Value != null ? true : false)));
|
|||
|
LAPSObj.Members.Add(new PSNoteProperty("Password", AdComputer.Members["ms-Mcs-AdmPwd"].Value));
|
|||
|
LAPSObj.Members.Add(new PSNoteProperty("Expiration", CurrentExpiration));
|
|||
|
return new PSObject[] { LAPSObj };
|
|||
|
}
|
|||
|
catch (Exception e)
|
|||
|
{
|
|||
|
Console.WriteLine("{0} Exception caught.", e);
|
|||
|
return new PSObject[] { };
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
class SIDRecordDictionaryProcessor : IRecordProcessor
|
|||
|
{
|
|||
|
public PSObject[] processRecord(Object record)
|
|||
|
{
|
|||
|
try
|
|||
|
{
|
|||
|
PSObject AdObject = (PSObject) record;
|
|||
|
switch (Convert.ToString(AdObject.Members["ObjectClass"].Value))
|
|||
|
{
|
|||
|
case "user":
|
|||
|
case "computer":
|
|||
|
case "group":
|
|||
|
ADWSClass.AdSIDDictionary.Add(Convert.ToString(AdObject.Members["objectsid"].Value), Convert.ToString(AdObject.Members["Name"].Value));
|
|||
|
break;
|
|||
|
}
|
|||
|
return new PSObject[] { };
|
|||
|
}
|
|||
|
catch (Exception e)
|
|||
|
{
|
|||
|
Console.WriteLine("{0} {1} Exception caught.", ((PSObject) record).Members["ObjectClass"].Value, e);
|
|||
|
return new PSObject[] { };
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
class DACLRecordProcessor : IRecordProcessor
|
|||
|
{
|
|||
|
public PSObject[] processRecord(Object record)
|
|||
|
{
|
|||
|
try
|
|||
|
{
|
|||
|
PSObject AdObject = (PSObject) record;
|
|||
|
String Name = null;
|
|||
|
String Type = null;
|
|||
|
List<PSObject> DACLList = new List<PSObject>();
|
|||
|
|
|||
|
Name = Convert.ToString(AdObject.Members["Name"].Value);
|
|||
|
|
|||
|
switch (Convert.ToString(AdObject.Members["objectClass"].Value))
|
|||
|
{
|
|||
|
case "user":
|
|||
|
Type = "User";
|
|||
|
break;
|
|||
|
case "computer":
|
|||
|
Type = "Computer";
|
|||
|
break;
|
|||
|
case "group":
|
|||
|
Type = "Group";
|
|||
|
break;
|
|||
|
case "container":
|
|||
|
Type = "Container";
|
|||
|
break;
|
|||
|
case "groupPolicyContainer":
|
|||
|
Type = "GPO";
|
|||
|
Name = Convert.ToString(AdObject.Members["DisplayName"].Value);
|
|||
|
break;
|
|||
|
case "organizationalUnit":
|
|||
|
Type = "OU";
|
|||
|
break;
|
|||
|
case "domainDNS":
|
|||
|
Type = "Domain";
|
|||
|
break;
|
|||
|
default:
|
|||
|
Type = Convert.ToString(AdObject.Members["objectClass"].Value);
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
// When the user is not allowed to query the ntsecuritydescriptor attribute.
|
|||
|
if (AdObject.Members["ntsecuritydescriptor"] != null)
|
|||
|
{
|
|||
|
DirectoryObjectSecurity DirObjSec = (DirectoryObjectSecurity) AdObject.Members["ntsecuritydescriptor"].Value;
|
|||
|
AuthorizationRuleCollection AccessRules = (AuthorizationRuleCollection) DirObjSec.GetAccessRules(true,true,typeof(System.Security.Principal.NTAccount));
|
|||
|
foreach (ActiveDirectoryAccessRule Rule in AccessRules)
|
|||
|
{
|
|||
|
String IdentityReference = Convert.ToString(Rule.IdentityReference);
|
|||
|
String Owner = Convert.ToString(DirObjSec.GetOwner(typeof(System.Security.Principal.SecurityIdentifier)));
|
|||
|
PSObject ObjectObj = new PSObject();
|
|||
|
ObjectObj.Members.Add(new PSNoteProperty("Name", CleanString(Name)));
|
|||
|
ObjectObj.Members.Add(new PSNoteProperty("Type", Type));
|
|||
|
ObjectObj.Members.Add(new PSNoteProperty("ObjectTypeName", ADWSClass.GUIDs[Convert.ToString(Rule.ObjectType)]));
|
|||
|
ObjectObj.Members.Add(new PSNoteProperty("InheritedObjectTypeName", ADWSClass.GUIDs[Convert.ToString(Rule.InheritedObjectType)]));
|
|||
|
ObjectObj.Members.Add(new PSNoteProperty("ActiveDirectoryRights", Rule.ActiveDirectoryRights));
|
|||
|
ObjectObj.Members.Add(new PSNoteProperty("AccessControlType", Rule.AccessControlType));
|
|||
|
ObjectObj.Members.Add(new PSNoteProperty("IdentityReferenceName", ADWSClass.AdSIDDictionary.ContainsKey(IdentityReference) ? ADWSClass.AdSIDDictionary[IdentityReference] : IdentityReference));
|
|||
|
ObjectObj.Members.Add(new PSNoteProperty("OwnerName", ADWSClass.AdSIDDictionary.ContainsKey(Owner) ? ADWSClass.AdSIDDictionary[Owner] : Owner));
|
|||
|
ObjectObj.Members.Add(new PSNoteProperty("Inherited", Rule.IsInherited));
|
|||
|
ObjectObj.Members.Add(new PSNoteProperty("ObjectFlags", Rule.ObjectFlags));
|
|||
|
ObjectObj.Members.Add(new PSNoteProperty("InheritanceFlags", Rule.InheritanceFlags));
|
|||
|
ObjectObj.Members.Add(new PSNoteProperty("InheritanceType", Rule.InheritanceType));
|
|||
|
ObjectObj.Members.Add(new PSNoteProperty("PropagationFlags", Rule.PropagationFlags));
|
|||
|
ObjectObj.Members.Add(new PSNoteProperty("ObjectType", Rule.ObjectType));
|
|||
|
ObjectObj.Members.Add(new PSNoteProperty("InheritedObjectType", Rule.InheritedObjectType));
|
|||
|
ObjectObj.Members.Add(new PSNoteProperty("IdentityReference", Rule.IdentityReference));
|
|||
|
ObjectObj.Members.Add(new PSNoteProperty("Owner", Owner));
|
|||
|
ObjectObj.Members.Add(new PSNoteProperty("DistinguishedName", AdObject.Members["DistinguishedName"].Value));
|
|||
|
DACLList.Add( ObjectObj );
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return DACLList.ToArray();
|
|||
|
}
|
|||
|
catch (Exception e)
|
|||
|
{
|
|||
|
Console.WriteLine("{0} Exception caught.", e);
|
|||
|
return new PSObject[] { };
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
class SACLRecordProcessor : IRecordProcessor
|
|||
|
{
|
|||
|
public PSObject[] processRecord(Object record)
|
|||
|
{
|
|||
|
try
|
|||
|
{
|
|||
|
PSObject AdObject = (PSObject) record;
|
|||
|
String Name = null;
|
|||
|
String Type = null;
|
|||
|
List<PSObject> SACLList = new List<PSObject>();
|
|||
|
|
|||
|
Name = Convert.ToString(AdObject.Members["Name"].Value);
|
|||
|
|
|||
|
switch (Convert.ToString(AdObject.Members["objectClass"].Value))
|
|||
|
{
|
|||
|
case "user":
|
|||
|
Type = "User";
|
|||
|
break;
|
|||
|
case "computer":
|
|||
|
Type = "Computer";
|
|||
|
break;
|
|||
|
case "group":
|
|||
|
Type = "Group";
|
|||
|
break;
|
|||
|
case "container":
|
|||
|
Type = "Container";
|
|||
|
break;
|
|||
|
case "groupPolicyContainer":
|
|||
|
Type = "GPO";
|
|||
|
Name = Convert.ToString(AdObject.Members["DisplayName"].Value);
|
|||
|
break;
|
|||
|
case "organizationalUnit":
|
|||
|
Type = "OU";
|
|||
|
break;
|
|||
|
case "domainDNS":
|
|||
|
Type = "Domain";
|
|||
|
break;
|
|||
|
default:
|
|||
|
Type = Convert.ToString(AdObject.Members["objectClass"].Value);
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
// When the user is not allowed to query the ntsecuritydescriptor attribute.
|
|||
|
if (AdObject.Members["ntsecuritydescriptor"] != null)
|
|||
|
{
|
|||
|
DirectoryObjectSecurity DirObjSec = (DirectoryObjectSecurity) AdObject.Members["ntsecuritydescriptor"].Value;
|
|||
|
AuthorizationRuleCollection AuditRules = (AuthorizationRuleCollection) DirObjSec.GetAuditRules(true,true,typeof(System.Security.Principal.NTAccount));
|
|||
|
foreach (ActiveDirectoryAuditRule Rule in AuditRules)
|
|||
|
{
|
|||
|
PSObject ObjectObj = new PSObject();
|
|||
|
ObjectObj.Members.Add(new PSNoteProperty("Name", CleanString(Name)));
|
|||
|
ObjectObj.Members.Add(new PSNoteProperty("Type", Type));
|
|||
|
ObjectObj.Members.Add(new PSNoteProperty("ObjectTypeName", ADWSClass.GUIDs[Convert.ToString(Rule.ObjectType)]));
|
|||
|
ObjectObj.Members.Add(new PSNoteProperty("InheritedObjectTypeName", ADWSClass.GUIDs[Convert.ToString(Rule.InheritedObjectType)]));
|
|||
|
ObjectObj.Members.Add(new PSNoteProperty("ActiveDirectoryRights", Rule.ActiveDirectoryRights));
|
|||
|
ObjectObj.Members.Add(new PSNoteProperty("IdentityReference", Rule.IdentityReference));
|
|||
|
ObjectObj.Members.Add(new PSNoteProperty("AuditFlags", Rule.AuditFlags));
|
|||
|
ObjectObj.Members.Add(new PSNoteProperty("ObjectFlags", Rule.ObjectFlags));
|
|||
|
ObjectObj.Members.Add(new PSNoteProperty("InheritanceFlags", Rule.InheritanceFlags));
|
|||
|
ObjectObj.Members.Add(new PSNoteProperty("InheritanceType", Rule.InheritanceType));
|
|||
|
ObjectObj.Members.Add(new PSNoteProperty("Inherited", Rule.IsInherited));
|
|||
|
ObjectObj.Members.Add(new PSNoteProperty("PropagationFlags", Rule.PropagationFlags));
|
|||
|
ObjectObj.Members.Add(new PSNoteProperty("ObjectType", Rule.ObjectType));
|
|||
|
ObjectObj.Members.Add(new PSNoteProperty("InheritedObjectType", Rule.InheritedObjectType));
|
|||
|
SACLList.Add( ObjectObj );
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return SACLList.ToArray();
|
|||
|
}
|
|||
|
catch (Exception e)
|
|||
|
{
|
|||
|
Console.WriteLine("{0} Exception caught.", e);
|
|||
|
return new PSObject[] { };
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
//The interface and implmentation class used to handle the results (this implementation just writes the strings to a file)
|
|||
|
|
|||
|
interface IResultsHandler
|
|||
|
{
|
|||
|
void processResults(Object[] t);
|
|||
|
|
|||
|
Object[] finalise();
|
|||
|
}
|
|||
|
|
|||
|
class SimpleResultsHandler : IResultsHandler
|
|||
|
{
|
|||
|
private Object lockObj = new Object();
|
|||
|
private List<Object> processed = new List<Object>();
|
|||
|
|
|||
|
public SimpleResultsHandler()
|
|||
|
{
|
|||
|
}
|
|||
|
|
|||
|
public void processResults(Object[] results)
|
|||
|
{
|
|||
|
lock (lockObj)
|
|||
|
{
|
|||
|
if (results.Length != 0)
|
|||
|
{
|
|||
|
for (var i = 0; i < results.Length; i++)
|
|||
|
{
|
|||
|
processed.Add((PSObject)results[i]);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
public Object[] finalise()
|
|||
|
{
|
|||
|
return processed.ToArray();
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
"@
|
|||
|
|
|||
|
$LDAPSource = @"
|
|||
|
// Thanks Dennis Albuquerque for the C# multithreading code
|
|||
|
using System;
|
|||
|
using System.Collections;
|
|||
|
using System.Collections.Generic;
|
|||
|
using System.Linq;
|
|||
|
using System.Net;
|
|||
|
using System.Threading;
|
|||
|
using System.DirectoryServices;
|
|||
|
using System.Security.Principal;
|
|||
|
using System.Security.AccessControl;
|
|||
|
using System.Management.Automation;
|
|||
|
|
|||
|
namespace ADRecon
|
|||
|
{
|
|||
|
public static class LDAPClass
|
|||
|
{
|
|||
|
private static DateTime Date1;
|
|||
|
private static int PassMaxAge;
|
|||
|
private static int DormantTimeSpan;
|
|||
|
private static Dictionary<String, String> AdGroupDictionary = new Dictionary<String, String>();
|
|||
|
private static String DomainSID;
|
|||
|
private static Dictionary<String, String> AdGPODictionary = new Dictionary<String, String>();
|
|||
|
private static Hashtable GUIDs = new Hashtable();
|
|||
|
private static Dictionary<String, String> AdSIDDictionary = new Dictionary<String, String>();
|
|||
|
private static readonly HashSet<string> Groups = new HashSet<string> ( new String[] {"268435456", "268435457", "536870912", "536870913"} );
|
|||
|
private static readonly HashSet<string> Users = new HashSet<string> ( new String[] { "805306368" } );
|
|||
|
private static readonly HashSet<string> Computers = new HashSet<string> ( new String[] { "805306369" }) ;
|
|||
|
private static readonly HashSet<string> TrustAccounts = new HashSet<string> ( new String[] { "805306370" } );
|
|||
|
|
|||
|
[Flags]
|
|||
|
//Values taken from https://support.microsoft.com/en-au/kb/305144
|
|||
|
public enum UACFlags
|
|||
|
{
|
|||
|
SCRIPT = 1, // 0x1
|
|||
|
ACCOUNTDISABLE = 2, // 0x2
|
|||
|
HOMEDIR_REQUIRED = 8, // 0x8
|
|||
|
LOCKOUT = 16, // 0x10
|
|||
|
PASSWD_NOTREQD = 32, // 0x20
|
|||
|
PASSWD_CANT_CHANGE = 64, // 0x40
|
|||
|
ENCRYPTED_TEXT_PASSWORD_ALLOWED = 128, // 0x80
|
|||
|
TEMP_DUPLICATE_ACCOUNT = 256, // 0x100
|
|||
|
NORMAL_ACCOUNT = 512, // 0x200
|
|||
|
INTERDOMAIN_TRUST_ACCOUNT = 2048, // 0x800
|
|||
|
WORKSTATION_TRUST_ACCOUNT = 4096, // 0x1000
|
|||
|
SERVER_TRUST_ACCOUNT = 8192, // 0x2000
|
|||
|
DONT_EXPIRE_PASSWD = 65536, // 0x10000
|
|||
|
MNS_LOGON_ACCOUNT = 131072, // 0x20000
|
|||
|
SMARTCARD_REQUIRED = 262144, // 0x40000
|
|||
|
TRUSTED_FOR_DELEGATION = 524288, // 0x80000
|
|||
|
NOT_DELEGATED = 1048576, // 0x100000
|
|||
|
USE_DES_KEY_ONLY = 2097152, // 0x200000
|
|||
|
DONT_REQUIRE_PREAUTH = 4194304, // 0x400000
|
|||
|
PASSWORD_EXPIRED = 8388608, // 0x800000
|
|||
|
TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION = 16777216, // 0x1000000
|
|||
|
PARTIAL_SECRETS_ACCOUNT = 67108864 // 0x04000000
|
|||
|
}
|
|||
|
|
|||
|
[Flags]
|
|||
|
//Values taken from https://blogs.msdn.microsoft.com/openspecification/2011/05/30/windows-configurations-for-kerberos-supported-encryption-type/
|
|||
|
public enum KerbEncFlags
|
|||
|
{
|
|||
|
ZERO = 0,
|
|||
|
DES_CBC_CRC = 1, // 0x1
|
|||
|
DES_CBC_MD5 = 2, // 0x2
|
|||
|
RC4_HMAC = 4, // 0x4
|
|||
|
AES128_CTS_HMAC_SHA1_96 = 8, // 0x18
|
|||
|
AES256_CTS_HMAC_SHA1_96 = 16 // 0x10
|
|||
|
}
|
|||
|
|
|||
|
[Flags]
|
|||
|
//Values taken from https://support.microsoft.com/en-au/kb/305144
|
|||
|
public enum GroupTypeFlags
|
|||
|
{
|
|||
|
GLOBAL_GROUP = 2, // 0x00000002
|
|||
|
DOMAIN_LOCAL_GROUP = 4, // 0x00000004
|
|||
|
LOCAL_GROUP = 4, // 0x00000004
|
|||
|
UNIVERSAL_GROUP = 8, // 0x00000008
|
|||
|
SECURITY_ENABLED = -2147483648 // 0x80000000
|
|||
|
}
|
|||
|
|
|||
|
private static readonly Dictionary<String, String> Replacements = new Dictionary<String, String>()
|
|||
|
{
|
|||
|
//{System.Environment.NewLine, ""},
|
|||
|
//{",", ";"},
|
|||
|
{"\"", "'"}
|
|||
|
};
|
|||
|
|
|||
|
public static String CleanString(Object StringtoClean)
|
|||
|
{
|
|||
|
// Remove extra spaces and new lines
|
|||
|
String CleanedString = String.Join(" ", ((Convert.ToString(StringtoClean)).Split((string[]) null, StringSplitOptions.RemoveEmptyEntries)));
|
|||
|
foreach (String Replacement in Replacements.Keys)
|
|||
|
{
|
|||
|
CleanedString = CleanedString.Replace(Replacement, Replacements[Replacement]);
|
|||
|
}
|
|||
|
return CleanedString;
|
|||
|
}
|
|||
|
|
|||
|
public static int ObjectCount(Object[] ADRObject)
|
|||
|
{
|
|||
|
return ADRObject.Length;
|
|||
|
}
|
|||
|
|
|||
|
public static bool LAPSCheck(Object[] AdComputers)
|
|||
|
{
|
|||
|
bool LAPS = false;
|
|||
|
foreach (SearchResult AdComputer in AdComputers)
|
|||
|
{
|
|||
|
if (AdComputer.Properties["ms-mcs-admpwdexpirationtime"].Count == 1)
|
|||
|
{
|
|||
|
LAPS = true;
|
|||
|
return LAPS;
|
|||
|
}
|
|||
|
}
|
|||
|
return LAPS;
|
|||
|
}
|
|||
|
|
|||
|
public static Object[] UserParser(Object[] AdUsers, DateTime Date1, int DormantTimeSpan, int PassMaxAge, int numOfThreads)
|
|||
|
{
|
|||
|
LDAPClass.Date1 = Date1;
|
|||
|
LDAPClass.DormantTimeSpan = DormantTimeSpan;
|
|||
|
LDAPClass.PassMaxAge = PassMaxAge;
|
|||
|
|
|||
|
Object[] ADRObj = runProcessor(AdUsers, numOfThreads, "Users");
|
|||
|
return ADRObj;
|
|||
|
}
|
|||
|
|
|||
|
public static Object[] UserSPNParser(Object[] AdUsers, int numOfThreads)
|
|||
|
{
|
|||
|
Object[] ADRObj = runProcessor(AdUsers, numOfThreads, "UserSPNs");
|
|||
|
return ADRObj;
|
|||
|
}
|
|||
|
|
|||
|
public static Object[] GroupParser(Object[] AdGroups, int numOfThreads)
|
|||
|
{
|
|||
|
Object[] ADRObj = runProcessor(AdGroups, numOfThreads, "Groups");
|
|||
|
return ADRObj;
|
|||
|
}
|
|||
|
|
|||
|
public static Object[] GroupMemberParser(Object[] AdGroups, Object[] AdGroupMembers, String DomainSID, int numOfThreads)
|
|||
|
{
|
|||
|
LDAPClass.AdGroupDictionary = new Dictionary<String, String>();
|
|||
|
runProcessor(AdGroups, numOfThreads, "GroupsDictionary");
|
|||
|
LDAPClass.DomainSID = DomainSID;
|
|||
|
Object[] ADRObj = runProcessor(AdGroupMembers, numOfThreads, "GroupMembers");
|
|||
|
return ADRObj;
|
|||
|
}
|
|||
|
|
|||
|
public static Object[] OUParser(Object[] AdOUs, int numOfThreads)
|
|||
|
{
|
|||
|
Object[] ADRObj = runProcessor(AdOUs, numOfThreads, "OUs");
|
|||
|
return ADRObj;
|
|||
|
}
|
|||
|
|
|||
|
public static Object[] GPOParser(Object[] AdGPOs, int numOfThreads)
|
|||
|
{
|
|||
|
Object[] ADRObj = runProcessor(AdGPOs, numOfThreads, "GPOs");
|
|||
|
return ADRObj;
|
|||
|
}
|
|||
|
|
|||
|
public static Object[] SOMParser(Object[] AdGPOs, Object[] AdSOMs, int numOfThreads)
|
|||
|
{
|
|||
|
LDAPClass.AdGPODictionary = new Dictionary<String, String>();
|
|||
|
runProcessor(AdGPOs, numOfThreads, "GPOsDictionary");
|
|||
|
Object[] ADRObj = runProcessor(AdSOMs, numOfThreads, "SOMs");
|
|||
|
return ADRObj;
|
|||
|
}
|
|||
|
|
|||
|
public static Object[] PrinterParser(Object[] ADPrinters, int numOfThreads)
|
|||
|
{
|
|||
|
Object[] ADRObj = runProcessor(ADPrinters, numOfThreads, "Printers");
|
|||
|
return ADRObj;
|
|||
|
}
|
|||
|
|
|||
|
public static Object[] ComputerParser(Object[] AdComputers, DateTime Date1, int DormantTimeSpan, int PassMaxAge, int numOfThreads)
|
|||
|
{
|
|||
|
LDAPClass.Date1 = Date1;
|
|||
|
LDAPClass.DormantTimeSpan = DormantTimeSpan;
|
|||
|
LDAPClass.PassMaxAge = PassMaxAge;
|
|||
|
|
|||
|
Object[] ADRObj = runProcessor(AdComputers, numOfThreads, "Computers");
|
|||
|
return ADRObj;
|
|||
|
}
|
|||
|
|
|||
|
public static Object[] ComputerSPNParser(Object[] AdComputers, int numOfThreads)
|
|||
|
{
|
|||
|
Object[] ADRObj = runProcessor(AdComputers, numOfThreads, "ComputerSPNs");
|
|||
|
return ADRObj;
|
|||
|
}
|
|||
|
|
|||
|
public static Object[] LAPSParser(Object[] AdComputers, int numOfThreads)
|
|||
|
{
|
|||
|
Object[] ADRObj = runProcessor(AdComputers, numOfThreads, "LAPS");
|
|||
|
return ADRObj;
|
|||
|
}
|
|||
|
|
|||
|
public static Object[] DACLParser(Object[] ADObjects, Object PSGUIDs, int numOfThreads)
|
|||
|
{
|
|||
|
LDAPClass.AdSIDDictionary = new Dictionary<String, String>();
|
|||
|
runProcessor(ADObjects, numOfThreads, "SIDDictionary");
|
|||
|
LDAPClass.GUIDs = (Hashtable) PSGUIDs;
|
|||
|
Object[] ADRObj = runProcessor(ADObjects, numOfThreads, "DACLs");
|
|||
|
return ADRObj;
|
|||
|
}
|
|||
|
|
|||
|
public static Object[] SACLParser(Object[] ADObjects, Object PSGUIDs, int numOfThreads)
|
|||
|
{
|
|||
|
LDAPClass.GUIDs = (Hashtable) PSGUIDs;
|
|||
|
Object[] ADRObj = runProcessor(ADObjects, numOfThreads, "SACLs");
|
|||
|
return ADRObj;
|
|||
|
}
|
|||
|
|
|||
|
static Object[] runProcessor(Object[] arrayToProcess, int numOfThreads, string processorType)
|
|||
|
{
|
|||
|
int totalRecords = arrayToProcess.Length;
|
|||
|
IRecordProcessor recordProcessor = recordProcessorFactory(processorType);
|
|||
|
IResultsHandler resultsHandler = new SimpleResultsHandler ();
|
|||
|
int numberOfRecordsPerThread = totalRecords / numOfThreads;
|
|||
|
int remainders = totalRecords % numOfThreads;
|
|||
|
|
|||
|
Thread[] threads = new Thread[numOfThreads];
|
|||
|
for (int i = 0; i < numOfThreads; i++)
|
|||
|
{
|
|||
|
int numberOfRecordsToProcess = numberOfRecordsPerThread;
|
|||
|
if (i == (numOfThreads - 1))
|
|||
|
{
|
|||
|
//last thread, do the remaining records
|
|||
|
numberOfRecordsToProcess += remainders;
|
|||
|
}
|
|||
|
|
|||
|
//split the full array into chunks to be given to different threads
|
|||
|
Object[] sliceToProcess = new Object[numberOfRecordsToProcess];
|
|||
|
Array.Copy(arrayToProcess, i * numberOfRecordsPerThread, sliceToProcess, 0, numberOfRecordsToProcess);
|
|||
|
ProcessorThread processorThread = new ProcessorThread(i, recordProcessor, resultsHandler, sliceToProcess);
|
|||
|
threads[i] = new Thread(processorThread.processThreadRecords);
|
|||
|
threads[i].Start();
|
|||
|
}
|
|||
|
foreach (Thread t in threads)
|
|||
|
{
|
|||
|
t.Join();
|
|||
|
}
|
|||
|
|
|||
|
return resultsHandler.finalise();
|
|||
|
}
|
|||
|
|
|||
|
static IRecordProcessor recordProcessorFactory(String name)
|
|||
|
{
|
|||
|
switch (name)
|
|||
|
{
|
|||
|
case "Users":
|
|||
|
return new UserRecordProcessor();
|
|||
|
case "UserSPNs":
|
|||
|
return new UserSPNRecordProcessor();
|
|||
|
case "Groups":
|
|||
|
return new GroupRecordProcessor();
|
|||
|
case "GroupsDictionary":
|
|||
|
return new GroupRecordDictionaryProcessor();
|
|||
|
case "GroupMembers":
|
|||
|
return new GroupMemberRecordProcessor();
|
|||
|
case "OUs":
|
|||
|
return new OURecordProcessor();
|
|||
|
case "GPOs":
|
|||
|
return new GPORecordProcessor();
|
|||
|
case "GPOsDictionary":
|
|||
|
return new GPORecordDictionaryProcessor();
|
|||
|
case "SOMs":
|
|||
|
return new SOMRecordProcessor();
|
|||
|
case "Printers":
|
|||
|
return new PrinterRecordProcessor();
|
|||
|
case "Computers":
|
|||
|
return new ComputerRecordProcessor();
|
|||
|
case "ComputerSPNs":
|
|||
|
return new ComputerSPNRecordProcessor();
|
|||
|
case "LAPS":
|
|||
|
return new LAPSRecordProcessor();
|
|||
|
case "SIDDictionary":
|
|||
|
return new SIDRecordDictionaryProcessor();
|
|||
|
case "DACLs":
|
|||
|
return new DACLRecordProcessor();
|
|||
|
case "SACLs":
|
|||
|
return new SACLRecordProcessor();
|
|||
|
}
|
|||
|
throw new ArgumentException("Invalid processor type " + name);
|
|||
|
}
|
|||
|
|
|||
|
class ProcessorThread
|
|||
|
{
|
|||
|
readonly int id;
|
|||
|
readonly IRecordProcessor recordProcessor;
|
|||
|
readonly IResultsHandler resultsHandler;
|
|||
|
readonly Object[] objectsToBeProcessed;
|
|||
|
|
|||
|
public ProcessorThread(int id, IRecordProcessor recordProcessor, IResultsHandler resultsHandler, Object[] objectsToBeProcessed)
|
|||
|
{
|
|||
|
this.recordProcessor = recordProcessor;
|
|||
|
this.id = id;
|
|||
|
this.resultsHandler = resultsHandler;
|
|||
|
this.objectsToBeProcessed = objectsToBeProcessed;
|
|||
|
}
|
|||
|
|
|||
|
public void processThreadRecords()
|
|||
|
{
|
|||
|
for (int i = 0; i < objectsToBeProcessed.Length; i++)
|
|||
|
{
|
|||
|
Object[] result = recordProcessor.processRecord(objectsToBeProcessed[i]);
|
|||
|
resultsHandler.processResults(result); //this is a thread safe operation
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
//The interface and implmentation class used to process a record (this implemmentation just returns a log type string)
|
|||
|
|
|||
|
interface IRecordProcessor
|
|||
|
{
|
|||
|
PSObject[] processRecord(Object record);
|
|||
|
}
|
|||
|
|
|||
|
class UserRecordProcessor : IRecordProcessor
|
|||
|
{
|
|||
|
public PSObject[] processRecord(Object record)
|
|||
|
{
|
|||
|
try
|
|||
|
{
|
|||
|
SearchResult AdUser = (SearchResult) record;
|
|||
|
bool? Enabled = null;
|
|||
|
bool? CannotChangePassword = null;
|
|||
|
bool? PasswordNeverExpires = null;
|
|||
|
bool? AccountLockedOut = null;
|
|||
|
bool? PasswordExpired = null;
|
|||
|
bool? ReversiblePasswordEncryption = null;
|
|||
|
bool? DelegationPermitted = null;
|
|||
|
bool? SmartcardRequired = null;
|
|||
|
bool? UseDESKeyOnly = null;
|
|||
|
bool? PasswordNotRequired = null;
|
|||
|
bool? TrustedforDelegation = null;
|
|||
|
bool? TrustedtoAuthforDelegation = null;
|
|||
|
bool? DoesNotRequirePreAuth = null;
|
|||
|
bool? KerberosRC4 = null;
|
|||
|
bool? KerberosAES128 = null;
|
|||
|
bool? KerberosAES256 = null;
|
|||
|
String DelegationType = null;
|
|||
|
String DelegationProtocol = null;
|
|||
|
String DelegationServices = null;
|
|||
|
bool MustChangePasswordatLogon = false;
|
|||
|
int? DaysSinceLastLogon = null;
|
|||
|
int? DaysSinceLastPasswordChange = null;
|
|||
|
int? AccountExpirationNumofDays = null;
|
|||
|
bool PasswordNotChangedafterMaxAge = false;
|
|||
|
bool NeverLoggedIn = false;
|
|||
|
bool Dormant = false;
|
|||
|
DateTime? LastLogonDate = null;
|
|||
|
DateTime? PasswordLastSet = null;
|
|||
|
DateTime? AccountExpires = null;
|
|||
|
byte[] ntSecurityDescriptor = null;
|
|||
|
bool DenyEveryone = false;
|
|||
|
bool DenySelf = false;
|
|||
|
String SIDHistory = "";
|
|||
|
|
|||
|
// When the user is not allowed to query the UserAccountControl attribute.
|
|||
|
if (AdUser.Properties["useraccountcontrol"].Count != 0)
|
|||
|
{
|
|||
|
var userFlags = (UACFlags) AdUser.Properties["useraccountcontrol"][0];
|
|||
|
Enabled = !((userFlags & UACFlags.ACCOUNTDISABLE) == UACFlags.ACCOUNTDISABLE);
|
|||
|
PasswordNeverExpires = (userFlags & UACFlags.DONT_EXPIRE_PASSWD) == UACFlags.DONT_EXPIRE_PASSWD;
|
|||
|
AccountLockedOut = (userFlags & UACFlags.LOCKOUT) == UACFlags.LOCKOUT;
|
|||
|
DelegationPermitted = !((userFlags & UACFlags.NOT_DELEGATED) == UACFlags.NOT_DELEGATED);
|
|||
|
SmartcardRequired = (userFlags & UACFlags.SMARTCARD_REQUIRED) == UACFlags.SMARTCARD_REQUIRED;
|
|||
|
ReversiblePasswordEncryption = (userFlags & UACFlags.ENCRYPTED_TEXT_PASSWORD_ALLOWED) == UACFlags.ENCRYPTED_TEXT_PASSWORD_ALLOWED;
|
|||
|
UseDESKeyOnly = (userFlags & UACFlags.USE_DES_KEY_ONLY) == UACFlags.USE_DES_KEY_ONLY;
|
|||
|
PasswordNotRequired = (userFlags & UACFlags.PASSWD_NOTREQD) == UACFlags.PASSWD_NOTREQD;
|
|||
|
PasswordExpired = (userFlags & UACFlags.PASSWORD_EXPIRED) == UACFlags.PASSWORD_EXPIRED;
|
|||
|
TrustedforDelegation = (userFlags & UACFlags.TRUSTED_FOR_DELEGATION) == UACFlags.TRUSTED_FOR_DELEGATION;
|
|||
|
TrustedtoAuthforDelegation = (userFlags & UACFlags.TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION) == UACFlags.TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION;
|
|||
|
DoesNotRequirePreAuth = (userFlags & UACFlags.DONT_REQUIRE_PREAUTH) == UACFlags.DONT_REQUIRE_PREAUTH;
|
|||
|
}
|
|||
|
if (AdUser.Properties["msds-supportedencryptiontypes"].Count != 0)
|
|||
|
{
|
|||
|
var userKerbEncFlags = (KerbEncFlags) AdUser.Properties["msds-supportedencryptiontypes"][0];
|
|||
|
if (userKerbEncFlags != KerbEncFlags.ZERO)
|
|||
|
{
|
|||
|
KerberosRC4 = (userKerbEncFlags & KerbEncFlags.RC4_HMAC) == KerbEncFlags.RC4_HMAC;
|
|||
|
KerberosAES128 = (userKerbEncFlags & KerbEncFlags.AES128_CTS_HMAC_SHA1_96) == KerbEncFlags.AES128_CTS_HMAC_SHA1_96;
|
|||
|
KerberosAES256 = (userKerbEncFlags & KerbEncFlags.AES256_CTS_HMAC_SHA1_96) == KerbEncFlags.AES256_CTS_HMAC_SHA1_96;
|
|||
|
}
|
|||
|
}
|
|||
|
// When the user is not allowed to query the ntsecuritydescriptor attribute.
|
|||
|
if (AdUser.Properties["ntsecuritydescriptor"].Count != 0)
|
|||
|
{
|
|||
|
ntSecurityDescriptor = (byte[]) AdUser.Properties["ntsecuritydescriptor"][0];
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
DirectoryEntry AdUserEntry = ((SearchResult)record).GetDirectoryEntry();
|
|||
|
ntSecurityDescriptor = (byte[]) AdUserEntry.ObjectSecurity.GetSecurityDescriptorBinaryForm();
|
|||
|
}
|
|||
|
if (ntSecurityDescriptor != null)
|
|||
|
{
|
|||
|
DirectoryObjectSecurity DirObjSec = new ActiveDirectorySecurity();
|
|||
|
DirObjSec.SetSecurityDescriptorBinaryForm(ntSecurityDescriptor);
|
|||
|
AuthorizationRuleCollection AccessRules = (AuthorizationRuleCollection) DirObjSec.GetAccessRules(true,false,typeof(System.Security.Principal.NTAccount));
|
|||
|
foreach (ActiveDirectoryAccessRule Rule in AccessRules)
|
|||
|
{
|
|||
|
if ((Convert.ToString(Rule.ObjectType)).Equals("ab721a53-1e2f-11d0-9819-00aa0040529b"))
|
|||
|
{
|
|||
|
if (Rule.AccessControlType.ToString() == "Deny")
|
|||
|
{
|
|||
|
String ObjectName = Convert.ToString(Rule.IdentityReference);
|
|||
|
if (ObjectName == "Everyone")
|
|||
|
{
|
|||
|
DenyEveryone = true;
|
|||
|
}
|
|||
|
if (ObjectName == "NT AUTHORITY\\SELF")
|
|||
|
{
|
|||
|
DenySelf = true;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
if (DenyEveryone && DenySelf)
|
|||
|
{
|
|||
|
CannotChangePassword = true;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
CannotChangePassword = false;
|
|||
|
}
|
|||
|
}
|
|||
|
if (AdUser.Properties["lastlogontimestamp"].Count != 0)
|
|||
|
{
|
|||
|
LastLogonDate = DateTime.FromFileTime((long)(AdUser.Properties["lastlogontimestamp"][0]));
|
|||
|
DaysSinceLastLogon = Math.Abs((Date1 - (DateTime)LastLogonDate).Days);
|
|||
|
if (DaysSinceLastLogon > DormantTimeSpan)
|
|||
|
{
|
|||
|
Dormant = true;
|
|||
|
}
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
NeverLoggedIn = true;
|
|||
|
}
|
|||
|
if (AdUser.Properties["pwdLastSet"].Count != 0)
|
|||
|
{
|
|||
|
if (Convert.ToString(AdUser.Properties["pwdlastset"][0]) == "0")
|
|||
|
{
|
|||
|
if ((bool) PasswordNeverExpires == false)
|
|||
|
{
|
|||
|
MustChangePasswordatLogon = true;
|
|||
|
}
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
PasswordLastSet = DateTime.FromFileTime((long)(AdUser.Properties["pwdlastset"][0]));
|
|||
|
DaysSinceLastPasswordChange = Math.Abs((Date1 - (DateTime)PasswordLastSet).Days);
|
|||
|
if (DaysSinceLastPasswordChange > PassMaxAge)
|
|||
|
{
|
|||
|
PasswordNotChangedafterMaxAge = true;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
if ((Int64) AdUser.Properties["accountExpires"][0] != (Int64) 9223372036854775807)
|
|||
|
{
|
|||
|
if ((Int64) AdUser.Properties["accountExpires"][0] != (Int64) 0)
|
|||
|
{
|
|||
|
try
|
|||
|
{
|
|||
|
//https://msdn.microsoft.com/en-us/library/ms675098(v=vs.85).aspx
|
|||
|
AccountExpires = DateTime.FromFileTime((long)(AdUser.Properties["accountExpires"][0]));
|
|||
|
AccountExpirationNumofDays = ((int)((DateTime)AccountExpires - Date1).Days);
|
|||
|
|
|||
|
}
|
|||
|
catch //(Exception e)
|
|||
|
{
|
|||
|
// Console.WriteLine("{0} Exception caught.", e);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
if ((bool) TrustedforDelegation)
|
|||
|
{
|
|||
|
DelegationType = "Unconstrained";
|
|||
|
DelegationServices = "Any";
|
|||
|
}
|
|||
|
if (AdUser.Properties["msDS-AllowedToDelegateTo"].Count >= 1)
|
|||
|
{
|
|||
|
DelegationType = "Constrained";
|
|||
|
for (int i = 0; i < AdUser.Properties["msDS-AllowedToDelegateTo"].Count; i++)
|
|||
|
{
|
|||
|
var delegateto = AdUser.Properties["msDS-AllowedToDelegateTo"][i];
|
|||
|
DelegationServices = DelegationServices + "," + Convert.ToString(delegateto);
|
|||
|
}
|
|||
|
DelegationServices = DelegationServices.TrimStart(',');
|
|||
|
}
|
|||
|
if ((bool) TrustedtoAuthforDelegation)
|
|||
|
{
|
|||
|
DelegationProtocol = "Any";
|
|||
|
}
|
|||
|
else if (DelegationType != null)
|
|||
|
{
|
|||
|
DelegationProtocol = "Kerberos";
|
|||
|
}
|
|||
|
if (AdUser.Properties["sidhistory"].Count >= 1)
|
|||
|
{
|
|||
|
string sids = "";
|
|||
|
for (int i = 0; i < AdUser.Properties["sidhistory"].Count; i++)
|
|||
|
{
|
|||
|
var history = AdUser.Properties["sidhistory"][i];
|
|||
|
sids = sids + "," + Convert.ToString(new SecurityIdentifier((byte[])history, 0));
|
|||
|
}
|
|||
|
SIDHistory = sids.TrimStart(',');
|
|||
|
}
|
|||
|
|
|||
|
PSObject UserObj = new PSObject();
|
|||
|
UserObj.Members.Add(new PSNoteProperty("UserName", (AdUser.Properties["samaccountname"].Count != 0 ? AdUser.Properties["samaccountname"][0] : "")));
|
|||
|
UserObj.Members.Add(new PSNoteProperty("Name", (AdUser.Properties["name"].Count != 0 ? CleanString(AdUser.Properties["name"][0]) : "")));
|
|||
|
UserObj.Members.Add(new PSNoteProperty("Enabled", Enabled));
|
|||
|
UserObj.Members.Add(new PSNoteProperty("Must Change Password at Logon", MustChangePasswordatLogon));
|
|||
|
UserObj.Members.Add(new PSNoteProperty("Cannot Change Password", CannotChangePassword));
|
|||
|
UserObj.Members.Add(new PSNoteProperty("Password Never Expires", PasswordNeverExpires));
|
|||
|
UserObj.Members.Add(new PSNoteProperty("Reversible Password Encryption", ReversiblePasswordEncryption));
|
|||
|
UserObj.Members.Add(new PSNoteProperty("Smartcard Logon Required", SmartcardRequired));
|
|||
|
UserObj.Members.Add(new PSNoteProperty("Delegation Permitted", DelegationPermitted));
|
|||
|
UserObj.Members.Add(new PSNoteProperty("Kerberos DES Only", UseDESKeyOnly));
|
|||
|
UserObj.Members.Add(new PSNoteProperty("Kerberos RC4", KerberosRC4));
|
|||
|
UserObj.Members.Add(new PSNoteProperty("Kerberos AES-128bit", KerberosAES128));
|
|||
|
UserObj.Members.Add(new PSNoteProperty("Kerberos AES-256bit", KerberosAES256));
|
|||
|
UserObj.Members.Add(new PSNoteProperty("Does Not Require Pre Auth", DoesNotRequirePreAuth));
|
|||
|
UserObj.Members.Add(new PSNoteProperty("Never Logged in", NeverLoggedIn));
|
|||
|
UserObj.Members.Add(new PSNoteProperty("Logon Age (days)", DaysSinceLastLogon));
|
|||
|
UserObj.Members.Add(new PSNoteProperty("Password Age (days)", DaysSinceLastPasswordChange));
|
|||
|
UserObj.Members.Add(new PSNoteProperty("Dormant (> " + DormantTimeSpan + " days)", Dormant));
|
|||
|
UserObj.Members.Add(new PSNoteProperty("Password Age (> " + PassMaxAge + " days)", PasswordNotChangedafterMaxAge));
|
|||
|
UserObj.Members.Add(new PSNoteProperty("Account Locked Out", AccountLockedOut));
|
|||
|
UserObj.Members.Add(new PSNoteProperty("Password Expired", PasswordExpired));
|
|||
|
UserObj.Members.Add(new PSNoteProperty("Password Not Required", PasswordNotRequired));
|
|||
|
UserObj.Members.Add(new PSNoteProperty("Delegation Type", DelegationType));
|
|||
|
UserObj.Members.Add(new PSNoteProperty("Delegation Protocol", DelegationProtocol));
|
|||
|
UserObj.Members.Add(new PSNoteProperty("Delegation Services", DelegationServices));
|
|||
|
UserObj.Members.Add(new PSNoteProperty("Logon Workstations", (AdUser.Properties["userworkstations"].Count != 0 ? AdUser.Properties["userworkstations"][0] : "")));
|
|||
|
UserObj.Members.Add(new PSNoteProperty("AdminCount", (AdUser.Properties["admincount"].Count != 0 ? AdUser.Properties["admincount"][0] : "")));
|
|||
|
UserObj.Members.Add(new PSNoteProperty("Primary GroupID", (AdUser.Properties["primarygroupid"].Count != 0 ? AdUser.Properties["primarygroupid"][0] : "")));
|
|||
|
UserObj.Members.Add(new PSNoteProperty("SID", Convert.ToString(new SecurityIdentifier((byte[])AdUser.Properties["objectSID"][0], 0))));
|
|||
|
UserObj.Members.Add(new PSNoteProperty("SIDHistory", SIDHistory));
|
|||
|
UserObj.Members.Add(new PSNoteProperty("Description", (AdUser.Properties["Description"].Count != 0 ? CleanString(AdUser.Properties["Description"][0]) : "")));
|
|||
|
UserObj.Members.Add(new PSNoteProperty("Title", (AdUser.Properties["Title"].Count != 0 ? CleanString(AdUser.Properties["Title"][0]) : "")));
|
|||
|
UserObj.Members.Add(new PSNoteProperty("Department", (AdUser.Properties["Department"].Count != 0 ? CleanString(AdUser.Properties["Department"][0]) : "")));
|
|||
|
UserObj.Members.Add(new PSNoteProperty("Company", (AdUser.Properties["Company"].Count != 0 ? CleanString(AdUser.Properties["Company"][0]) : "")));
|
|||
|
UserObj.Members.Add(new PSNoteProperty("Manager", (AdUser.Properties["Manager"].Count != 0 ? CleanString(AdUser.Properties["Manager"][0]) : "")));
|
|||
|
UserObj.Members.Add(new PSNoteProperty("Info", (AdUser.Properties["info"].Count != 0 ? CleanString(AdUser.Properties["info"][0]) : "")));
|
|||
|
UserObj.Members.Add(new PSNoteProperty("Last Logon Date", LastLogonDate));
|
|||
|
UserObj.Members.Add(new PSNoteProperty("Password LastSet", PasswordLastSet));
|
|||
|
UserObj.Members.Add(new PSNoteProperty("Account Expiration Date", AccountExpires));
|
|||
|
UserObj.Members.Add(new PSNoteProperty("Account Expiration (days)", AccountExpirationNumofDays));
|
|||
|
UserObj.Members.Add(new PSNoteProperty("Mobile", (AdUser.Properties["mobile"].Count != 0 ? CleanString(AdUser.Properties["mobile"][0]) : "")));
|
|||
|
UserObj.Members.Add(new PSNoteProperty("Email", (AdUser.Properties["mail"].Count != 0 ? CleanString(AdUser.Properties["mail"][0]) : "")));
|
|||
|
UserObj.Members.Add(new PSNoteProperty("HomeDirectory", (AdUser.Properties["homedirectory"].Count != 0 ? AdUser.Properties["homedirectory"][0] : "")));
|
|||
|
UserObj.Members.Add(new PSNoteProperty("ProfilePath", (AdUser.Properties["profilepath"].Count != 0 ? AdUser.Properties["profilepath"][0] : "")));
|
|||
|
UserObj.Members.Add(new PSNoteProperty("ScriptPath", (AdUser.Properties["scriptpath"].Count != 0 ? AdUser.Properties["scriptpath"][0] : "")));
|
|||
|
UserObj.Members.Add(new PSNoteProperty("UserAccountControl", (AdUser.Properties["useraccountcontrol"].Count != 0 ? AdUser.Properties["useraccountcontrol"][0] : "")));
|
|||
|
UserObj.Members.Add(new PSNoteProperty("First Name", (AdUser.Properties["givenName"].Count != 0 ? CleanString(AdUser.Properties["givenName"][0]) : "")));
|
|||
|
UserObj.Members.Add(new PSNoteProperty("Middle Name", (AdUser.Properties["middleName"].Count != 0 ? CleanString(AdUser.Properties["middleName"][0]) : "")));
|
|||
|
UserObj.Members.Add(new PSNoteProperty("Last Name", (AdUser.Properties["sn"].Count != 0 ? CleanString(AdUser.Properties["sn"][0]) : "")));
|
|||
|
UserObj.Members.Add(new PSNoteProperty("Country", (AdUser.Properties["c"].Count != 0 ? CleanString(AdUser.Properties["c"][0]) : "")));
|
|||
|
UserObj.Members.Add(new PSNoteProperty("whenCreated", (AdUser.Properties["whencreated"].Count != 0 ? AdUser.Properties["whencreated"][0] : "")));
|
|||
|
UserObj.Members.Add(new PSNoteProperty("whenChanged", (AdUser.Properties["whenchanged"].Count != 0 ? AdUser.Properties["whenchanged"][0] : "")));
|
|||
|
UserObj.Members.Add(new PSNoteProperty("DistinguishedName", (AdUser.Properties["distinguishedname"].Count != 0 ? CleanString(AdUser.Properties["distinguishedname"][0]) : "")));
|
|||
|
UserObj.Members.Add(new PSNoteProperty("CanonicalName", (AdUser.Properties["canonicalname"].Count != 0 ? AdUser.Properties["canonicalname"][0] : "")));
|
|||
|
return new PSObject[] { UserObj };
|
|||
|
}
|
|||
|
catch (Exception e)
|
|||
|
{
|
|||
|
Console.WriteLine("{0} Exception caught.", e);
|
|||
|
return new PSObject[] { };
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
class UserSPNRecordProcessor : IRecordProcessor
|
|||
|
{
|
|||
|
public PSObject[] processRecord(Object record)
|
|||
|
{
|
|||
|
try
|
|||
|
{
|
|||
|
SearchResult AdUser = (SearchResult) record;
|
|||
|
List<PSObject> SPNList = new List<PSObject>();
|
|||
|
bool? Enabled = null;
|
|||
|
String Memberof = null;
|
|||
|
DateTime? PasswordLastSet = null;
|
|||
|
|
|||
|
if (AdUser.Properties["pwdlastset"].Count != 0)
|
|||
|
{
|
|||
|
if (Convert.ToString(AdUser.Properties["pwdlastset"][0]) != "0")
|
|||
|
{
|
|||
|
PasswordLastSet = DateTime.FromFileTime((long)(AdUser.Properties["pwdLastSet"][0]));
|
|||
|
}
|
|||
|
}
|
|||
|
// When the user is not allowed to query the UserAccountControl attribute.
|
|||
|
if (AdUser.Properties["useraccountcontrol"].Count != 0)
|
|||
|
{
|
|||
|
var userFlags = (UACFlags) AdUser.Properties["useraccountcontrol"][0];
|
|||
|
Enabled = !((userFlags & UACFlags.ACCOUNTDISABLE) == UACFlags.ACCOUNTDISABLE);
|
|||
|
}
|
|||
|
String Description = (AdUser.Properties["Description"].Count != 0 ? CleanString(AdUser.Properties["Description"][0]) : "");
|
|||
|
String PrimaryGroupID = (AdUser.Properties["primarygroupid"].Count != 0 ? Convert.ToString(AdUser.Properties["primarygroupid"][0]) : "");
|
|||
|
if (AdUser.Properties["memberof"].Count != 0)
|
|||
|
{
|
|||
|
foreach (String Member in AdUser.Properties["memberof"])
|
|||
|
{
|
|||
|
Memberof = Memberof + "," + ((Convert.ToString(Member)).Split(',')[0]).Split('=')[1];
|
|||
|
}
|
|||
|
Memberof = Memberof.TrimStart(',');
|
|||
|
}
|
|||
|
foreach (String SPN in AdUser.Properties["serviceprincipalname"])
|
|||
|
{
|
|||
|
String[] SPNArray = SPN.Split('/');
|
|||
|
PSObject UserSPNObj = new PSObject();
|
|||
|
UserSPNObj.Members.Add(new PSNoteProperty("Name", AdUser.Properties["name"][0]));
|
|||
|
UserSPNObj.Members.Add(new PSNoteProperty("Username", AdUser.Properties["samaccountname"][0]));
|
|||
|
UserSPNObj.Members.Add(new PSNoteProperty("Enabled", Enabled));
|
|||
|
UserSPNObj.Members.Add(new PSNoteProperty("Service", SPNArray[0]));
|
|||
|
UserSPNObj.Members.Add(new PSNoteProperty("Host", SPNArray[1]));
|
|||
|
UserSPNObj.Members.Add(new PSNoteProperty("Password Last Set", PasswordLastSet));
|
|||
|
UserSPNObj.Members.Add(new PSNoteProperty("Description", Description));
|
|||
|
UserSPNObj.Members.Add(new PSNoteProperty("Primary GroupID", PrimaryGroupID));
|
|||
|
UserSPNObj.Members.Add(new PSNoteProperty("Memberof", Memberof));
|
|||
|
SPNList.Add( UserSPNObj );
|
|||
|
}
|
|||
|
return SPNList.ToArray();
|
|||
|
}
|
|||
|
catch (Exception e)
|
|||
|
{
|
|||
|
Console.WriteLine("{0} Exception caught.", e);
|
|||
|
return new PSObject[] { };
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
class GroupRecordProcessor : IRecordProcessor
|
|||
|
{
|
|||
|
public PSObject[] processRecord(Object record)
|
|||
|
{
|
|||
|
try
|
|||
|
{
|
|||
|
SearchResult AdGroup = (SearchResult) record;
|
|||
|
String ManagedByValue = AdGroup.Properties["managedby"].Count != 0 ? Convert.ToString(AdGroup.Properties["managedby"][0]) : "";
|
|||
|
String ManagedBy = "";
|
|||
|
String GroupCategory = null;
|
|||
|
String GroupScope = null;
|
|||
|
String SIDHistory = "";
|
|||
|
|
|||
|
if (AdGroup.Properties["managedBy"].Count != 0)
|
|||
|
{
|
|||
|
ManagedBy = (ManagedByValue.Split(',')[0]).Split('=')[1];
|
|||
|
}
|
|||
|
|
|||
|
if (AdGroup.Properties["grouptype"].Count != 0)
|
|||
|
{
|
|||
|
var groupTypeFlags = (GroupTypeFlags) AdGroup.Properties["grouptype"][0];
|
|||
|
GroupCategory = (groupTypeFlags & GroupTypeFlags.SECURITY_ENABLED) == GroupTypeFlags.SECURITY_ENABLED ? "Security" : "Distribution";
|
|||
|
|
|||
|
if ((groupTypeFlags & GroupTypeFlags.UNIVERSAL_GROUP) == GroupTypeFlags.UNIVERSAL_GROUP)
|
|||
|
{
|
|||
|
GroupScope = "Universal";
|
|||
|
}
|
|||
|
else if ((groupTypeFlags & GroupTypeFlags.GLOBAL_GROUP) == GroupTypeFlags.GLOBAL_GROUP)
|
|||
|
{
|
|||
|
GroupScope = "Global";
|
|||
|
}
|
|||
|
else if ((groupTypeFlags & GroupTypeFlags.DOMAIN_LOCAL_GROUP) == GroupTypeFlags.DOMAIN_LOCAL_GROUP)
|
|||
|
{
|
|||
|
GroupScope = "DomainLocal";
|
|||
|
}
|
|||
|
}
|
|||
|
if (AdGroup.Properties["sidhistory"].Count >= 1)
|
|||
|
{
|
|||
|
string sids = "";
|
|||
|
for (int i = 0; i < AdGroup.Properties["sidhistory"].Count; i++)
|
|||
|
{
|
|||
|
var history = AdGroup.Properties["sidhistory"][i];
|
|||
|
sids = sids + "," + Convert.ToString(new SecurityIdentifier((byte[])history, 0));
|
|||
|
}
|
|||
|
SIDHistory = sids.TrimStart(',');
|
|||
|
}
|
|||
|
|
|||
|
PSObject GroupObj = new PSObject();
|
|||
|
GroupObj.Members.Add(new PSNoteProperty("Name", AdGroup.Properties["samaccountname"][0]));
|
|||
|
GroupObj.Members.Add(new PSNoteProperty("AdminCount", (AdGroup.Properties["admincount"].Count != 0 ? AdGroup.Properties["admincount"][0] : "")));
|
|||
|
GroupObj.Members.Add(new PSNoteProperty("GroupCategory", GroupCategory));
|
|||
|
GroupObj.Members.Add(new PSNoteProperty("GroupScope", GroupScope));
|
|||
|
GroupObj.Members.Add(new PSNoteProperty("ManagedBy", ManagedBy));
|
|||
|
GroupObj.Members.Add(new PSNoteProperty("SID", Convert.ToString(new SecurityIdentifier((byte[])AdGroup.Properties["objectSID"][0], 0))));
|
|||
|
GroupObj.Members.Add(new PSNoteProperty("SIDHistory", SIDHistory));
|
|||
|
GroupObj.Members.Add(new PSNoteProperty("Description", (AdGroup.Properties["Description"].Count != 0 ? CleanString(AdGroup.Properties["Description"][0]) : "")));
|
|||
|
GroupObj.Members.Add(new PSNoteProperty("whenCreated", AdGroup.Properties["whencreated"][0]));
|
|||
|
GroupObj.Members.Add(new PSNoteProperty("whenChanged", AdGroup.Properties["whenchanged"][0]));
|
|||
|
GroupObj.Members.Add(new PSNoteProperty("DistinguishedName", CleanString(AdGroup.Properties["distinguishedname"][0])));
|
|||
|
GroupObj.Members.Add(new PSNoteProperty("CanonicalName", AdGroup.Properties["canonicalname"][0]));
|
|||
|
return new PSObject[] { GroupObj };
|
|||
|
}
|
|||
|
catch (Exception e)
|
|||
|
{
|
|||
|
Console.WriteLine("{0} Exception caught.", e);
|
|||
|
return new PSObject[] { };
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
class GroupRecordDictionaryProcessor : IRecordProcessor
|
|||
|
{
|
|||
|
public PSObject[] processRecord(Object record)
|
|||
|
{
|
|||
|
try
|
|||
|
{
|
|||
|
SearchResult AdGroup = (SearchResult) record;
|
|||
|
LDAPClass.AdGroupDictionary.Add((Convert.ToString(new SecurityIdentifier((byte[])AdGroup.Properties["objectSID"][0], 0))),(Convert.ToString(AdGroup.Properties["samaccountname"][0])));
|
|||
|
return new PSObject[] { };
|
|||
|
}
|
|||
|
catch (Exception e)
|
|||
|
{
|
|||
|
Console.WriteLine("{0} Exception caught.", e);
|
|||
|
return new PSObject[] { };
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
class GroupMemberRecordProcessor : IRecordProcessor
|
|||
|
{
|
|||
|
public PSObject[] processRecord(Object record)
|
|||
|
{
|
|||
|
try
|
|||
|
{
|
|||
|
// https://github.com/BloodHoundAD/BloodHound/blob/master/PowerShell/BloodHound.ps1
|
|||
|
SearchResult AdGroup = (SearchResult) record;
|
|||
|
List<PSObject> GroupsList = new List<PSObject>();
|
|||
|
string SamAccountType = AdGroup.Properties["samaccounttype"].Count != 0 ? Convert.ToString(AdGroup.Properties["samaccounttype"][0]) : "";
|
|||
|
string AccountType = "";
|
|||
|
string GroupName = "";
|
|||
|
string MemberUserName = "-";
|
|||
|
string MemberName = "";
|
|||
|
|
|||
|
if (Groups.Contains(SamAccountType))
|
|||
|
{
|
|||
|
AccountType = "group";
|
|||
|
MemberName = ((Convert.ToString(AdGroup.Properties["DistinguishedName"][0])).Split(',')[0]).Split('=')[1];
|
|||
|
foreach (String GroupMember in AdGroup.Properties["memberof"])
|
|||
|
{
|
|||
|
GroupName = ((Convert.ToString(GroupMember)).Split(',')[0]).Split('=')[1];
|
|||
|
PSObject GroupMemberObj = new PSObject();
|
|||
|
GroupMemberObj.Members.Add(new PSNoteProperty("Group Name", GroupName));
|
|||
|
GroupMemberObj.Members.Add(new PSNoteProperty("Member UserName", MemberUserName));
|
|||
|
GroupMemberObj.Members.Add(new PSNoteProperty("Member Name", MemberName));
|
|||
|
GroupMemberObj.Members.Add(new PSNoteProperty("AccountType", AccountType));
|
|||
|
GroupsList.Add( GroupMemberObj );
|
|||
|
}
|
|||
|
}
|
|||
|
if (Users.Contains(SamAccountType))
|
|||
|
{
|
|||
|
AccountType = "user";
|
|||
|
MemberName = ((Convert.ToString(AdGroup.Properties["DistinguishedName"][0])).Split(',')[0]).Split('=')[1];
|
|||
|
MemberUserName = Convert.ToString(AdGroup.Properties["sAMAccountName"][0]);
|
|||
|
String PrimaryGroupID = Convert.ToString(AdGroup.Properties["primaryGroupID"][0]);
|
|||
|
try
|
|||
|
{
|
|||
|
GroupName = LDAPClass.AdGroupDictionary[LDAPClass.DomainSID + "-" + PrimaryGroupID];
|
|||
|
}
|
|||
|
catch //(Exception e)
|
|||
|
{
|
|||
|
//Console.WriteLine("{0} Exception caught.", e);
|
|||
|
GroupName = PrimaryGroupID;
|
|||
|
}
|
|||
|
|
|||
|
{
|
|||
|
PSObject GroupMemberObj = new PSObject();
|
|||
|
GroupMemberObj.Members.Add(new PSNoteProperty("Group Name", GroupName));
|
|||
|
GroupMemberObj.Members.Add(new PSNoteProperty("Member UserName", MemberUserName));
|
|||
|
GroupMemberObj.Members.Add(new PSNoteProperty("Member Name", MemberName));
|
|||
|
GroupMemberObj.Members.Add(new PSNoteProperty("AccountType", AccountType));
|
|||
|
GroupsList.Add( GroupMemberObj );
|
|||
|
}
|
|||
|
|
|||
|
foreach (String GroupMember in AdGroup.Properties["memberof"])
|
|||
|
{
|
|||
|
GroupName = ((Convert.ToString(GroupMember)).Split(',')[0]).Split('=')[1];
|
|||
|
PSObject GroupMemberObj = new PSObject();
|
|||
|
GroupMemberObj.Members.Add(new PSNoteProperty("Group Name", GroupName));
|
|||
|
GroupMemberObj.Members.Add(new PSNoteProperty("Member UserName", MemberUserName));
|
|||
|
GroupMemberObj.Members.Add(new PSNoteProperty("Member Name", MemberName));
|
|||
|
GroupMemberObj.Members.Add(new PSNoteProperty("AccountType", AccountType));
|
|||
|
GroupsList.Add( GroupMemberObj );
|
|||
|
}
|
|||
|
}
|
|||
|
if (Computers.Contains(SamAccountType))
|
|||
|
{
|
|||
|
AccountType = "computer";
|
|||
|
MemberName = ((Convert.ToString(AdGroup.Properties["DistinguishedName"][0])).Split(',')[0]).Split('=')[1];
|
|||
|
MemberUserName = Convert.ToString(AdGroup.Properties["sAMAccountName"][0]);
|
|||
|
String PrimaryGroupID = Convert.ToString(AdGroup.Properties["primaryGroupID"][0]);
|
|||
|
try
|
|||
|
{
|
|||
|
GroupName = LDAPClass.AdGroupDictionary[LDAPClass.DomainSID + "-" + PrimaryGroupID];
|
|||
|
}
|
|||
|
catch //(Exception e)
|
|||
|
{
|
|||
|
//Console.WriteLine("{0} Exception caught.", e);
|
|||
|
GroupName = PrimaryGroupID;
|
|||
|
}
|
|||
|
|
|||
|
{
|
|||
|
PSObject GroupMemberObj = new PSObject();
|
|||
|
GroupMemberObj.Members.Add(new PSNoteProperty("Group Name", GroupName));
|
|||
|
GroupMemberObj.Members.Add(new PSNoteProperty("Member UserName", MemberUserName));
|
|||
|
GroupMemberObj.Members.Add(new PSNoteProperty("Member Name", MemberName));
|
|||
|
GroupMemberObj.Members.Add(new PSNoteProperty("AccountType", AccountType));
|
|||
|
GroupsList.Add( GroupMemberObj );
|
|||
|
}
|
|||
|
|
|||
|
foreach (String GroupMember in AdGroup.Properties["memberof"])
|
|||
|
{
|
|||
|
GroupName = ((Convert.ToString(GroupMember)).Split(',')[0]).Split('=')[1];
|
|||
|
PSObject GroupMemberObj = new PSObject();
|
|||
|
GroupMemberObj.Members.Add(new PSNoteProperty("Group Name", GroupName));
|
|||
|
GroupMemberObj.Members.Add(new PSNoteProperty("Member UserName", MemberUserName));
|
|||
|
GroupMemberObj.Members.Add(new PSNoteProperty("Member Name", MemberName));
|
|||
|
GroupMemberObj.Members.Add(new PSNoteProperty("AccountType", AccountType));
|
|||
|
GroupsList.Add( GroupMemberObj );
|
|||
|
}
|
|||
|
}
|
|||
|
if (TrustAccounts.Contains(SamAccountType))
|
|||
|
{
|
|||
|
// TO DO
|
|||
|
}
|
|||
|
return GroupsList.ToArray();
|
|||
|
}
|
|||
|
catch (Exception e)
|
|||
|
{
|
|||
|
Console.WriteLine("{0} Exception caught.", e);
|
|||
|
return new PSObject[] { };
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
class OURecordProcessor : IRecordProcessor
|
|||
|
{
|
|||
|
public PSObject[] processRecord(Object record)
|
|||
|
{
|
|||
|
try
|
|||
|
{
|
|||
|
SearchResult AdOU = (SearchResult) record;
|
|||
|
|
|||
|
PSObject OUObj = new PSObject();
|
|||
|
OUObj.Members.Add(new PSNoteProperty("Name", AdOU.Properties["name"][0]));
|
|||
|
OUObj.Members.Add(new PSNoteProperty("Depth", ((Convert.ToString(AdOU.Properties["distinguishedname"][0]).Split(new string[] { "OU=" }, StringSplitOptions.None)).Length -1)));
|
|||
|
OUObj.Members.Add(new PSNoteProperty("Description", (AdOU.Properties["description"].Count != 0 ? AdOU.Properties["description"][0] : "")));
|
|||
|
OUObj.Members.Add(new PSNoteProperty("whenCreated", AdOU.Properties["whencreated"][0]));
|
|||
|
OUObj.Members.Add(new PSNoteProperty("whenChanged", AdOU.Properties["whenchanged"][0]));
|
|||
|
OUObj.Members.Add(new PSNoteProperty("DistinguishedName", AdOU.Properties["distinguishedname"][0]));
|
|||
|
return new PSObject[] { OUObj };
|
|||
|
}
|
|||
|
catch (Exception e)
|
|||
|
{
|
|||
|
Console.WriteLine("{0} Exception caught.", e);
|
|||
|
return new PSObject[] { };
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
class GPORecordProcessor : IRecordProcessor
|
|||
|
{
|
|||
|
public PSObject[] processRecord(Object record)
|
|||
|
{
|
|||
|
try
|
|||
|
{
|
|||
|
SearchResult AdGPO = (SearchResult) record;
|
|||
|
|
|||
|
PSObject GPOObj = new PSObject();
|
|||
|
GPOObj.Members.Add(new PSNoteProperty("DisplayName", CleanString(AdGPO.Properties["displayname"][0])));
|
|||
|
GPOObj.Members.Add(new PSNoteProperty("GUID", CleanString(AdGPO.Properties["name"][0])));
|
|||
|
GPOObj.Members.Add(new PSNoteProperty("whenCreated", AdGPO.Properties["whenCreated"][0]));
|
|||
|
GPOObj.Members.Add(new PSNoteProperty("whenChanged", AdGPO.Properties["whenChanged"][0]));
|
|||
|
GPOObj.Members.Add(new PSNoteProperty("DistinguishedName", CleanString(AdGPO.Properties["distinguishedname"][0])));
|
|||
|
GPOObj.Members.Add(new PSNoteProperty("FilePath", AdGPO.Properties["gpcfilesyspath"][0]));
|
|||
|
return new PSObject[] { GPOObj };
|
|||
|
}
|
|||
|
catch (Exception e)
|
|||
|
{
|
|||
|
Console.WriteLine("{0} Exception caught.", e);
|
|||
|
return new PSObject[] { };
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
class GPORecordDictionaryProcessor : IRecordProcessor
|
|||
|
{
|
|||
|
public PSObject[] processRecord(Object record)
|
|||
|
{
|
|||
|
try
|
|||
|
{
|
|||
|
SearchResult AdGPO = (SearchResult) record;
|
|||
|
LDAPClass.AdGPODictionary.Add((Convert.ToString(AdGPO.Properties["distinguishedname"][0]).ToUpper()), (Convert.ToString(AdGPO.Properties["displayname"][0])));
|
|||
|
return new PSObject[] { };
|
|||
|
}
|
|||
|
catch (Exception e)
|
|||
|
{
|
|||
|
Console.WriteLine("{0} Exception caught.", e);
|
|||
|
return new PSObject[] { };
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
class SOMRecordProcessor : IRecordProcessor
|
|||
|
{
|
|||
|
public PSObject[] processRecord(Object record)
|
|||
|
{
|
|||
|
try
|
|||
|
{
|
|||
|
SearchResult AdSOM = (SearchResult) record;
|
|||
|
|
|||
|
List<PSObject> SOMsList = new List<PSObject>();
|
|||
|
int Depth = 0;
|
|||
|
bool BlockInheritance = false;
|
|||
|
bool? LinkEnabled = null;
|
|||
|
bool? Enforced = null;
|
|||
|
String gPLink = (AdSOM.Properties["gPLink"].Count != 0 ? Convert.ToString(AdSOM.Properties["gPLink"][0]) : "");
|
|||
|
String GPOName = null;
|
|||
|
|
|||
|
Depth = ((Convert.ToString(AdSOM.Properties["distinguishedname"][0]).Split(new string[] { "OU=" }, StringSplitOptions.None)).Length -1);
|
|||
|
if (AdSOM.Properties["gPOptions"].Count != 0)
|
|||
|
{
|
|||
|
if ((int) AdSOM.Properties["gPOptions"][0] == 1)
|
|||
|
{
|
|||
|
BlockInheritance = true;
|
|||
|
}
|
|||
|
}
|
|||
|
var GPLinks = gPLink.Split(']', '[').Where(x => x.StartsWith("LDAP"));
|
|||
|
int Order = (GPLinks.ToArray()).Length;
|
|||
|
if (Order == 0)
|
|||
|
{
|
|||
|
PSObject SOMObj = new PSObject();
|
|||
|
SOMObj.Members.Add(new PSNoteProperty("Name", AdSOM.Properties["name"][0]));
|
|||
|
SOMObj.Members.Add(new PSNoteProperty("Depth", Depth));
|
|||
|
SOMObj.Members.Add(new PSNoteProperty("DistinguishedName", AdSOM.Properties["distinguishedname"][0]));
|
|||
|
SOMObj.Members.Add(new PSNoteProperty("Link Order", null));
|
|||
|
SOMObj.Members.Add(new PSNoteProperty("GPO", GPOName));
|
|||
|
SOMObj.Members.Add(new PSNoteProperty("Enforced", Enforced));
|
|||
|
SOMObj.Members.Add(new PSNoteProperty("Link Enabled", LinkEnabled));
|
|||
|
SOMObj.Members.Add(new PSNoteProperty("BlockInheritance", BlockInheritance));
|
|||
|
SOMObj.Members.Add(new PSNoteProperty("gPLink", gPLink));
|
|||
|
SOMObj.Members.Add(new PSNoteProperty("gPOptions", (AdSOM.Properties["gpoptions"].Count != 0 ? AdSOM.Properties["gpoptions"][0] : "")));
|
|||
|
SOMsList.Add( SOMObj );
|
|||
|
}
|
|||
|
foreach (String link in GPLinks)
|
|||
|
{
|
|||
|
String[] linksplit = link.Split('/', ';');
|
|||
|
if (!Convert.ToBoolean((Convert.ToInt32(linksplit[3]) & 1)))
|
|||
|
{
|
|||
|
LinkEnabled = true;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
LinkEnabled = false;
|
|||
|
}
|
|||
|
if (Convert.ToBoolean((Convert.ToInt32(linksplit[3]) & 2)))
|
|||
|
{
|
|||
|
Enforced = true;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
Enforced = false;
|
|||
|
}
|
|||
|
GPOName = LDAPClass.AdGPODictionary.ContainsKey(linksplit[2].ToUpper()) ? LDAPClass.AdGPODictionary[linksplit[2].ToUpper()] : linksplit[2].Split('=',',')[1];
|
|||
|
PSObject SOMObj = new PSObject();
|
|||
|
SOMObj.Members.Add(new PSNoteProperty("Name", AdSOM.Properties["name"][0]));
|
|||
|
SOMObj.Members.Add(new PSNoteProperty("Depth", Depth));
|
|||
|
SOMObj.Members.Add(new PSNoteProperty("DistinguishedName", AdSOM.Properties["distinguishedname"][0]));
|
|||
|
SOMObj.Members.Add(new PSNoteProperty("Link Order", Order));
|
|||
|
SOMObj.Members.Add(new PSNoteProperty("GPO", GPOName));
|
|||
|
SOMObj.Members.Add(new PSNoteProperty("Enforced", Enforced));
|
|||
|
SOMObj.Members.Add(new PSNoteProperty("Link Enabled", LinkEnabled));
|
|||
|
SOMObj.Members.Add(new PSNoteProperty("BlockInheritance", BlockInheritance));
|
|||
|
SOMObj.Members.Add(new PSNoteProperty("gPLink", gPLink));
|
|||
|
SOMObj.Members.Add(new PSNoteProperty("gPOptions", (AdSOM.Properties["gpoptions"].Count != 0 ? AdSOM.Properties["gpoptions"][0] : "")));
|
|||
|
SOMsList.Add( SOMObj );
|
|||
|
Order--;
|
|||
|
}
|
|||
|
return SOMsList.ToArray();
|
|||
|
}
|
|||
|
catch (Exception e)
|
|||
|
{
|
|||
|
Console.WriteLine("{0} Exception caught.", e);
|
|||
|
return new PSObject[] { };
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
class PrinterRecordProcessor : IRecordProcessor
|
|||
|
{
|
|||
|
public PSObject[] processRecord(Object record)
|
|||
|
{
|
|||
|
try
|
|||
|
{
|
|||
|
SearchResult AdPrinter = (SearchResult) record;
|
|||
|
|
|||
|
PSObject PrinterObj = new PSObject();
|
|||
|
PrinterObj.Members.Add(new PSNoteProperty("Name", AdPrinter.Properties["Name"][0]));
|
|||
|
PrinterObj.Members.Add(new PSNoteProperty("ServerName", AdPrinter.Properties["serverName"][0]));
|
|||
|
PrinterObj.Members.Add(new PSNoteProperty("ShareName", AdPrinter.Properties["printShareName"][0]));
|
|||
|
PrinterObj.Members.Add(new PSNoteProperty("DriverName", AdPrinter.Properties["driverName"][0]));
|
|||
|
PrinterObj.Members.Add(new PSNoteProperty("DriverVersion", AdPrinter.Properties["driverVersion"][0]));
|
|||
|
PrinterObj.Members.Add(new PSNoteProperty("PortName", AdPrinter.Properties["portName"][0]));
|
|||
|
PrinterObj.Members.Add(new PSNoteProperty("URL", AdPrinter.Properties["url"][0]));
|
|||
|
PrinterObj.Members.Add(new PSNoteProperty("whenCreated", AdPrinter.Properties["whenCreated"][0]));
|
|||
|
PrinterObj.Members.Add(new PSNoteProperty("whenChanged", AdPrinter.Properties["whenChanged"][0]));
|
|||
|
return new PSObject[] { PrinterObj };
|
|||
|
}
|
|||
|
catch (Exception e)
|
|||
|
{
|
|||
|
Console.WriteLine("{0} Exception caught.", e);
|
|||
|
return new PSObject[] { };
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
class ComputerRecordProcessor : IRecordProcessor
|
|||
|
{
|
|||
|
public PSObject[] processRecord(Object record)
|
|||
|
{
|
|||
|
try
|
|||
|
{
|
|||
|
SearchResult AdComputer = (SearchResult) record;
|
|||
|
bool Dormant = false;
|
|||
|
bool? Enabled = null;
|
|||
|
bool PasswordNotChangedafterMaxAge = false;
|
|||
|
bool? TrustedforDelegation = null;
|
|||
|
bool? TrustedtoAuthforDelegation = null;
|
|||
|
String DelegationType = null;
|
|||
|
String DelegationProtocol = null;
|
|||
|
String DelegationServices = null;
|
|||
|
String StrIPAddress = null;
|
|||
|
int? DaysSinceLastLogon = null;
|
|||
|
int? DaysSinceLastPasswordChange = null;
|
|||
|
DateTime? LastLogonDate = null;
|
|||
|
DateTime? PasswordLastSet = null;
|
|||
|
|
|||
|
if (AdComputer.Properties["dnshostname"].Count != 0)
|
|||
|
{
|
|||
|
try
|
|||
|
{
|
|||
|
StrIPAddress = Convert.ToString(Dns.GetHostEntry(Convert.ToString(AdComputer.Properties["dnshostname"][0])).AddressList[0]);
|
|||
|
}
|
|||
|
catch
|
|||
|
{
|
|||
|
StrIPAddress = null;
|
|||
|
}
|
|||
|
}
|
|||
|
// When the user is not allowed to query the UserAccountControl attribute.
|
|||
|
if (AdComputer.Properties["useraccountcontrol"].Count != 0)
|
|||
|
{
|
|||
|
var userFlags = (UACFlags) AdComputer.Properties["useraccountcontrol"][0];
|
|||
|
Enabled = !((userFlags & UACFlags.ACCOUNTDISABLE) == UACFlags.ACCOUNTDISABLE);
|
|||
|
TrustedforDelegation = (userFlags & UACFlags.TRUSTED_FOR_DELEGATION) == UACFlags.TRUSTED_FOR_DELEGATION;
|
|||
|
TrustedtoAuthforDelegation = (userFlags & UACFlags.TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION) == UACFlags.TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION;
|
|||
|
}
|
|||
|
if (AdComputer.Properties["lastlogontimestamp"].Count != 0)
|
|||
|
{
|
|||
|
LastLogonDate = DateTime.FromFileTime((long)(AdComputer.Properties["lastlogontimestamp"][0]));
|
|||
|
DaysSinceLastLogon = Math.Abs((Date1 - (DateTime)LastLogonDate).Days);
|
|||
|
if (DaysSinceLastLogon > DormantTimeSpan)
|
|||
|
{
|
|||
|
Dormant = true;
|
|||
|
}
|
|||
|
}
|
|||
|
if (AdComputer.Properties["pwdlastset"].Count != 0)
|
|||
|
{
|
|||
|
PasswordLastSet = DateTime.FromFileTime((long)(AdComputer.Properties["pwdlastset"][0]));
|
|||
|
DaysSinceLastPasswordChange = Math.Abs((Date1 - (DateTime)PasswordLastSet).Days);
|
|||
|
if (DaysSinceLastPasswordChange > PassMaxAge)
|
|||
|
{
|
|||
|
PasswordNotChangedafterMaxAge = true;
|
|||
|
}
|
|||
|
}
|
|||
|
if ( ((bool) TrustedforDelegation) && ((int) AdComputer.Properties["primarygroupid"][0] == 515) )
|
|||
|
{
|
|||
|
DelegationType = "Unconstrained";
|
|||
|
DelegationServices = "Any";
|
|||
|
}
|
|||
|
if (AdComputer.Properties["msDS-AllowedToDelegateTo"].Count >= 1)
|
|||
|
{
|
|||
|
DelegationType = "Constrained";
|
|||
|
for (int i = 0; i < AdComputer.Properties["msDS-AllowedToDelegateTo"].Count; i++)
|
|||
|
{
|
|||
|
var delegateto = AdComputer.Properties["msDS-AllowedToDelegateTo"][i];
|
|||
|
DelegationServices = DelegationServices + "," + Convert.ToString(delegateto);
|
|||
|
}
|
|||
|
DelegationServices = DelegationServices.TrimStart(',');
|
|||
|
}
|
|||
|
if ((bool) TrustedtoAuthforDelegation)
|
|||
|
{
|
|||
|
DelegationProtocol = "Any";
|
|||
|
}
|
|||
|
else if (DelegationType != null)
|
|||
|
{
|
|||
|
DelegationProtocol = "Kerberos";
|
|||
|
}
|
|||
|
string SIDHistory = "";
|
|||
|
if (AdComputer.Properties["sidhistory"].Count >= 1)
|
|||
|
{
|
|||
|
string sids = "";
|
|||
|
for (int i = 0; i < AdComputer.Properties["sidhistory"].Count; i++)
|
|||
|
{
|
|||
|
var history = AdComputer.Properties["sidhistory"][i];
|
|||
|
sids = sids + "," + Convert.ToString(new SecurityIdentifier((byte[])history, 0));
|
|||
|
}
|
|||
|
SIDHistory = sids.TrimStart(',');
|
|||
|
}
|
|||
|
String OperatingSystem = CleanString((AdComputer.Properties["operatingsystem"].Count != 0 ? AdComputer.Properties["operatingsystem"][0] : "-") + " " + (AdComputer.Properties["operatingsystemhotfix"].Count != 0 ? AdComputer.Properties["operatingsystemhotfix"][0] : " ") + " " + (AdComputer.Properties["operatingsystemservicepack"].Count != 0 ? AdComputer.Properties["operatingsystemservicepack"][0] : " ") + " " + (AdComputer.Properties["operatingsystemversion"].Count != 0 ? AdComputer.Properties["operatingsystemversion"][0] : " "));
|
|||
|
|
|||
|
PSObject ComputerObj = new PSObject();
|
|||
|
ComputerObj.Members.Add(new PSNoteProperty("Name", (AdComputer.Properties["name"].Count != 0 ? AdComputer.Properties["name"][0] : "")));
|
|||
|
ComputerObj.Members.Add(new PSNoteProperty("DNSHostName", (AdComputer.Properties["dnshostname"].Count != 0 ? AdComputer.Properties["dnshostname"][0] : "")));
|
|||
|
ComputerObj.Members.Add(new PSNoteProperty("Enabled", Enabled));
|
|||
|
ComputerObj.Members.Add(new PSNoteProperty("IPv4Address", StrIPAddress));
|
|||
|
ComputerObj.Members.Add(new PSNoteProperty("Operating System", OperatingSystem));
|
|||
|
ComputerObj.Members.Add(new PSNoteProperty("Logon Age (days)", DaysSinceLastLogon));
|
|||
|
ComputerObj.Members.Add(new PSNoteProperty("Password Age (days)", DaysSinceLastPasswordChange));
|
|||
|
ComputerObj.Members.Add(new PSNoteProperty("Dormant (> " + DormantTimeSpan + " days)", Dormant));
|
|||
|
ComputerObj.Members.Add(new PSNoteProperty("Password Age (> " + PassMaxAge + " days)", PasswordNotChangedafterMaxAge));
|
|||
|
ComputerObj.Members.Add(new PSNoteProperty("Delegation Type", DelegationType));
|
|||
|
ComputerObj.Members.Add(new PSNoteProperty("Delegation Protocol", DelegationProtocol));
|
|||
|
ComputerObj.Members.Add(new PSNoteProperty("Delegation Services", DelegationServices));
|
|||
|
ComputerObj.Members.Add(new PSNoteProperty("UserName", (AdComputer.Properties["samaccountname"].Count != 0 ? AdComputer.Properties["samaccountname"][0] : "")));
|
|||
|
ComputerObj.Members.Add(new PSNoteProperty("Primary Group ID", (AdComputer.Properties["primarygroupid"].Count != 0 ? AdComputer.Properties["primarygroupid"][0] : "")));
|
|||
|
ComputerObj.Members.Add(new PSNoteProperty("SID", Convert.ToString(new SecurityIdentifier((byte[])AdComputer.Properties["objectSID"][0], 0))));
|
|||
|
ComputerObj.Members.Add(new PSNoteProperty("SIDHistory", SIDHistory));
|
|||
|
ComputerObj.Members.Add(new PSNoteProperty("Description", (AdComputer.Properties["Description"].Count != 0 ? AdComputer.Properties["Description"][0] : "")));
|
|||
|
ComputerObj.Members.Add(new PSNoteProperty("ms-ds-CreatorSid", (AdComputer.Properties["ms-ds-CreatorSid"].Count != 0 ? Convert.ToString(new SecurityIdentifier((byte[])AdComputer.Properties["ms-ds-CreatorSid"][0], 0)) : "")));
|
|||
|
ComputerObj.Members.Add(new PSNoteProperty("Last Logon Date", LastLogonDate));
|
|||
|
ComputerObj.Members.Add(new PSNoteProperty("Password LastSet", PasswordLastSet));
|
|||
|
ComputerObj.Members.Add(new PSNoteProperty("UserAccountControl", (AdComputer.Properties["useraccountcontrol"].Count != 0 ? AdComputer.Properties["useraccountcontrol"][0] : "")));
|
|||
|
ComputerObj.Members.Add(new PSNoteProperty("whenCreated", AdComputer.Properties["whencreated"][0]));
|
|||
|
ComputerObj.Members.Add(new PSNoteProperty("whenChanged", AdComputer.Properties["whenchanged"][0]));
|
|||
|
ComputerObj.Members.Add(new PSNoteProperty("Distinguished Name", AdComputer.Properties["distinguishedname"][0]));
|
|||
|
return new PSObject[] { ComputerObj };
|
|||
|
}
|
|||
|
catch (Exception e)
|
|||
|
{
|
|||
|
Console.WriteLine("{0} Exception caught.", e);
|
|||
|
return new PSObject[] { };
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
class ComputerSPNRecordProcessor : IRecordProcessor
|
|||
|
{
|
|||
|
public PSObject[] processRecord(Object record)
|
|||
|
{
|
|||
|
try
|
|||
|
{
|
|||
|
SearchResult AdComputer = (SearchResult) record;
|
|||
|
List<PSObject> SPNList = new List<PSObject>();
|
|||
|
|
|||
|
foreach (String SPN in AdComputer.Properties["serviceprincipalname"])
|
|||
|
{
|
|||
|
String[] SPNArray = SPN.Split('/');
|
|||
|
bool flag = true;
|
|||
|
foreach (PSObject Obj in SPNList)
|
|||
|
{
|
|||
|
if ( (String) Obj.Members["Service"].Value == SPNArray[0] )
|
|||
|
{
|
|||
|
Obj.Members["Host"].Value = String.Join(",", (Obj.Members["Host"].Value + "," + SPNArray[1]).Split(',').Distinct().ToArray());
|
|||
|
flag = false;
|
|||
|
}
|
|||
|
}
|
|||
|
if (flag)
|
|||
|
{
|
|||
|
PSObject ComputerSPNObj = new PSObject();
|
|||
|
ComputerSPNObj.Members.Add(new PSNoteProperty("Name", AdComputer.Properties["name"][0]));
|
|||
|
ComputerSPNObj.Members.Add(new PSNoteProperty("Service", SPNArray[0]));
|
|||
|
ComputerSPNObj.Members.Add(new PSNoteProperty("Host", SPNArray[1]));
|
|||
|
SPNList.Add( ComputerSPNObj );
|
|||
|
}
|
|||
|
}
|
|||
|
return SPNList.ToArray();
|
|||
|
}
|
|||
|
catch (Exception e)
|
|||
|
{
|
|||
|
Console.WriteLine("{0} Exception caught.", e);
|
|||
|
return new PSObject[] { };
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
class LAPSRecordProcessor : IRecordProcessor
|
|||
|
{
|
|||
|
public PSObject[] processRecord(Object record)
|
|||
|
{
|
|||
|
try
|
|||
|
{
|
|||
|
SearchResult AdComputer = (SearchResult) record;
|
|||
|
bool PasswordStored = false;
|
|||
|
DateTime? CurrentExpiration = null;
|
|||
|
if (AdComputer.Properties["ms-mcs-admpwdexpirationtime"].Count != 0)
|
|||
|
{
|
|||
|
CurrentExpiration = DateTime.FromFileTime((long)(AdComputer.Properties["ms-mcs-admpwdexpirationtime"][0]));
|
|||
|
PasswordStored = true;
|
|||
|
}
|
|||
|
PSObject LAPSObj = new PSObject();
|
|||
|
LAPSObj.Members.Add(new PSNoteProperty("Hostname", (AdComputer.Properties["dnshostname"].Count != 0 ? AdComputer.Properties["dnshostname"][0] : AdComputer.Properties["cn"][0] )));
|
|||
|
LAPSObj.Members.Add(new PSNoteProperty("Stored", PasswordStored));
|
|||
|
LAPSObj.Members.Add(new PSNoteProperty("Readable", (AdComputer.Properties["ms-mcs-admpwd"].Count != 0 ? true : false)));
|
|||
|
LAPSObj.Members.Add(new PSNoteProperty("Password", (AdComputer.Properties["ms-mcs-admpwd"].Count != 0 ? AdComputer.Properties["ms-mcs-admpwd"][0] : null)));
|
|||
|
LAPSObj.Members.Add(new PSNoteProperty("Expiration", CurrentExpiration));
|
|||
|
return new PSObject[] { LAPSObj };
|
|||
|
}
|
|||
|
catch (Exception e)
|
|||
|
{
|
|||
|
Console.WriteLine("{0} Exception caught.", e);
|
|||
|
return new PSObject[] { };
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
class SIDRecordDictionaryProcessor : IRecordProcessor
|
|||
|
{
|
|||
|
public PSObject[] processRecord(Object record)
|
|||
|
{
|
|||
|
try
|
|||
|
{
|
|||
|
SearchResult AdObject = (SearchResult) record;
|
|||
|
switch (Convert.ToString(AdObject.Properties["objectclass"][AdObject.Properties["objectclass"].Count-1]))
|
|||
|
{
|
|||
|
case "user":
|
|||
|
case "computer":
|
|||
|
case "group":
|
|||
|
LDAPClass.AdSIDDictionary.Add(Convert.ToString(new SecurityIdentifier((byte[])AdObject.Properties["objectSID"][0], 0)), (Convert.ToString(AdObject.Properties["name"][0])));
|
|||
|
break;
|
|||
|
}
|
|||
|
return new PSObject[] { };
|
|||
|
}
|
|||
|
catch (Exception e)
|
|||
|
{
|
|||
|
Console.WriteLine("{0} Exception caught.", e);
|
|||
|
return new PSObject[] { };
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
class DACLRecordProcessor : IRecordProcessor
|
|||
|
{
|
|||
|
public PSObject[] processRecord(Object record)
|
|||
|
{
|
|||
|
try
|
|||
|
{
|
|||
|
SearchResult AdObject = (SearchResult) record;
|
|||
|
byte[] ntSecurityDescriptor = null;
|
|||
|
String Name = null;
|
|||
|
String Type = null;
|
|||
|
List<PSObject> DACLList = new List<PSObject>();
|
|||
|
|
|||
|
Name = Convert.ToString(AdObject.Properties["name"][0]);
|
|||
|
|
|||
|
switch (Convert.ToString(AdObject.Properties["objectclass"][AdObject.Properties["objectclass"].Count-1]))
|
|||
|
{
|
|||
|
case "user":
|
|||
|
Type = "User";
|
|||
|
break;
|
|||
|
case "computer":
|
|||
|
Type = "Computer";
|
|||
|
break;
|
|||
|
case "group":
|
|||
|
Type = "Group";
|
|||
|
break;
|
|||
|
case "container":
|
|||
|
Type = "Container";
|
|||
|
break;
|
|||
|
case "groupPolicyContainer":
|
|||
|
Type = "GPO";
|
|||
|
Name = Convert.ToString(AdObject.Properties["displayname"][0]);
|
|||
|
break;
|
|||
|
case "organizationalUnit":
|
|||
|
Type = "OU";
|
|||
|
break;
|
|||
|
case "domainDNS":
|
|||
|
Type = "Domain";
|
|||
|
break;
|
|||
|
default:
|
|||
|
Type = Convert.ToString(AdObject.Properties["objectclass"][AdObject.Properties["objectclass"].Count-1]);
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
// When the user is not allowed to query the ntsecuritydescriptor attribute.
|
|||
|
if (AdObject.Properties["ntsecuritydescriptor"].Count != 0)
|
|||
|
{
|
|||
|
ntSecurityDescriptor = (byte[]) AdObject.Properties["ntsecuritydescriptor"][0];
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
DirectoryEntry AdObjectEntry = ((SearchResult)record).GetDirectoryEntry();
|
|||
|
ntSecurityDescriptor = (byte[]) AdObjectEntry.ObjectSecurity.GetSecurityDescriptorBinaryForm();
|
|||
|
}
|
|||
|
if (ntSecurityDescriptor != null)
|
|||
|
{
|
|||
|
DirectoryObjectSecurity DirObjSec = new ActiveDirectorySecurity();
|
|||
|
DirObjSec.SetSecurityDescriptorBinaryForm(ntSecurityDescriptor);
|
|||
|
AuthorizationRuleCollection AccessRules = (AuthorizationRuleCollection) DirObjSec.GetAccessRules(true,true,typeof(System.Security.Principal.NTAccount));
|
|||
|
foreach (ActiveDirectoryAccessRule Rule in AccessRules)
|
|||
|
{
|
|||
|
String IdentityReference = Convert.ToString(Rule.IdentityReference);
|
|||
|
String Owner = Convert.ToString(DirObjSec.GetOwner(typeof(System.Security.Principal.SecurityIdentifier)));
|
|||
|
PSObject ObjectObj = new PSObject();
|
|||
|
ObjectObj.Members.Add(new PSNoteProperty("Name", CleanString(Name)));
|
|||
|
ObjectObj.Members.Add(new PSNoteProperty("Type", Type));
|
|||
|
ObjectObj.Members.Add(new PSNoteProperty("ObjectTypeName", LDAPClass.GUIDs[Convert.ToString(Rule.ObjectType)]));
|
|||
|
ObjectObj.Members.Add(new PSNoteProperty("InheritedObjectTypeName", LDAPClass.GUIDs[Convert.ToString(Rule.InheritedObjectType)]));
|
|||
|
ObjectObj.Members.Add(new PSNoteProperty("ActiveDirectoryRights", Rule.ActiveDirectoryRights));
|
|||
|
ObjectObj.Members.Add(new PSNoteProperty("AccessControlType", Rule.AccessControlType));
|
|||
|
ObjectObj.Members.Add(new PSNoteProperty("IdentityReferenceName", LDAPClass.AdSIDDictionary.ContainsKey(IdentityReference) ? LDAPClass.AdSIDDictionary[IdentityReference] : IdentityReference));
|
|||
|
ObjectObj.Members.Add(new PSNoteProperty("OwnerName", LDAPClass.AdSIDDictionary.ContainsKey(Owner) ? LDAPClass.AdSIDDictionary[Owner] : Owner));
|
|||
|
ObjectObj.Members.Add(new PSNoteProperty("Inherited", Rule.IsInherited));
|
|||
|
ObjectObj.Members.Add(new PSNoteProperty("ObjectFlags", Rule.ObjectFlags));
|
|||
|
ObjectObj.Members.Add(new PSNoteProperty("InheritanceFlags", Rule.InheritanceFlags));
|
|||
|
ObjectObj.Members.Add(new PSNoteProperty("InheritanceType", Rule.InheritanceType));
|
|||
|
ObjectObj.Members.Add(new PSNoteProperty("PropagationFlags", Rule.PropagationFlags));
|
|||
|
ObjectObj.Members.Add(new PSNoteProperty("ObjectType", Rule.ObjectType));
|
|||
|
ObjectObj.Members.Add(new PSNoteProperty("InheritedObjectType", Rule.InheritedObjectType));
|
|||
|
ObjectObj.Members.Add(new PSNoteProperty("IdentityReference", Rule.IdentityReference));
|
|||
|
ObjectObj.Members.Add(new PSNoteProperty("Owner", Owner));
|
|||
|
ObjectObj.Members.Add(new PSNoteProperty("DistinguishedName", AdObject.Properties["distinguishedname"][0]));
|
|||
|
DACLList.Add( ObjectObj );
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return DACLList.ToArray();
|
|||
|
}
|
|||
|
catch (Exception e)
|
|||
|
{
|
|||
|
Console.WriteLine("{0} Exception caught.", e);
|
|||
|
return new PSObject[] { };
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
class SACLRecordProcessor : IRecordProcessor
|
|||
|
{
|
|||
|
public PSObject[] processRecord(Object record)
|
|||
|
{
|
|||
|
try
|
|||
|
{
|
|||
|
SearchResult AdObject = (SearchResult) record;
|
|||
|
byte[] ntSecurityDescriptor = null;
|
|||
|
String Name = null;
|
|||
|
String Type = null;
|
|||
|
List<PSObject> SACLList = new List<PSObject>();
|
|||
|
|
|||
|
Name = Convert.ToString(AdObject.Properties["name"][0]);
|
|||
|
|
|||
|
switch (Convert.ToString(AdObject.Properties["objectclass"][AdObject.Properties["objectclass"].Count-1]))
|
|||
|
{
|
|||
|
case "user":
|
|||
|
Type = "User";
|
|||
|
break;
|
|||
|
case "computer":
|
|||
|
Type = "Computer";
|
|||
|
break;
|
|||
|
case "group":
|
|||
|
Type = "Group";
|
|||
|
break;
|
|||
|
case "container":
|
|||
|
Type = "Container";
|
|||
|
break;
|
|||
|
case "groupPolicyContainer":
|
|||
|
Type = "GPO";
|
|||
|
Name = Convert.ToString(AdObject.Properties["displayname"][0]);
|
|||
|
break;
|
|||
|
case "organizationalUnit":
|
|||
|
Type = "OU";
|
|||
|
break;
|
|||
|
case "domainDNS":
|
|||
|
Type = "Domain";
|
|||
|
break;
|
|||
|
default:
|
|||
|
Type = Convert.ToString(AdObject.Properties["objectclass"][AdObject.Properties["objectclass"].Count-1]);
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
// When the user is not allowed to query the ntsecuritydescriptor attribute.
|
|||
|
if (AdObject.Properties["ntsecuritydescriptor"].Count != 0)
|
|||
|
{
|
|||
|
ntSecurityDescriptor = (byte[]) AdObject.Properties["ntsecuritydescriptor"][0];
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
DirectoryEntry AdObjectEntry = ((SearchResult)record).GetDirectoryEntry();
|
|||
|
ntSecurityDescriptor = (byte[]) AdObjectEntry.ObjectSecurity.GetSecurityDescriptorBinaryForm();
|
|||
|
}
|
|||
|
if (ntSecurityDescriptor != null)
|
|||
|
{
|
|||
|
DirectoryObjectSecurity DirObjSec = new ActiveDirectorySecurity();
|
|||
|
DirObjSec.SetSecurityDescriptorBinaryForm(ntSecurityDescriptor);
|
|||
|
AuthorizationRuleCollection AuditRules = (AuthorizationRuleCollection) DirObjSec.GetAuditRules(true,true,typeof(System.Security.Principal.NTAccount));
|
|||
|
foreach (ActiveDirectoryAuditRule Rule in AuditRules)
|
|||
|
{
|
|||
|
String IdentityReference = Convert.ToString(Rule.IdentityReference);
|
|||
|
PSObject ObjectObj = new PSObject();
|
|||
|
ObjectObj.Members.Add(new PSNoteProperty("Name", CleanString(Name)));
|
|||
|
ObjectObj.Members.Add(new PSNoteProperty("Type", Type));
|
|||
|
ObjectObj.Members.Add(new PSNoteProperty("ObjectTypeName", LDAPClass.GUIDs[Convert.ToString(Rule.ObjectType)]));
|
|||
|
ObjectObj.Members.Add(new PSNoteProperty("InheritedObjectTypeName", LDAPClass.GUIDs[Convert.ToString(Rule.InheritedObjectType)]));
|
|||
|
ObjectObj.Members.Add(new PSNoteProperty("ActiveDirectoryRights", Rule.ActiveDirectoryRights));
|
|||
|
ObjectObj.Members.Add(new PSNoteProperty("IdentityReferenceName", LDAPClass.AdSIDDictionary.ContainsKey(IdentityReference) ? LDAPClass.AdSIDDictionary[IdentityReference] : IdentityReference));
|
|||
|
ObjectObj.Members.Add(new PSNoteProperty("AuditFlags", Rule.AuditFlags));
|
|||
|
ObjectObj.Members.Add(new PSNoteProperty("ObjectFlags", Rule.ObjectFlags));
|
|||
|
ObjectObj.Members.Add(new PSNoteProperty("InheritanceFlags", Rule.InheritanceFlags));
|
|||
|
ObjectObj.Members.Add(new PSNoteProperty("InheritanceType", Rule.InheritanceType));
|
|||
|
ObjectObj.Members.Add(new PSNoteProperty("Inherited", Rule.IsInherited));
|
|||
|
ObjectObj.Members.Add(new PSNoteProperty("PropagationFlags", Rule.PropagationFlags));
|
|||
|
ObjectObj.Members.Add(new PSNoteProperty("ObjectType", Rule.ObjectType));
|
|||
|
ObjectObj.Members.Add(new PSNoteProperty("InheritedObjectType", Rule.InheritedObjectType));
|
|||
|
ObjectObj.Members.Add(new PSNoteProperty("IdentityReference", Rule.IdentityReference));
|
|||
|
SACLList.Add( ObjectObj );
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return SACLList.ToArray();
|
|||
|
}
|
|||
|
catch (Exception e)
|
|||
|
{
|
|||
|
Console.WriteLine("{0} Exception caught.", e);
|
|||
|
return new PSObject[] { };
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
//The interface and implmentation class used to handle the results (this implementation just writes the strings to a file)
|
|||
|
|
|||
|
interface IResultsHandler
|
|||
|
{
|
|||
|
void processResults(Object[] t);
|
|||
|
|
|||
|
Object[] finalise();
|
|||
|
}
|
|||
|
|
|||
|
class SimpleResultsHandler : IResultsHandler
|
|||
|
{
|
|||
|
private Object lockObj = new Object();
|
|||
|
private List<Object> processed = new List<Object>();
|
|||
|
|
|||
|
public SimpleResultsHandler()
|
|||
|
{
|
|||
|
}
|
|||
|
|
|||
|
public void processResults(Object[] results)
|
|||
|
{
|
|||
|
lock (lockObj)
|
|||
|
{
|
|||
|
if (results.Length != 0)
|
|||
|
{
|
|||
|
for (var i = 0; i < results.Length; i++)
|
|||
|
{
|
|||
|
processed.Add((PSObject)results[i]);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
public Object[] finalise()
|
|||
|
{
|
|||
|
return processed.ToArray();
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
"@
|
|||
|
|
|||
|
#Add-Type -TypeDefinition $Source -ReferencedAssemblies ([System.String[]]@(([system.reflection.assembly]::LoadWithPartialName("Microsoft.ActiveDirectory.Management")).Location,([system.reflection.assembly]::LoadWithPartialName("System.DirectoryServices")).Location))
|
|||
|
|
|||
|
# modified version from https://github.com/vletoux/SmbScanner/blob/master/smbscanner.ps1
|
|||
|
$PingCastleSMBScannerSource = @"
|
|||
|
using System;
|
|||
|
using System.Collections.Generic;
|
|||
|
using System.Diagnostics;
|
|||
|
using System.IO;
|
|||
|
using System.Net;
|
|||
|
using System.Net.Sockets;
|
|||
|
using System.Text;
|
|||
|
using System.Runtime.InteropServices;
|
|||
|
using System.Management.Automation;
|
|||
|
|
|||
|
namespace ADRecon
|
|||
|
{
|
|||
|
public class PingCastleScannersSMBScanner
|
|||
|
{
|
|||
|
[StructLayout(LayoutKind.Explicit)]
|
|||
|
struct SMB_Header {
|
|||
|
[FieldOffset(0)]
|
|||
|
public UInt32 Protocol;
|
|||
|
[FieldOffset(4)]
|
|||
|
public byte Command;
|
|||
|
[FieldOffset(5)]
|
|||
|
public int Status;
|
|||
|
[FieldOffset(9)]
|
|||
|
public byte Flags;
|
|||
|
[FieldOffset(10)]
|
|||
|
public UInt16 Flags2;
|
|||
|
[FieldOffset(12)]
|
|||
|
public UInt16 PIDHigh;
|
|||
|
[FieldOffset(14)]
|
|||
|
public UInt64 SecurityFeatures;
|
|||
|
[FieldOffset(22)]
|
|||
|
public UInt16 Reserved;
|
|||
|
[FieldOffset(24)]
|
|||
|
public UInt16 TID;
|
|||
|
[FieldOffset(26)]
|
|||
|
public UInt16 PIDLow;
|
|||
|
[FieldOffset(28)]
|
|||
|
public UInt16 UID;
|
|||
|
[FieldOffset(30)]
|
|||
|
public UInt16 MID;
|
|||
|
};
|
|||
|
// https://msdn.microsoft.com/en-us/library/cc246529.aspx
|
|||
|
[StructLayout(LayoutKind.Explicit)]
|
|||
|
struct SMB2_Header {
|
|||
|
[FieldOffset(0)]
|
|||
|
public UInt32 ProtocolId;
|
|||
|
[FieldOffset(4)]
|
|||
|
public UInt16 StructureSize;
|
|||
|
[FieldOffset(6)]
|
|||
|
public UInt16 CreditCharge;
|
|||
|
[FieldOffset(8)]
|
|||
|
public UInt32 Status; // to do SMB3
|
|||
|
[FieldOffset(12)]
|
|||
|
public UInt16 Command;
|
|||
|
[FieldOffset(14)]
|
|||
|
public UInt16 CreditRequest_Response;
|
|||
|
[FieldOffset(16)]
|
|||
|
public UInt32 Flags;
|
|||
|
[FieldOffset(20)]
|
|||
|
public UInt32 NextCommand;
|
|||
|
[FieldOffset(24)]
|
|||
|
public UInt64 MessageId;
|
|||
|
[FieldOffset(32)]
|
|||
|
public UInt32 Reserved;
|
|||
|
[FieldOffset(36)]
|
|||
|
public UInt32 TreeId;
|
|||
|
[FieldOffset(40)]
|
|||
|
public UInt64 SessionId;
|
|||
|
[FieldOffset(48)]
|
|||
|
public UInt64 Signature1;
|
|||
|
[FieldOffset(56)]
|
|||
|
public UInt64 Signature2;
|
|||
|
}
|
|||
|
[StructLayout(LayoutKind.Explicit)]
|
|||
|
struct SMB2_NegotiateRequest
|
|||
|
{
|
|||
|
[FieldOffset(0)]
|
|||
|
public UInt16 StructureSize;
|
|||
|
[FieldOffset(2)]
|
|||
|
public UInt16 DialectCount;
|
|||
|
[FieldOffset(4)]
|
|||
|
public UInt16 SecurityMode;
|
|||
|
[FieldOffset(6)]
|
|||
|
public UInt16 Reserved;
|
|||
|
[FieldOffset(8)]
|
|||
|
public UInt32 Capabilities;
|
|||
|
[FieldOffset(12)]
|
|||
|
public Guid ClientGuid;
|
|||
|
[FieldOffset(28)]
|
|||
|
public UInt64 ClientStartTime;
|
|||
|
[FieldOffset(36)]
|
|||
|
public UInt16 DialectToTest;
|
|||
|
}
|
|||
|
const int SMB_COM_NEGOTIATE = 0x72;
|
|||
|
const int SMB2_NEGOTIATE = 0;
|
|||
|
const int SMB_FLAGS_CASE_INSENSITIVE = 0x08;
|
|||
|
const int SMB_FLAGS_CANONICALIZED_PATHS = 0x10;
|
|||
|
const int SMB_FLAGS2_LONG_NAMES = 0x0001;
|
|||
|
const int SMB_FLAGS2_EAS = 0x0002;
|
|||
|
const int SMB_FLAGS2_SECURITY_SIGNATURE_REQUIRED = 0x0010 ;
|
|||
|
const int SMB_FLAGS2_IS_LONG_NAME = 0x0040;
|
|||
|
const int SMB_FLAGS2_ESS = 0x0800;
|
|||
|
const int SMB_FLAGS2_NT_STATUS = 0x4000;
|
|||
|
const int SMB_FLAGS2_UNICODE = 0x8000;
|
|||
|
const int SMB_DB_FORMAT_DIALECT = 0x02;
|
|||
|
static byte[] GenerateSmbHeaderFromCommand(byte command)
|
|||
|
{
|
|||
|
SMB_Header header = new SMB_Header();
|
|||
|
header.Protocol = 0x424D53FF;
|
|||
|
header.Command = command;
|
|||
|
header.Status = 0;
|
|||
|
header.Flags = SMB_FLAGS_CASE_INSENSITIVE | SMB_FLAGS_CANONICALIZED_PATHS;
|
|||
|
header.Flags2 = SMB_FLAGS2_LONG_NAMES | SMB_FLAGS2_EAS | SMB_FLAGS2_SECURITY_SIGNATURE_REQUIRED | SMB_FLAGS2_IS_LONG_NAME | SMB_FLAGS2_ESS | SMB_FLAGS2_NT_STATUS | SMB_FLAGS2_UNICODE;
|
|||
|
header.PIDHigh = 0;
|
|||
|
header.SecurityFeatures = 0;
|
|||
|
header.Reserved = 0;
|
|||
|
header.TID = 0xffff;
|
|||
|
header.PIDLow = 0xFEFF;
|
|||
|
header.UID = 0;
|
|||
|
header.MID = 0;
|
|||
|
return getBytes(header);
|
|||
|
}
|
|||
|
static byte[] GenerateSmb2HeaderFromCommand(byte command)
|
|||
|
{
|
|||
|
SMB2_Header header = new SMB2_Header();
|
|||
|
header.ProtocolId = 0x424D53FE;
|
|||
|
header.Command = command;
|
|||
|
header.StructureSize = 64;
|
|||
|
header.Command = command;
|
|||
|
header.MessageId = 0;
|
|||
|
header.Reserved = 0xFEFF;
|
|||
|
return getBytes(header);
|
|||
|
}
|
|||
|
static byte[] getBytes(object structure)
|
|||
|
{
|
|||
|
int size = Marshal.SizeOf(structure);
|
|||
|
byte[] arr = new byte[size];
|
|||
|
IntPtr ptr = Marshal.AllocHGlobal(size);
|
|||
|
Marshal.StructureToPtr(structure, ptr, true);
|
|||
|
Marshal.Copy(ptr, arr, 0, size);
|
|||
|
Marshal.FreeHGlobal(ptr);
|
|||
|
return arr;
|
|||
|
}
|
|||
|
static byte[] getDialect(string dialect)
|
|||
|
{
|
|||
|
byte[] dialectBytes = Encoding.ASCII.GetBytes(dialect);
|
|||
|
byte[] output = new byte[dialectBytes.Length + 2];
|
|||
|
output[0] = 2;
|
|||
|
output[output.Length - 1] = 0;
|
|||
|
Array.Copy(dialectBytes, 0, output, 1, dialectBytes.Length);
|
|||
|
return output;
|
|||
|
}
|
|||
|
static byte[] GetNegotiateMessage(byte[] dialect)
|
|||
|
{
|
|||
|
byte[] output = new byte[dialect.Length + 3];
|
|||
|
output[0] = 0;
|
|||
|
output[1] = (byte) dialect.Length;
|
|||
|
output[2] = 0;
|
|||
|
Array.Copy(dialect, 0, output, 3, dialect.Length);
|
|||
|
return output;
|
|||
|
}
|
|||
|
// MS-SMB2 2.2.3 SMB2 NEGOTIATE Request
|
|||
|
static byte[] GetNegotiateMessageSmbv2(int DialectToTest)
|
|||
|
{
|
|||
|
SMB2_NegotiateRequest request = new SMB2_NegotiateRequest();
|
|||
|
request.StructureSize = 36;
|
|||
|
request.DialectCount = 1;
|
|||
|
request.SecurityMode = 1; // signing enabled
|
|||
|
request.ClientGuid = Guid.NewGuid();
|
|||
|
request.DialectToTest = (UInt16) DialectToTest;
|
|||
|
return getBytes(request);
|
|||
|
}
|
|||
|
static byte[] GetNegotiatePacket(byte[] header, byte[] smbPacket)
|
|||
|
{
|
|||
|
byte[] output = new byte[smbPacket.Length + header.Length + 4];
|
|||
|
output[0] = 0;
|
|||
|
output[1] = 0;
|
|||
|
output[2] = 0;
|
|||
|
output[3] = (byte)(smbPacket.Length + header.Length);
|
|||
|
Array.Copy(header, 0, output, 4, header.Length);
|
|||
|
Array.Copy(smbPacket, 0, output, 4 + header.Length, smbPacket.Length);
|
|||
|
return output;
|
|||
|
}
|
|||
|
public static bool DoesServerSupportDialect(string server, string dialect)
|
|||
|
{
|
|||
|
Trace.WriteLine("Checking " + server + " for SMBV1 dialect " + dialect);
|
|||
|
TcpClient client = new TcpClient();
|
|||
|
try
|
|||
|
{
|
|||
|
client.Connect(server, 445);
|
|||
|
}
|
|||
|
catch (Exception)
|
|||
|
{
|
|||
|
throw new Exception("port 445 is closed on " + server);
|
|||
|
}
|
|||
|
try
|
|||
|
{
|
|||
|
NetworkStream stream = client.GetStream();
|
|||
|
byte[] header = GenerateSmbHeaderFromCommand(SMB_COM_NEGOTIATE);
|
|||
|
byte[] dialectEncoding = getDialect(dialect);
|
|||
|
byte[] negotiatemessage = GetNegotiateMessage(dialectEncoding);
|
|||
|
byte[] packet = GetNegotiatePacket(header, negotiatemessage);
|
|||
|
stream.Write(packet, 0, packet.Length);
|
|||
|
stream.Flush();
|
|||
|
byte[] netbios = new byte[4];
|
|||
|
if (stream.Read(netbios, 0, netbios.Length) != netbios.Length)
|
|||
|
{
|
|||
|
return false;
|
|||
|
}
|
|||
|
byte[] smbHeader = new byte[Marshal.SizeOf(typeof(SMB_Header))];
|
|||
|
if (stream.Read(smbHeader, 0, smbHeader.Length) != smbHeader.Length)
|
|||
|
{
|
|||
|
return false;
|
|||
|
}
|
|||
|
byte[] negotiateresponse = new byte[3];
|
|||
|
if (stream.Read(negotiateresponse, 0, negotiateresponse.Length) != negotiateresponse.Length)
|
|||
|
{
|
|||
|
return false;
|
|||
|
}
|
|||
|
if (negotiateresponse[1] == 0 && negotiateresponse[2] == 0)
|
|||
|
{
|
|||
|
Trace.WriteLine("Checking " + server + " for SMBV1 dialect " + dialect + " = Supported");
|
|||
|
return true;
|
|||
|
}
|
|||
|
Trace.WriteLine("Checking " + server + " for SMBV1 dialect " + dialect + " = Not supported");
|
|||
|
return false;
|
|||
|
}
|
|||
|
catch (Exception)
|
|||
|
{
|
|||
|
throw new ApplicationException("Smb1 is not supported on " + server);
|
|||
|
}
|
|||
|
}
|
|||
|
public static bool DoesServerSupportDialectWithSmbV2(string server, int dialect, bool checkSMBSigning)
|
|||
|
{
|
|||
|
Trace.WriteLine("Checking " + server + " for SMBV2 dialect 0x" + dialect.ToString("X2"));
|
|||
|
TcpClient client = new TcpClient();
|
|||
|
try
|
|||
|
{
|
|||
|
client.Connect(server, 445);
|
|||
|
}
|
|||
|
catch (Exception)
|
|||
|
{
|
|||
|
throw new Exception("port 445 is closed on " + server);
|
|||
|
}
|
|||
|
try
|
|||
|
{
|
|||
|
NetworkStream stream = client.GetStream();
|
|||
|
byte[] header = GenerateSmb2HeaderFromCommand(SMB2_NEGOTIATE);
|
|||
|
byte[] negotiatemessage = GetNegotiateMessageSmbv2(dialect);
|
|||
|
byte[] packet = GetNegotiatePacket(header, negotiatemessage);
|
|||
|
stream.Write(packet, 0, packet.Length);
|
|||
|
stream.Flush();
|
|||
|
byte[] netbios = new byte[4];
|
|||
|
if( stream.Read(netbios, 0, netbios.Length) != netbios.Length)
|
|||
|
{
|
|||
|
return false;
|
|||
|
}
|
|||
|
byte[] smbHeader = new byte[Marshal.SizeOf(typeof(SMB2_Header))];
|
|||
|
if (stream.Read(smbHeader, 0, smbHeader.Length) != smbHeader.Length)
|
|||
|
{
|
|||
|
return false;
|
|||
|
}
|
|||
|
if (smbHeader[8] != 0 || smbHeader[9] != 0 || smbHeader[10] != 0 || smbHeader[11] != 0)
|
|||
|
{
|
|||
|
Trace.WriteLine("Checking " + server + " for SMBV2 dialect 0x" + dialect.ToString("X2") + " = Not supported via error code");
|
|||
|
return false;
|
|||
|
}
|
|||
|
byte[] negotiateresponse = new byte[6];
|
|||
|
if (stream.Read(negotiateresponse, 0, negotiateresponse.Length) != negotiateresponse.Length)
|
|||
|
{
|
|||
|
return false;
|
|||
|
}
|
|||
|
if (checkSMBSigning)
|
|||
|
{
|
|||
|
// https://support.microsoft.com/en-in/help/887429/overview-of-server-message-block-signing
|
|||
|
// https://msdn.microsoft.com/en-us/library/cc246561.aspx
|
|||
|
if (negotiateresponse[2] == 3)
|
|||
|
{
|
|||
|
Trace.WriteLine("Checking " + server + " for SMBV2 SMB Signing dialect 0x" + dialect.ToString("X2") + " = Supported");
|
|||
|
return true;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
return false;
|
|||
|
}
|
|||
|
}
|
|||
|
int selectedDialect = negotiateresponse[5] * 0x100 + negotiateresponse[4];
|
|||
|
if (selectedDialect == dialect)
|
|||
|
{
|
|||
|
Trace.WriteLine("Checking " + server + " for SMBV2 dialect 0x" + dialect.ToString("X2") + " = Supported");
|
|||
|
return true;
|
|||
|
}
|
|||
|
Trace.WriteLine("Checking " + server + " for SMBV2 dialect 0x" + dialect.ToString("X2") + " = Not supported via not returned dialect");
|
|||
|
return false;
|
|||
|
}
|
|||
|
catch (Exception)
|
|||
|
{
|
|||
|
throw new ApplicationException("Smb2 is not supported on " + server);
|
|||
|
}
|
|||
|
}
|
|||
|
public static bool SupportSMB1(string server)
|
|||
|
{
|
|||
|
try
|
|||
|
{
|
|||
|
return DoesServerSupportDialect(server, "NT LM 0.12");
|
|||
|
}
|
|||
|
catch (Exception)
|
|||
|
{
|
|||
|
return false;
|
|||
|
}
|
|||
|
}
|
|||
|
public static bool SupportSMB2(string server)
|
|||
|
{
|
|||
|
try
|
|||
|
{
|
|||
|
return (DoesServerSupportDialectWithSmbV2(server, 0x0202, false) || DoesServerSupportDialectWithSmbV2(server, 0x0210, false));
|
|||
|
}
|
|||
|
catch (Exception)
|
|||
|
{
|
|||
|
return false;
|
|||
|
}
|
|||
|
}
|
|||
|
public static bool SupportSMB3(string server)
|
|||
|
{
|
|||
|
try
|
|||
|
{
|
|||
|
return (DoesServerSupportDialectWithSmbV2(server, 0x0300, false) || DoesServerSupportDialectWithSmbV2(server, 0x0302, false) || DoesServerSupportDialectWithSmbV2(server, 0x0311, false));
|
|||
|
}
|
|||
|
catch (Exception)
|
|||
|
{
|
|||
|
return false;
|
|||
|
}
|
|||
|
}
|
|||
|
public static string Name { get { return "smb"; } }
|
|||
|
public static PSObject GetPSObject(string computer)
|
|||
|
{
|
|||
|
PSObject DCSMBObj = new PSObject();
|
|||
|
if (computer == "")
|
|||
|
{
|
|||
|
DCSMBObj.Members.Add(new PSNoteProperty("SMB Port Open", null));
|
|||
|
DCSMBObj.Members.Add(new PSNoteProperty("SMB1(NT LM 0.12)", null));
|
|||
|
DCSMBObj.Members.Add(new PSNoteProperty("SMB2(0x0202)", null));
|
|||
|
DCSMBObj.Members.Add(new PSNoteProperty("SMB2(0x0210)", null));
|
|||
|
DCSMBObj.Members.Add(new PSNoteProperty("SMB3(0x0300)", null));
|
|||
|
DCSMBObj.Members.Add(new PSNoteProperty("SMB3(0x0302)", null));
|
|||
|
DCSMBObj.Members.Add(new PSNoteProperty("SMB3(0x0311)", null));
|
|||
|
DCSMBObj.Members.Add(new PSNoteProperty("SMB Signing", null));
|
|||
|
return DCSMBObj;
|
|||
|
}
|
|||
|
bool isPortOpened = true;
|
|||
|
bool SMBv1 = false;
|
|||
|
bool SMBv2_0x0202 = false;
|
|||
|
bool SMBv2_0x0210 = false;
|
|||
|
bool SMBv3_0x0300 = false;
|
|||
|
bool SMBv3_0x0302 = false;
|
|||
|
bool SMBv3_0x0311 = false;
|
|||
|
bool SMBSigning = false;
|
|||
|
try
|
|||
|
{
|
|||
|
try
|
|||
|
{
|
|||
|
SMBv1 = DoesServerSupportDialect(computer, "NT LM 0.12");
|
|||
|
}
|
|||
|
catch (ApplicationException)
|
|||
|
{
|
|||
|
}
|
|||
|
try
|
|||
|
{
|
|||
|
SMBv2_0x0202 = DoesServerSupportDialectWithSmbV2(computer, 0x0202, false);
|
|||
|
SMBv2_0x0210 = DoesServerSupportDialectWithSmbV2(computer, 0x0210, false);
|
|||
|
SMBv3_0x0300 = DoesServerSupportDialectWithSmbV2(computer, 0x0300, false);
|
|||
|
SMBv3_0x0302 = DoesServerSupportDialectWithSmbV2(computer, 0x0302, false);
|
|||
|
SMBv3_0x0311 = DoesServerSupportDialectWithSmbV2(computer, 0x0311, false);
|
|||
|
}
|
|||
|
catch (ApplicationException)
|
|||
|
{
|
|||
|
}
|
|||
|
}
|
|||
|
catch (Exception)
|
|||
|
{
|
|||
|
isPortOpened = false;
|
|||
|
}
|
|||
|
if (SMBv3_0x0311)
|
|||
|
{
|
|||
|
SMBSigning = DoesServerSupportDialectWithSmbV2(computer, 0x0311, true);
|
|||
|
}
|
|||
|
else if (SMBv3_0x0302)
|
|||
|
{
|
|||
|
SMBSigning = DoesServerSupportDialectWithSmbV2(computer, 0x0302, true);
|
|||
|
}
|
|||
|
else if (SMBv3_0x0300)
|
|||
|
{
|
|||
|
SMBSigning = DoesServerSupportDialectWithSmbV2(computer, 0x0300, true);
|
|||
|
}
|
|||
|
else if (SMBv2_0x0210)
|
|||
|
{
|
|||
|
SMBSigning = DoesServerSupportDialectWithSmbV2(computer, 0x0210, true);
|
|||
|
}
|
|||
|
else if (SMBv2_0x0202)
|
|||
|
{
|
|||
|
SMBSigning = DoesServerSupportDialectWithSmbV2(computer, 0x0202, true);
|
|||
|
}
|
|||
|
DCSMBObj.Members.Add(new PSNoteProperty("SMB Port Open", isPortOpened));
|
|||
|
DCSMBObj.Members.Add(new PSNoteProperty("SMB1(NT LM 0.12)", SMBv1));
|
|||
|
DCSMBObj.Members.Add(new PSNoteProperty("SMB2(0x0202)", SMBv2_0x0202));
|
|||
|
DCSMBObj.Members.Add(new PSNoteProperty("SMB2(0x0210)", SMBv2_0x0210));
|
|||
|
DCSMBObj.Members.Add(new PSNoteProperty("SMB3(0x0300)", SMBv3_0x0300));
|
|||
|
DCSMBObj.Members.Add(new PSNoteProperty("SMB3(0x0302)", SMBv3_0x0302));
|
|||
|
DCSMBObj.Members.Add(new PSNoteProperty("SMB3(0x0311)", SMBv3_0x0311));
|
|||
|
DCSMBObj.Members.Add(new PSNoteProperty("SMB Signing", SMBSigning));
|
|||
|
return DCSMBObj;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
"@
|
|||
|
|
|||
|
# Import the LogonUser, ImpersonateLoggedOnUser and RevertToSelf Functions from advapi32.dll and the CloseHandle Function from kernel32.dll
|
|||
|
# https://docs.microsoft.com/en-gb/powershell/module/Microsoft.PowerShell.Utility/Add-Type?view=powershell-5.1
|
|||
|
# https://msdn.microsoft.com/en-us/library/windows/desktop/aa378184(v=vs.85).aspx
|
|||
|
# https://msdn.microsoft.com/en-us/library/windows/desktop/aa378612(v=vs.85).aspx
|
|||
|
# https://msdn.microsoft.com/en-us/library/windows/desktop/aa379317(v=vs.85).aspx
|
|||
|
|
|||
|
$Advapi32Def = @'
|
|||
|
[DllImport("advapi32.dll", SetLastError = true)]
|
|||
|
public static extern bool LogonUser(string lpszUsername, string lpszDomain, string lpszPassword, int dwLogonType, int dwLogonProvider, out IntPtr phToken);
|
|||
|
|
|||
|
[DllImport("advapi32.dll", SetLastError = true)]
|
|||
|
public static extern bool ImpersonateLoggedOnUser(IntPtr hToken);
|
|||
|
|
|||
|
[DllImport("advapi32.dll", SetLastError = true)]
|
|||
|
public static extern bool RevertToSelf();
|
|||
|
'@
|
|||
|
|
|||
|
# https://msdn.microsoft.com/en-us/library/windows/desktop/ms724211(v=vs.85).aspx
|
|||
|
|
|||
|
$Kernel32Def = @'
|
|||
|
[DllImport("kernel32.dll", SetLastError = true)]
|
|||
|
public static extern bool CloseHandle(IntPtr hObject);
|
|||
|
'@
|
|||
|
|
|||
|
Function Get-DateDiff
|
|||
|
{
|
|||
|
<#
|
|||
|
.SYNOPSIS
|
|||
|
Get difference between two dates.
|
|||
|
|
|||
|
.DESCRIPTION
|
|||
|
Returns the difference between two dates.
|
|||
|
|
|||
|
.PARAMETER Date1
|
|||
|
[DateTime]
|
|||
|
Date
|
|||
|
|
|||
|
.PARAMETER Date2
|
|||
|
[DateTime]
|
|||
|
Date
|
|||
|
|
|||
|
.OUTPUTS
|
|||
|
[System.ValueType.TimeSpan]
|
|||
|
Returns the difference between the two dates.
|
|||
|
#>
|
|||
|
param (
|
|||
|
[Parameter(Mandatory = $true)]
|
|||
|
[DateTime] $Date1,
|
|||
|
|
|||
|
[Parameter(Mandatory = $true)]
|
|||
|
[DateTime] $Date2
|
|||
|
)
|
|||
|
|
|||
|
If ($Date2 -gt $Date1)
|
|||
|
{
|
|||
|
$DDiff = $Date2 - $Date1
|
|||
|
}
|
|||
|
Else
|
|||
|
{
|
|||
|
$DDiff = $Date1 - $Date2
|
|||
|
}
|
|||
|
Return $DDiff
|
|||
|
}
|
|||
|
|
|||
|
Function Get-DNtoFQDN
|
|||
|
{
|
|||
|
<#
|
|||
|
.SYNOPSIS
|
|||
|
Gets Domain Distinguished Name (DN) from the Fully Qualified Domain Name (FQDN).
|
|||
|
|
|||
|
.DESCRIPTION
|
|||
|
Converts Domain Distinguished Name (DN) to Fully Qualified Domain Name (FQDN).
|
|||
|
|
|||
|
.PARAMETER ADObjectDN
|
|||
|
[string]
|
|||
|
Domain Distinguished Name (DN)
|
|||
|
|
|||
|
.OUTPUTS
|
|||
|
[String]
|
|||
|
Returns the Fully Qualified Domain Name (FQDN).
|
|||
|
|
|||
|
.LINK
|
|||
|
https://adsecurity.org/?p=440
|
|||
|
#>
|
|||
|
param(
|
|||
|
[Parameter(Mandatory = $true)]
|
|||
|
[string] $ADObjectDN
|
|||
|
)
|
|||
|
|
|||
|
$Index = $ADObjectDN.IndexOf('DC=')
|
|||
|
If ($Index)
|
|||
|
{
|
|||
|
$ADObjectDNDomainName = $($ADObjectDN.SubString($Index)) -replace 'DC=','' -replace ',','.'
|
|||
|
}
|
|||
|
Else
|
|||
|
{
|
|||
|
# Modified version from https://adsecurity.org/?p=440
|
|||
|
[array] $ADObjectDNArray = $ADObjectDN -Split ("DC=")
|
|||
|
$ADObjectDNArray | ForEach-Object {
|
|||
|
[array] $temp = $_ -Split (",")
|
|||
|
[string] $ADObjectDNArrayItemDomainName += $temp[0] + "."
|
|||
|
}
|
|||
|
$ADObjectDNDomainName = $ADObjectDNArrayItemDomainName.Substring(1, $ADObjectDNArrayItemDomainName.Length - 2)
|
|||
|
}
|
|||
|
Return $ADObjectDNDomainName
|
|||
|
}
|
|||
|
|
|||
|
Function Export-ADRCSV
|
|||
|
{
|
|||
|
<#
|
|||
|
.SYNOPSIS
|
|||
|
Exports Object to a CSV file.
|
|||
|
|
|||
|
.DESCRIPTION
|
|||
|
Exports Object to a CSV file using Export-CSV.
|
|||
|
|
|||
|
.PARAMETER ADRObj
|
|||
|
[PSObject]
|
|||
|
ADRObj
|
|||
|
|
|||
|
.PARAMETER ADFileName
|
|||
|
[String]
|
|||
|
Path to save the CSV File.
|
|||
|
|
|||
|
.OUTPUTS
|
|||
|
CSV file.
|
|||
|
#>
|
|||
|
param(
|
|||
|
[Parameter(Mandatory = $true)]
|
|||
|
[ValidateNotNullOrEmpty()]
|
|||
|
[PSObject] $ADRObj,
|
|||
|
|
|||
|
[Parameter(Mandatory = $true)]
|
|||
|
[ValidateNotNullOrEmpty()]
|
|||
|
[String] $ADFileName
|
|||
|
)
|
|||
|
|
|||
|
Try
|
|||
|
{
|
|||
|
$ADRObj | Export-Csv -Path $ADFileName -NoTypeInformation
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Warning "[Export-ADRCSV] Failed to export $($ADFileName)."
|
|||
|
Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
Function Export-ADRXML
|
|||
|
{
|
|||
|
<#
|
|||
|
.SYNOPSIS
|
|||
|
Exports Object to a XML file.
|
|||
|
|
|||
|
.DESCRIPTION
|
|||
|
Exports Object to a XML file using Export-Clixml.
|
|||
|
|
|||
|
.PARAMETER ADRObj
|
|||
|
[PSObject]
|
|||
|
ADRObj
|
|||
|
|
|||
|
.PARAMETER ADFileName
|
|||
|
[String]
|
|||
|
Path to save the XML File.
|
|||
|
|
|||
|
.OUTPUTS
|
|||
|
XML file.
|
|||
|
#>
|
|||
|
param(
|
|||
|
[Parameter(Mandatory = $true)]
|
|||
|
[ValidateNotNullOrEmpty()]
|
|||
|
[PSObject] $ADRObj,
|
|||
|
|
|||
|
[Parameter(Mandatory = $true)]
|
|||
|
[ValidateNotNullOrEmpty()]
|
|||
|
[String] $ADFileName
|
|||
|
)
|
|||
|
|
|||
|
Try
|
|||
|
{
|
|||
|
(ConvertTo-Xml -NoTypeInformation -InputObject $ADRObj).Save($ADFileName)
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Warning "[Export-ADRXML] Failed to export $($ADFileName)."
|
|||
|
Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
Function Export-ADRJSON
|
|||
|
{
|
|||
|
<#
|
|||
|
.SYNOPSIS
|
|||
|
Exports Object to a JSON file.
|
|||
|
|
|||
|
.DESCRIPTION
|
|||
|
Exports Object to a JSON file using ConvertTo-Json.
|
|||
|
|
|||
|
.PARAMETER ADRObj
|
|||
|
[PSObject]
|
|||
|
ADRObj
|
|||
|
|
|||
|
.PARAMETER ADFileName
|
|||
|
[String]
|
|||
|
Path to save the JSON File.
|
|||
|
|
|||
|
.OUTPUTS
|
|||
|
JSON file.
|
|||
|
#>
|
|||
|
param(
|
|||
|
[Parameter(Mandatory = $true)]
|
|||
|
[ValidateNotNullOrEmpty()]
|
|||
|
[PSObject] $ADRObj,
|
|||
|
|
|||
|
[Parameter(Mandatory = $true)]
|
|||
|
[ValidateNotNullOrEmpty()]
|
|||
|
[String] $ADFileName
|
|||
|
)
|
|||
|
|
|||
|
Try
|
|||
|
{
|
|||
|
ConvertTo-JSON -InputObject $ADRObj | Out-File -FilePath $ADFileName
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Warning "[Export-ADRJSON] Failed to export $($ADFileName)."
|
|||
|
Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
Function Export-ADRHTML
|
|||
|
{
|
|||
|
<#
|
|||
|
.SYNOPSIS
|
|||
|
Exports Object to a HTML file.
|
|||
|
|
|||
|
.DESCRIPTION
|
|||
|
Exports Object to a HTML file using ConvertTo-Html.
|
|||
|
|
|||
|
.PARAMETER ADRObj
|
|||
|
[PSObject]
|
|||
|
ADRObj
|
|||
|
|
|||
|
.PARAMETER ADFileName
|
|||
|
[String]
|
|||
|
Path to save the HTML File.
|
|||
|
|
|||
|
.OUTPUTS
|
|||
|
HTML file.
|
|||
|
#>
|
|||
|
param(
|
|||
|
[Parameter(Mandatory = $true)]
|
|||
|
[ValidateNotNullOrEmpty()]
|
|||
|
[PSObject] $ADRObj,
|
|||
|
|
|||
|
[Parameter(Mandatory = $true)]
|
|||
|
[ValidateNotNullOrEmpty()]
|
|||
|
[String] $ADFileName,
|
|||
|
|
|||
|
[Parameter(Mandatory = $false)]
|
|||
|
[String] $ADROutputDir = $null
|
|||
|
)
|
|||
|
|
|||
|
$Header = @"
|
|||
|
<style type="text/css">
|
|||
|
th {
|
|||
|
color:white;
|
|||
|
background-color:blue;
|
|||
|
}
|
|||
|
td, th {
|
|||
|
border:0px solid black;
|
|||
|
border-collapse:collapse;
|
|||
|
white-space:pre;
|
|||
|
}
|
|||
|
tr:nth-child(2n+1) {
|
|||
|
background-color: #dddddd;
|
|||
|
}
|
|||
|
tr:hover td {
|
|||
|
background-color: #c1d5f8;
|
|||
|
}
|
|||
|
table, tr, td, th {
|
|||
|
padding: 0px;
|
|||
|
margin: 0px;
|
|||
|
white-space:pre;
|
|||
|
}
|
|||
|
table {
|
|||
|
margin-left:1px;
|
|||
|
}
|
|||
|
</style>
|
|||
|
"@
|
|||
|
Try
|
|||
|
{
|
|||
|
If ($ADFileName.Contains("Index"))
|
|||
|
{
|
|||
|
$HTMLPath = -join($ADROutputDir,'\','HTML-Files')
|
|||
|
$HTMLPath = $((Convert-Path $HTMLPath).TrimEnd("\"))
|
|||
|
$HTMLFiles = Get-ChildItem -Path $HTMLPath -name
|
|||
|
$HTML = $HTMLFiles | ConvertTo-HTML -Title "ADRecon" -Property @{Label="Table of Contents";Expression={"<a href='$($_)'>$($_)</a>"}} -Head $Header
|
|||
|
|
|||
|
Add-Type -AssemblyName System.Web
|
|||
|
[System.Web.HttpUtility]::HtmlDecode($HTML) | Out-File -FilePath $ADFileName
|
|||
|
}
|
|||
|
Else
|
|||
|
{
|
|||
|
If ($ADRObj -is [array])
|
|||
|
{
|
|||
|
$ADRObj | Select-Object * | ConvertTo-HTML -As Table -Head $Header | Out-File -FilePath $ADFileName
|
|||
|
}
|
|||
|
Else
|
|||
|
{
|
|||
|
ConvertTo-HTML -InputObject $ADRObj -As Table -Head $Header | Out-File -FilePath $ADFileName
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Warning "[Export-ADRHTML] Failed to export $($ADFileName)."
|
|||
|
Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
Function Export-ADR
|
|||
|
{
|
|||
|
<#
|
|||
|
.SYNOPSIS
|
|||
|
Helper function for all output types supported.
|
|||
|
|
|||
|
.DESCRIPTION
|
|||
|
Helper function for all output types supported.
|
|||
|
|
|||
|
.PARAMETER ADObjectDN
|
|||
|
[PSObject]
|
|||
|
ADRObj
|
|||
|
|
|||
|
.PARAMETER ADROutputDir
|
|||
|
[String]
|
|||
|
Path for ADRecon output folder.
|
|||
|
|
|||
|
.PARAMETER OutputType
|
|||
|
[array]
|
|||
|
Output Type.
|
|||
|
|
|||
|
.PARAMETER ADRModuleName
|
|||
|
[String]
|
|||
|
Module Name.
|
|||
|
|
|||
|
.OUTPUTS
|
|||
|
STDOUT, CSV, XML, JSON and/or HTML file, etc.
|
|||
|
#>
|
|||
|
param(
|
|||
|
[Parameter(Mandatory = $true)]
|
|||
|
[PSObject] $ADRObj,
|
|||
|
|
|||
|
[Parameter(Mandatory = $true)]
|
|||
|
[String] $ADROutputDir,
|
|||
|
|
|||
|
[Parameter(Mandatory = $true)]
|
|||
|
[array] $OutputType,
|
|||
|
|
|||
|
[Parameter(Mandatory = $true)]
|
|||
|
[String] $ADRModuleName
|
|||
|
)
|
|||
|
|
|||
|
Switch ($OutputType)
|
|||
|
{
|
|||
|
'STDOUT'
|
|||
|
{
|
|||
|
If ($ADRModuleName -ne "AboutADRecon")
|
|||
|
{
|
|||
|
If ($ADRObj -is [array])
|
|||
|
{
|
|||
|
# Fix for InvalidOperationException: The object of type "Microsoft.PowerShell.Commands.Internal.Format.FormatStartData" is not valid or not in the correct sequence.
|
|||
|
$ADRObj | Out-String -Stream
|
|||
|
}
|
|||
|
Else
|
|||
|
{
|
|||
|
# Fix for InvalidOperationException: The object of type "Microsoft.PowerShell.Commands.Internal.Format.FormatStartData" is not valid or not in the correct sequence.
|
|||
|
$ADRObj | Format-List | Out-String -Stream
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
'CSV'
|
|||
|
{
|
|||
|
$ADFileName = -join($ADROutputDir,'\','CSV-Files','\',$ADRModuleName,'.csv')
|
|||
|
Export-ADRCSV -ADRObj $ADRObj -ADFileName $ADFileName
|
|||
|
}
|
|||
|
'XML'
|
|||
|
{
|
|||
|
$ADFileName = -join($ADROutputDir,'\','XML-Files','\',$ADRModuleName,'.xml')
|
|||
|
Export-ADRXML -ADRObj $ADRObj -ADFileName $ADFileName
|
|||
|
}
|
|||
|
'JSON'
|
|||
|
{
|
|||
|
$ADFileName = -join($ADROutputDir,'\','JSON-Files','\',$ADRModuleName,'.json')
|
|||
|
Export-ADRJSON -ADRObj $ADRObj -ADFileName $ADFileName
|
|||
|
}
|
|||
|
'HTML'
|
|||
|
{
|
|||
|
$ADFileName = -join($ADROutputDir,'\','HTML-Files','\',$ADRModuleName,'.html')
|
|||
|
Export-ADRHTML -ADRObj $ADRObj -ADFileName $ADFileName -ADROutputDir $ADROutputDir
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
Function Get-ADRExcelComObj
|
|||
|
{
|
|||
|
<#
|
|||
|
.SYNOPSIS
|
|||
|
Creates a ComObject to interact with Microsoft Excel.
|
|||
|
|
|||
|
.DESCRIPTION
|
|||
|
Creates a ComObject to interact with Microsoft Excel if installed, else warning is raised.
|
|||
|
|
|||
|
.OUTPUTS
|
|||
|
[System.__ComObject] and [System.MarshalByRefObject]
|
|||
|
Creates global variables $excel and $workbook.
|
|||
|
#>
|
|||
|
|
|||
|
#Check if Excel is installed.
|
|||
|
Try
|
|||
|
{
|
|||
|
# Suppress verbose output
|
|||
|
$SaveVerbosePreference = $script:VerbosePreference
|
|||
|
$script:VerbosePreference = 'SilentlyContinue'
|
|||
|
$global:excel = New-Object -ComObject excel.application
|
|||
|
If ($SaveVerbosePreference)
|
|||
|
{
|
|||
|
$script:VerbosePreference = $SaveVerbosePreference
|
|||
|
Remove-Variable SaveVerbosePreference
|
|||
|
}
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
If ($SaveVerbosePreference)
|
|||
|
{
|
|||
|
$script:VerbosePreference = $SaveVerbosePreference
|
|||
|
Remove-Variable SaveVerbosePreference
|
|||
|
}
|
|||
|
Write-Warning "[Get-ADRExcelComObj] Excel does not appear to be installed. Skipping generation of ADRecon-Report.xlsx. Use the -GenExcel parameter to generate the ADRecon-Report.xslx on a host with Microsoft Excel installed."
|
|||
|
Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
Return $null
|
|||
|
}
|
|||
|
$excel.Visible = $true
|
|||
|
$excel.Interactive = $false
|
|||
|
$global:workbook = $excel.Workbooks.Add()
|
|||
|
If ($workbook.Worksheets.Count -eq 3)
|
|||
|
{
|
|||
|
$workbook.WorkSheets.Item(3).Delete()
|
|||
|
$workbook.WorkSheets.Item(2).Delete()
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
Function Get-ADRExcelComObjRelease
|
|||
|
{
|
|||
|
<#
|
|||
|
.SYNOPSIS
|
|||
|
Releases the ComObject created to interact with Microsoft Excel.
|
|||
|
|
|||
|
.DESCRIPTION
|
|||
|
Releases the ComObject created to interact with Microsoft Excel.
|
|||
|
|
|||
|
.PARAMETER ComObjtoRelease
|
|||
|
ComObjtoRelease
|
|||
|
|
|||
|
.PARAMETER Final
|
|||
|
Final
|
|||
|
#>
|
|||
|
param(
|
|||
|
[Parameter(Mandatory = $true)]
|
|||
|
$ComObjtoRelease,
|
|||
|
|
|||
|
[Parameter(Mandatory = $false)]
|
|||
|
[bool] $Final = $false
|
|||
|
)
|
|||
|
# https://msdn.microsoft.com/en-us/library/system.runtime.interopservices.marshal.releasecomobject(v=vs.110).aspx
|
|||
|
# https://msdn.microsoft.com/en-us/library/system.runtime.interopservices.marshal.finalreleasecomobject(v=vs.110).aspx
|
|||
|
If ($Final)
|
|||
|
{
|
|||
|
[System.Runtime.InteropServices.Marshal]::FinalReleaseComObject($ComObjtoRelease) | Out-Null
|
|||
|
}
|
|||
|
Else
|
|||
|
{
|
|||
|
[System.Runtime.InteropServices.Marshal]::ReleaseComObject($ComObjtoRelease) | Out-Null
|
|||
|
}
|
|||
|
[System.GC]::Collect()
|
|||
|
[System.GC]::WaitForPendingFinalizers()
|
|||
|
}
|
|||
|
|
|||
|
Function Get-ADRExcelWorkbook
|
|||
|
{
|
|||
|
<#
|
|||
|
.SYNOPSIS
|
|||
|
Adds a WorkSheet to the Workbook.
|
|||
|
|
|||
|
.DESCRIPTION
|
|||
|
Adds a WorkSheet to the Workbook using the $workboook global variable and assigns it a name.
|
|||
|
|
|||
|
.PARAMETER name
|
|||
|
[string]
|
|||
|
Name of the WorkSheet.
|
|||
|
#>
|
|||
|
param (
|
|||
|
[Parameter(Mandatory = $true)]
|
|||
|
[string] $name
|
|||
|
)
|
|||
|
|
|||
|
$workbook.Worksheets.Add() | Out-Null
|
|||
|
$worksheet = $workbook.Worksheets.Item(1)
|
|||
|
$worksheet.Name = $name
|
|||
|
|
|||
|
Get-ADRExcelComObjRelease -ComObjtoRelease $worksheet
|
|||
|
Remove-Variable worksheet
|
|||
|
}
|
|||
|
|
|||
|
Function Get-ADRExcelImport
|
|||
|
{
|
|||
|
<#
|
|||
|
.SYNOPSIS
|
|||
|
Helper to import CSV to the current WorkSheet.
|
|||
|
|
|||
|
.DESCRIPTION
|
|||
|
Helper to import CSV to the current WorkSheet. Supports two methods.
|
|||
|
|
|||
|
.PARAMETER ADFileName
|
|||
|
[string]
|
|||
|
Filename of the CSV file to import.
|
|||
|
|
|||
|
.PARAMETER method
|
|||
|
[int]
|
|||
|
Method to use for the import.
|
|||
|
|
|||
|
.PARAMETER row
|
|||
|
[int]
|
|||
|
Row.
|
|||
|
|
|||
|
.PARAMETER column
|
|||
|
[int]
|
|||
|
Column.
|
|||
|
#>
|
|||
|
param (
|
|||
|
[Parameter(Mandatory = $true)]
|
|||
|
[string] $ADFileName,
|
|||
|
|
|||
|
[Parameter(Mandatory = $false)]
|
|||
|
[int] $method = 1,
|
|||
|
|
|||
|
[Parameter(Mandatory = $false)]
|
|||
|
[int] $row = 1,
|
|||
|
|
|||
|
[Parameter(Mandatory = $false)]
|
|||
|
[int] $column = 1
|
|||
|
)
|
|||
|
|
|||
|
$excel.ScreenUpdating = $false
|
|||
|
If ($method -eq 1)
|
|||
|
{
|
|||
|
If (Test-Path $ADFileName)
|
|||
|
{
|
|||
|
$worksheet = $workbook.Worksheets.Item(1)
|
|||
|
$TxtConnector = ("TEXT;" + $ADFileName)
|
|||
|
$CellRef = $worksheet.Range("A1")
|
|||
|
#Build, use and remove the text file connector
|
|||
|
$Connector = $worksheet.QueryTables.add($TxtConnector, $CellRef)
|
|||
|
|
|||
|
#65001: Unicode (UTF-8)
|
|||
|
$worksheet.QueryTables.item($Connector.name).TextFilePlatform = 65001
|
|||
|
$worksheet.QueryTables.item($Connector.name).TextFileCommaDelimiter = $True
|
|||
|
$worksheet.QueryTables.item($Connector.name).TextFileParseType = 1
|
|||
|
$worksheet.QueryTables.item($Connector.name).Refresh() | Out-Null
|
|||
|
$worksheet.QueryTables.item($Connector.name).delete()
|
|||
|
|
|||
|
Get-ADRExcelComObjRelease -ComObjtoRelease $CellRef
|
|||
|
Remove-Variable CellRef
|
|||
|
Get-ADRExcelComObjRelease -ComObjtoRelease $Connector
|
|||
|
Remove-Variable Connector
|
|||
|
|
|||
|
$listObject = $worksheet.ListObjects.Add([Microsoft.Office.Interop.Excel.XlListObjectSourceType]::xlSrcRange, $worksheet.UsedRange, $null, [Microsoft.Office.Interop.Excel.XlYesNoGuess]::xlYes, $null)
|
|||
|
$listObject.TableStyle = "TableStyleLight2" # Style Cheat Sheet: https://msdn.microsoft.com/en-au/library/documentformat.openxml.spreadsheet.tablestyle.aspx
|
|||
|
$worksheet.UsedRange.EntireColumn.AutoFit() | Out-Null
|
|||
|
}
|
|||
|
Remove-Variable ADFileName
|
|||
|
}
|
|||
|
Elseif ($method -eq 2)
|
|||
|
{
|
|||
|
$worksheet = $workbook.Worksheets.Item(1)
|
|||
|
If (Test-Path $ADFileName)
|
|||
|
{
|
|||
|
$ADTemp = Import-Csv -Path $ADFileName
|
|||
|
$ADTemp | ForEach-Object {
|
|||
|
Foreach ($prop in $_.PSObject.Properties)
|
|||
|
{
|
|||
|
$worksheet.Cells.Item($row, $column) = $prop.Name
|
|||
|
$worksheet.Cells.Item($row, $column + 1) = $prop.Value
|
|||
|
$row++
|
|||
|
}
|
|||
|
}
|
|||
|
Remove-Variable ADTemp
|
|||
|
$listObject = $worksheet.ListObjects.Add([Microsoft.Office.Interop.Excel.XlListObjectSourceType]::xlSrcRange, $worksheet.UsedRange, $null, [Microsoft.Office.Interop.Excel.XlYesNoGuess]::xlYes, $null)
|
|||
|
$listObject.TableStyle = "TableStyleLight2" # Style Cheat Sheet: https://msdn.microsoft.com/en-au/library/documentformat.openxml.spreadsheet.tablestyle.aspx
|
|||
|
$usedRange = $worksheet.UsedRange
|
|||
|
$usedRange.EntireColumn.AutoFit() | Out-Null
|
|||
|
}
|
|||
|
Else
|
|||
|
{
|
|||
|
$worksheet.Cells.Item($row, $column) = "Error!"
|
|||
|
}
|
|||
|
Remove-Variable ADFileName
|
|||
|
}
|
|||
|
$excel.ScreenUpdating = $true
|
|||
|
|
|||
|
Get-ADRExcelComObjRelease -ComObjtoRelease $worksheet
|
|||
|
Remove-Variable worksheet
|
|||
|
}
|
|||
|
|
|||
|
# Thanks Anant Shrivastava for the suggestion of using Pivot Tables for generation of the Stats sheets.
|
|||
|
Function Get-ADRExcelPivotTable
|
|||
|
{
|
|||
|
<#
|
|||
|
.SYNOPSIS
|
|||
|
Helper to add Pivot Table to the current WorkSheet.
|
|||
|
|
|||
|
.DESCRIPTION
|
|||
|
Helper to add Pivot Table to the current WorkSheet.
|
|||
|
|
|||
|
.PARAMETER SrcSheetName
|
|||
|
[string]
|
|||
|
Source Sheet Name.
|
|||
|
|
|||
|
.PARAMETER PivotTableName
|
|||
|
[string]
|
|||
|
Pivot Table Name.
|
|||
|
|
|||
|
.PARAMETER PivotRows
|
|||
|
[array]
|
|||
|
Row names from Source Sheet.
|
|||
|
|
|||
|
.PARAMETER PivotColumns
|
|||
|
[array]
|
|||
|
Column names from Source Sheet.
|
|||
|
|
|||
|
.PARAMETER PivotFilters
|
|||
|
[array]
|
|||
|
Row/Column names from Source Sheet to use as filters.
|
|||
|
|
|||
|
.PARAMETER PivotValues
|
|||
|
[array]
|
|||
|
Row/Column names from Source Sheet to use for Values.
|
|||
|
|
|||
|
.PARAMETER PivotPercentage
|
|||
|
[array]
|
|||
|
Row/Column names from Source Sheet to use for Percentage.
|
|||
|
|
|||
|
.PARAMETER PivotLocation
|
|||
|
[array]
|
|||
|
Location of the Pivot Table in Row/Column.
|
|||
|
#>
|
|||
|
param (
|
|||
|
[Parameter(Mandatory = $true)]
|
|||
|
[string] $SrcSheetName,
|
|||
|
|
|||
|
[Parameter(Mandatory = $true)]
|
|||
|
[string] $PivotTableName,
|
|||
|
|
|||
|
[Parameter(Mandatory = $false)]
|
|||
|
[array] $PivotRows,
|
|||
|
|
|||
|
[Parameter(Mandatory = $false)]
|
|||
|
[array] $PivotColumns,
|
|||
|
|
|||
|
[Parameter(Mandatory = $false)]
|
|||
|
[array] $PivotFilters,
|
|||
|
|
|||
|
[Parameter(Mandatory = $false)]
|
|||
|
[array] $PivotValues,
|
|||
|
|
|||
|
[Parameter(Mandatory = $false)]
|
|||
|
[array] $PivotPercentage,
|
|||
|
|
|||
|
[Parameter(Mandatory = $false)]
|
|||
|
[string] $PivotLocation = "R1C1"
|
|||
|
)
|
|||
|
|
|||
|
$excel.ScreenUpdating = $false
|
|||
|
$SrcWorksheet = $workbook.Sheets.Item($SrcSheetName)
|
|||
|
$workbook.ShowPivotTableFieldList = $false
|
|||
|
|
|||
|
# https://msdn.microsoft.com/en-us/vba/excel-vba/articles/xlpivottablesourcetype-enumeration-excel
|
|||
|
# https://msdn.microsoft.com/en-us/vba/excel-vba/articles/xlpivottableversionlist-enumeration-excel
|
|||
|
# https://msdn.microsoft.com/en-us/vba/excel-vba/articles/xlpivotfieldorientation-enumeration-excel
|
|||
|
# https://msdn.microsoft.com/en-us/vba/excel-vba/articles/constants-enumeration-excel
|
|||
|
# https://msdn.microsoft.com/en-us/vba/excel-vba/articles/xlsortorder-enumeration-excel
|
|||
|
# https://msdn.microsoft.com/en-us/vba/excel-vba/articles/xlpivotfiltertype-enumeration-excel
|
|||
|
|
|||
|
# xlDatabase = 1 # this just means local sheet data
|
|||
|
# xlPivotTableVersion12 = 3 # Excel 2007
|
|||
|
$PivotFailed = $false
|
|||
|
Try
|
|||
|
{
|
|||
|
$PivotCaches = $workbook.PivotCaches().Create([Microsoft.Office.Interop.Excel.XlPivotTableSourceType]::xlDatabase, $SrcWorksheet.UsedRange, [Microsoft.Office.Interop.Excel.XlPivotTableVersionList]::xlPivotTableVersion12)
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
$PivotFailed = $true
|
|||
|
Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
}
|
|||
|
If ( $PivotFailed -eq $true )
|
|||
|
{
|
|||
|
$rows = $SrcWorksheet.UsedRange.Rows.Count
|
|||
|
If ($SrcSheetName -eq "Computer SPNs")
|
|||
|
{
|
|||
|
$PivotCols = "A1:B"
|
|||
|
}
|
|||
|
ElseIf ($SrcSheetName -eq "Users")
|
|||
|
{
|
|||
|
$PivotCols = "A1:AI"
|
|||
|
}
|
|||
|
$UsedRange = $SrcWorksheet.Range($PivotCols+$rows)
|
|||
|
$PivotCaches = $workbook.PivotCaches().Create([Microsoft.Office.Interop.Excel.XlPivotTableSourceType]::xlDatabase, $UsedRange, [Microsoft.Office.Interop.Excel.XlPivotTableVersionList]::xlPivotTableVersion12)
|
|||
|
Remove-Variable rows
|
|||
|
Remove-Variable PivotCols
|
|||
|
Remove-Variable UsedRange
|
|||
|
}
|
|||
|
Remove-Variable PivotFailed
|
|||
|
$PivotTable = $PivotCaches.CreatePivotTable($PivotLocation,$PivotTableName)
|
|||
|
# $workbook.ShowPivotTableFieldList = $true
|
|||
|
|
|||
|
If ($PivotRows)
|
|||
|
{
|
|||
|
ForEach ($Row in $PivotRows)
|
|||
|
{
|
|||
|
$PivotField = $PivotTable.PivotFields($Row)
|
|||
|
$PivotField.Orientation = [Microsoft.Office.Interop.Excel.XlPivotFieldOrientation]::xlRowField
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
If ($PivotColumns)
|
|||
|
{
|
|||
|
ForEach ($Col in $PivotColumns)
|
|||
|
{
|
|||
|
$PivotField = $PivotTable.PivotFields($Col)
|
|||
|
$PivotField.Orientation = [Microsoft.Office.Interop.Excel.XlPivotFieldOrientation]::xlColumnField
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
If ($PivotFilters)
|
|||
|
{
|
|||
|
ForEach ($Fil in $PivotFilters)
|
|||
|
{
|
|||
|
$PivotField = $PivotTable.PivotFields($Fil)
|
|||
|
$PivotField.Orientation = [Microsoft.Office.Interop.Excel.XlPivotFieldOrientation]::xlPageField
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
If ($PivotValues)
|
|||
|
{
|
|||
|
ForEach ($Val in $PivotValues)
|
|||
|
{
|
|||
|
$PivotField = $PivotTable.PivotFields($Val)
|
|||
|
$PivotField.Orientation = [Microsoft.Office.Interop.Excel.XlPivotFieldOrientation]::xlDataField
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
If ($PivotPercentage)
|
|||
|
{
|
|||
|
ForEach ($Val in $PivotPercentage)
|
|||
|
{
|
|||
|
$PivotField = $PivotTable.PivotFields($Val)
|
|||
|
$PivotField.Orientation = [Microsoft.Office.Interop.Excel.XlPivotFieldOrientation]::xlDataField
|
|||
|
$PivotField.Calculation = [Microsoft.Office.Interop.Excel.XlPivotFieldCalculation]::xlPercentOfTotal
|
|||
|
$PivotTable.ShowValuesRow = $false
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
# $PivotFields.Caption = ""
|
|||
|
$excel.ScreenUpdating = $true
|
|||
|
|
|||
|
Get-ADRExcelComObjRelease -ComObjtoRelease $PivotField
|
|||
|
Remove-Variable PivotField
|
|||
|
Get-ADRExcelComObjRelease -ComObjtoRelease $PivotTable
|
|||
|
Remove-Variable PivotTable
|
|||
|
Get-ADRExcelComObjRelease -ComObjtoRelease $PivotCaches
|
|||
|
Remove-Variable PivotCaches
|
|||
|
Get-ADRExcelComObjRelease -ComObjtoRelease $SrcWorksheet
|
|||
|
Remove-Variable SrcWorksheet
|
|||
|
}
|
|||
|
|
|||
|
Function Get-ADRExcelAttributeStats
|
|||
|
{
|
|||
|
<#
|
|||
|
.SYNOPSIS
|
|||
|
Helper to add Attribute Stats to the current WorkSheet.
|
|||
|
|
|||
|
.DESCRIPTION
|
|||
|
Helper to add Attribute Stats to the current WorkSheet.
|
|||
|
|
|||
|
.PARAMETER SrcSheetName
|
|||
|
[string]
|
|||
|
Source Sheet Name.
|
|||
|
|
|||
|
.PARAMETER Title1
|
|||
|
[string]
|
|||
|
Title1.
|
|||
|
|
|||
|
.PARAMETER Title2
|
|||
|
[string]
|
|||
|
Title2.
|
|||
|
|
|||
|
.PARAMETER ObjAttributes
|
|||
|
[OrderedDictionary]
|
|||
|
Attributes.
|
|||
|
#>
|
|||
|
param (
|
|||
|
[Parameter(Mandatory = $true)]
|
|||
|
[string] $SrcSheetName,
|
|||
|
|
|||
|
[Parameter(Mandatory = $true)]
|
|||
|
[string] $Title1,
|
|||
|
|
|||
|
[Parameter(Mandatory = $true)]
|
|||
|
[string] $Title2,
|
|||
|
|
|||
|
[Parameter(Mandatory = $true)]
|
|||
|
[System.Object] $ObjAttributes
|
|||
|
)
|
|||
|
|
|||
|
$excel.ScreenUpdating = $false
|
|||
|
$worksheet = $workbook.Worksheets.Item(1)
|
|||
|
$SrcWorksheet = $workbook.Sheets.Item($SrcSheetName)
|
|||
|
|
|||
|
$row = 1
|
|||
|
$column = 1
|
|||
|
$worksheet.Cells.Item($row, $column) = $Title1
|
|||
|
$worksheet.Cells.Item($row,$column).Style = "Heading 2"
|
|||
|
$worksheet.Cells.Item($row,$column).HorizontalAlignment = -4108
|
|||
|
$MergeCells = $worksheet.Range("A1:C1")
|
|||
|
$MergeCells.Select() | Out-Null
|
|||
|
$MergeCells.MergeCells = $true
|
|||
|
Remove-Variable MergeCells
|
|||
|
|
|||
|
Get-ADRExcelPivotTable -SrcSheetName $SrcSheetName -PivotTableName "User Status" -PivotRows @("Enabled") -PivotValues @("UserName") -PivotPercentage @("UserName") -PivotLocation "R2C1"
|
|||
|
$excel.ScreenUpdating = $false
|
|||
|
|
|||
|
$row = 2
|
|||
|
"Type","Count","Percentage" | ForEach-Object {
|
|||
|
$worksheet.Cells.Item($row, $column) = $_
|
|||
|
$worksheet.Cells.Item($row, $column).Font.Bold = $True
|
|||
|
$column++
|
|||
|
}
|
|||
|
|
|||
|
$row = 3
|
|||
|
$column = 1
|
|||
|
For($row = 3; $row -le 6; $row++)
|
|||
|
{
|
|||
|
$temptext = [string] $worksheet.Cells.Item($row, $column).Text
|
|||
|
switch ($temptext.ToUpper())
|
|||
|
{
|
|||
|
"TRUE" { $worksheet.Cells.Item($row, $column) = "Enabled" }
|
|||
|
"FALSE" { $worksheet.Cells.Item($row, $column) = "Disabled" }
|
|||
|
"GRAND TOTAL" { $worksheet.Cells.Item($row, $column) = "Total" }
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
$row = 1
|
|||
|
$column = 6
|
|||
|
$worksheet.Cells.Item($row, $column) = $Title2
|
|||
|
$worksheet.Cells.Item($row,$column).Style = "Heading 2"
|
|||
|
$worksheet.Cells.Item($row,$column).HorizontalAlignment = -4108
|
|||
|
$MergeCells = $worksheet.Range("F1:L1")
|
|||
|
$MergeCells.Select() | Out-Null
|
|||
|
$MergeCells.MergeCells = $true
|
|||
|
Remove-Variable MergeCells
|
|||
|
|
|||
|
$row++
|
|||
|
"Category","Enabled Count","Enabled Percentage","Disabled Count","Disabled Percentage","Total Count","Total Percentage" | ForEach-Object {
|
|||
|
$worksheet.Cells.Item($row, $column) = $_
|
|||
|
$worksheet.Cells.Item($row, $column).Font.Bold = $True
|
|||
|
$column++
|
|||
|
}
|
|||
|
|
|||
|
$ExcelColumn = ($SrcWorksheet.Columns.Find("Enabled"))
|
|||
|
$EnabledColAddress = "$($ExcelColumn.Address($false,$false).Substring(0,$ExcelColumn.Address($false,$false).Length-1)):$($ExcelColumn.Address($false,$false).Substring(0,$ExcelColumn.Address($false,$false).Length-1))"
|
|||
|
|
|||
|
$column = 6
|
|||
|
$i = 2
|
|||
|
|
|||
|
$ObjAttributes.keys | ForEach-Object {
|
|||
|
$ExcelColumn = ($SrcWorksheet.Columns.Find($_))
|
|||
|
$ColAddress = "$($ExcelColumn.Address($false,$false).Substring(0,$ExcelColumn.Address($false,$false).Length-1)):$($ExcelColumn.Address($false,$false).Substring(0,$ExcelColumn.Address($false,$false).Length-1))"
|
|||
|
$row++
|
|||
|
$i++
|
|||
|
If ($_ -eq "Delegation Typ")
|
|||
|
{
|
|||
|
$worksheet.Cells.Item($row, $column) = "Unconstrained Delegation"
|
|||
|
}
|
|||
|
ElseIf ($_ -eq "Delegation Type")
|
|||
|
{
|
|||
|
$worksheet.Cells.Item($row, $column) = "Constrained Delegation"
|
|||
|
}
|
|||
|
Else
|
|||
|
{
|
|||
|
$worksheet.Cells.Item($row, $column).Formula = '=' + $SrcWorksheet.Name + '!' + $ExcelColumn.Address($false,$false)
|
|||
|
}
|
|||
|
$worksheet.Cells.Item($row, $column+1).Formula = '=COUNTIFS(' + $SrcWorksheet.Name + '!' + $EnabledColAddress + ',"TRUE",' + $SrcWorksheet.Name + '!' + $ColAddress + ',' + $ObjAttributes[$_] + ')'
|
|||
|
$worksheet.Cells.Item($row, $column+2).Formula = '=IFERROR(G' + $i + '/VLOOKUP("Enabled",A3:B6,2,FALSE),0)'
|
|||
|
$worksheet.Cells.Item($row, $column+3).Formula = '=COUNTIFS(' + $SrcWorksheet.Name + '!' + $EnabledColAddress + ',"FALSE",' + $SrcWorksheet.Name + '!' + $ColAddress + ',' + $ObjAttributes[$_] + ')'
|
|||
|
$worksheet.Cells.Item($row, $column+4).Formula = '=IFERROR(I' + $i + '/VLOOKUP("Disabled",A3:B6,2,FALSE),0)'
|
|||
|
If ( ($_ -eq "SIDHistory") -or ($_ -eq "ms-ds-CreatorSid") )
|
|||
|
{
|
|||
|
$worksheet.Cells.Item($row, $column+5).Formula = '=COUNTIF(' + $SrcWorksheet.Name + '!' + $ColAddress + ',' + $ObjAttributes[$_] + ')-1'
|
|||
|
}
|
|||
|
Else
|
|||
|
{
|
|||
|
$worksheet.Cells.Item($row, $column+5).Formula = '=COUNTIF(' + $SrcWorksheet.Name + '!' + $ColAddress + ',' + $ObjAttributes[$_] + ')'
|
|||
|
}
|
|||
|
$worksheet.Cells.Item($row, $column+6).Formula = '=IFERROR(K' + $i + '/VLOOKUP("Total",A3:B6,2,FALSE),0)'
|
|||
|
}
|
|||
|
|
|||
|
# http://www.excelhowto.com/macros/formatting-a-range-of-cells-in-excel-vba/
|
|||
|
"H", "J" , "L" | ForEach-Object {
|
|||
|
$rng = $_ + $($row - $ObjAttributes.Count + 1) + ":" + $_ + $($row)
|
|||
|
$worksheet.Range($rng).NumberFormat = "0.00%"
|
|||
|
}
|
|||
|
$excel.ScreenUpdating = $true
|
|||
|
|
|||
|
Get-ADRExcelComObjRelease -ComObjtoRelease $SrcWorksheet
|
|||
|
Remove-Variable SrcWorksheet
|
|||
|
Get-ADRExcelComObjRelease -ComObjtoRelease $worksheet
|
|||
|
Remove-Variable worksheet
|
|||
|
}
|
|||
|
|
|||
|
Function Get-ADRExcelChart
|
|||
|
{
|
|||
|
<#
|
|||
|
.SYNOPSIS
|
|||
|
Helper to add charts to the current WorkSheet.
|
|||
|
|
|||
|
.DESCRIPTION
|
|||
|
Helper to add charts to the current WorkSheet.
|
|||
|
|
|||
|
.PARAMETER ChartType
|
|||
|
[int]
|
|||
|
Chart Type.
|
|||
|
|
|||
|
.PARAMETER ChartLayout
|
|||
|
[int]
|
|||
|
Chart Layout.
|
|||
|
|
|||
|
.PARAMETER ChartTitle
|
|||
|
[string]
|
|||
|
Title of the Chart.
|
|||
|
|
|||
|
.PARAMETER RangetoCover
|
|||
|
WorkSheet Range to be covered by the Chart.
|
|||
|
|
|||
|
.PARAMETER ChartData
|
|||
|
Data for the Chart.
|
|||
|
|
|||
|
.PARAMETER StartRow
|
|||
|
Start row to calculate data for the Chart.
|
|||
|
|
|||
|
.PARAMETER StartColumn
|
|||
|
Start column to calculate data for the Chart.
|
|||
|
#>
|
|||
|
param (
|
|||
|
[Parameter(Mandatory = $true)]
|
|||
|
[string] $ChartType,
|
|||
|
|
|||
|
[Parameter(Mandatory = $true)]
|
|||
|
[int] $ChartLayout,
|
|||
|
|
|||
|
[Parameter(Mandatory = $true)]
|
|||
|
[string] $ChartTitle,
|
|||
|
|
|||
|
[Parameter(Mandatory = $true)]
|
|||
|
$RangetoCover,
|
|||
|
|
|||
|
[Parameter(Mandatory = $false)]
|
|||
|
$ChartData = $null,
|
|||
|
|
|||
|
[Parameter(Mandatory = $false)]
|
|||
|
$StartRow = $null,
|
|||
|
|
|||
|
[Parameter(Mandatory = $false)]
|
|||
|
$StartColumn = $null
|
|||
|
)
|
|||
|
|
|||
|
$excel.ScreenUpdating = $false
|
|||
|
$excel.DisplayAlerts = $false
|
|||
|
$worksheet = $workbook.Worksheets.Item(1)
|
|||
|
$chart = $worksheet.Shapes.AddChart().Chart
|
|||
|
# https://msdn.microsoft.com/en-us/vba/excel-vba/articles/xlcharttype-enumeration-excel
|
|||
|
$chart.chartType = [int]([Microsoft.Office.Interop.Excel.XLChartType]::$ChartType)
|
|||
|
$chart.ApplyLayout($ChartLayout)
|
|||
|
If ($null -eq $ChartData)
|
|||
|
{
|
|||
|
If ($null -eq $StartRow)
|
|||
|
{
|
|||
|
$start = $worksheet.Range("A1")
|
|||
|
}
|
|||
|
Else
|
|||
|
{
|
|||
|
$start = $worksheet.Range($StartRow)
|
|||
|
}
|
|||
|
# get the last cell
|
|||
|
$X = $worksheet.Range($start,$start.End([Microsoft.Office.Interop.Excel.XLDirection]::xlDown))
|
|||
|
If ($null -eq $StartColumn)
|
|||
|
{
|
|||
|
$start = $worksheet.Range("B1")
|
|||
|
}
|
|||
|
Else
|
|||
|
{
|
|||
|
$start = $worksheet.Range($StartColumn)
|
|||
|
}
|
|||
|
# get the last cell
|
|||
|
$Y = $worksheet.Range($start,$start.End([Microsoft.Office.Interop.Excel.XLDirection]::xlDown))
|
|||
|
$ChartData = $worksheet.Range($X,$Y)
|
|||
|
|
|||
|
Get-ADRExcelComObjRelease -ComObjtoRelease $X
|
|||
|
Remove-Variable X
|
|||
|
Get-ADRExcelComObjRelease -ComObjtoRelease $Y
|
|||
|
Remove-Variable Y
|
|||
|
Get-ADRExcelComObjRelease -ComObjtoRelease $start
|
|||
|
Remove-Variable start
|
|||
|
}
|
|||
|
$chart.SetSourceData($ChartData)
|
|||
|
# https://docs.microsoft.com/en-us/dotnet/api/microsoft.office.interop.excel.chartclass.plotby?redirectedfrom=MSDN&view=excel-pia#Microsoft_Office_Interop_Excel_ChartClass_PlotBy
|
|||
|
$chart.PlotBy = [Microsoft.Office.Interop.Excel.XlRowCol]::xlColumns
|
|||
|
$chart.seriesCollection(1).Select() | Out-Null
|
|||
|
$chart.SeriesCollection(1).ApplyDataLabels() | out-Null
|
|||
|
# modify the chart title
|
|||
|
$chart.HasTitle = $True
|
|||
|
$chart.ChartTitle.Text = $ChartTitle
|
|||
|
# Reposition the Chart
|
|||
|
$temp = $worksheet.Range($RangetoCover)
|
|||
|
# $chart.parent.placement = 3
|
|||
|
$chart.parent.top = $temp.Top
|
|||
|
$chart.parent.left = $temp.Left
|
|||
|
$chart.parent.width = $temp.Width
|
|||
|
If ($ChartTitle -ne "Privileged Groups in AD")
|
|||
|
{
|
|||
|
$chart.parent.height = $temp.Height
|
|||
|
}
|
|||
|
# $chart.Legend.Delete()
|
|||
|
$excel.ScreenUpdating = $true
|
|||
|
$excel.DisplayAlerts = $true
|
|||
|
|
|||
|
Get-ADRExcelComObjRelease -ComObjtoRelease $chart
|
|||
|
Remove-Variable chart
|
|||
|
Get-ADRExcelComObjRelease -ComObjtoRelease $ChartData
|
|||
|
Remove-Variable ChartData
|
|||
|
Get-ADRExcelComObjRelease -ComObjtoRelease $temp
|
|||
|
Remove-Variable temp
|
|||
|
Get-ADRExcelComObjRelease -ComObjtoRelease $worksheet
|
|||
|
Remove-Variable worksheet
|
|||
|
}
|
|||
|
|
|||
|
Function Get-ADRExcelSort
|
|||
|
{
|
|||
|
<#
|
|||
|
.SYNOPSIS
|
|||
|
Sorts a WorkSheet in the active Workbook.
|
|||
|
|
|||
|
.DESCRIPTION
|
|||
|
Sorts a WorkSheet in the active Workbook.
|
|||
|
|
|||
|
.PARAMETER ColumnName
|
|||
|
[string]
|
|||
|
Name of the Column.
|
|||
|
#>
|
|||
|
param (
|
|||
|
[Parameter(Mandatory = $true)]
|
|||
|
[string] $ColumnName
|
|||
|
)
|
|||
|
|
|||
|
$worksheet = $workbook.Worksheets.Item(1)
|
|||
|
$worksheet.Activate();
|
|||
|
|
|||
|
$ExcelColumn = ($worksheet.Columns.Find($ColumnName))
|
|||
|
If ($ExcelColumn)
|
|||
|
{
|
|||
|
If ($ExcelColumn.Text -ne $ColumnName)
|
|||
|
{
|
|||
|
$BeginAddress = $ExcelColumn.Address(0,0,1,1)
|
|||
|
$End = $False
|
|||
|
Do {
|
|||
|
Write-Verbose "[Get-ADRExcelSort] $($ExcelColumn.Text) selected instead of $($ColumnName) in the $($worksheet.Name) worksheet."
|
|||
|
$ExcelColumn = ($worksheet.Columns.FindNext($ExcelColumn))
|
|||
|
$Address = $ExcelColumn.Address(0,0,1,1)
|
|||
|
If ( ($Address -eq $BeginAddress) -or ($ExcelColumn.Text -eq $ColumnName) )
|
|||
|
{
|
|||
|
$End = $True
|
|||
|
}
|
|||
|
} Until ($End -eq $True)
|
|||
|
}
|
|||
|
If ($ExcelColumn.Text -eq $ColumnName)
|
|||
|
{
|
|||
|
# Sort by Column
|
|||
|
$workSheet.ListObjects.Item(1).Sort.SortFields.Clear()
|
|||
|
$workSheet.ListObjects.Item(1).Sort.SortFields.Add($ExcelColumn) | Out-Null
|
|||
|
$worksheet.ListObjects.Item(1).Sort.Apply()
|
|||
|
}
|
|||
|
Else
|
|||
|
{
|
|||
|
Write-Verbose "[Get-ADRExcelSort] $($ColumnName) not found in the $($worksheet.Name) worksheet."
|
|||
|
}
|
|||
|
}
|
|||
|
Else
|
|||
|
{
|
|||
|
Write-Verbose "[Get-ADRExcelSort] $($ColumnName) not found in the $($worksheet.Name) worksheet."
|
|||
|
}
|
|||
|
Get-ADRExcelComObjRelease -ComObjtoRelease $worksheet
|
|||
|
Remove-Variable worksheet
|
|||
|
}
|
|||
|
|
|||
|
Function Export-ADRExcel
|
|||
|
{
|
|||
|
<#
|
|||
|
.SYNOPSIS
|
|||
|
Automates the generation of the ADRecon report.
|
|||
|
|
|||
|
.DESCRIPTION
|
|||
|
Automates the generation of the ADRecon report. If specific files exist, they are imported into the ADRecon report.
|
|||
|
|
|||
|
.PARAMETER ExcelPath
|
|||
|
[string]
|
|||
|
Path for ADRecon output folder containing the CSV files to generate the ADRecon-Report.xlsx
|
|||
|
|
|||
|
.OUTPUTS
|
|||
|
Creates the ADRecon-Report.xlsx report in the folder.
|
|||
|
#>
|
|||
|
param(
|
|||
|
[Parameter(Mandatory = $true)]
|
|||
|
[string] $ExcelPath
|
|||
|
)
|
|||
|
|
|||
|
$ExcelPath = $((Convert-Path $ExcelPath).TrimEnd("\"))
|
|||
|
$ReportPath = -join($ExcelPath,'\','CSV-Files')
|
|||
|
If (!(Test-Path $ReportPath))
|
|||
|
{
|
|||
|
Write-Warning "[Export-ADRExcel] Could not locate the CSV-Files directory ... Exiting"
|
|||
|
Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
Return $null
|
|||
|
}
|
|||
|
Get-ADRExcelComObj
|
|||
|
If ($excel)
|
|||
|
{
|
|||
|
Write-Output "[*] Generating ADRecon-Report.xlsx"
|
|||
|
|
|||
|
$ADFileName = -join($ReportPath,'\','AboutADRecon.csv')
|
|||
|
If (Test-Path $ADFileName)
|
|||
|
{
|
|||
|
Get-ADRExcelImport -ADFileName $ADFileName
|
|||
|
Remove-Variable ADFileName
|
|||
|
|
|||
|
$workbook.Worksheets.Item(1).Name = "About ADRecon"
|
|||
|
$workbook.Worksheets.Item(1).Hyperlinks.Add($workbook.Worksheets.Item(1).Cells.Item(3,2) , "https://github.com/sense-of-security/ADRecon", "" , "", "github.com/sense-of-security/ADRecon") | Out-Null
|
|||
|
$workbook.Worksheets.Item(1).UsedRange.EntireColumn.AutoFit() | Out-Null
|
|||
|
}
|
|||
|
|
|||
|
$ADFileName = -join($ReportPath,'\','Forest.csv')
|
|||
|
If (Test-Path $ADFileName)
|
|||
|
{
|
|||
|
Get-ADRExcelWorkbook -Name "Forest"
|
|||
|
Get-ADRExcelImport -ADFileName $ADFileName
|
|||
|
Remove-Variable ADFileName
|
|||
|
}
|
|||
|
|
|||
|
$ADFileName = -join($ReportPath,'\','Domain.csv')
|
|||
|
If (Test-Path $ADFileName)
|
|||
|
{
|
|||
|
Get-ADRExcelWorkbook -Name "Domain"
|
|||
|
Get-ADRExcelImport -ADFileName $ADFileName
|
|||
|
$DomainObj = Import-CSV -Path $ADFileName
|
|||
|
Remove-Variable ADFileName
|
|||
|
$DomainName = -join($DomainObj[0].Value,"-")
|
|||
|
Remove-Variable DomainObj
|
|||
|
}
|
|||
|
|
|||
|
$ADFileName = -join($ReportPath,'\','Trusts.csv')
|
|||
|
If (Test-Path $ADFileName)
|
|||
|
{
|
|||
|
Get-ADRExcelWorkbook -Name "Trusts"
|
|||
|
Get-ADRExcelImport -ADFileName $ADFileName
|
|||
|
Remove-Variable ADFileName
|
|||
|
}
|
|||
|
|
|||
|
$ADFileName = -join($ReportPath,'\','Subnets.csv')
|
|||
|
If (Test-Path $ADFileName)
|
|||
|
{
|
|||
|
Get-ADRExcelWorkbook -Name "Subnets"
|
|||
|
Get-ADRExcelImport -ADFileName $ADFileName
|
|||
|
Remove-Variable ADFileName
|
|||
|
}
|
|||
|
|
|||
|
$ADFileName = -join($ReportPath,'\','Sites.csv')
|
|||
|
If (Test-Path $ADFileName)
|
|||
|
{
|
|||
|
Get-ADRExcelWorkbook -Name "Sites"
|
|||
|
Get-ADRExcelImport -ADFileName $ADFileName
|
|||
|
Remove-Variable ADFileName
|
|||
|
}
|
|||
|
|
|||
|
$ADFileName = -join($ReportPath,'\','FineGrainedPasswordPolicy.csv')
|
|||
|
If (Test-Path $ADFileName)
|
|||
|
{
|
|||
|
Get-ADRExcelWorkbook -Name "Fine Grained Password Policy"
|
|||
|
Get-ADRExcelImport -ADFileName $ADFileName
|
|||
|
Remove-Variable ADFileName
|
|||
|
}
|
|||
|
|
|||
|
$ADFileName = -join($ReportPath,'\','DefaultPasswordPolicy.csv')
|
|||
|
If (Test-Path $ADFileName)
|
|||
|
{
|
|||
|
Get-ADRExcelWorkbook -Name "Default Password Policy"
|
|||
|
Get-ADRExcelImport -ADFileName $ADFileName
|
|||
|
Remove-Variable ADFileName
|
|||
|
|
|||
|
$excel.ScreenUpdating = $false
|
|||
|
$worksheet = $workbook.Worksheets.Item(1)
|
|||
|
# https://docs.microsoft.com/en-us/office/vba/api/excel.xlhalign
|
|||
|
$worksheet.Range("B2:G10").HorizontalAlignment = -4108
|
|||
|
# https://docs.microsoft.com/en-us/office/vba/api/excel.range.borderaround
|
|||
|
|
|||
|
"A2:B10", "C2:D10", "E2:F10", "G2:G10" | ForEach-Object {
|
|||
|
$worksheet.Range($_).BorderAround(1) | Out-Null
|
|||
|
}
|
|||
|
|
|||
|
# https://docs.microsoft.com/en-us/dotnet/api/microsoft.office.interop.excel.formatconditions.add?view=excel-pia
|
|||
|
# $worksheet.Range().FormatConditions.Add
|
|||
|
# http://dmcritchie.mvps.org/excel/colors.htm
|
|||
|
# Values for Font.ColorIndex
|
|||
|
|
|||
|
$ObjValues = @(
|
|||
|
# PCI Enforce password history (passwords)
|
|||
|
"C2", '=IF(B2<4,TRUE, FALSE)'
|
|||
|
|
|||
|
# PCI Maximum password age (days)
|
|||
|
"C3", '=IF(OR(B3=0,B3>90),TRUE, FALSE)'
|
|||
|
|
|||
|
# PCI Minimum password age (days)
|
|||
|
|
|||
|
# PCI Minimum password length (characters)
|
|||
|
"C5", '=IF(B5<7,TRUE, FALSE)'
|
|||
|
|
|||
|
# PCI Password must meet complexity requirements
|
|||
|
"C6", '=IF(B6<>TRUE,TRUE, FALSE)'
|
|||
|
|
|||
|
# PCI Store password using reversible encryption for all users in the domain
|
|||
|
|
|||
|
# PCI Account lockout duration (mins)
|
|||
|
"C8", '=IF(AND(B8>=1,B8<30),TRUE, FALSE)'
|
|||
|
|
|||
|
# PCI Account lockout threshold (attempts)
|
|||
|
"C9", '=IF(OR(B9=0,B9>6),TRUE, FALSE)'
|
|||
|
|
|||
|
# PCI Reset account lockout counter after (mins)
|
|||
|
|
|||
|
# ASD ISM Enforce password history (passwords)
|
|||
|
"E2", '=IF(B2<8,TRUE, FALSE)'
|
|||
|
|
|||
|
# ASD ISM Maximum password age (days)
|
|||
|
"E3", '=IF(OR(B3=0,B3>90),TRUE, FALSE)'
|
|||
|
|
|||
|
# ASD ISM Minimum password age (days)
|
|||
|
"E4", '=IF(B4=0,TRUE, FALSE)'
|
|||
|
|
|||
|
# ASD ISM Minimum password length (characters)
|
|||
|
"E5", '=IF(B5<13,TRUE, FALSE)'
|
|||
|
|
|||
|
# ASD ISM Password must meet complexity requirements
|
|||
|
"E6", '=IF(B6<>TRUE,TRUE, FALSE)'
|
|||
|
|
|||
|
# ASD ISM Store password using reversible encryption for all users in the domain
|
|||
|
|
|||
|
# ASD ISM Account lockout duration (mins)
|
|||
|
|
|||
|
# ASD ISM Account lockout threshold (attempts)
|
|||
|
"E9", '=IF(OR(B9=0,B9>5),TRUE, FALSE)'
|
|||
|
|
|||
|
# ASD ISM Reset account lockout counter after (mins)
|
|||
|
|
|||
|
# CIS Benchmark Enforce password history (passwords)
|
|||
|
"G2", '=IF(B2<24,TRUE, FALSE)'
|
|||
|
|
|||
|
# CIS Benchmark Maximum password age (days)
|
|||
|
"G3", '=IF(OR(B3=0,B3>60),TRUE, FALSE)'
|
|||
|
|
|||
|
# CIS Benchmark Minimum password age (days)
|
|||
|
"G4", '=IF(B4=0,TRUE, FALSE)'
|
|||
|
|
|||
|
# CIS Benchmark Minimum password length (characters)
|
|||
|
"G5", '=IF(B5<14,TRUE, FALSE)'
|
|||
|
|
|||
|
# CIS Benchmark Password must meet complexity requirements
|
|||
|
"G6", '=IF(B6<>TRUE,TRUE, FALSE)'
|
|||
|
|
|||
|
# CIS Benchmark Store password using reversible encryption for all users in the domain
|
|||
|
"G7", '=IF(B7<>FALSE,TRUE, FALSE)'
|
|||
|
|
|||
|
# CIS Benchmark Account lockout duration (mins)
|
|||
|
"G8", '=IF(AND(B8>=1,B8<15),TRUE, FALSE)'
|
|||
|
|
|||
|
# CIS Benchmark Account lockout threshold (attempts)
|
|||
|
"G9", '=IF(OR(B9=0,B9>10),TRUE, FALSE)'
|
|||
|
|
|||
|
# CIS Benchmark Reset account lockout counter after (mins)
|
|||
|
"G10", '=IF(B10<15,TRUE, FALSE)' )
|
|||
|
|
|||
|
For ($i = 0; $i -lt $($ObjValues.Count); $i++)
|
|||
|
{
|
|||
|
$worksheet.Range($ObjValues[$i]).FormatConditions.Add([Microsoft.Office.Interop.Excel.XlFormatConditionType]::xlExpression, 0, $ObjValues[$i+1]) | Out-Null
|
|||
|
$i++
|
|||
|
}
|
|||
|
|
|||
|
"C2", "C3" , "C5", "C6", "C8", "C9", "E2", "E3" , "E4", "E5", "E6", "E9", "G2", "G3", "G4", "G5", "G6", "G7", "G8", "G9", "G10" | ForEach-Object {
|
|||
|
$worksheet.Range($_).FormatConditions.Item(1).StopIfTrue = $false
|
|||
|
$worksheet.Range($_).FormatConditions.Item(1).Font.ColorIndex = 3
|
|||
|
}
|
|||
|
|
|||
|
$workbook.Worksheets.Item(1).Hyperlinks.Add($workbook.Worksheets.Item(1).Cells.Item(1,4) , "https://www.pcisecuritystandards.org/document_library?category=pcidss&document=pci_dss", "" , "", "PCI DSS v3.2.1") | Out-Null
|
|||
|
$workbook.Worksheets.Item(1).Hyperlinks.Add($workbook.Worksheets.Item(1).Cells.Item(1,6) , "https://acsc.gov.au/infosec/ism/", "" , "", "2018 ISM Controls") | Out-Null
|
|||
|
$workbook.Worksheets.Item(1).Hyperlinks.Add($workbook.Worksheets.Item(1).Cells.Item(1,7) , "https://www.cisecurity.org/benchmark/microsoft_windows_server/", "" , "", "CIS Benchmark 2016") | Out-Null
|
|||
|
|
|||
|
$excel.ScreenUpdating = $true
|
|||
|
Get-ADRExcelComObjRelease -ComObjtoRelease $worksheet
|
|||
|
Remove-Variable worksheet
|
|||
|
}
|
|||
|
|
|||
|
$ADFileName = -join($ReportPath,'\','DomainControllers.csv')
|
|||
|
If (Test-Path $ADFileName)
|
|||
|
{
|
|||
|
Get-ADRExcelWorkbook -Name "Domain Controllers"
|
|||
|
Get-ADRExcelImport -ADFileName $ADFileName
|
|||
|
Remove-Variable ADFileName
|
|||
|
}
|
|||
|
|
|||
|
$ADFileName = -join($ReportPath,'\','DACLs.csv')
|
|||
|
If (Test-Path $ADFileName)
|
|||
|
{
|
|||
|
Get-ADRExcelWorkbook -Name "DACLs"
|
|||
|
Get-ADRExcelImport -ADFileName $ADFileName
|
|||
|
Remove-Variable ADFileName
|
|||
|
}
|
|||
|
|
|||
|
$ADFileName = -join($ReportPath,'\','SACLs.csv')
|
|||
|
If (Test-Path $ADFileName)
|
|||
|
{
|
|||
|
Get-ADRExcelWorkbook -Name "SACLs"
|
|||
|
Get-ADRExcelImport -ADFileName $ADFileName
|
|||
|
Remove-Variable ADFileName
|
|||
|
}
|
|||
|
|
|||
|
$ADFileName = -join($ReportPath,'\','GPOs.csv')
|
|||
|
If (Test-Path $ADFileName)
|
|||
|
{
|
|||
|
Get-ADRExcelWorkbook -Name "GPOs"
|
|||
|
Get-ADRExcelImport -ADFileName $ADFileName
|
|||
|
Remove-Variable ADFileName
|
|||
|
}
|
|||
|
|
|||
|
$ADFileName = -join($ReportPath,'\','gPLinks.csv')
|
|||
|
If (Test-Path $ADFileName)
|
|||
|
{
|
|||
|
Get-ADRExcelWorkbook -Name "gPLinks"
|
|||
|
Get-ADRExcelImport -ADFileName $ADFileName
|
|||
|
Remove-Variable ADFileName
|
|||
|
}
|
|||
|
|
|||
|
$ADFileName = -join($ReportPath,'\','DNSNodes','.csv')
|
|||
|
If (Test-Path $ADFileName)
|
|||
|
{
|
|||
|
Get-ADRExcelWorkbook -Name "DNS Records"
|
|||
|
Get-ADRExcelImport -ADFileName $ADFileName
|
|||
|
Remove-Variable ADFileName
|
|||
|
}
|
|||
|
|
|||
|
$ADFileName = -join($ReportPath,'\','DNSZones.csv')
|
|||
|
If (Test-Path $ADFileName)
|
|||
|
{
|
|||
|
Get-ADRExcelWorkbook -Name "DNS Zones"
|
|||
|
Get-ADRExcelImport -ADFileName $ADFileName
|
|||
|
Remove-Variable ADFileName
|
|||
|
}
|
|||
|
|
|||
|
$ADFileName = -join($ReportPath,'\','Printers.csv')
|
|||
|
If (Test-Path $ADFileName)
|
|||
|
{
|
|||
|
Get-ADRExcelWorkbook -Name "Printers"
|
|||
|
Get-ADRExcelImport -ADFileName $ADFileName
|
|||
|
Remove-Variable ADFileName
|
|||
|
}
|
|||
|
|
|||
|
$ADFileName = -join($ReportPath,'\','BitLockerRecoveryKeys.csv')
|
|||
|
If (Test-Path $ADFileName)
|
|||
|
{
|
|||
|
Get-ADRExcelWorkbook -Name "BitLocker"
|
|||
|
Get-ADRExcelImport -ADFileName $ADFileName
|
|||
|
Remove-Variable ADFileName
|
|||
|
}
|
|||
|
|
|||
|
$ADFileName = -join($ReportPath,'\','LAPS.csv')
|
|||
|
If (Test-Path $ADFileName)
|
|||
|
{
|
|||
|
Get-ADRExcelWorkbook -Name "LAPS"
|
|||
|
Get-ADRExcelImport -ADFileName $ADFileName
|
|||
|
Remove-Variable ADFileName
|
|||
|
}
|
|||
|
|
|||
|
$ADFileName = -join($ReportPath,'\','ComputerSPNs.csv')
|
|||
|
If (Test-Path $ADFileName)
|
|||
|
{
|
|||
|
Get-ADRExcelWorkbook -Name "Computer SPNs"
|
|||
|
Get-ADRExcelImport -ADFileName $ADFileName
|
|||
|
Remove-Variable ADFileName
|
|||
|
|
|||
|
Get-ADRExcelSort -ColumnName "Name"
|
|||
|
}
|
|||
|
|
|||
|
$ADFileName = -join($ReportPath,'\','Computers.csv')
|
|||
|
If (Test-Path $ADFileName)
|
|||
|
{
|
|||
|
Get-ADRExcelWorkbook -Name "Computers"
|
|||
|
Get-ADRExcelImport -ADFileName $ADFileName
|
|||
|
Remove-Variable ADFileName
|
|||
|
|
|||
|
Get-ADRExcelSort -ColumnName "UserName"
|
|||
|
|
|||
|
$worksheet = $workbook.Worksheets.Item(1)
|
|||
|
# Freeze First Row and Column
|
|||
|
$worksheet.Select()
|
|||
|
$worksheet.Application.ActiveWindow.splitcolumn = 1
|
|||
|
$worksheet.Application.ActiveWindow.splitrow = 1
|
|||
|
$worksheet.Application.ActiveWindow.FreezePanes = $true
|
|||
|
|
|||
|
Get-ADRExcelComObjRelease -ComObjtoRelease $worksheet
|
|||
|
Remove-Variable worksheet
|
|||
|
}
|
|||
|
|
|||
|
$ADFileName = -join($ReportPath,'\','OUs.csv')
|
|||
|
If (Test-Path $ADFileName)
|
|||
|
{
|
|||
|
Get-ADRExcelWorkbook -Name "OUs"
|
|||
|
Get-ADRExcelImport -ADFileName $ADFileName
|
|||
|
Remove-Variable ADFileName
|
|||
|
}
|
|||
|
|
|||
|
$ADFileName = -join($ReportPath,'\','UserSPNs.csv')
|
|||
|
If (Test-Path $ADFileName)
|
|||
|
{
|
|||
|
Get-ADRExcelWorkbook -Name "User SPNs"
|
|||
|
Get-ADRExcelImport -ADFileName $ADFileName
|
|||
|
Remove-Variable ADFileName
|
|||
|
}
|
|||
|
|
|||
|
$ADFileName = -join($ReportPath,'\','Groups.csv')
|
|||
|
If (Test-Path $ADFileName)
|
|||
|
{
|
|||
|
Get-ADRExcelWorkbook -Name "Groups"
|
|||
|
Get-ADRExcelImport -ADFileName $ADFileName
|
|||
|
Remove-Variable ADFileName
|
|||
|
|
|||
|
Get-ADRExcelSort -ColumnName "DistinguishedName"
|
|||
|
}
|
|||
|
|
|||
|
$ADFileName = -join($ReportPath,'\','GroupMembers.csv')
|
|||
|
If (Test-Path $ADFileName)
|
|||
|
{
|
|||
|
Get-ADRExcelWorkbook -Name "Group Members"
|
|||
|
Get-ADRExcelImport -ADFileName $ADFileName
|
|||
|
Remove-Variable ADFileName
|
|||
|
|
|||
|
Get-ADRExcelSort -ColumnName "Group Name"
|
|||
|
}
|
|||
|
|
|||
|
$ADFileName = -join($ReportPath,'\','Users.csv')
|
|||
|
If (Test-Path $ADFileName)
|
|||
|
{
|
|||
|
Get-ADRExcelWorkbook -Name "Users"
|
|||
|
Get-ADRExcelImport -ADFileName $ADFileName
|
|||
|
Remove-Variable ADFileName
|
|||
|
|
|||
|
Get-ADRExcelSort -ColumnName "UserName"
|
|||
|
|
|||
|
$worksheet = $workbook.Worksheets.Item(1)
|
|||
|
|
|||
|
# Freeze First Row and Column
|
|||
|
$worksheet.Select()
|
|||
|
$worksheet.Application.ActiveWindow.splitcolumn = 1
|
|||
|
$worksheet.Application.ActiveWindow.splitrow = 1
|
|||
|
$worksheet.Application.ActiveWindow.FreezePanes = $true
|
|||
|
|
|||
|
$worksheet.Cells.Item(1,3).Interior.ColorIndex = 5
|
|||
|
$worksheet.Cells.Item(1,3).font.ColorIndex = 2
|
|||
|
# Set Filter to Enabled Accounts only
|
|||
|
$worksheet.UsedRange.Select() | Out-Null
|
|||
|
$excel.Selection.AutoFilter(3,$true) | Out-Null
|
|||
|
$worksheet.Cells.Item(1,1).Select() | Out-Null
|
|||
|
Get-ADRExcelComObjRelease -ComObjtoRelease $worksheet
|
|||
|
Remove-Variable worksheet
|
|||
|
}
|
|||
|
|
|||
|
# Computer Role Stats
|
|||
|
$ADFileName = -join($ReportPath,'\','ComputerSPNs.csv')
|
|||
|
If (Test-Path $ADFileName)
|
|||
|
{
|
|||
|
Get-ADRExcelWorkbook -Name "Computer Role Stats"
|
|||
|
Remove-Variable ADFileName
|
|||
|
|
|||
|
$worksheet = $workbook.Worksheets.Item(1)
|
|||
|
$PivotTableName = "Computer SPNs"
|
|||
|
Get-ADRExcelPivotTable -SrcSheetName "Computer SPNs" -PivotTableName $PivotTableName -PivotRows @("Service") -PivotValues @("Service")
|
|||
|
|
|||
|
$worksheet.Cells.Item(1,1) = "Computer Role"
|
|||
|
$worksheet.Cells.Item(1,2) = "Count"
|
|||
|
|
|||
|
# https://msdn.microsoft.com/en-us/vba/excel-vba/articles/xlsortorder-enumeration-excel
|
|||
|
$worksheet.PivotTables($PivotTableName).PivotFields("Service").AutoSort([Microsoft.Office.Interop.Excel.XlSortOrder]::xlDescending,"Count")
|
|||
|
|
|||
|
Get-ADRExcelChart -ChartType "xlColumnClustered" -ChartLayout 10 -ChartTitle "Computer Roles in AD" -RangetoCover "D2:U16"
|
|||
|
$workbook.Worksheets.Item(1).Hyperlinks.Add($workbook.Worksheets.Item(1).Cells.Item(1,4) , "" , "'Computer SPNs'!A1", "", "Raw Data") | Out-Null
|
|||
|
$excel.Windows.Item(1).Displaygridlines = $false
|
|||
|
Remove-Variable PivotTableName
|
|||
|
|
|||
|
Get-ADRExcelComObjRelease -ComObjtoRelease $worksheet
|
|||
|
Remove-Variable worksheet
|
|||
|
}
|
|||
|
|
|||
|
# Operating System Stats
|
|||
|
$ADFileName = -join($ReportPath,'\','Computers.csv')
|
|||
|
If (Test-Path $ADFileName)
|
|||
|
{
|
|||
|
Get-ADRExcelWorkbook -Name "Operating System Stats"
|
|||
|
Remove-Variable ADFileName
|
|||
|
|
|||
|
$worksheet = $workbook.Worksheets.Item(1)
|
|||
|
$PivotTableName = "Operating Systems"
|
|||
|
Get-ADRExcelPivotTable -SrcSheetName "Computers" -PivotTableName $PivotTableName -PivotRows @("Operating System") -PivotValues @("Operating System")
|
|||
|
|
|||
|
$worksheet.Cells.Item(1,1) = "Operating System"
|
|||
|
$worksheet.Cells.Item(1,2) = "Count"
|
|||
|
|
|||
|
# https://msdn.microsoft.com/en-us/vba/excel-vba/articles/xlsortorder-enumeration-excel
|
|||
|
$worksheet.PivotTables($PivotTableName).PivotFields("Operating System").AutoSort([Microsoft.Office.Interop.Excel.XlSortOrder]::xlDescending,"Count")
|
|||
|
|
|||
|
Get-ADRExcelChart -ChartType "xlColumnClustered" -ChartLayout 10 -ChartTitle "Operating Systems in AD" -RangetoCover "D2:S16"
|
|||
|
$workbook.Worksheets.Item(1).Hyperlinks.Add($workbook.Worksheets.Item(1).Cells.Item(1,4) , "" , "Computers!A1", "", "Raw Data") | Out-Null
|
|||
|
$excel.Windows.Item(1).Displaygridlines = $false
|
|||
|
Remove-Variable PivotTableName
|
|||
|
|
|||
|
Get-ADRExcelComObjRelease -ComObjtoRelease $worksheet
|
|||
|
Remove-Variable worksheet
|
|||
|
}
|
|||
|
|
|||
|
# Group Stats
|
|||
|
$ADFileName = -join($ReportPath,'\','GroupMembers.csv')
|
|||
|
If (Test-Path $ADFileName)
|
|||
|
{
|
|||
|
Get-ADRExcelWorkbook -Name "Privileged Group Stats"
|
|||
|
Remove-Variable ADFileName
|
|||
|
|
|||
|
$worksheet = $workbook.Worksheets.Item(1)
|
|||
|
$PivotTableName = "Group Members"
|
|||
|
Get-ADRExcelPivotTable -SrcSheetName "Group Members" -PivotTableName $PivotTableName -PivotRows @("Group Name")-PivotFilters @("AccountType") -PivotValues @("AccountType")
|
|||
|
|
|||
|
# Set the filter
|
|||
|
$worksheet.PivotTables($PivotTableName).PivotFields("AccountType").CurrentPage = "user"
|
|||
|
|
|||
|
$worksheet.Cells.Item(1,2).Interior.ColorIndex = 5
|
|||
|
$worksheet.Cells.Item(1,2).font.ColorIndex = 2
|
|||
|
|
|||
|
$worksheet.Cells.Item(3,1) = "Group Name"
|
|||
|
$worksheet.Cells.Item(3,2) = "Count (Not-Recursive)"
|
|||
|
|
|||
|
$excel.ScreenUpdating = $false
|
|||
|
# Create a copy of the Pivot Table
|
|||
|
$PivotTableTemp = ($workbook.PivotCaches().Item($workbook.PivotCaches().Count)).CreatePivotTable("R1C5","PivotTableTemp")
|
|||
|
$PivotFieldTemp = $PivotTableTemp.PivotFields("Group Name")
|
|||
|
# Set a filter
|
|||
|
$PivotFieldTemp.Orientation = [Microsoft.Office.Interop.Excel.XlPivotFieldOrientation]::xlPageField
|
|||
|
Try
|
|||
|
{
|
|||
|
$PivotFieldTemp.CurrentPage = "Domain Admins"
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
# No Direct Domain Admins. Good Job!
|
|||
|
$NoDA = $true
|
|||
|
}
|
|||
|
If ($NoDA)
|
|||
|
{
|
|||
|
Try
|
|||
|
{
|
|||
|
$PivotFieldTemp.CurrentPage = "Administrators"
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
# No Direct Administrators
|
|||
|
}
|
|||
|
}
|
|||
|
# Create a Slicer
|
|||
|
$PivotSlicer = $workbook.SlicerCaches.Add($PivotTableTemp,$PivotFieldTemp)
|
|||
|
# Add Original Pivot Table to the Slicer
|
|||
|
$PivotSlicer.PivotTables.AddPivotTable($worksheet.PivotTables($PivotTableName))
|
|||
|
# Delete the Slicer
|
|||
|
$PivotSlicer.Delete()
|
|||
|
# Delete the Pivot Table Copy
|
|||
|
$PivotTableTemp.TableRange2.Delete() | Out-Null
|
|||
|
|
|||
|
Get-ADRExcelComObjRelease -ComObjtoRelease $PivotFieldTemp
|
|||
|
Get-ADRExcelComObjRelease -ComObjtoRelease $PivotSlicer
|
|||
|
Get-ADRExcelComObjRelease -ComObjtoRelease $PivotTableTemp
|
|||
|
|
|||
|
Remove-Variable PivotFieldTemp
|
|||
|
Remove-Variable PivotSlicer
|
|||
|
Remove-Variable PivotTableTemp
|
|||
|
|
|||
|
"Account Operators","Administrators","Backup Operators","Cert Publishers","Crypto Operators","DnsAdmins","Domain Admins","Enterprise Admins","Enterprise Key Admins","Incoming Forest Trust Builders","Key Admins","Microsoft Advanced Threat Analytics Administrators","Network Operators","Print Operators","Remote Desktop Users","Schema Admins","Server Operators" | ForEach-Object {
|
|||
|
Try
|
|||
|
{
|
|||
|
$worksheet.PivotTables($PivotTableName).PivotFields("Group Name").PivotItems($_).Visible = $true
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
# when PivotItem is not found
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
# https://msdn.microsoft.com/en-us/vba/excel-vba/articles/xlsortorder-enumeration-excel
|
|||
|
$worksheet.PivotTables($PivotTableName).PivotFields("Group Name").AutoSort([Microsoft.Office.Interop.Excel.XlSortOrder]::xlDescending,"Count (Not-Recursive)")
|
|||
|
|
|||
|
$worksheet.Cells.Item(3,1).Interior.ColorIndex = 5
|
|||
|
$worksheet.Cells.Item(3,1).font.ColorIndex = 2
|
|||
|
|
|||
|
$excel.ScreenUpdating = $true
|
|||
|
|
|||
|
Get-ADRExcelChart -ChartType "xlColumnClustered" -ChartLayout 10 -ChartTitle "Privileged Groups in AD" -RangetoCover "D2:P16" -StartRow "A3" -StartColumn "B3"
|
|||
|
$workbook.Worksheets.Item(1).Hyperlinks.Add($workbook.Worksheets.Item(1).Cells.Item(1,4) , "" , "'Group Members'!A1", "", "Raw Data") | Out-Null
|
|||
|
$excel.Windows.Item(1).Displaygridlines = $false
|
|||
|
|
|||
|
Get-ADRExcelComObjRelease -ComObjtoRelease $worksheet
|
|||
|
Remove-Variable worksheet
|
|||
|
}
|
|||
|
|
|||
|
# Computer Stats
|
|||
|
$ADFileName = -join($ReportPath,'\','Computers.csv')
|
|||
|
If (Test-Path $ADFileName)
|
|||
|
{
|
|||
|
Get-ADRExcelWorkbook -Name "Computer Stats"
|
|||
|
Remove-Variable ADFileName
|
|||
|
|
|||
|
$ObjAttributes = New-Object System.Collections.Specialized.OrderedDictionary
|
|||
|
$ObjAttributes.Add("Delegation Typ",'"Unconstrained"')
|
|||
|
$ObjAttributes.Add("Delegation Type",'"Constrained"')
|
|||
|
$ObjAttributes.Add("SIDHistory",'"*"')
|
|||
|
$ObjAttributes.Add("Dormant",'"TRUE"')
|
|||
|
$ObjAttributes.Add("Password Age (> ",'"TRUE"')
|
|||
|
$ObjAttributes.Add("ms-ds-CreatorSid",'"*"')
|
|||
|
|
|||
|
Get-ADRExcelAttributeStats -SrcSheetName "Computers" -Title1 "Computer Accounts in AD" -Title2 "Status of Computer Accounts" -ObjAttributes $ObjAttributes
|
|||
|
Remove-Variable ObjAttributes
|
|||
|
|
|||
|
Get-ADRExcelChart -ChartType "xlPie" -ChartLayout 3 -ChartTitle "Computer Accounts in AD" -RangetoCover "A11:D23" -ChartData $workbook.Worksheets.Item(1).Range("A3:A4,B3:B4")
|
|||
|
$workbook.Worksheets.Item(1).Hyperlinks.Add($workbook.Worksheets.Item(1).Cells.Item(10,1) , "" , "Computers!A1", "", "Raw Data") | Out-Null
|
|||
|
|
|||
|
Get-ADRExcelChart -ChartType "xlBarClustered" -ChartLayout 1 -ChartTitle "Status of Computer Accounts" -RangetoCover "F11:L23" -ChartData $workbook.Worksheets.Item(1).Range("F2:F8,G2:G8")
|
|||
|
$workbook.Worksheets.Item(1).Hyperlinks.Add($workbook.Worksheets.Item(1).Cells.Item(10,6) , "" , "Computers!A1", "", "Raw Data") | Out-Null
|
|||
|
|
|||
|
$workbook.Worksheets.Item(1).UsedRange.EntireColumn.AutoFit() | Out-Null
|
|||
|
$excel.Windows.Item(1).Displaygridlines = $false
|
|||
|
}
|
|||
|
|
|||
|
# User Stats
|
|||
|
$ADFileName = -join($ReportPath,'\','Users.csv')
|
|||
|
If (Test-Path $ADFileName)
|
|||
|
{
|
|||
|
Get-ADRExcelWorkbook -Name "User Stats"
|
|||
|
Remove-Variable ADFileName
|
|||
|
|
|||
|
$ObjAttributes = New-Object System.Collections.Specialized.OrderedDictionary
|
|||
|
$ObjAttributes.Add("Must Change Password at Logon",'"TRUE"')
|
|||
|
$ObjAttributes.Add("Cannot Change Password",'"TRUE"')
|
|||
|
$ObjAttributes.Add("Password Never Expires",'"TRUE"')
|
|||
|
$ObjAttributes.Add("Reversible Password Encryption",'"TRUE"')
|
|||
|
$ObjAttributes.Add("Smartcard Logon Required",'"TRUE"')
|
|||
|
$ObjAttributes.Add("Delegation Permitted",'"TRUE"')
|
|||
|
$ObjAttributes.Add("Kerberos DES Only",'"TRUE"')
|
|||
|
$ObjAttributes.Add("Kerberos RC4",'"TRUE"')
|
|||
|
$ObjAttributes.Add("Does Not Require Pre Auth",'"TRUE"')
|
|||
|
$ObjAttributes.Add("Password Age (> ",'"TRUE"')
|
|||
|
$ObjAttributes.Add("Account Locked Out",'"TRUE"')
|
|||
|
$ObjAttributes.Add("Never Logged in",'"TRUE"')
|
|||
|
$ObjAttributes.Add("Dormant",'"TRUE"')
|
|||
|
$ObjAttributes.Add("Password Not Required",'"TRUE"')
|
|||
|
$ObjAttributes.Add("Delegation Typ",'"Unconstrained"')
|
|||
|
$ObjAttributes.Add("SIDHistory",'"*"')
|
|||
|
|
|||
|
Get-ADRExcelAttributeStats -SrcSheetName "Users" -Title1 "User Accounts in AD" -Title2 "Status of User Accounts" -ObjAttributes $ObjAttributes
|
|||
|
Remove-Variable ObjAttributes
|
|||
|
|
|||
|
Get-ADRExcelChart -ChartType "xlPie" -ChartLayout 3 -ChartTitle "User Accounts in AD" -RangetoCover "A21:D33" -ChartData $workbook.Worksheets.Item(1).Range("A3:A4,B3:B4")
|
|||
|
$workbook.Worksheets.Item(1).Hyperlinks.Add($workbook.Worksheets.Item(1).Cells.Item(20,1) , "" , "Users!A1", "", "Raw Data") | Out-Null
|
|||
|
|
|||
|
Get-ADRExcelChart -ChartType "xlBarClustered" -ChartLayout 1 -ChartTitle "Status of User Accounts" -RangetoCover "F21:L43" -ChartData $workbook.Worksheets.Item(1).Range("F2:F18,G2:G18")
|
|||
|
$workbook.Worksheets.Item(1).Hyperlinks.Add($workbook.Worksheets.Item(1).Cells.Item(20,6) , "" , "Users!A1", "", "Raw Data") | Out-Null
|
|||
|
|
|||
|
$workbook.Worksheets.Item(1).UsedRange.EntireColumn.AutoFit() | Out-Null
|
|||
|
$excel.Windows.Item(1).Displaygridlines = $false
|
|||
|
}
|
|||
|
|
|||
|
# Create Table of Contents
|
|||
|
Get-ADRExcelWorkbook -Name "Table of Contents"
|
|||
|
$worksheet = $workbook.Worksheets.Item(1)
|
|||
|
|
|||
|
$excel.ScreenUpdating = $false
|
|||
|
# Image format and properties
|
|||
|
# $path = "C:\SOS_Logo.jpg"
|
|||
|
# $base64sos = [convert]::ToBase64String((Get-Content $path -Encoding byte))
|
|||
|
|
|||
|
$base64sos = "/9j/4AAQSkZJRgABAgEASABIAAD/7QAsUGhvdG9zaG9wIDMuMAA4QklNA+0AAAAAABAASAAAAAEAAQBIAAAAAQAB/+Fik2h0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8APD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4KPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNC4yLjItYzA2MyA1My4zNTE3MzUsIDIwMDgvMDcvMjItMTg6MTE6MTIgICAgICAgICI+CiAgIDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+CiAgICAgIDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiCiAgICAgICAgICAgIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyI+CiAgICAgICAgIDxkYzpmb3JtYXQ+aW1hZ2UvanBlZzwvZGM6Zm9ybWF0PgogICAgICA8L3JkZjpEZXNjcmlwdGlvbj4KICAgICAgPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIKICAgICAgICAgICAgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIgogICAgICAgICAgICB4bWxuczp4bXBHSW1nPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvZy9pbWcvIj4KICAgICAgICAgPHhtcDpNZXRhZGF0YURhdGU+MjAxMy0xMC0wM1QxMToyNjoyNSsxMDowMDwveG1wOk1ldGFkYXRhRGF0ZT4KICAgICAgICAgPHhtcDpNb2RpZnlEYXRlPjIwMTMtMTAtMDNUMDE6MjY6MzBaPC94bXA6TW9kaWZ5RGF0ZT4KICAgICAgICAgPHhtcDpDcmVhdGVEYXRlPjIwMTMtMTAtMDNUMTE6MjY6MjUrMTA6MDA8L3htcDpDcmVhdGVEYXRlPgogICAgICAgICA8eG1wOkNyZWF0b3JUb29sPkFkb2JlIElsbHVzdHJhdG9yIENTNDwveG1wOkNyZWF0b3JUb29sPgogICAgICAgICA8eG1wOlRodW1ibmFpbHM+CiAgICAgICAgICAgIDxyZGY6QWx0PgogICAgICAgICAgICAgICA8cmRmOmxpIHJkZjpwYXJzZVR5cGU9IlJlc291cmNlIj4KICAgICAgICAgICAgICAgICAgPHhtcEdJbWc6d2lkdGg+MjU2PC94bXBHSW1nOndpZHRoPgogICAgICAgICAgICAgICAgICA8eG1wR0ltZzpoZWlnaHQ+OTY8L3htcEdJbWc6aGVpZ2h0PgogICAgICAgICAgICAgICAgICA8eG1wR0ltZzpmb3JtYXQ+SlBFRzwveG1wR0ltZzpmb3JtYXQ+CiAgICAgICAgICAgICAgICAgIDx4bXBHSW1nOmltYWdlPi85ai80QUFRU2taSlJnQUJBZ0VCTEFFc0FBRC83UUFzVUdodmRHOXphRzl3SURNdU1BQTRRa2xOQSswQUFBQUFBQkFCTEFBQUFBRUEmI3hBO0FRRXNBQUFBQVFBQi8rNEFEa0ZrYjJKbEFHVEFBQUFBQWYvYkFJUUFCZ1FFQkFVRUJnVUZCZ2tHQlFZSkN3Z0dCZ2dMREFvS0N3b0smI3hBO0RCQU1EQXdNREF3UURBNFBFQThPREJNVEZCUVRFeHdiR3hzY0h4OGZIeDhmSHg4Zkh3RUhCd2NOREEwWUVCQVlHaFVSRlJvZkh4OGYmI3hBO0h4OGZIeDhmSHg4Zkh4OGZIeDhmSHg4Zkh4OGZIeDhmSHg4Zkh4OGZIeDhmSHg4Zkh4OGZIeDhmSHg4Zi84QUFFUWdBWUFFQUF3RVImI3hBO0FBSVJBUU1SQWYvRUFhSUFBQUFIQVFFQkFRRUFBQUFBQUFBQUFBUUZBd0lHQVFBSENBa0tDd0VBQWdJREFRRUJBUUVBQUFBQUFBQUEmI3hBO0FRQUNBd1FGQmdjSUNRb0xFQUFDQVFNREFnUUNCZ2NEQkFJR0FuTUJBZ01SQkFBRklSSXhRVkVHRTJFaWNZRVVNcEdoQnhXeFFpUEImI3hBO1V0SGhNeFppOENSeWd2RWxRelJUa3FLeVkzUENOVVFuazZPek5oZFVaSFREMHVJSUpvTUpDaGdaaEpSRlJxUzBWdE5WS0JyeTQvUEUmI3hBOzFPVDBaWFdGbGFXMXhkWGw5V1oyaHBhbXRzYlc1dlkzUjFkbmQ0ZVhwN2ZIMStmM09FaFlhSGlJbUtpNHlOam8rQ2s1U1ZscGVZbVomI3hBO3FibkoyZW41S2pwS1dtcDZpcHFxdXNyYTZ2b1JBQUlDQVFJREJRVUVCUVlFQ0FNRGJRRUFBaEVEQkNFU01VRUZVUk5oSWdaeGdaRXkmI3hBO29iSHdGTUhSNFNOQ0ZWSmljdkV6SkRSRGdoYVNVeVdpWTdMQ0IzUFNOZUpFZ3hkVWt3Z0pDaGdaSmpaRkdpZGtkRlUzOHFPend5Z3AmI3hBOzArUHpoSlNrdE1UVTVQUmxkWVdWcGJYRjFlWDFSbFptZG9hV3ByYkcxdWIyUjFkbmQ0ZVhwN2ZIMStmM09FaFlhSGlJbUtpNHlOam8mI3hBOytEbEpXV2w1aVptcHVjblo2ZmtxT2twYWFucUttcXE2eXRycSt2L2FBQXdEQVFBQ0VRTVJBRDhBOVU0cTdGVXQ4eUN5ZlJMcUMrWGwmI3hBO1ozU3JhejlQaFM1WVFsOS81ZWZMNk1qT0hFQ085dHdacFlweG5IbkVnL0o4b1hWMzVrOHY2bmM2Y2wvZFdzOWpLOERDR2FTT2hqYmomI3hBO1VjU052aEZNNS9lSjdpK3lZOGVEVVl4TXhqSVNGN2dIbTk5L0pIemJkYTc1WGt0Nys0ZTUxSFRwVEhKTkt4ZVI0cFBpalptSkpQN1MmI3hBOzcrR2JYUlpUS0pCNWg4NzlxT3o0NmZVQ1VBSXdtT1E1V09mNkM5RXpOZWFkaXJzVmRpcnNWZGlyc1ZkaXJzVmRpcnNWZGlyc1ZkaXImI3hBO3NWZGlyc1ZkaXJzVmRpcnNWZGlyc1ZkaXJzVmRpcnNWZGlyc1ZRT3U2WU5WMFRVTk1MY0JmVzB0dnpxUVY5VkNuSUViaWxhN1lxK1kmI3hBOy9QNWZVNGRGODJGQXNtdDJnWFVWVUNpYWhaL3VMcE50dnRybW4xdVBobmZlK2wreU9zOFRUbkdlZU0vWWR4K2xNZnlROHhmb256dkQmI3hBO2F5TlMyMVZEYXVPM3FING9qL3dRNC9Ua05KazRjZzg5bkk5cU5INDJrTWg5V1AxZkRyK3Y0UHBuTjIrV094VjJLdXhWMkt1eFYyS3UmI3hBO3hWMkt1eFYyS3V4VjJLdXhWMkt1eFYyS3V4VjJLdXhWMkt1eFYyS3V4VjJLdXhWMkt1eFYyS3V4Vjg0YXRwdk81L01YeWF5VW4wYSsmI3hBOy93QVQ2U1ArWGE3Vld1bFgvSlFTQS9QTVRXNCtLRjl6MEhzenJQQjFjUWZwbjZUK2o3WG5GdGNUVzF4RmNRTVVtaGRaSW5IVU1ocXAmI3hBOytnak5NK3FUZ0pSTVR5TDdFOHQ2MURyZWcyR3JRMENYa0tTbFIreTVIeHIvQUxGcWpPZ3haT09JTDRycmRNY0dhV00vd212MUpsbGomI3hBO2l1SkFCSk5BT3B4VkFycm1rU1QvQUZlRzZqdUp3YU5GQWZXWmEvemlQa1ZIdTJWZU5DNkJ2M2J1UWRMa0E0akVnZWUzeXZtdnM5WDAmI3hBO3ErbnVMZXl2SWJtZTBLaTZqaGtXUm9pOWVJY0tUeEo0blk1YTQ2M1d0YTB2Uk5MdU5WMVM0VzFzTFJPYzg3M
|
|||
|
|
|||
|
$bytes = [System.Convert]::FromBase64String($base64sos)
|
|||
|
Remove-Variable base64sos
|
|||
|
|
|||
|
$CompanyLogo = -join($ReportPath,'\','SOS_Logo.jpg')
|
|||
|
$p = New-Object IO.MemoryStream($bytes, 0, $bytes.length)
|
|||
|
$p.Write($bytes, 0, $bytes.length)
|
|||
|
Add-Type -AssemblyName System.Drawing
|
|||
|
$picture = [System.Drawing.Image]::FromStream($p, $true)
|
|||
|
$picture.Save($CompanyLogo)
|
|||
|
|
|||
|
Remove-Variable bytes
|
|||
|
Remove-Variable p
|
|||
|
Remove-Variable picture
|
|||
|
|
|||
|
$LinkToFile = $false
|
|||
|
$SaveWithDocument = $true
|
|||
|
$Left = 0
|
|||
|
$Top = 0
|
|||
|
$Width = 135
|
|||
|
$Height = 50
|
|||
|
|
|||
|
# Add image to the Sheet
|
|||
|
$worksheet.Shapes.AddPicture($CompanyLogo, $LinkToFile, $SaveWithDocument, $Left, $Top, $Width, $Height) | Out-Null
|
|||
|
|
|||
|
Remove-Variable LinkToFile
|
|||
|
Remove-Variable SaveWithDocument
|
|||
|
Remove-Variable Left
|
|||
|
Remove-Variable Top
|
|||
|
Remove-Variable Width
|
|||
|
Remove-Variable Height
|
|||
|
|
|||
|
If (Test-Path -Path $CompanyLogo)
|
|||
|
{
|
|||
|
Remove-Item $CompanyLogo
|
|||
|
}
|
|||
|
Remove-Variable CompanyLogo
|
|||
|
|
|||
|
$row = 5
|
|||
|
$column = 1
|
|||
|
$worksheet.Cells.Item($row,$column)= "Table of Contents"
|
|||
|
$worksheet.Cells.Item($row,$column).Style = "Heading 2"
|
|||
|
$row++
|
|||
|
|
|||
|
For($i=2; $i -le $workbook.Worksheets.Count; $i++)
|
|||
|
{
|
|||
|
$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
|
|||
|
$row++
|
|||
|
}
|
|||
|
|
|||
|
$row++
|
|||
|
$worksheet.Cells.Item($row, 1) = "© Sense of Security 2018"
|
|||
|
$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
|
|||
|
|
|||
|
$worksheet.UsedRange.EntireColumn.AutoFit() | Out-Null
|
|||
|
|
|||
|
$excel.Windows.Item(1).Displaygridlines = $false
|
|||
|
$excel.ScreenUpdating = $true
|
|||
|
$ADStatFileName = -join($ExcelPath,'\',$DomainName,'ADRecon-Report.xlsx')
|
|||
|
Try
|
|||
|
{
|
|||
|
# Disable prompt if file exists
|
|||
|
$excel.DisplayAlerts = $False
|
|||
|
$workbook.SaveAs($ADStatFileName)
|
|||
|
Write-Output "[+] Excelsheet Saved to: $ADStatFileName"
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Error "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
}
|
|||
|
$excel.Quit()
|
|||
|
Get-ADRExcelComObjRelease -ComObjtoRelease $worksheet -Final $true
|
|||
|
Remove-Variable worksheet
|
|||
|
Get-ADRExcelComObjRelease -ComObjtoRelease $workbook -Final $true
|
|||
|
Remove-Variable -Name workbook -Scope Global
|
|||
|
Get-ADRExcelComObjRelease -ComObjtoRelease $excel -Final $true
|
|||
|
Remove-Variable -Name excel -Scope Global
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
Function Get-ADRDomain
|
|||
|
{
|
|||
|
<#
|
|||
|
.SYNOPSIS
|
|||
|
Returns information of the current (or specified) domain.
|
|||
|
|
|||
|
.DESCRIPTION
|
|||
|
Returns information of the current (or specified) domain.
|
|||
|
|
|||
|
.PARAMETER Protocol
|
|||
|
[string]
|
|||
|
Which protocol to use; ADWS (default) or LDAP.
|
|||
|
|
|||
|
.PARAMETER objDomain
|
|||
|
[DirectoryServices.DirectoryEntry]
|
|||
|
Domain Directory Entry object.
|
|||
|
|
|||
|
.PARAMETER objDomainRootDSE
|
|||
|
[DirectoryServices.DirectoryEntry]
|
|||
|
RootDSE Directory Entry object.
|
|||
|
|
|||
|
.PARAMETER DomainController
|
|||
|
[string]
|
|||
|
IP Address of the Domain Controller.
|
|||
|
|
|||
|
.PARAMETER Credential
|
|||
|
[Management.Automation.PSCredential]
|
|||
|
Credentials.
|
|||
|
|
|||
|
.OUTPUTS
|
|||
|
PSObject.
|
|||
|
#>
|
|||
|
param(
|
|||
|
[Parameter(Mandatory = $true)]
|
|||
|
[string] $Protocol,
|
|||
|
|
|||
|
[Parameter(Mandatory = $false)]
|
|||
|
[DirectoryServices.DirectoryEntry] $objDomain,
|
|||
|
|
|||
|
[Parameter(Mandatory = $false)]
|
|||
|
[DirectoryServices.DirectoryEntry] $objDomainRootDSE,
|
|||
|
|
|||
|
[Parameter(Mandatory = $false)]
|
|||
|
[string] $DomainController,
|
|||
|
|
|||
|
[Parameter(Mandatory = $false)]
|
|||
|
[Management.Automation.PSCredential] $Credential = [Management.Automation.PSCredential]::Empty
|
|||
|
)
|
|||
|
|
|||
|
If ($Protocol -eq 'ADWS')
|
|||
|
{
|
|||
|
Try
|
|||
|
{
|
|||
|
$ADDomain = Get-ADDomain
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Warning "[Get-ADRDomain] Error getting Domain Context"
|
|||
|
Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
Return $null
|
|||
|
}
|
|||
|
If ($ADDomain)
|
|||
|
{
|
|||
|
$DomainObj = @()
|
|||
|
|
|||
|
# Values taken from https://technet.microsoft.com/en-us/library/hh852281(v=wps.630).aspx
|
|||
|
$FLAD = @{
|
|||
|
0 = "Windows2000";
|
|||
|
1 = "Windows2003/Interim";
|
|||
|
2 = "Windows2003";
|
|||
|
3 = "Windows2008";
|
|||
|
4 = "Windows2008R2";
|
|||
|
5 = "Windows2012";
|
|||
|
6 = "Windows2012R2";
|
|||
|
7 = "Windows2016"
|
|||
|
}
|
|||
|
$DomainMode = $FLAD[[convert]::ToInt32($ADDomain.DomainMode)] + "Domain"
|
|||
|
Remove-Variable FLAD
|
|||
|
If (-Not $DomainMode)
|
|||
|
{
|
|||
|
$DomainMode = $ADDomain.DomainMode
|
|||
|
}
|
|||
|
|
|||
|
$ObjValues = @("Name", $ADDomain.DNSRoot, "NetBIOS", $ADDomain.NetBIOSName, "Functional Level", $DomainMode, "DomainSID", $ADDomain.DomainSID.Value)
|
|||
|
|
|||
|
For ($i = 0; $i -lt $($ObjValues.Count); $i++)
|
|||
|
{
|
|||
|
$Obj = New-Object PSObject
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Category" -Value $ObjValues[$i]
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Value" -Value $ObjValues[$i+1]
|
|||
|
$i++
|
|||
|
$DomainObj += $Obj
|
|||
|
}
|
|||
|
Remove-Variable DomainMode
|
|||
|
|
|||
|
For($i=0; $i -lt $ADDomain.ReplicaDirectoryServers.Count; $i++)
|
|||
|
{
|
|||
|
$Obj = New-Object PSObject
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Category" -Value "Domain Controller"
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Value" -Value $ADDomain.ReplicaDirectoryServers[$i]
|
|||
|
$DomainObj += $Obj
|
|||
|
}
|
|||
|
For($i=0; $i -lt $ADDomain.ReadOnlyReplicaDirectoryServers.Count; $i++)
|
|||
|
{
|
|||
|
$Obj = New-Object PSObject
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Category" -Value "Read Only Domain Controller"
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Value" -Value $ADDomain.ReadOnlyReplicaDirectoryServers[$i]
|
|||
|
$DomainObj += $Obj
|
|||
|
}
|
|||
|
|
|||
|
Try
|
|||
|
{
|
|||
|
$ADForest = Get-ADForest $ADDomain.Forest
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Verbose "[Get-ADRDomain] Error getting Forest Context"
|
|||
|
Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
}
|
|||
|
|
|||
|
If (-Not $ADForest)
|
|||
|
{
|
|||
|
Try
|
|||
|
{
|
|||
|
$ADForest = Get-ADForest -Server $DomainController
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Warning "[Get-ADRDomain] Error getting Forest Context"
|
|||
|
Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
}
|
|||
|
}
|
|||
|
If ($ADForest)
|
|||
|
{
|
|||
|
$DomainCreation = Get-ADObject -SearchBase "$($ADForest.PartitionsContainer)" -LDAPFilter "(&(objectClass=crossRef)(systemFlags=3)(Name=$($ADDomain.Name)))" -Properties whenCreated
|
|||
|
If (-Not $DomainCreation)
|
|||
|
{
|
|||
|
$DomainCreation = Get-ADObject -SearchBase "$($ADForest.PartitionsContainer)" -LDAPFilter "(&(objectClass=crossRef)(systemFlags=3)(Name=$($ADDomain.NetBIOSName)))" -Properties whenCreated
|
|||
|
}
|
|||
|
Remove-Variable ADForest
|
|||
|
}
|
|||
|
# Get RIDAvailablePool
|
|||
|
Try
|
|||
|
{
|
|||
|
$RIDManager = Get-ADObject -Identity "CN=RID Manager$,CN=System,$($ADDomain.DistinguishedName)" -Properties rIDAvailablePool
|
|||
|
$RIDproperty = $RIDManager.rIDAvailablePool
|
|||
|
[int32] $totalSIDS = $($RIDproperty) / ([math]::Pow(2,32))
|
|||
|
[int64] $temp64val = $totalSIDS * ([math]::Pow(2,32))
|
|||
|
$RIDsIssued = [int32]($($RIDproperty) - $temp64val)
|
|||
|
$RIDsRemaining = $totalSIDS - $RIDsIssued
|
|||
|
Remove-Variable RIDManager
|
|||
|
Remove-Variable RIDproperty
|
|||
|
Remove-Variable totalSIDS
|
|||
|
Remove-Variable temp64val
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Warning "[Get-ADRDomain] Error accessing CN=RID Manager$,CN=System,$($ADDomain.DistinguishedName)"
|
|||
|
Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
}
|
|||
|
If ($DomainCreation)
|
|||
|
{
|
|||
|
$Obj = New-Object PSObject
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Category" -Value "Creation Date"
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Value" -Value $DomainCreation.whenCreated
|
|||
|
$DomainObj += $Obj
|
|||
|
Remove-Variable DomainCreation
|
|||
|
}
|
|||
|
|
|||
|
$Obj = New-Object PSObject
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Category" -Value "ms-DS-MachineAccountQuota"
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Value" -Value $((Get-ADObject -Identity ($ADDomain.DistinguishedName) -Properties ms-DS-MachineAccountQuota).'ms-DS-MachineAccountQuota')
|
|||
|
$DomainObj += $Obj
|
|||
|
|
|||
|
If ($RIDsIssued)
|
|||
|
{
|
|||
|
$Obj = New-Object PSObject
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Category" -Value "RIDs Issued"
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Value" -Value $RIDsIssued
|
|||
|
$DomainObj += $Obj
|
|||
|
Remove-Variable RIDsIssued
|
|||
|
}
|
|||
|
If ($RIDsRemaining)
|
|||
|
{
|
|||
|
$Obj = New-Object PSObject
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Category" -Value "RIDs Remaining"
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Value" -Value $RIDsRemaining
|
|||
|
$DomainObj += $Obj
|
|||
|
Remove-Variable RIDsRemaining
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
If ($Protocol -eq 'LDAP')
|
|||
|
{
|
|||
|
If ($Credential -ne [Management.Automation.PSCredential]::Empty)
|
|||
|
{
|
|||
|
$DomainFQDN = Get-DNtoFQDN($objDomain.distinguishedName)
|
|||
|
$DomainContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext("Domain",$($DomainFQDN),$($Credential.UserName),$($Credential.GetNetworkCredential().password))
|
|||
|
Try
|
|||
|
{
|
|||
|
$ADDomain = [System.DirectoryServices.ActiveDirectory.Domain]::GetDomain($DomainContext)
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Warning "[Get-ADRDomain] Error getting Domain Context"
|
|||
|
Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
Return $null
|
|||
|
}
|
|||
|
Remove-Variable DomainContext
|
|||
|
# Get RIDAvailablePool
|
|||
|
Try
|
|||
|
{
|
|||
|
$SearchPath = "CN=RID Manager$,CN=System"
|
|||
|
$objSearchPath = New-Object System.DirectoryServices.DirectoryEntry "LDAP://$($DomainController)/$SearchPath,$($objDomain.distinguishedName)", $Credential.UserName,$Credential.GetNetworkCredential().Password
|
|||
|
$objSearcherPath = New-Object System.DirectoryServices.DirectorySearcher $objSearchPath
|
|||
|
$objSearcherPath.PropertiesToLoad.AddRange(("ridavailablepool"))
|
|||
|
$objSearcherResult = $objSearcherPath.FindAll()
|
|||
|
$RIDproperty = $objSearcherResult.Properties.ridavailablepool
|
|||
|
[int32] $totalSIDS = $($RIDproperty) / ([math]::Pow(2,32))
|
|||
|
[int64] $temp64val = $totalSIDS * ([math]::Pow(2,32))
|
|||
|
$RIDsIssued = [int32]($($RIDproperty) - $temp64val)
|
|||
|
$RIDsRemaining = $totalSIDS - $RIDsIssued
|
|||
|
Remove-Variable SearchPath
|
|||
|
$objSearchPath.Dispose()
|
|||
|
$objSearcherPath.Dispose()
|
|||
|
$objSearcherResult.Dispose()
|
|||
|
Remove-Variable RIDproperty
|
|||
|
Remove-Variable totalSIDS
|
|||
|
Remove-Variable temp64val
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Warning "[Get-ADRDomain] Error accessing CN=RID Manager$,CN=System,$($SearchPath),$($objDomain.distinguishedName)"
|
|||
|
Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
}
|
|||
|
Try
|
|||
|
{
|
|||
|
$ForestContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext("Forest",$($ADDomain.Forest),$($Credential.UserName),$($Credential.GetNetworkCredential().password))
|
|||
|
$ADForest = [System.DirectoryServices.ActiveDirectory.Forest]::GetForest($ForestContext)
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Warning "[Get-ADRDomain] Error getting Forest Context"
|
|||
|
Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
}
|
|||
|
If ($ForestContext)
|
|||
|
{
|
|||
|
Remove-Variable ForestContext
|
|||
|
}
|
|||
|
If ($ADForest)
|
|||
|
{
|
|||
|
$GlobalCatalog = $ADForest.FindGlobalCatalog()
|
|||
|
}
|
|||
|
If ($GlobalCatalog)
|
|||
|
{
|
|||
|
$DN = "GC://$($GlobalCatalog.IPAddress)/$($objDomain.distinguishedname)"
|
|||
|
Try
|
|||
|
{
|
|||
|
$ADObject = New-Object -TypeName System.DirectoryServices.DirectoryEntry -ArgumentList ($($DN),$($Credential.UserName),$($Credential.GetNetworkCredential().password))
|
|||
|
$ADDomainSID = New-Object System.Security.Principal.SecurityIdentifier($ADObject.objectSid[0], 0)
|
|||
|
$ADObject.Dispose()
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Warning "[Get-ADRDomain] Error retrieving Domain SID using the GlobalCatalog $($GlobalCatalog.IPAddress). Using SID from the ObjDomain."
|
|||
|
Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
$ADDomainSID = New-Object System.Security.Principal.SecurityIdentifier($objDomain.objectSid[0], 0)
|
|||
|
}
|
|||
|
}
|
|||
|
Else
|
|||
|
{
|
|||
|
$ADDomainSID = New-Object System.Security.Principal.SecurityIdentifier($objDomain.objectSid[0], 0)
|
|||
|
}
|
|||
|
}
|
|||
|
Else
|
|||
|
{
|
|||
|
$ADDomain = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()
|
|||
|
$ADForest = [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest()
|
|||
|
Try
|
|||
|
{
|
|||
|
$GlobalCatalog = $ADForest.FindGlobalCatalog()
|
|||
|
$DN = "GC://$($GlobalCatalog)/$($objDomain.distinguishedname)"
|
|||
|
$ADObject = New-Object -TypeName System.DirectoryServices.DirectoryEntry -ArgumentList ($DN)
|
|||
|
$ADDomainSID = New-Object System.Security.Principal.SecurityIdentifier($ADObject.objectSid[0], 0)
|
|||
|
$ADObject.dispose()
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Warning "[Get-ADRDomain] Error retrieving Domain SID using the GlobalCatalog $($GlobalCatalog.IPAddress). Using SID from the ObjDomain."
|
|||
|
Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
$ADDomainSID = New-Object System.Security.Principal.SecurityIdentifier($objDomain.objectSid[0], 0)
|
|||
|
}
|
|||
|
# Get RIDAvailablePool
|
|||
|
Try
|
|||
|
{
|
|||
|
$RIDManager = ([ADSI]"LDAP://CN=RID Manager$,CN=System,$($objDomain.distinguishedName)")
|
|||
|
$RIDproperty = $ObjDomain.ConvertLargeIntegerToInt64($RIDManager.Properties.rIDAvailablePool.value)
|
|||
|
[int32] $totalSIDS = $($RIDproperty) / ([math]::Pow(2,32))
|
|||
|
[int64] $temp64val = $totalSIDS * ([math]::Pow(2,32))
|
|||
|
$RIDsIssued = [int32]($($RIDproperty) - $temp64val)
|
|||
|
$RIDsRemaining = $totalSIDS - $RIDsIssued
|
|||
|
Remove-Variable RIDManager
|
|||
|
Remove-Variable RIDproperty
|
|||
|
Remove-Variable totalSIDS
|
|||
|
Remove-Variable temp64val
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Warning "[Get-ADRDomain] Error accessing CN=RID Manager$,CN=System,$($SearchPath),$($objDomain.distinguishedName)"
|
|||
|
Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
If ($ADDomain)
|
|||
|
{
|
|||
|
$DomainObj = @()
|
|||
|
|
|||
|
# Values taken from https://technet.microsoft.com/en-us/library/hh852281(v=wps.630).aspx
|
|||
|
$FLAD = @{
|
|||
|
0 = "Windows2000";
|
|||
|
1 = "Windows2003/Interim";
|
|||
|
2 = "Windows2003";
|
|||
|
3 = "Windows2008";
|
|||
|
4 = "Windows2008R2";
|
|||
|
5 = "Windows2012";
|
|||
|
6 = "Windows2012R2";
|
|||
|
7 = "Windows2016"
|
|||
|
}
|
|||
|
$DomainMode = $FLAD[[convert]::ToInt32($objDomainRootDSE.domainFunctionality,10)] + "Domain"
|
|||
|
Remove-Variable FLAD
|
|||
|
|
|||
|
$ObjValues = @("Name", $ADDomain.Name, "NetBIOS", $objDomain.dc.value, "Functional Level", $DomainMode, "DomainSID", $ADDomainSID.Value)
|
|||
|
|
|||
|
For ($i = 0; $i -lt $($ObjValues.Count); $i++)
|
|||
|
{
|
|||
|
$Obj = New-Object PSObject
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Category" -Value $ObjValues[$i]
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Value" -Value $ObjValues[$i+1]
|
|||
|
$i++
|
|||
|
$DomainObj += $Obj
|
|||
|
}
|
|||
|
Remove-Variable DomainMode
|
|||
|
|
|||
|
For($i=0; $i -lt $ADDomain.DomainControllers.Count; $i++)
|
|||
|
{
|
|||
|
$Obj = New-Object PSObject
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Category" -Value "Domain Controller"
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Value" -Value $ADDomain.DomainControllers[$i]
|
|||
|
$DomainObj += $Obj
|
|||
|
}
|
|||
|
|
|||
|
$Obj = New-Object PSObject
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Category" -Value "Creation Date"
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Value" -Value $objDomain.whencreated.value
|
|||
|
$DomainObj += $Obj
|
|||
|
|
|||
|
$Obj = New-Object PSObject
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Category" -Value "ms-DS-MachineAccountQuota"
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Value" -Value $objDomain.'ms-DS-MachineAccountQuota'.value
|
|||
|
$DomainObj += $Obj
|
|||
|
|
|||
|
If ($RIDsIssued)
|
|||
|
{
|
|||
|
$Obj = New-Object PSObject
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Category" -Value "RIDs Issued"
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Value" -Value $RIDsIssued
|
|||
|
$DomainObj += $Obj
|
|||
|
Remove-Variable RIDsIssued
|
|||
|
}
|
|||
|
If ($RIDsRemaining)
|
|||
|
{
|
|||
|
$Obj = New-Object PSObject
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Category" -Value "RIDs Remaining"
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Value" -Value $RIDsRemaining
|
|||
|
$DomainObj += $Obj
|
|||
|
Remove-Variable RIDsRemaining
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
If ($DomainObj)
|
|||
|
{
|
|||
|
Return $DomainObj
|
|||
|
}
|
|||
|
Else
|
|||
|
{
|
|||
|
Return $null
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
Function Get-ADRForest
|
|||
|
{
|
|||
|
<#
|
|||
|
.SYNOPSIS
|
|||
|
Returns information of the current (or specified) forest.
|
|||
|
|
|||
|
.DESCRIPTION
|
|||
|
Returns information of the current (or specified) forest.
|
|||
|
|
|||
|
.PARAMETER Protocol
|
|||
|
[string]
|
|||
|
Which protocol to use; ADWS (default) or LDAP.
|
|||
|
|
|||
|
.PARAMETER objDomain
|
|||
|
[DirectoryServices.DirectoryEntry]
|
|||
|
Domain Directory Entry object.
|
|||
|
|
|||
|
.PARAMETER objDomainRootDSE
|
|||
|
[DirectoryServices.DirectoryEntry]
|
|||
|
RootDSE Directory Entry object.
|
|||
|
|
|||
|
.PARAMETER DomainController
|
|||
|
[string]
|
|||
|
IP Address of the Domain Controller.
|
|||
|
|
|||
|
.PARAMETER Credential
|
|||
|
[Management.Automation.PSCredential]
|
|||
|
Credentials.
|
|||
|
|
|||
|
.OUTPUTS
|
|||
|
PSObject.
|
|||
|
#>
|
|||
|
param(
|
|||
|
[Parameter(Mandatory = $true)]
|
|||
|
[string] $Protocol,
|
|||
|
|
|||
|
[Parameter(Mandatory = $false)]
|
|||
|
[DirectoryServices.DirectoryEntry] $objDomain,
|
|||
|
|
|||
|
[Parameter(Mandatory = $false)]
|
|||
|
[DirectoryServices.DirectoryEntry] $objDomainRootDSE,
|
|||
|
|
|||
|
[Parameter(Mandatory = $false)]
|
|||
|
[string] $DomainController,
|
|||
|
|
|||
|
[Parameter(Mandatory = $false)]
|
|||
|
[Management.Automation.PSCredential] $Credential = [Management.Automation.PSCredential]::Empty
|
|||
|
)
|
|||
|
|
|||
|
If ($Protocol -eq 'ADWS')
|
|||
|
{
|
|||
|
Try
|
|||
|
{
|
|||
|
$ADDomain = Get-ADDomain
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Warning "[Get-ADRForest] Error getting Domain Context"
|
|||
|
Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
Return $null
|
|||
|
}
|
|||
|
|
|||
|
Try
|
|||
|
{
|
|||
|
$ADForest = Get-ADForest $ADDomain.Forest
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Verbose "[Get-ADRForest] Error getting Forest Context"
|
|||
|
Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
}
|
|||
|
Remove-Variable ADDomain
|
|||
|
|
|||
|
If (-Not $ADForest)
|
|||
|
{
|
|||
|
Try
|
|||
|
{
|
|||
|
$ADForest = Get-ADForest -Server $DomainController
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Warning "[Get-ADRForest] Error getting Forest Context"
|
|||
|
Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
Return $null
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
If ($ADForest)
|
|||
|
{
|
|||
|
# Get Tombstone Lifetime
|
|||
|
Try
|
|||
|
{
|
|||
|
$ADForestCNC = (Get-ADRootDSE).configurationNamingContext
|
|||
|
$ADForestDSCP = Get-ADObject -Identity "CN=Directory Service,CN=Windows NT,CN=Services,$($ADForestCNC)" -Partition $ADForestCNC -Properties *
|
|||
|
$ADForestTombstoneLifetime = $ADForestDSCP.tombstoneLifetime
|
|||
|
Remove-Variable ADForestCNC
|
|||
|
Remove-Variable ADForestDSCP
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Warning "[Get-ADRForest] Error retrieving Tombstone Lifetime"
|
|||
|
Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
}
|
|||
|
|
|||
|
# Check Recycle Bin Feature Status
|
|||
|
If ([convert]::ToInt32($ADForest.ForestMode) -ge 6)
|
|||
|
{
|
|||
|
Try
|
|||
|
{
|
|||
|
$ADRecycleBin = Get-ADOptionalFeature -Identity "Recycle Bin Feature"
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Warning "[Get-ADRForest] Error retrieving Recycle Bin Feature"
|
|||
|
Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
# Check Privileged Access Management Feature status
|
|||
|
If ([convert]::ToInt32($ADForest.ForestMode) -ge 7)
|
|||
|
{
|
|||
|
Try
|
|||
|
{
|
|||
|
$PrivilegedAccessManagement = Get-ADOptionalFeature -Identity "Privileged Access Management Feature"
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Warning "[Get-ADRForest] Error retrieving Privileged Acceess Management Feature"
|
|||
|
Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
$ForestObj = @()
|
|||
|
|
|||
|
# Values taken from https://technet.microsoft.com/en-us/library/hh852281(v=wps.630).aspx
|
|||
|
$FLAD = @{
|
|||
|
0 = "Windows2000";
|
|||
|
1 = "Windows2003/Interim";
|
|||
|
2 = "Windows2003";
|
|||
|
3 = "Windows2008";
|
|||
|
4 = "Windows2008R2";
|
|||
|
5 = "Windows2012";
|
|||
|
6 = "Windows2012R2";
|
|||
|
7 = "Windows2016"
|
|||
|
}
|
|||
|
$ForestMode = $FLAD[[convert]::ToInt32($ADForest.ForestMode)] + "Forest"
|
|||
|
Remove-Variable FLAD
|
|||
|
|
|||
|
If (-Not $ForestMode)
|
|||
|
{
|
|||
|
$ForestMode = $ADForest.ForestMode
|
|||
|
}
|
|||
|
|
|||
|
$ObjValues = @("Name", $ADForest.Name, "Functional Level", $ForestMode, "Domain Naming Master", $ADForest.DomainNamingMaster, "Schema Master", $ADForest.SchemaMaster, "RootDomain", $ADForest.RootDomain, "Domain Count", $ADForest.Domains.Count, "Site Count", $ADForest.Sites.Count, "Global Catalog Count", $ADForest.GlobalCatalogs.Count)
|
|||
|
|
|||
|
For ($i = 0; $i -lt $($ObjValues.Count); $i++)
|
|||
|
{
|
|||
|
$Obj = New-Object PSObject
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Category" -Value $ObjValues[$i]
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Value" -Value $ObjValues[$i+1]
|
|||
|
$i++
|
|||
|
$ForestObj += $Obj
|
|||
|
}
|
|||
|
Remove-Variable ForestMode
|
|||
|
|
|||
|
For($i=0; $i -lt $ADForest.Domains.Count; $i++)
|
|||
|
{
|
|||
|
$Obj = New-Object PSObject
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Category" -Value "Domain"
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Value" -Value $ADForest.Domains[$i]
|
|||
|
$ForestObj += $Obj
|
|||
|
}
|
|||
|
For($i=0; $i -lt $ADForest.Sites.Count; $i++)
|
|||
|
{
|
|||
|
$Obj = New-Object PSObject
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Category" -Value "Site"
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Value" -Value $ADForest.Sites[$i]
|
|||
|
$ForestObj += $Obj
|
|||
|
}
|
|||
|
For($i=0; $i -lt $ADForest.GlobalCatalogs.Count; $i++)
|
|||
|
{
|
|||
|
$Obj = New-Object PSObject
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Category" -Value "GlobalCatalog"
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Value" -Value $ADForest.GlobalCatalogs[$i]
|
|||
|
$ForestObj += $Obj
|
|||
|
}
|
|||
|
|
|||
|
$Obj = New-Object PSObject
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Category" -Value "Tombstone Lifetime"
|
|||
|
If ($ADForestTombstoneLifetime)
|
|||
|
{
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Value" -Value $ADForestTombstoneLifetime
|
|||
|
Remove-Variable ADForestTombstoneLifetime
|
|||
|
}
|
|||
|
Else
|
|||
|
{
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Value" -Value "Not Retrieved"
|
|||
|
}
|
|||
|
$ForestObj += $Obj
|
|||
|
|
|||
|
$Obj = New-Object PSObject
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Category" -Value "Recycle Bin (2008 R2 onwards)"
|
|||
|
If ($ADRecycleBin)
|
|||
|
{
|
|||
|
If ($ADRecycleBin.EnabledScopes.Count -gt 0)
|
|||
|
{
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Value" -Value "Enabled"
|
|||
|
$ForestObj += $Obj
|
|||
|
For($i=0; $i -lt $($ADRecycleBin.EnabledScopes.Count); $i++)
|
|||
|
{
|
|||
|
$Obj = New-Object PSObject
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Category" -Value "Enabled Scope"
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Value" -Value $ADRecycleBin.EnabledScopes[$i]
|
|||
|
$ForestObj += $Obj
|
|||
|
}
|
|||
|
}
|
|||
|
Else
|
|||
|
{
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Value" -Value "Disabled"
|
|||
|
$ForestObj += $Obj
|
|||
|
}
|
|||
|
Remove-Variable ADRecycleBin
|
|||
|
}
|
|||
|
Else
|
|||
|
{
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Value" -Value "Disabled"
|
|||
|
$ForestObj += $Obj
|
|||
|
}
|
|||
|
|
|||
|
$Obj = New-Object PSObject
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Category" -Value "Privileged Access Management (2016 onwards)"
|
|||
|
If ($PrivilegedAccessManagement)
|
|||
|
{
|
|||
|
If ($PrivilegedAccessManagement.EnabledScopes.Count -gt 0)
|
|||
|
{
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Value" -Value "Enabled"
|
|||
|
$ForestObj += $Obj
|
|||
|
For($i=0; $i -lt $($PrivilegedAccessManagement.EnabledScopes.Count); $i++)
|
|||
|
{
|
|||
|
$Obj = New-Object PSObject
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Category" -Value "Enabled Scope"
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Value" -Value $PrivilegedAccessManagement.EnabledScopes[$i]
|
|||
|
$ForestObj += $Obj
|
|||
|
}
|
|||
|
}
|
|||
|
Else
|
|||
|
{
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Value" -Value "Disabled"
|
|||
|
$ForestObj += $Obj
|
|||
|
}
|
|||
|
Remove-Variable PrivilegedAccessManagement
|
|||
|
}
|
|||
|
Else
|
|||
|
{
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Value" -Value "Disabled"
|
|||
|
$ForestObj += $Obj
|
|||
|
}
|
|||
|
Remove-Variable ADForest
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
If ($Protocol -eq 'LDAP')
|
|||
|
{
|
|||
|
If ($Credential -ne [Management.Automation.PSCredential]::Empty)
|
|||
|
{
|
|||
|
$DomainFQDN = Get-DNtoFQDN($objDomain.distinguishedName)
|
|||
|
$DomainContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext("Domain",$($DomainFQDN),$($Credential.UserName),$($Credential.GetNetworkCredential().password))
|
|||
|
Try
|
|||
|
{
|
|||
|
$ADDomain = [System.DirectoryServices.ActiveDirectory.Domain]::GetDomain($DomainContext)
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Warning "[Get-ADRForest] Error getting Domain Context"
|
|||
|
Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
Return $null
|
|||
|
}
|
|||
|
Remove-Variable DomainContext
|
|||
|
|
|||
|
$ForestContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext("Forest",$($ADDomain.Forest),$($Credential.UserName),$($Credential.GetNetworkCredential().password))
|
|||
|
Remove-Variable ADDomain
|
|||
|
Try
|
|||
|
{
|
|||
|
$ADForest = [System.DirectoryServices.ActiveDirectory.Forest]::GetForest($ForestContext)
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Warning "[Get-ADRForest] Error getting Forest Context"
|
|||
|
Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
Return $null
|
|||
|
}
|
|||
|
Remove-Variable ForestContext
|
|||
|
|
|||
|
# Get Tombstone Lifetime
|
|||
|
Try
|
|||
|
{
|
|||
|
$SearchPath = "CN=Directory Service,CN=Windows NT,CN=Services"
|
|||
|
$objSearchPath = New-Object System.DirectoryServices.DirectoryEntry "LDAP://$($DomainController)/$SearchPath,$($objDomainRootDSE.configurationNamingContext)", $Credential.UserName,$Credential.GetNetworkCredential().Password
|
|||
|
$objSearcherPath = New-Object System.DirectoryServices.DirectorySearcher $objSearchPath
|
|||
|
$objSearcherPath.Filter="(name=Directory Service)"
|
|||
|
$objSearcherResult = $objSearcherPath.FindAll()
|
|||
|
$ADForestTombstoneLifetime = $objSearcherResult.Properties.tombstoneLifetime
|
|||
|
Remove-Variable SearchPath
|
|||
|
$objSearchPath.Dispose()
|
|||
|
$objSearcherPath.Dispose()
|
|||
|
$objSearcherResult.Dispose()
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Warning "[Get-ADRForest] Error retrieving Tombstone Lifetime"
|
|||
|
Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
}
|
|||
|
# Check Recycle Bin Feature Status
|
|||
|
If ([convert]::ToInt32($objDomainRootDSE.forestFunctionality,10) -ge 6)
|
|||
|
{
|
|||
|
Try
|
|||
|
{
|
|||
|
$SearchPath = "CN=Recycle Bin Feature,CN=Optional Features,CN=Directory Service,CN=Windows NT,CN=Services,CN=Configuration"
|
|||
|
$objSearchPath = New-Object System.DirectoryServices.DirectoryEntry "LDAP://$($DomainController)/$($SearchPath),$($objDomain.distinguishedName)", $Credential.UserName,$Credential.GetNetworkCredential().Password
|
|||
|
$objSearcherPath = New-Object System.DirectoryServices.DirectorySearcher $objSearchPath
|
|||
|
$ADRecycleBin = $objSearcherPath.FindAll()
|
|||
|
Remove-Variable SearchPath
|
|||
|
$objSearchPath.Dispose()
|
|||
|
$objSearcherPath.Dispose()
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Warning "[Get-ADRForest] Error retrieving Recycle Bin Feature"
|
|||
|
Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
}
|
|||
|
}
|
|||
|
# Check Privileged Access Management Feature status
|
|||
|
If ([convert]::ToInt32($objDomainRootDSE.forestFunctionality,10) -ge 7)
|
|||
|
{
|
|||
|
Try
|
|||
|
{
|
|||
|
$SearchPath = "CN=Privileged Access Management Feature,CN=Optional Features,CN=Directory Service,CN=Windows NT,CN=Services,CN=Configuration"
|
|||
|
$objSearchPath = New-Object System.DirectoryServices.DirectoryEntry "LDAP://$($DomainController)/$($SearchPath),$($objDomain.distinguishedName)", $Credential.UserName,$Credential.GetNetworkCredential().Password
|
|||
|
$objSearcherPath = New-Object System.DirectoryServices.DirectorySearcher $objSearchPath
|
|||
|
$PrivilegedAccessManagement = $objSearcherPath.FindAll()
|
|||
|
Remove-Variable SearchPath
|
|||
|
$objSearchPath.Dispose()
|
|||
|
$objSearcherPath.Dispose()
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Warning "[Get-ADRForest] Error retrieving Privileged Access Management Feature"
|
|||
|
Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
Else
|
|||
|
{
|
|||
|
$ADDomain = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()
|
|||
|
$ADForest = [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest()
|
|||
|
|
|||
|
# Get Tombstone Lifetime
|
|||
|
$ADForestTombstoneLifetime = ([ADSI]"LDAP://CN=Directory Service,CN=Windows NT,CN=Services,$($objDomainRootDSE.configurationNamingContext)").tombstoneLifetime.value
|
|||
|
|
|||
|
# Check Recycle Bin Feature Status
|
|||
|
If ([convert]::ToInt32($objDomainRootDSE.forestFunctionality,10) -ge 6)
|
|||
|
{
|
|||
|
$ADRecycleBin = ([ADSI]"LDAP://CN=Recycle Bin Feature,CN=Optional Features,CN=Directory Service,CN=Windows NT,CN=Services,CN=Configuration,$($objDomain.distinguishedName)")
|
|||
|
}
|
|||
|
# Check Privileged Access Management Feature Status
|
|||
|
If ([convert]::ToInt32($objDomainRootDSE.forestFunctionality,10) -ge 7)
|
|||
|
{
|
|||
|
$PrivilegedAccessManagement = ([ADSI]"LDAP://CN=Privileged Access Management Feature,CN=Optional Features,CN=Directory Service,CN=Windows NT,CN=Services,CN=Configuration,$($objDomain.distinguishedName)")
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
If ($ADForest)
|
|||
|
{
|
|||
|
$ForestObj = @()
|
|||
|
|
|||
|
# Values taken from https://technet.microsoft.com/en-us/library/hh852281(v=wps.630).aspx
|
|||
|
$FLAD = @{
|
|||
|
0 = "Windows2000";
|
|||
|
1 = "Windows2003/Interim";
|
|||
|
2 = "Windows2003";
|
|||
|
3 = "Windows2008";
|
|||
|
4 = "Windows2008R2";
|
|||
|
5 = "Windows2012";
|
|||
|
6 = "Windows2012R2";
|
|||
|
7 = "Windows2016"
|
|||
|
}
|
|||
|
$ForestMode = $FLAD[[convert]::ToInt32($objDomainRootDSE.forestFunctionality,10)] + "Forest"
|
|||
|
Remove-Variable FLAD
|
|||
|
|
|||
|
$ObjValues = @("Name", $ADForest.Name, "Functional Level", $ForestMode, "Domain Naming Master", $ADForest.NamingRoleOwner, "Schema Master", $ADForest.SchemaRoleOwner, "RootDomain", $ADForest.RootDomain, "Domain Count", $ADForest.Domains.Count, "Site Count", $ADForest.Sites.Count, "Global Catalog Count", $ADForest.GlobalCatalogs.Count)
|
|||
|
|
|||
|
For ($i = 0; $i -lt $($ObjValues.Count); $i++)
|
|||
|
{
|
|||
|
$Obj = New-Object PSObject
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Category" -Value $ObjValues[$i]
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Value" -Value $ObjValues[$i+1]
|
|||
|
$i++
|
|||
|
$ForestObj += $Obj
|
|||
|
}
|
|||
|
Remove-Variable ForestMode
|
|||
|
|
|||
|
For($i=0; $i -lt $ADForest.Domains.Count; $i++)
|
|||
|
{
|
|||
|
$Obj = New-Object PSObject
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Category" -Value "Domain"
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Value" -Value $ADForest.Domains[$i]
|
|||
|
$ForestObj += $Obj
|
|||
|
}
|
|||
|
For($i=0; $i -lt $ADForest.Sites.Count; $i++)
|
|||
|
{
|
|||
|
$Obj = New-Object PSObject
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Category" -Value "Site"
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Value" -Value $ADForest.Sites[$i]
|
|||
|
$ForestObj += $Obj
|
|||
|
}
|
|||
|
For($i=0; $i -lt $ADForest.GlobalCatalogs.Count; $i++)
|
|||
|
{
|
|||
|
$Obj = New-Object PSObject
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Category" -Value "GlobalCatalog"
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Value" -Value $ADForest.GlobalCatalogs[$i]
|
|||
|
$ForestObj += $Obj
|
|||
|
}
|
|||
|
|
|||
|
$Obj = New-Object PSObject
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Category" -Value "Tombstone Lifetime"
|
|||
|
If ($ADForestTombstoneLifetime)
|
|||
|
{
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Value" -Value $ADForestTombstoneLifetime
|
|||
|
Remove-Variable ADForestTombstoneLifetime
|
|||
|
}
|
|||
|
Else
|
|||
|
{
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Value" -Value "Not Retrieved"
|
|||
|
}
|
|||
|
$ForestObj += $Obj
|
|||
|
|
|||
|
$Obj = New-Object PSObject
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Category" -Value "Recycle Bin (2008 R2 onwards)"
|
|||
|
If ($ADRecycleBin)
|
|||
|
{
|
|||
|
If ($ADRecycleBin.Properties.'msDS-EnabledFeatureBL'.Count -gt 0)
|
|||
|
{
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Value" -Value "Enabled"
|
|||
|
$ForestObj += $Obj
|
|||
|
For($i=0; $i -lt $($ADRecycleBin.Properties.'msDS-EnabledFeatureBL'.Count); $i++)
|
|||
|
{
|
|||
|
$Obj = New-Object PSObject
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Category" -Value "Enabled Scope"
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Value" -Value $ADRecycleBin.Properties.'msDS-EnabledFeatureBL'[$i]
|
|||
|
$ForestObj += $Obj
|
|||
|
}
|
|||
|
}
|
|||
|
Else
|
|||
|
{
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Value" -Value "Disabled"
|
|||
|
$ForestObj += $Obj
|
|||
|
}
|
|||
|
Remove-Variable ADRecycleBin
|
|||
|
}
|
|||
|
Else
|
|||
|
{
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Value" -Value "Disabled"
|
|||
|
$ForestObj += $Obj
|
|||
|
}
|
|||
|
|
|||
|
$Obj = New-Object PSObject
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Category" -Value "Privileged Access Management (2016 onwards)"
|
|||
|
If ($PrivilegedAccessManagement)
|
|||
|
{
|
|||
|
If ($PrivilegedAccessManagement.Properties.'msDS-EnabledFeatureBL'.Count -gt 0)
|
|||
|
{
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Value" -Value "Enabled"
|
|||
|
$ForestObj += $Obj
|
|||
|
For($i=0; $i -lt $($PrivilegedAccessManagement.Properties.'msDS-EnabledFeatureBL'.Count); $i++)
|
|||
|
{
|
|||
|
$Obj = New-Object PSObject
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Category" -Value "Enabled Scope"
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Value" -Value $PrivilegedAccessManagement.Properties.'msDS-EnabledFeatureBL'[$i]
|
|||
|
$ForestObj += $Obj
|
|||
|
}
|
|||
|
}
|
|||
|
Else
|
|||
|
{
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Value" -Value "Disabled"
|
|||
|
$ForestObj += $Obj
|
|||
|
}
|
|||
|
Remove-Variable PrivilegedAccessManagement
|
|||
|
}
|
|||
|
Else
|
|||
|
{
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Value" -Value "Disabled"
|
|||
|
$ForestObj += $Obj
|
|||
|
}
|
|||
|
|
|||
|
Remove-Variable ADForest
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
If ($ForestObj)
|
|||
|
{
|
|||
|
Return $ForestObj
|
|||
|
}
|
|||
|
Else
|
|||
|
{
|
|||
|
Return $null
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
Function Get-ADRTrust
|
|||
|
{
|
|||
|
<#
|
|||
|
.SYNOPSIS
|
|||
|
Returns the Trusts of the current (or specified) domain.
|
|||
|
|
|||
|
.DESCRIPTION
|
|||
|
Returns the Trusts of the current (or specified) domain.
|
|||
|
|
|||
|
.PARAMETER Protocol
|
|||
|
[string]
|
|||
|
Which protocol to use; ADWS (default) or LDAP.
|
|||
|
|
|||
|
.PARAMETER objDomain
|
|||
|
[DirectoryServices.DirectoryEntry]
|
|||
|
Domain Directory Entry object.
|
|||
|
|
|||
|
.OUTPUTS
|
|||
|
PSObject.
|
|||
|
#>
|
|||
|
param(
|
|||
|
[Parameter(Mandatory = $true)]
|
|||
|
[string] $Protocol,
|
|||
|
|
|||
|
[Parameter(Mandatory = $false)]
|
|||
|
[DirectoryServices.DirectoryEntry] $objDomain
|
|||
|
)
|
|||
|
|
|||
|
# Values taken from https://msdn.microsoft.com/en-us/library/cc223768.aspx
|
|||
|
$TDAD = @{
|
|||
|
0 = "Disabled";
|
|||
|
1 = "Inbound";
|
|||
|
2 = "Outbound";
|
|||
|
3 = "BiDirectional";
|
|||
|
}
|
|||
|
|
|||
|
# Values taken from https://msdn.microsoft.com/en-us/library/cc223771.aspx
|
|||
|
$TTAD = @{
|
|||
|
1 = "Downlevel";
|
|||
|
2 = "Uplevel";
|
|||
|
3 = "MIT";
|
|||
|
4 = "DCE";
|
|||
|
}
|
|||
|
|
|||
|
If ($Protocol -eq 'ADWS')
|
|||
|
{
|
|||
|
Try
|
|||
|
{
|
|||
|
$ADTrusts = Get-ADObject -LDAPFilter "(objectClass=trustedDomain)" -Properties DistinguishedName,trustPartner,trustdirection,trusttype,TrustAttributes,whenCreated,whenChanged
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Warning "[Get-ADRTrust] Error while enumerating trustedDomain Objects"
|
|||
|
Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
Return $null
|
|||
|
}
|
|||
|
|
|||
|
If ($ADTrusts)
|
|||
|
{
|
|||
|
Write-Verbose "[*] Total Trusts: $([ADRecon.ADWSClass]::ObjectCount($ADTrusts))"
|
|||
|
# Trust Info
|
|||
|
$ADTrustObj = @()
|
|||
|
$ADTrusts | ForEach-Object {
|
|||
|
# Create the object for each instance.
|
|||
|
$Obj = New-Object PSObject
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Source Domain" -Value (Get-DNtoFQDN $_.DistinguishedName)
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Target Domain" -Value $_.trustPartner
|
|||
|
$TrustDirection = [string] $TDAD[$_.trustdirection]
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Trust Direction" -Value $TrustDirection
|
|||
|
$TrustType = [string] $TTAD[$_.trusttype]
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Trust Type" -Value $TrustType
|
|||
|
|
|||
|
$TrustAttributes = $null
|
|||
|
If ([int32] $_.TrustAttributes -band 0x00000001) { $TrustAttributes += "Non Transitive," }
|
|||
|
If ([int32] $_.TrustAttributes -band 0x00000002) { $TrustAttributes += "UpLevel," }
|
|||
|
If ([int32] $_.TrustAttributes -band 0x00000004) { $TrustAttributes += "Quarantined," } #SID Filtering
|
|||
|
If ([int32] $_.TrustAttributes -band 0x00000008) { $TrustAttributes += "Forest Transitive," }
|
|||
|
If ([int32] $_.TrustAttributes -band 0x00000010) { $TrustAttributes += "Cross Organization," } #Selective Auth
|
|||
|
If ([int32] $_.TrustAttributes -band 0x00000020) { $TrustAttributes += "Within Forest," }
|
|||
|
If ([int32] $_.TrustAttributes -band 0x00000040) { $TrustAttributes += "Treat as External," }
|
|||
|
If ([int32] $_.TrustAttributes -band 0x00000080) { $TrustAttributes += "Uses RC4 Encryption," }
|
|||
|
If ([int32] $_.TrustAttributes -band 0x00000200) { $TrustAttributes += "No TGT Delegation," }
|
|||
|
If ([int32] $_.TrustAttributes -band 0x00000400) { $TrustAttributes += "PIM Trust," }
|
|||
|
If ($TrustAttributes)
|
|||
|
{
|
|||
|
$TrustAttributes = $TrustAttributes.TrimEnd(",")
|
|||
|
}
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Attributes" -Value $TrustAttributes
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "whenCreated" -Value ([DateTime] $($_.whenCreated))
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "whenChanged" -Value ([DateTime] $($_.whenChanged))
|
|||
|
$ADTrustObj += $Obj
|
|||
|
}
|
|||
|
Remove-Variable ADTrusts
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
If ($Protocol -eq 'LDAP')
|
|||
|
{
|
|||
|
$objSearcher = New-Object System.DirectoryServices.DirectorySearcher $objDomain
|
|||
|
$ObjSearcher.PageSize = $PageSize
|
|||
|
$ObjSearcher.Filter = "(objectClass=trustedDomain)"
|
|||
|
$ObjSearcher.PropertiesToLoad.AddRange(("distinguishedname","trustpartner","trustdirection","trusttype","trustattributes","whencreated","whenchanged"))
|
|||
|
$ObjSearcher.SearchScope = "Subtree"
|
|||
|
|
|||
|
Try
|
|||
|
{
|
|||
|
$ADTrusts = $ObjSearcher.FindAll()
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Warning "[Get-ADRTrust] Error while enumerating trustedDomain Objects"
|
|||
|
Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
Return $null
|
|||
|
}
|
|||
|
$ObjSearcher.dispose()
|
|||
|
|
|||
|
If ($ADTrusts)
|
|||
|
{
|
|||
|
Write-Verbose "[*] Total Trusts: $([ADRecon.LDAPClass]::ObjectCount($ADTrusts))"
|
|||
|
# Trust Info
|
|||
|
$ADTrustObj = @()
|
|||
|
$ADTrusts | ForEach-Object {
|
|||
|
# Create the object for each instance.
|
|||
|
$Obj = New-Object PSObject
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Source Domain" -Value $(Get-DNtoFQDN ([string] $_.Properties.distinguishedname))
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Target Domain" -Value $([string] $_.Properties.trustpartner)
|
|||
|
$TrustDirection = [string] $TDAD[$_.Properties.trustdirection]
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Trust Direction" -Value $TrustDirection
|
|||
|
$TrustType = [string] $TTAD[$_.Properties.trusttype]
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Trust Type" -Value $TrustType
|
|||
|
|
|||
|
$TrustAttributes = $null
|
|||
|
If ([int32] $_.Properties.trustattributes[0] -band 0x00000001) { $TrustAttributes += "Non Transitive," }
|
|||
|
If ([int32] $_.Properties.trustattributes[0] -band 0x00000002) { $TrustAttributes += "UpLevel," }
|
|||
|
If ([int32] $_.Properties.trustattributes[0] -band 0x00000004) { $TrustAttributes += "Quarantined," } #SID Filtering
|
|||
|
If ([int32] $_.Properties.trustattributes[0] -band 0x00000008) { $TrustAttributes += "Forest Transitive," }
|
|||
|
If ([int32] $_.Properties.trustattributes[0] -band 0x00000010) { $TrustAttributes += "Cross Organization," } #Selective Auth
|
|||
|
If ([int32] $_.Properties.trustattributes[0] -band 0x00000020) { $TrustAttributes += "Within Forest," }
|
|||
|
If ([int32] $_.Properties.trustattributes[0] -band 0x00000040) { $TrustAttributes += "Treat as External," }
|
|||
|
If ([int32] $_.Properties.trustattributes[0] -band 0x00000080) { $TrustAttributes += "Uses RC4 Encryption," }
|
|||
|
If ([int32] $_.Properties.trustattributes[0] -band 0x00000200) { $TrustAttributes += "No TGT Delegation," }
|
|||
|
If ([int32] $_.Properties.trustattributes[0] -band 0x00000400) { $TrustAttributes += "PIM Trust," }
|
|||
|
If ($TrustAttributes)
|
|||
|
{
|
|||
|
$TrustAttributes = $TrustAttributes.TrimEnd(",")
|
|||
|
}
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Attributes" -Value $TrustAttributes
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "whenCreated" -Value ([DateTime] $($_.Properties.whencreated))
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "whenChanged" -Value ([DateTime] $($_.Properties.whenchanged))
|
|||
|
$ADTrustObj += $Obj
|
|||
|
}
|
|||
|
Remove-Variable ADTrusts
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
If ($ADTrustObj)
|
|||
|
{
|
|||
|
Return $ADTrustObj
|
|||
|
}
|
|||
|
Else
|
|||
|
{
|
|||
|
Return $null
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
Function Get-ADRSite
|
|||
|
{
|
|||
|
<#
|
|||
|
.SYNOPSIS
|
|||
|
Returns the Sites of the current (or specified) domain.
|
|||
|
|
|||
|
.DESCRIPTION
|
|||
|
Returns the Sites of the current (or specified) domain.
|
|||
|
|
|||
|
.PARAMETER Protocol
|
|||
|
[string]
|
|||
|
Which protocol to use; ADWS (default) or LDAP.
|
|||
|
|
|||
|
.PARAMETER objDomain
|
|||
|
[DirectoryServices.DirectoryEntry]
|
|||
|
Domain Directory Entry object.
|
|||
|
|
|||
|
.PARAMETER objDomainRootDSE
|
|||
|
[DirectoryServices.DirectoryEntry]
|
|||
|
RootDSE Directory Entry object.
|
|||
|
|
|||
|
.PARAMETER DomainController
|
|||
|
[string]
|
|||
|
IP Address of the Domain Controller.
|
|||
|
|
|||
|
.PARAMETER Credential
|
|||
|
[Management.Automation.PSCredential]
|
|||
|
Credentials.
|
|||
|
|
|||
|
.OUTPUTS
|
|||
|
PSObject.
|
|||
|
#>
|
|||
|
param(
|
|||
|
[Parameter(Mandatory = $true)]
|
|||
|
[string] $Protocol,
|
|||
|
|
|||
|
[Parameter(Mandatory = $false)]
|
|||
|
[DirectoryServices.DirectoryEntry] $objDomain,
|
|||
|
|
|||
|
[Parameter(Mandatory = $false)]
|
|||
|
[DirectoryServices.DirectoryEntry] $objDomainRootDSE,
|
|||
|
|
|||
|
[Parameter(Mandatory = $false)]
|
|||
|
[string] $DomainController,
|
|||
|
|
|||
|
[Parameter(Mandatory = $false)]
|
|||
|
[Management.Automation.PSCredential] $Credential = [Management.Automation.PSCredential]::Empty
|
|||
|
)
|
|||
|
|
|||
|
If ($Protocol -eq 'ADWS')
|
|||
|
{
|
|||
|
Try
|
|||
|
{
|
|||
|
$SearchPath = "CN=Sites"
|
|||
|
$ADSites = Get-ADObject -SearchBase "$SearchPath,$((Get-ADRootDSE).configurationNamingContext)" -LDAPFilter "(objectClass=site)" -Properties Name,Description,whenCreated,whenChanged
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Warning "[Get-ADRSite] Error while enumerating Site Objects"
|
|||
|
Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
Return $null
|
|||
|
}
|
|||
|
|
|||
|
If ($ADSites)
|
|||
|
{
|
|||
|
Write-Verbose "[*] Total Sites: $([ADRecon.ADWSClass]::ObjectCount($ADSites))"
|
|||
|
# Sites Info
|
|||
|
$ADSiteObj = @()
|
|||
|
$ADSites | ForEach-Object {
|
|||
|
# Create the object for each instance.
|
|||
|
$Obj = New-Object PSObject
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Name" -Value $_.Name
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Description" -Value $_.Description
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "whenCreated" -Value $_.whenCreated
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "whenChanged" -Value $_.whenChanged
|
|||
|
$ADSiteObj += $Obj
|
|||
|
}
|
|||
|
Remove-Variable ADSites
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
If ($Protocol -eq 'LDAP')
|
|||
|
{
|
|||
|
$SearchPath = "CN=Sites"
|
|||
|
If ($Credential -ne [Management.Automation.PSCredential]::Empty)
|
|||
|
{
|
|||
|
$objSearchPath = New-Object System.DirectoryServices.DirectoryEntry "LDAP://$($DomainController)/$SearchPath,$($objDomainRootDSE.ConfigurationNamingContext)", $Credential.UserName,$Credential.GetNetworkCredential().Password
|
|||
|
}
|
|||
|
Else
|
|||
|
{
|
|||
|
$objSearchPath = New-Object System.DirectoryServices.DirectoryEntry "LDAP://$SearchPath,$($objDomainRootDSE.ConfigurationNamingContext)"
|
|||
|
}
|
|||
|
$objSearcher = New-Object System.DirectoryServices.DirectorySearcher $objSearchPath
|
|||
|
$ObjSearcher.Filter = "(objectClass=site)"
|
|||
|
$ObjSearcher.SearchScope = "Subtree"
|
|||
|
|
|||
|
Try
|
|||
|
{
|
|||
|
$ADSites = $ObjSearcher.FindAll()
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Warning "[Get-ADRSite] Error while enumerating Site Objects"
|
|||
|
Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
Return $null
|
|||
|
}
|
|||
|
$ObjSearcher.dispose()
|
|||
|
|
|||
|
If ($ADSites)
|
|||
|
{
|
|||
|
Write-Verbose "[*] Total Sites: $([ADRecon.LDAPClass]::ObjectCount($ADSites))"
|
|||
|
# Site Info
|
|||
|
$ADSiteObj = @()
|
|||
|
$ADSites | ForEach-Object {
|
|||
|
# Create the object for each instance.
|
|||
|
$Obj = New-Object PSObject
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Name" -Value $([string] $_.Properties.name)
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Description" -Value $([string] $_.Properties.description)
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "whenCreated" -Value ([DateTime] $($_.Properties.whencreated))
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "whenChanged" -Value ([DateTime] $($_.Properties.whenchanged))
|
|||
|
$ADSiteObj += $Obj
|
|||
|
}
|
|||
|
Remove-Variable ADSites
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
If ($ADSiteObj)
|
|||
|
{
|
|||
|
Return $ADSiteObj
|
|||
|
}
|
|||
|
Else
|
|||
|
{
|
|||
|
Return $null
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
Function Get-ADRSubnet
|
|||
|
{
|
|||
|
<#
|
|||
|
.SYNOPSIS
|
|||
|
Returns the Subnets of the current (or specified) domain.
|
|||
|
|
|||
|
.DESCRIPTION
|
|||
|
Returns the Subnets of the current (or specified) domain.
|
|||
|
|
|||
|
.PARAMETER Protocol
|
|||
|
[string]
|
|||
|
Which protocol to use; ADWS (default) or LDAP.
|
|||
|
|
|||
|
.PARAMETER objDomain
|
|||
|
[DirectoryServices.DirectoryEntry]
|
|||
|
Domain Directory Entry object.
|
|||
|
|
|||
|
.PARAMETER objDomainRootDSE
|
|||
|
[DirectoryServices.DirectoryEntry]
|
|||
|
RootDSE Directory Entry object.
|
|||
|
|
|||
|
.PARAMETER DomainController
|
|||
|
[string]
|
|||
|
IP Address of the Domain Controller.
|
|||
|
|
|||
|
.PARAMETER Credential
|
|||
|
[Management.Automation.PSCredential]
|
|||
|
Credentials.
|
|||
|
|
|||
|
.OUTPUTS
|
|||
|
PSObject.
|
|||
|
#>
|
|||
|
param(
|
|||
|
[Parameter(Mandatory = $true)]
|
|||
|
[string] $Protocol,
|
|||
|
|
|||
|
[Parameter(Mandatory = $false)]
|
|||
|
[DirectoryServices.DirectoryEntry] $objDomain,
|
|||
|
|
|||
|
[Parameter(Mandatory = $false)]
|
|||
|
[DirectoryServices.DirectoryEntry] $objDomainRootDSE,
|
|||
|
|
|||
|
[Parameter(Mandatory = $false)]
|
|||
|
[string] $DomainController,
|
|||
|
|
|||
|
[Parameter(Mandatory = $false)]
|
|||
|
[Management.Automation.PSCredential] $Credential = [Management.Automation.PSCredential]::Empty
|
|||
|
)
|
|||
|
|
|||
|
If ($Protocol -eq 'ADWS')
|
|||
|
{
|
|||
|
Try
|
|||
|
{
|
|||
|
$SearchPath = "CN=Subnets,CN=Sites"
|
|||
|
$ADSubnets = Get-ADObject -SearchBase "$SearchPath,$((Get-ADRootDSE).configurationNamingContext)" -LDAPFilter "(objectClass=subnet)" -Properties Name,Description,siteObject,whenCreated,whenChanged
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Warning "[Get-ADRSubnet] Error while enumerating Subnet Objects"
|
|||
|
Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
Return $null
|
|||
|
}
|
|||
|
|
|||
|
If ($ADSubnets)
|
|||
|
{
|
|||
|
Write-Verbose "[*] Total Subnets: $([ADRecon.ADWSClass]::ObjectCount($ADSubnets))"
|
|||
|
# Subnets Info
|
|||
|
$ADSubnetObj = @()
|
|||
|
$ADSubnets | ForEach-Object {
|
|||
|
# Create the object for each instance.
|
|||
|
$Obj = New-Object PSObject
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Site" -Value $(($_.siteObject -Split ",")[0] -replace 'CN=','')
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Name" -Value $_.Name
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Description" -Value $_.Description
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "whenCreated" -Value $_.whenCreated
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "whenChanged" -Value $_.whenChanged
|
|||
|
$ADSubnetObj += $Obj
|
|||
|
}
|
|||
|
Remove-Variable ADSubnets
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
If ($Protocol -eq 'LDAP')
|
|||
|
{
|
|||
|
$SearchPath = "CN=Subnets,CN=Sites"
|
|||
|
If ($Credential -ne [Management.Automation.PSCredential]::Empty)
|
|||
|
{
|
|||
|
$objSearchPath = New-Object System.DirectoryServices.DirectoryEntry "LDAP://$($DomainController)/$SearchPath,$($objDomainRootDSE.ConfigurationNamingContext)", $Credential.UserName,$Credential.GetNetworkCredential().Password
|
|||
|
}
|
|||
|
Else
|
|||
|
{
|
|||
|
$objSearchPath = New-Object System.DirectoryServices.DirectoryEntry "LDAP://$SearchPath,$($objDomainRootDSE.ConfigurationNamingContext)"
|
|||
|
}
|
|||
|
$objSearcher = New-Object System.DirectoryServices.DirectorySearcher $objSearchPath
|
|||
|
$ObjSearcher.Filter = "(objectClass=subnet)"
|
|||
|
$ObjSearcher.SearchScope = "Subtree"
|
|||
|
|
|||
|
Try
|
|||
|
{
|
|||
|
$ADSubnets = $ObjSearcher.FindAll()
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Warning "[Get-ADRSubnet] Error while enumerating Subnet Objects"
|
|||
|
Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
Return $null
|
|||
|
}
|
|||
|
$ObjSearcher.dispose()
|
|||
|
|
|||
|
If ($ADSubnets)
|
|||
|
{
|
|||
|
Write-Verbose "[*] Total Subnets: $([ADRecon.LDAPClass]::ObjectCount($ADSubnets))"
|
|||
|
# Subnets Info
|
|||
|
$ADSubnetObj = @()
|
|||
|
$ADSubnets | ForEach-Object {
|
|||
|
# Create the object for each instance.
|
|||
|
$Obj = New-Object PSObject
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Site" -Value $((([string] $_.Properties.siteobject) -Split ",")[0] -replace 'CN=','')
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Name" -Value $([string] $_.Properties.name)
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Description" -Value $([string] $_.Properties.description)
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "whenCreated" -Value ([DateTime] $($_.Properties.whencreated))
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "whenChanged" -Value ([DateTime] $($_.Properties.whenchanged))
|
|||
|
$ADSubnetObj += $Obj
|
|||
|
}
|
|||
|
Remove-Variable ADSubnets
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
If ($ADSubnetObj)
|
|||
|
{
|
|||
|
Return $ADSubnetObj
|
|||
|
}
|
|||
|
Else
|
|||
|
{
|
|||
|
Return $null
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
Function Get-ADRDefaultPasswordPolicy
|
|||
|
{
|
|||
|
<#
|
|||
|
.SYNOPSIS
|
|||
|
Returns the Default Password Policy of the current (or specified) domain.
|
|||
|
|
|||
|
.DESCRIPTION
|
|||
|
Returns the Default Password Policy of the current (or specified) domain.
|
|||
|
|
|||
|
.PARAMETER Protocol
|
|||
|
[string]
|
|||
|
Which protocol to use; ADWS (default) or LDAP.
|
|||
|
|
|||
|
.PARAMETER objDomain
|
|||
|
[DirectoryServices.DirectoryEntry]
|
|||
|
Domain Directory Entry object.
|
|||
|
|
|||
|
.OUTPUTS
|
|||
|
PSObject.
|
|||
|
#>
|
|||
|
param(
|
|||
|
[Parameter(Mandatory = $true)]
|
|||
|
[string] $Protocol,
|
|||
|
|
|||
|
[Parameter(Mandatory = $false)]
|
|||
|
[DirectoryServices.DirectoryEntry] $objDomain
|
|||
|
)
|
|||
|
|
|||
|
If ($Protocol -eq 'ADWS')
|
|||
|
{
|
|||
|
Try
|
|||
|
{
|
|||
|
$ADpasspolicy = Get-ADDefaultDomainPasswordPolicy
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Warning "[Get-ADRDefaultPasswordPolicy] Error while enumerating the Default Password Policy"
|
|||
|
Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
Return $null
|
|||
|
}
|
|||
|
|
|||
|
If ($ADpasspolicy)
|
|||
|
{
|
|||
|
$ObjValues = @( "Enforce password history (passwords)", $ADpasspolicy.PasswordHistoryCount, "4", "Req. 8.2.5", "8", "Control: 0423", "24 or more",
|
|||
|
"Maximum password age (days)", $ADpasspolicy.MaxPasswordAge.days, "90", "Req. 8.2.4", "90", "Control: 0423", "1 to 60",
|
|||
|
"Minimum password age (days)", $ADpasspolicy.MinPasswordAge.days, "N/A", "-", "1", "Control: 0423", "1 or more",
|
|||
|
"Minimum password length (characters)", $ADpasspolicy.MinPasswordLength, "7", "Req. 8.2.3", "13", "Control: 0421", "14 or more",
|
|||
|
"Password must meet complexity requirements", $ADpasspolicy.ComplexityEnabled, $true, "Req. 8.2.3", $true, "Control: 0421", $true,
|
|||
|
"Store password using reversible encryption for all users in the domain", $ADpasspolicy.ReversibleEncryptionEnabled, "N/A", "-", "N/A", "-", $false,
|
|||
|
"Account lockout duration (mins)", $ADpasspolicy.LockoutDuration.minutes, "0 (manual unlock) or 30", "Req. 8.1.7", "N/A", "-", "15 or more",
|
|||
|
"Account lockout threshold (attempts)", $ADpasspolicy.LockoutThreshold, "1 to 6", "Req. 8.1.6", "1 to 5", "Control: 1403", "1 to 10",
|
|||
|
"Reset account lockout counter after (mins)", $ADpasspolicy.LockoutObservationWindow.minutes, "N/A", "-", "N/A", "-", "15 or more" )
|
|||
|
|
|||
|
Remove-Variable ADpasspolicy
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
If ($Protocol -eq 'LDAP')
|
|||
|
{
|
|||
|
If ($ObjDomain)
|
|||
|
{
|
|||
|
#Value taken from https://msdn.microsoft.com/en-us/library/ms679431(v=vs.85).aspx
|
|||
|
$pwdProperties = @{
|
|||
|
"DOMAIN_PASSWORD_COMPLEX" = 1;
|
|||
|
"DOMAIN_PASSWORD_NO_ANON_CHANGE" = 2;
|
|||
|
"DOMAIN_PASSWORD_NO_CLEAR_CHANGE" = 4;
|
|||
|
"DOMAIN_LOCKOUT_ADMINS" = 8;
|
|||
|
"DOMAIN_PASSWORD_STORE_CLEARTEXT" = 16;
|
|||
|
"DOMAIN_REFUSE_PASSWORD_CHANGE" = 32
|
|||
|
}
|
|||
|
|
|||
|
If (($ObjDomain.pwdproperties.value -band $pwdProperties["DOMAIN_PASSWORD_COMPLEX"]) -eq $pwdProperties["DOMAIN_PASSWORD_COMPLEX"])
|
|||
|
{
|
|||
|
$ComplexPasswords = $true
|
|||
|
}
|
|||
|
Else
|
|||
|
{
|
|||
|
$ComplexPasswords = $false
|
|||
|
}
|
|||
|
|
|||
|
If (($ObjDomain.pwdproperties.value -band $pwdProperties["DOMAIN_PASSWORD_STORE_CLEARTEXT"]) -eq $pwdProperties["DOMAIN_PASSWORD_STORE_CLEARTEXT"])
|
|||
|
{
|
|||
|
$ReversibleEncryption = $true
|
|||
|
}
|
|||
|
Else
|
|||
|
{
|
|||
|
$ReversibleEncryption = $false
|
|||
|
}
|
|||
|
|
|||
|
$LockoutDuration = $($ObjDomain.ConvertLargeIntegerToInt64($ObjDomain.lockoutduration.value)/-600000000)
|
|||
|
|
|||
|
If ($LockoutDuration -gt 99999)
|
|||
|
{
|
|||
|
$LockoutDuration = 0
|
|||
|
}
|
|||
|
|
|||
|
$ObjValues = @( "Enforce password history (passwords)", $ObjDomain.PwdHistoryLength.value, "4", "Req. 8.2.5", "8", "Control: 0423", "24 or more",
|
|||
|
"Maximum password age (days)", $($ObjDomain.ConvertLargeIntegerToInt64($ObjDomain.maxpwdage.value) /-864000000000), "90", "Req. 8.2.4", "90", "Control: 0423", "1 to 60",
|
|||
|
"Minimum password age (days)", $($ObjDomain.ConvertLargeIntegerToInt64($ObjDomain.minpwdage.value) /-864000000000), "N/A", "-", "1", "Control: 0423", "1 or more",
|
|||
|
"Minimum password length (characters)", $ObjDomain.MinPwdLength.value, "7", "Req. 8.2.3", "13", "Control: 0421", "14 or more",
|
|||
|
"Password must meet complexity requirements", $ComplexPasswords, $true, "Req. 8.2.3", $true, "Control: 0421", $true,
|
|||
|
"Store password using reversible encryption for all users in the domain", $ReversibleEncryption, "N/A", "-", "N/A", "-", $false,
|
|||
|
"Account lockout duration (mins)", $LockoutDuration, "0 (manual unlock) or 30", "Req. 8.1.7", "N/A", "-", "15 or more",
|
|||
|
"Account lockout threshold (attempts)", $ObjDomain.LockoutThreshold.value, "1 to 6", "Req. 8.1.6", "1 to 5", "Control: 1403", "1 to 10",
|
|||
|
"Reset account lockout counter after (mins)", $($ObjDomain.ConvertLargeIntegerToInt64($ObjDomain.lockoutobservationWindow.value)/-600000000), "N/A", "-", "N/A", "-", "15 or more" )
|
|||
|
|
|||
|
Remove-Variable pwdProperties
|
|||
|
Remove-Variable ComplexPasswords
|
|||
|
Remove-Variable ReversibleEncryption
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
If ($ObjValues)
|
|||
|
{
|
|||
|
$ADPassPolObj = @()
|
|||
|
For ($i = 0; $i -lt $($ObjValues.Count); $i++)
|
|||
|
{
|
|||
|
$Obj = New-Object PSObject
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Policy" -Value $ObjValues[$i]
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Current Value" -Value $ObjValues[$i+1]
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "PCI DSS Requirement" -Value $ObjValues[$i+2]
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "PCI DSS v3.2.1" -Value $ObjValues[$i+3]
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "ASD ISM" -Value $ObjValues[$i+4]
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "2018 ISM Controls" -Value $ObjValues[$i+5]
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "CIS Benchmark 2016" -Value $ObjValues[$i+6]
|
|||
|
$i += 6
|
|||
|
$ADPassPolObj += $Obj
|
|||
|
}
|
|||
|
Remove-Variable ObjValues
|
|||
|
Return $ADPassPolObj
|
|||
|
}
|
|||
|
Else
|
|||
|
{
|
|||
|
Return $null
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
Function Get-ADRFineGrainedPasswordPolicy
|
|||
|
{
|
|||
|
<#
|
|||
|
.SYNOPSIS
|
|||
|
Returns the Fine Grained Password Policy of the current (or specified) domain.
|
|||
|
|
|||
|
.DESCRIPTION
|
|||
|
Returns the Fine Grained Password Policy of the current (or specified) domain.
|
|||
|
|
|||
|
.PARAMETER Protocol
|
|||
|
[string]
|
|||
|
Which protocol to use; ADWS (default) or LDAP.
|
|||
|
|
|||
|
.PARAMETER objDomain
|
|||
|
[DirectoryServices.DirectoryEntry]
|
|||
|
Domain Directory Entry object.
|
|||
|
|
|||
|
.OUTPUTS
|
|||
|
PSObject.
|
|||
|
#>
|
|||
|
param(
|
|||
|
[Parameter(Mandatory = $true)]
|
|||
|
[string] $Protocol,
|
|||
|
|
|||
|
[Parameter(Mandatory = $false)]
|
|||
|
[DirectoryServices.DirectoryEntry] $objDomain
|
|||
|
)
|
|||
|
|
|||
|
If ($Protocol -eq 'ADWS')
|
|||
|
{
|
|||
|
Try
|
|||
|
{
|
|||
|
$ADFinepasspolicy = Get-ADFineGrainedPasswordPolicy -Filter *
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Warning "[Get-ADRFineGrainedPasswordPolicy] Error while enumerating the Fine Grained Password Policy"
|
|||
|
Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
Return $null
|
|||
|
}
|
|||
|
|
|||
|
If ($ADFinepasspolicy)
|
|||
|
{
|
|||
|
$ADPassPolObj = @()
|
|||
|
|
|||
|
$ADFinepasspolicy | ForEach-Object {
|
|||
|
For($i=0; $i -lt $($_.AppliesTo.Count); $i++)
|
|||
|
{
|
|||
|
$AppliesTo = $AppliesTo + "," + $_.AppliesTo[$i]
|
|||
|
}
|
|||
|
If ($null -ne $AppliesTo)
|
|||
|
{
|
|||
|
$AppliesTo = $AppliesTo.TrimStart(",")
|
|||
|
}
|
|||
|
$ObjValues = @("Name", $($_.Name), "Applies To", $AppliesTo, "Enforce password history", $_.PasswordHistoryCount, "Maximum password age (days)", $_.MaxPasswordAge.days, "Minimum password age (days)", $_.MinPasswordAge.days, "Minimum password length", $_.MinPasswordLength, "Password must meet complexity requirements", $_.ComplexityEnabled, "Store password using reversible encryption", $_.ReversibleEncryptionEnabled, "Account lockout duration (mins)", $_.LockoutDuration.minutes, "Account lockout threshold", $_.LockoutThreshold, "Reset account lockout counter after (mins)", $_.LockoutObservationWindow.minutes, "Precedence", $($_.Precedence))
|
|||
|
For ($i = 0; $i -lt $($ObjValues.Count); $i++)
|
|||
|
{
|
|||
|
$Obj = New-Object PSObject
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Policy" -Value $ObjValues[$i]
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Value" -Value $ObjValues[$i+1]
|
|||
|
$i++
|
|||
|
$ADPassPolObj += $Obj
|
|||
|
}
|
|||
|
}
|
|||
|
Remove-Variable ADFinepasspolicy
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
If ($Protocol -eq 'LDAP')
|
|||
|
{
|
|||
|
If ($ObjDomain)
|
|||
|
{
|
|||
|
$objSearcher = New-Object System.DirectoryServices.DirectorySearcher $objDomain
|
|||
|
$ObjSearcher.PageSize = $PageSize
|
|||
|
$ObjSearcher.Filter = "(objectClass=msDS-PasswordSettings)"
|
|||
|
$ObjSearcher.SearchScope = "Subtree"
|
|||
|
Try
|
|||
|
{
|
|||
|
$ADFinepasspolicy = $ObjSearcher.FindAll()
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Warning "[Get-ADRFineGrainedPasswordPolicy] Error while enumerating the Fine Grained Password Policy"
|
|||
|
Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
Return $null
|
|||
|
}
|
|||
|
|
|||
|
If ($ADFinepasspolicy)
|
|||
|
{
|
|||
|
If ([ADRecon.LDAPClass]::ObjectCount($ADFinepasspolicy) -ge 1)
|
|||
|
{
|
|||
|
$ADPassPolObj = @()
|
|||
|
$ADFinepasspolicy | ForEach-Object {
|
|||
|
For($i=0; $i -lt $($_.Properties.'msds-psoappliesto'.Count); $i++)
|
|||
|
{
|
|||
|
$AppliesTo = $AppliesTo + "," + $_.Properties.'msds-psoappliesto'[$i]
|
|||
|
}
|
|||
|
If ($null -ne $AppliesTo)
|
|||
|
{
|
|||
|
$AppliesTo = $AppliesTo.TrimStart(",")
|
|||
|
}
|
|||
|
$ObjValues = @("Name", $($_.Properties.name), "Applies To", $AppliesTo, "Enforce password history", $($_.Properties.'msds-passwordhistorylength'), "Maximum password age (days)", $($($_.Properties.'msds-maximumpasswordage') /-864000000000), "Minimum password age (days)", $($($_.Properties.'msds-minimumpasswordage') /-864000000000), "Minimum password length", $($_.Properties.'msds-minimumpasswordlength'), "Password must meet complexity requirements", $($_.Properties.'msds-passwordcomplexityenabled'), "Store password using reversible encryption", $($_.Properties.'msds-passwordreversibleencryptionenabled'), "Account lockout duration (mins)", $($($_.Properties.'msds-lockoutduration')/-600000000), "Account lockout threshold", $($_.Properties.'msds-lockoutthreshold'), "Reset account lockout counter after (mins)", $($($_.Properties.'msds-lockoutobservationwindow')/-600000000), "Precedence", $($_.Properties.'msds-passwordsettingsprecedence'))
|
|||
|
For ($i = 0; $i -lt $($ObjValues.Count); $i++)
|
|||
|
{
|
|||
|
$Obj = New-Object PSObject
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Policy" -Value $ObjValues[$i]
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Value" -Value $ObjValues[$i+1]
|
|||
|
$i++
|
|||
|
$ADPassPolObj += $Obj
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
Remove-Variable ADFinepasspolicy
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
If ($ADPassPolObj)
|
|||
|
{
|
|||
|
Return $ADPassPolObj
|
|||
|
}
|
|||
|
Else
|
|||
|
{
|
|||
|
Return $null
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
Function Get-ADRDomainController
|
|||
|
{
|
|||
|
<#
|
|||
|
.SYNOPSIS
|
|||
|
Returns the domain controllers for the current (or specified) forest.
|
|||
|
|
|||
|
.DESCRIPTION
|
|||
|
Returns the domain controllers for the current (or specified) forest.
|
|||
|
|
|||
|
.PARAMETER Protocol
|
|||
|
[string]
|
|||
|
Which protocol to use; ADWS (default) or LDAP.
|
|||
|
|
|||
|
.PARAMETER objDomain
|
|||
|
[DirectoryServices.DirectoryEntry]
|
|||
|
Domain Directory Entry object.
|
|||
|
|
|||
|
.PARAMETER Credential
|
|||
|
[Management.Automation.PSCredential]
|
|||
|
Credentials.
|
|||
|
|
|||
|
.OUTPUTS
|
|||
|
PSObject.
|
|||
|
#>
|
|||
|
param(
|
|||
|
[Parameter(Mandatory = $true)]
|
|||
|
[string] $Protocol,
|
|||
|
|
|||
|
[Parameter(Mandatory = $false)]
|
|||
|
[DirectoryServices.DirectoryEntry] $objDomain,
|
|||
|
|
|||
|
[Parameter(Mandatory = $false)]
|
|||
|
[Management.Automation.PSCredential] $Credential = [Management.Automation.PSCredential]::Empty
|
|||
|
)
|
|||
|
|
|||
|
If ($Protocol -eq 'ADWS')
|
|||
|
{
|
|||
|
Try
|
|||
|
{
|
|||
|
$ADDomainControllers = Get-ADDomainController -Filter *
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Warning "[Get-ADRDomainController] Error while enumerating DomainController Objects"
|
|||
|
Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
Return $null
|
|||
|
}
|
|||
|
|
|||
|
# DC Info
|
|||
|
If ($ADDomainControllers)
|
|||
|
{
|
|||
|
Write-Verbose "[*] Total Domain Controllers: $([ADRecon.ADWSClass]::ObjectCount($ADDomainControllers))"
|
|||
|
# DC Info
|
|||
|
$DCObj = @()
|
|||
|
$ADDomainControllers | ForEach-Object {
|
|||
|
# Create the object for each instance.
|
|||
|
$Obj = New-Object PSObject
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Domain" -Value $_.Domain
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Site" -Value $_.Site
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Name" -Value $_.Name
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "IPv4Address" -Value $_.IPv4Address
|
|||
|
$OSVersion = [ADRecon.ADWSClass]::CleanString($($_.OperatingSystem + " " + $_.OperatingSystemHotfix + " " + $_.OperatingSystemServicePack + " " + $_.OperatingSystemVersion))
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Operating System" -Value $OSVersion
|
|||
|
Remove-Variable OSVersion
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Hostname" -Value $_.HostName
|
|||
|
If ($_.OperationMasterRoles -like 'DomainNamingMaster')
|
|||
|
{
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Naming" -Value $true
|
|||
|
}
|
|||
|
Else
|
|||
|
{
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Naming" -Value $false
|
|||
|
}
|
|||
|
If ($_.OperationMasterRoles -like 'SchemaMaster')
|
|||
|
{
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Schema" -Value $true
|
|||
|
}
|
|||
|
Else
|
|||
|
{
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Schema" -Value $false
|
|||
|
}
|
|||
|
If ($_.OperationMasterRoles -like 'InfrastructureMaster')
|
|||
|
{
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Infra" -Value $true
|
|||
|
}
|
|||
|
Else
|
|||
|
{
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Infra" -Value $false
|
|||
|
}
|
|||
|
If ($_.OperationMasterRoles -like 'RIDMaster')
|
|||
|
{
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "RID" -Value $true
|
|||
|
}
|
|||
|
Else
|
|||
|
{
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "RID" -Value $false
|
|||
|
}
|
|||
|
If ($_.OperationMasterRoles -like 'PDCEmulator')
|
|||
|
{
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "PDC" -Value $true
|
|||
|
}
|
|||
|
Else
|
|||
|
{
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "PDC" -Value $false
|
|||
|
}
|
|||
|
$DCSMBObj = [ADRecon.PingCastleScannersSMBScanner]::GetPSObject($_.IPv4Address)
|
|||
|
ForEach ($Property in $DCSMBObj.psobject.Properties)
|
|||
|
{
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name $Property.Name -Value $Property.value
|
|||
|
}
|
|||
|
$DCObj += $Obj
|
|||
|
}
|
|||
|
Remove-Variable ADDomainControllers
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
If ($Protocol -eq 'LDAP')
|
|||
|
{
|
|||
|
If ($Credential -ne [Management.Automation.PSCredential]::Empty)
|
|||
|
{
|
|||
|
$DomainFQDN = Get-DNtoFQDN($objDomain.distinguishedName)
|
|||
|
$DomainContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext("Domain",$($DomainFQDN),$($Credential.UserName),$($Credential.GetNetworkCredential().password))
|
|||
|
Try
|
|||
|
{
|
|||
|
$ADDomain = [System.DirectoryServices.ActiveDirectory.Domain]::GetDomain($DomainContext)
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Warning "[Get-ADRDomainController] Error getting Domain Context"
|
|||
|
Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
Return $null
|
|||
|
}
|
|||
|
Remove-Variable DomainContext
|
|||
|
}
|
|||
|
Else
|
|||
|
{
|
|||
|
$ADDomain = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()
|
|||
|
}
|
|||
|
|
|||
|
If ($ADDomain.DomainControllers)
|
|||
|
{
|
|||
|
Write-Verbose "[*] Total Domain Controllers: $([ADRecon.LDAPClass]::ObjectCount($ADDomain.DomainControllers))"
|
|||
|
# DC Info
|
|||
|
$DCObj = @()
|
|||
|
$ADDomain.DomainControllers | ForEach-Object {
|
|||
|
# Create the object for each instance.
|
|||
|
$Obj = New-Object PSObject
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Domain" -Value $_.Domain
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Site" -Value $_.SiteName
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Name" -Value ($_.Name -Split ("\."))[0]
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "IPAddress" -Value $_.IPAddress
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Operating System" -Value $_.OSVersion
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Hostname" -Value $_.Name
|
|||
|
If ($null -ne $_.Roles)
|
|||
|
{
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Naming" -Value $($_.Roles.Contains("NamingRole"))
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Schema" -Value $($_.Roles.Contains("SchemaRole"))
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Infra" -Value $($_.Roles.Contains("InfrastructureRole"))
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "RID" -Value $($_.Roles.Contains("RidRole"))
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "PDC" -Value $($_.Roles.Contains("PdcRole"))
|
|||
|
}
|
|||
|
Else
|
|||
|
{
|
|||
|
|
|||
|
"Naming", "Schema", "Infra", "RID", "PDC" | ForEach-Object {
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name $_ -Value $false
|
|||
|
}
|
|||
|
}
|
|||
|
$DCSMBObj = [ADRecon.PingCastleScannersSMBScanner]::GetPSObject($_.IPAddress)
|
|||
|
ForEach ($Property in $DCSMBObj.psobject.Properties)
|
|||
|
{
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name $Property.Name -Value $Property.value
|
|||
|
}
|
|||
|
$DCObj += $Obj
|
|||
|
}
|
|||
|
Remove-Variable ADDomain
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
If ($DCObj)
|
|||
|
{
|
|||
|
Return $DCObj
|
|||
|
}
|
|||
|
Else
|
|||
|
{
|
|||
|
Return $null
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
Function Get-ADRUser
|
|||
|
{
|
|||
|
<#
|
|||
|
.SYNOPSIS
|
|||
|
Returns all users in the current (or specified) domain.
|
|||
|
|
|||
|
.DESCRIPTION
|
|||
|
Returns all users in the current (or specified) domain.
|
|||
|
|
|||
|
.PARAMETER Protocol
|
|||
|
[string]
|
|||
|
Which protocol to use; ADWS (default) or LDAP.
|
|||
|
|
|||
|
.PARAMETER date
|
|||
|
[DateTime]
|
|||
|
Date when ADRecon was executed.
|
|||
|
|
|||
|
.PARAMETER objDomain
|
|||
|
[DirectoryServices.DirectoryEntry]
|
|||
|
Domain Directory Entry object.
|
|||
|
|
|||
|
.PARAMETER DormantTimeSpan
|
|||
|
[int]
|
|||
|
Timespan for Dormant accounts. Default 90 days.
|
|||
|
|
|||
|
.PARAMETER PageSize
|
|||
|
[int]
|
|||
|
The PageSize to set for the LDAP searcher object. Default 200.
|
|||
|
|
|||
|
.PARAMETER Threads
|
|||
|
[int]
|
|||
|
The number of threads to use during processing of objects. Default 10.
|
|||
|
|
|||
|
.OUTPUTS
|
|||
|
PSObject.
|
|||
|
#>
|
|||
|
param(
|
|||
|
[Parameter(Mandatory = $true)]
|
|||
|
[string] $Protocol,
|
|||
|
|
|||
|
[Parameter(Mandatory = $true)]
|
|||
|
[DateTime] $date,
|
|||
|
|
|||
|
[Parameter(Mandatory = $false)]
|
|||
|
[DirectoryServices.DirectoryEntry] $objDomain,
|
|||
|
|
|||
|
[Parameter(Mandatory = $true)]
|
|||
|
[int] $DormantTimeSpan = 90,
|
|||
|
|
|||
|
[Parameter(Mandatory = $true)]
|
|||
|
[int] $PageSize,
|
|||
|
|
|||
|
[Parameter(Mandatory = $false)]
|
|||
|
[int] $Threads = 10
|
|||
|
)
|
|||
|
|
|||
|
If ($Protocol -eq 'ADWS')
|
|||
|
{
|
|||
|
Try
|
|||
|
{
|
|||
|
$ADUsers = @( Get-ADUser -Filter * -ResultPageSize $PageSize -Properties AccountExpirationDate,accountExpires,AccountNotDelegated,AdminCount,AllowReversiblePasswordEncryption,c,CannotChangePassword,CanonicalName,Company,Department,Description,DistinguishedName,DoesNotRequirePreAuth,Enabled,givenName,homeDirectory,Info,LastLogonDate,lastLogonTimestamp,LockedOut,LogonWorkstations,mail,Manager,middleName,mobile,'msDS-AllowedToDelegateTo','msDS-SupportedEncryptionTypes',Name,PasswordExpired,PasswordLastSet,PasswordNeverExpires,PasswordNotRequired,primaryGroupID,profilePath,pwdlastset,SamAccountName,ScriptPath,SID,SIDHistory,SmartcardLogonRequired,sn,Title,TrustedForDelegation,TrustedToAuthForDelegation,UseDESKeyOnly,UserAccountControl,whenChanged,whenCreated )
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Warning "[Get-ADRUser] Error while enumerating User Objects"
|
|||
|
Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
Return $null
|
|||
|
}
|
|||
|
|
|||
|
If ($ADUsers)
|
|||
|
{
|
|||
|
Try
|
|||
|
{
|
|||
|
$ADpasspolicy = Get-ADDefaultDomainPasswordPolicy
|
|||
|
$PassMaxAge = $ADpasspolicy.MaxPasswordAge.days
|
|||
|
Remove-Variable ADpasspolicy
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Warning "[Get-ADRUser] Error retrieving Max Password Age from the Default Password Policy. Using value as 90 days"
|
|||
|
Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
$PassMaxAge = 90
|
|||
|
}
|
|||
|
|
|||
|
Write-Verbose "[*] Total Users: $([ADRecon.ADWSClass]::ObjectCount($ADUsers))"
|
|||
|
$UserObj = [ADRecon.ADWSClass]::UserParser($ADUsers, $date, $DormantTimeSpan, $PassMaxAge, $Threads)
|
|||
|
Remove-Variable ADUsers
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
If ($Protocol -eq 'LDAP')
|
|||
|
{
|
|||
|
$objSearcher = New-Object System.DirectoryServices.DirectorySearcher $objDomain
|
|||
|
$ObjSearcher.PageSize = $PageSize
|
|||
|
$ObjSearcher.Filter = "(samAccountType=805306368)"
|
|||
|
# https://msdn.microsoft.com/en-us/library/system.directoryservices.securitymasks(v=vs.110).aspx
|
|||
|
$ObjSearcher.SecurityMasks = [System.DirectoryServices.SecurityMasks]'Dacl'
|
|||
|
$ObjSearcher.PropertiesToLoad.AddRange(("accountExpires","admincount","c","canonicalname","company","department","description","distinguishedname","givenName","homedirectory","info","lastLogontimestamp","mail","manager","middleName","mobile","msDS-AllowedToDelegateTo","msDS-SupportedEncryptionTypes","name","ntsecuritydescriptor","objectsid","primarygroupid","profilepath","pwdLastSet","samaccountName","scriptpath","sidhistory","sn","title","useraccountcontrol","userworkstations","whenchanged","whencreated"))
|
|||
|
$ObjSearcher.SearchScope = "Subtree"
|
|||
|
Try
|
|||
|
{
|
|||
|
$ADUsers = $ObjSearcher.FindAll()
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Warning "[Get-ADRUser] Error while enumerating User Objects"
|
|||
|
Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
Return $null
|
|||
|
}
|
|||
|
$ObjSearcher.dispose()
|
|||
|
|
|||
|
If ($ADUsers)
|
|||
|
{
|
|||
|
$PassMaxAge = $($ObjDomain.ConvertLargeIntegerToInt64($ObjDomain.maxpwdage.value) /-864000000000)
|
|||
|
If (-Not $PassMaxAge)
|
|||
|
{
|
|||
|
Write-Warning "[Get-ADRUser] Error retrieving Max Password Age from the Default Password Policy. Using value as 90 days"
|
|||
|
Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
$PassMaxAge = 90
|
|||
|
}
|
|||
|
|
|||
|
Write-Verbose "[*] Total Users: $([ADRecon.LDAPClass]::ObjectCount($ADUsers))"
|
|||
|
$UserObj = [ADRecon.LDAPClass]::UserParser($ADUsers, $date, $DormantTimeSpan, $PassMaxAge, $Threads)
|
|||
|
Remove-Variable ADUsers
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
If ($UserObj)
|
|||
|
{
|
|||
|
Return $UserObj
|
|||
|
}
|
|||
|
Else
|
|||
|
{
|
|||
|
Return $null
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
Function Get-ADRUserSPN
|
|||
|
{
|
|||
|
<#
|
|||
|
.SYNOPSIS
|
|||
|
Returns all user service principal name (SPN) in the current (or specified) domain.
|
|||
|
|
|||
|
.DESCRIPTION
|
|||
|
Returns all user service principal name (SPN) in the current (or specified) domain.
|
|||
|
|
|||
|
.PARAMETER Protocol
|
|||
|
[string]
|
|||
|
Which protocol to use; ADWS (default) or LDAP.
|
|||
|
|
|||
|
.PARAMETER objDomain
|
|||
|
[DirectoryServices.DirectoryEntry]
|
|||
|
Domain Directory Entry object.
|
|||
|
|
|||
|
.PARAMETER PageSize
|
|||
|
[int]
|
|||
|
The PageSize to set for the LDAP searcher object. Default 200.
|
|||
|
|
|||
|
.PARAMETER Threads
|
|||
|
[int]
|
|||
|
The number of threads to use during processing of objects. Default 10.
|
|||
|
|
|||
|
.OUTPUTS
|
|||
|
PSObject.
|
|||
|
#>
|
|||
|
param(
|
|||
|
[Parameter(Mandatory = $true)]
|
|||
|
[string] $Protocol,
|
|||
|
|
|||
|
[Parameter(Mandatory = $false)]
|
|||
|
[DirectoryServices.DirectoryEntry] $objDomain,
|
|||
|
|
|||
|
[Parameter(Mandatory = $true)]
|
|||
|
[int] $PageSize,
|
|||
|
|
|||
|
[Parameter(Mandatory = $false)]
|
|||
|
[int] $Threads = 10
|
|||
|
)
|
|||
|
|
|||
|
If ($Protocol -eq 'ADWS')
|
|||
|
{
|
|||
|
Try
|
|||
|
{
|
|||
|
$ADUsers = @( Get-ADObject -LDAPFilter "(&(samAccountType=805306368)(servicePrincipalName=*))" -Properties Name,Description,memberOf,sAMAccountName,servicePrincipalName,primaryGroupID,pwdLastSet,userAccountControl -ResultPageSize $PageSize )
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Warning "[Get-ADRUserSPN] Error while enumerating UserSPN Objects"
|
|||
|
Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
Return $null
|
|||
|
}
|
|||
|
|
|||
|
If ($ADUsers)
|
|||
|
{
|
|||
|
Write-Verbose "[*] Total UserSPNs: $([ADRecon.ADWSClass]::ObjectCount($ADUsers))"
|
|||
|
$UserSPNObj = [ADRecon.ADWSClass]::UserSPNParser($ADUsers, $Threads)
|
|||
|
Remove-Variable ADUsers
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
If ($Protocol -eq 'LDAP')
|
|||
|
{
|
|||
|
$objSearcher = New-Object System.DirectoryServices.DirectorySearcher $objDomain
|
|||
|
$ObjSearcher.PageSize = $PageSize
|
|||
|
$ObjSearcher.Filter = "(&(samAccountType=805306368)(servicePrincipalName=*))"
|
|||
|
$ObjSearcher.PropertiesToLoad.AddRange(("name","description","memberof","samaccountname","serviceprincipalname","primarygroupid","pwdlastset","useraccountcontrol"))
|
|||
|
$ObjSearcher.SearchScope = "Subtree"
|
|||
|
Try
|
|||
|
{
|
|||
|
$ADUsers = $ObjSearcher.FindAll()
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Warning "[Get-ADRUserSPN] Error while enumerating UserSPN Objects"
|
|||
|
Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
Return $null
|
|||
|
}
|
|||
|
$ObjSearcher.dispose()
|
|||
|
|
|||
|
If ($ADUsers)
|
|||
|
{
|
|||
|
Write-Verbose "[*] Total UserSPNs: $([ADRecon.LDAPClass]::ObjectCount($ADUsers))"
|
|||
|
$UserSPNObj = [ADRecon.LDAPClass]::UserSPNParser($ADUsers, $Threads)
|
|||
|
Remove-Variable ADUsers
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
If ($UserSPNObj)
|
|||
|
{
|
|||
|
Return $UserSPNObj
|
|||
|
}
|
|||
|
Else
|
|||
|
{
|
|||
|
Return $null
|
|||
|
}
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
#TODO
|
|||
|
Function Get-ADRPasswordAttributes
|
|||
|
{
|
|||
|
<#
|
|||
|
.SYNOPSIS
|
|||
|
Returns all objects with plaintext passwords in the current (or specified) domain.
|
|||
|
|
|||
|
.DESCRIPTION
|
|||
|
Returns all objects with plaintext passwords in the current (or specified) domain.
|
|||
|
|
|||
|
.PARAMETER Protocol
|
|||
|
[string]
|
|||
|
Which protocol to use; ADWS (default) or LDAP.
|
|||
|
|
|||
|
.PARAMETER objDomain
|
|||
|
[DirectoryServices.DirectoryEntry]
|
|||
|
Domain Directory Entry object.
|
|||
|
|
|||
|
.PARAMETER PageSize
|
|||
|
[int]
|
|||
|
The PageSize to set for the LDAP searcher object. Default 200.
|
|||
|
|
|||
|
.OUTPUTS
|
|||
|
PSObject.
|
|||
|
|
|||
|
.LINK
|
|||
|
https://www.ibm.com/support/knowledgecenter/en/ssw_aix_71/com.ibm.aix.security/ad_password_attribute_selection.htm
|
|||
|
https://msdn.microsoft.com/en-us/library/cc223248.aspx
|
|||
|
https://msdn.microsoft.com/en-us/library/cc223249.aspx
|
|||
|
#>
|
|||
|
param(
|
|||
|
[Parameter(Mandatory = $true)]
|
|||
|
[string] $Protocol,
|
|||
|
|
|||
|
[Parameter(Mandatory = $false)]
|
|||
|
[DirectoryServices.DirectoryEntry] $objDomain,
|
|||
|
|
|||
|
[Parameter(Mandatory = $true)]
|
|||
|
[int] $PageSize
|
|||
|
)
|
|||
|
|
|||
|
If ($Protocol -eq 'ADWS')
|
|||
|
{
|
|||
|
Try
|
|||
|
{
|
|||
|
$ADUsers = Get-ADObject -LDAPFilter '(|(UserPassword=*)(UnixUserPassword=*)(unicodePwd=*)(msSFU30Password=*))' -ResultPageSize $PageSize -Properties *
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Warning "[Get-ADRPasswordAttributes] Error while enumerating Password Attributes"
|
|||
|
Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
Return $null
|
|||
|
}
|
|||
|
|
|||
|
If ($ADUsers)
|
|||
|
{
|
|||
|
Write-Warning "[*] Total PasswordAttribute Objects: $([ADRecon.ADWSClass]::ObjectCount($ADUsers))"
|
|||
|
$UserObj = $ADUsers
|
|||
|
Remove-Variable ADUsers
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
If ($Protocol -eq 'LDAP')
|
|||
|
{
|
|||
|
$objSearcher = New-Object System.DirectoryServices.DirectorySearcher $objDomain
|
|||
|
$ObjSearcher.PageSize = $PageSize
|
|||
|
$ObjSearcher.Filter = "(|(UserPassword=*)(UnixUserPassword=*)(unicodePwd=*)(msSFU30Password=*))"
|
|||
|
$ObjSearcher.SearchScope = "Subtree"
|
|||
|
Try
|
|||
|
{
|
|||
|
$ADUsers = $ObjSearcher.FindAll()
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Warning "[Get-ADRPasswordAttributes] Error while enumerating Password Attributes"
|
|||
|
Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
Return $null
|
|||
|
}
|
|||
|
$ObjSearcher.dispose()
|
|||
|
|
|||
|
If ($ADUsers)
|
|||
|
{
|
|||
|
$cnt = [ADRecon.LDAPClass]::ObjectCount($ADUsers)
|
|||
|
If ($cnt -gt 0)
|
|||
|
{
|
|||
|
Write-Warning "[*] Total PasswordAttribute Objects: $cnt"
|
|||
|
}
|
|||
|
$UserObj = $ADUsers
|
|||
|
Remove-Variable ADUsers
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
If ($UserObj)
|
|||
|
{
|
|||
|
Return $UserObj
|
|||
|
}
|
|||
|
Else
|
|||
|
{
|
|||
|
Return $null
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
Function Get-ADRGroup
|
|||
|
{
|
|||
|
<#
|
|||
|
.SYNOPSIS
|
|||
|
Returns all groups in the current (or specified) domain.
|
|||
|
|
|||
|
.DESCRIPTION
|
|||
|
Returns all groups in the current (or specified) domain.
|
|||
|
|
|||
|
.PARAMETER Protocol
|
|||
|
[string]
|
|||
|
Which protocol to use; ADWS (default) or LDAP.
|
|||
|
|
|||
|
.PARAMETER objDomain
|
|||
|
[DirectoryServices.DirectoryEntry]
|
|||
|
Domain Directory Entry object.
|
|||
|
|
|||
|
.PARAMETER PageSize
|
|||
|
[int]
|
|||
|
The PageSize to set for the LDAP searcher object. Default 200.
|
|||
|
|
|||
|
.PARAMETER Threads
|
|||
|
[int]
|
|||
|
The number of threads to use during processing of objects. Default 10.
|
|||
|
|
|||
|
.OUTPUTS
|
|||
|
PSObject.
|
|||
|
#>
|
|||
|
param(
|
|||
|
[Parameter(Mandatory = $true)]
|
|||
|
[string] $Protocol,
|
|||
|
|
|||
|
[Parameter(Mandatory = $false)]
|
|||
|
[DirectoryServices.DirectoryEntry] $objDomain,
|
|||
|
|
|||
|
[Parameter(Mandatory = $true)]
|
|||
|
[int] $PageSize,
|
|||
|
|
|||
|
[Parameter(Mandatory = $false)]
|
|||
|
[int] $Threads = 10
|
|||
|
)
|
|||
|
|
|||
|
If ($Protocol -eq 'ADWS')
|
|||
|
{
|
|||
|
Try
|
|||
|
{
|
|||
|
$ADGroups = @( Get-ADGroup -Filter * -ResultPageSize $PageSize -Properties AdminCount,CanonicalName,DistinguishedName,Description,GroupCategory,GroupScope,SamAccountName,SID,SIDHistory,managedBy,whenChanged,whenCreated )
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Warning "[Get-ADRGroup] Error while enumerating Group Objects"
|
|||
|
Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
Return $null
|
|||
|
}
|
|||
|
|
|||
|
If ($ADGroups)
|
|||
|
{
|
|||
|
Write-Verbose "[*] Total Groups: $([ADRecon.ADWSClass]::ObjectCount($ADGroups))"
|
|||
|
$GroupObj = [ADRecon.ADWSClass]::GroupParser($ADGroups, $Threads)
|
|||
|
Remove-Variable ADGroups
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
If ($Protocol -eq 'LDAP')
|
|||
|
{
|
|||
|
$objSearcher = New-Object System.DirectoryServices.DirectorySearcher $objDomain
|
|||
|
$ObjSearcher.PageSize = $PageSize
|
|||
|
$ObjSearcher.Filter = "(objectClass=group)"
|
|||
|
$ObjSearcher.PropertiesToLoad.AddRange(("admincount","canonicalname", "distinguishedname", "description", "grouptype","samaccountname", "sidhistory", "managedby", "objectsid", "whencreated", "whenchanged"))
|
|||
|
$ObjSearcher.SearchScope = "Subtree"
|
|||
|
|
|||
|
Try
|
|||
|
{
|
|||
|
$ADGroups = $ObjSearcher.FindAll()
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Warning "[Get-ADRGroup] Error while enumerating Group Objects"
|
|||
|
Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
Return $null
|
|||
|
}
|
|||
|
$ObjSearcher.dispose()
|
|||
|
|
|||
|
If ($ADGroups)
|
|||
|
{
|
|||
|
Write-Verbose "[*] Total Groups: $([ADRecon.LDAPClass]::ObjectCount($ADGroups))"
|
|||
|
$GroupObj = [ADRecon.LDAPClass]::GroupParser($ADGroups, $Threads)
|
|||
|
Remove-Variable ADGroups
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
If ($GroupObj)
|
|||
|
{
|
|||
|
Return $GroupObj
|
|||
|
}
|
|||
|
Else
|
|||
|
{
|
|||
|
Return $null
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
Function Get-ADRGroupMember
|
|||
|
{
|
|||
|
<#
|
|||
|
.SYNOPSIS
|
|||
|
Returns all groups and their members in the current (or specified) domain.
|
|||
|
|
|||
|
.DESCRIPTION
|
|||
|
Returns all groups and their members in the current (or specified) domain.
|
|||
|
|
|||
|
.PARAMETER Protocol
|
|||
|
[string]
|
|||
|
Which protocol to use; ADWS (default) or LDAP.
|
|||
|
|
|||
|
.PARAMETER objDomain
|
|||
|
[DirectoryServices.DirectoryEntry]
|
|||
|
Domain Directory Entry object.
|
|||
|
|
|||
|
.PARAMETER PageSize
|
|||
|
[int]
|
|||
|
The PageSize to set for the LDAP searcher object. Default 200.
|
|||
|
|
|||
|
.PARAMETER Threads
|
|||
|
[int]
|
|||
|
The number of threads to use during processing of objects. Default 10.
|
|||
|
|
|||
|
.OUTPUTS
|
|||
|
PSObject.
|
|||
|
#>
|
|||
|
param(
|
|||
|
[Parameter(Mandatory = $true)]
|
|||
|
[string] $Protocol,
|
|||
|
|
|||
|
[Parameter(Mandatory = $false)]
|
|||
|
[DirectoryServices.DirectoryEntry] $objDomain,
|
|||
|
|
|||
|
[Parameter(Mandatory = $true)]
|
|||
|
[int] $PageSize,
|
|||
|
|
|||
|
[Parameter(Mandatory = $false)]
|
|||
|
[int] $Threads = 10
|
|||
|
)
|
|||
|
|
|||
|
If ($Protocol -eq 'ADWS')
|
|||
|
{
|
|||
|
Try
|
|||
|
{
|
|||
|
$ADDomain = Get-ADDomain
|
|||
|
$ADDomainSID = $ADDomain.DomainSID.Value
|
|||
|
Remove-Variable ADDomain
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Warning "[Get-ADRGroupMember] Error getting Domain Context"
|
|||
|
Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
Return $null
|
|||
|
}
|
|||
|
|
|||
|
Try
|
|||
|
{
|
|||
|
$ADGroups = $ADGroups = @( Get-ADGroup -Filter * -ResultPageSize $PageSize -Properties SamAccountName,SID )
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Warning "[Get-ADRGroupMember] Error while enumerating Group Objects"
|
|||
|
Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
}
|
|||
|
|
|||
|
Try
|
|||
|
{
|
|||
|
$ADGroupMembers = @( Get-ADObject -LDAPFilter '(|(memberof=*)(primarygroupid=*))' -Properties DistinguishedName,memberof,primaryGroupID,sAMAccountName,samaccounttype )
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Warning "[Get-ADRGroupMember] Error while enumerating GroupMember Objects"
|
|||
|
Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
Return $null
|
|||
|
}
|
|||
|
|
|||
|
If ( ($ADDomainSID) -and ($ADGroups) -and ($ADGroupMembers) )
|
|||
|
{
|
|||
|
Write-Verbose "[*] Total GroupMember Objects: $([ADRecon.ADWSClass]::ObjectCount($ADGroupMembers))"
|
|||
|
$GroupMemberObj = [ADRecon.ADWSClass]::GroupMemberParser($ADGroups, $ADGroupMembers, $ADDomainSID, $Threads)
|
|||
|
Remove-Variable ADGroups
|
|||
|
Remove-Variable ADGroupMembers
|
|||
|
Remove-Variable ADDomainSID
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
If ($Protocol -eq 'LDAP')
|
|||
|
{
|
|||
|
|
|||
|
If ($Credential -ne [Management.Automation.PSCredential]::Empty)
|
|||
|
{
|
|||
|
$DomainFQDN = Get-DNtoFQDN($objDomain.distinguishedName)
|
|||
|
$DomainContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext("Domain",$($DomainFQDN),$($Credential.UserName),$($Credential.GetNetworkCredential().password))
|
|||
|
Try
|
|||
|
{
|
|||
|
$ADDomain = [System.DirectoryServices.ActiveDirectory.Domain]::GetDomain($DomainContext)
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Warning "[Get-ADRGroupMember] Error getting Domain Context"
|
|||
|
Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
Return $null
|
|||
|
}
|
|||
|
Remove-Variable DomainContext
|
|||
|
Try
|
|||
|
{
|
|||
|
$ForestContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext("Forest",$($ADDomain.Forest),$($Credential.UserName),$($Credential.GetNetworkCredential().password))
|
|||
|
$ADForest = [System.DirectoryServices.ActiveDirectory.Forest]::GetForest($ForestContext)
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Warning "[Get-ADRGroupMember] Error getting Forest Context"
|
|||
|
Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
}
|
|||
|
If ($ForestContext)
|
|||
|
{
|
|||
|
Remove-Variable ForestContext
|
|||
|
}
|
|||
|
If ($ADForest)
|
|||
|
{
|
|||
|
$GlobalCatalog = $ADForest.FindGlobalCatalog()
|
|||
|
}
|
|||
|
If ($GlobalCatalog)
|
|||
|
{
|
|||
|
$DN = "GC://$($GlobalCatalog.IPAddress)/$($objDomain.distinguishedname)"
|
|||
|
Try
|
|||
|
{
|
|||
|
$ADObject = New-Object -TypeName System.DirectoryServices.DirectoryEntry -ArgumentList ($($DN),$($Credential.UserName),$($Credential.GetNetworkCredential().password))
|
|||
|
$ADDomainSID = New-Object System.Security.Principal.SecurityIdentifier($ADObject.objectSid[0], 0)
|
|||
|
$ADObject.Dispose()
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Warning "[Get-ADRGroupMember] Error retrieving Domain SID using the GlobalCatalog $($GlobalCatalog.IPAddress). Using SID from the ObjDomain."
|
|||
|
Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
$ADDomainSID = New-Object System.Security.Principal.SecurityIdentifier($objDomain.objectSid[0], 0)
|
|||
|
}
|
|||
|
}
|
|||
|
Else
|
|||
|
{
|
|||
|
$ADDomainSID = New-Object System.Security.Principal.SecurityIdentifier($objDomain.objectSid[0], 0)
|
|||
|
}
|
|||
|
}
|
|||
|
Else
|
|||
|
{
|
|||
|
$ADDomain = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()
|
|||
|
$ADForest = [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest()
|
|||
|
Try
|
|||
|
{
|
|||
|
$GlobalCatalog = $ADForest.FindGlobalCatalog()
|
|||
|
$DN = "GC://$($GlobalCatalog)/$($objDomain.distinguishedname)"
|
|||
|
$ADObject = New-Object -TypeName System.DirectoryServices.DirectoryEntry -ArgumentList ($DN)
|
|||
|
$ADDomainSID = New-Object System.Security.Principal.SecurityIdentifier($ADObject.objectSid[0], 0)
|
|||
|
$ADObject.dispose()
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Warning "[Get-ADRGroupMember] Error retrieving Domain SID using the GlobalCatalog $($GlobalCatalog.IPAddress). Using SID from the ObjDomain."
|
|||
|
Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
$ADDomainSID = New-Object System.Security.Principal.SecurityIdentifier($objDomain.objectSid[0], 0)
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
$objSearcher = New-Object System.DirectoryServices.DirectorySearcher $objDomain
|
|||
|
$ObjSearcher.PageSize = $PageSize
|
|||
|
$ObjSearcher.Filter = "(objectClass=group)"
|
|||
|
$ObjSearcher.PropertiesToLoad.AddRange(("samaccountname", "objectsid"))
|
|||
|
$ObjSearcher.SearchScope = "Subtree"
|
|||
|
|
|||
|
Try
|
|||
|
{
|
|||
|
$ADGroups = $ObjSearcher.FindAll()
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Warning "[Get-ADRGroupMember] Error while enumerating Group Objects"
|
|||
|
Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
Return $null
|
|||
|
}
|
|||
|
$ObjSearcher.dispose()
|
|||
|
|
|||
|
$objSearcher = New-Object System.DirectoryServices.DirectorySearcher $objDomain
|
|||
|
$ObjSearcher.PageSize = $PageSize
|
|||
|
$ObjSearcher.Filter = "(|(memberof=*)(primarygroupid=*))"
|
|||
|
$ObjSearcher.PropertiesToLoad.AddRange(("distinguishedname", "dnshostname", "primarygroupid", "memberof", "samaccountname", "samaccounttype"))
|
|||
|
$ObjSearcher.SearchScope = "Subtree"
|
|||
|
|
|||
|
Try
|
|||
|
{
|
|||
|
$ADGroupMembers = $ObjSearcher.FindAll()
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Warning "[Get-ADRGroupMember] Error while enumerating GroupMember Objects"
|
|||
|
Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
Return $null
|
|||
|
}
|
|||
|
$ObjSearcher.dispose()
|
|||
|
|
|||
|
If ( ($ADDomainSID) -and ($ADGroups) -and ($ADGroupMembers) )
|
|||
|
{
|
|||
|
Write-Verbose "[*] Total GroupMember Objects: $([ADRecon.LDAPClass]::ObjectCount($ADGroupMembers))"
|
|||
|
$GroupMemberObj = [ADRecon.LDAPClass]::GroupMemberParser($ADGroups, $ADGroupMembers, $ADDomainSID, $Threads)
|
|||
|
Remove-Variable ADGroups
|
|||
|
Remove-Variable ADGroupMembers
|
|||
|
Remove-Variable ADDomainSID
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
If ($GroupMemberObj)
|
|||
|
{
|
|||
|
Return $GroupMemberObj
|
|||
|
}
|
|||
|
Else
|
|||
|
{
|
|||
|
Return $null
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
Function Get-ADROU
|
|||
|
{
|
|||
|
<#
|
|||
|
.SYNOPSIS
|
|||
|
Returns all Organizational Units (OU) in the current (or specified) domain.
|
|||
|
|
|||
|
.DESCRIPTION
|
|||
|
Returns all Organizational Units (OU) in the current (or specified) domain.
|
|||
|
|
|||
|
.PARAMETER Protocol
|
|||
|
[string]
|
|||
|
Which protocol to use; ADWS (default) or LDAP.
|
|||
|
|
|||
|
.PARAMETER objDomain
|
|||
|
[DirectoryServices.DirectoryEntry]
|
|||
|
Domain Directory Entry object.
|
|||
|
|
|||
|
.PARAMETER PageSize
|
|||
|
[int]
|
|||
|
The PageSize to set for the LDAP searcher object. Default 200.
|
|||
|
|
|||
|
.PARAMETER Threads
|
|||
|
[int]
|
|||
|
The number of threads to use during processing of objects. Default 10.
|
|||
|
|
|||
|
.OUTPUTS
|
|||
|
PSObject.
|
|||
|
#>
|
|||
|
param(
|
|||
|
[Parameter(Mandatory = $true)]
|
|||
|
[string] $Protocol,
|
|||
|
|
|||
|
[Parameter(Mandatory = $false)]
|
|||
|
[DirectoryServices.DirectoryEntry] $objDomain,
|
|||
|
|
|||
|
[Parameter(Mandatory = $true)]
|
|||
|
[int] $PageSize,
|
|||
|
|
|||
|
[Parameter(Mandatory = $false)]
|
|||
|
[int] $Threads = 10
|
|||
|
)
|
|||
|
|
|||
|
If ($Protocol -eq 'ADWS')
|
|||
|
{
|
|||
|
Try
|
|||
|
{
|
|||
|
$ADOUs = @( Get-ADOrganizationalUnit -Filter * -Properties DistinguishedName,Description,Name,whenCreated,whenChanged )
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Warning "[Get-ADROU] Error while enumerating OU Objects"
|
|||
|
Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
Return $null
|
|||
|
}
|
|||
|
|
|||
|
If ($ADOUs)
|
|||
|
{
|
|||
|
Write-Verbose "[*] Total OUs: $([ADRecon.ADWSClass]::ObjectCount($ADOUs))"
|
|||
|
$OUObj = [ADRecon.ADWSClass]::OUParser($ADOUs, $Threads)
|
|||
|
Remove-Variable ADOUs
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
If ($Protocol -eq 'LDAP')
|
|||
|
{
|
|||
|
$objSearcher = New-Object System.DirectoryServices.DirectorySearcher $objDomain
|
|||
|
$ObjSearcher.PageSize = $PageSize
|
|||
|
$ObjSearcher.Filter = "(objectclass=organizationalunit)"
|
|||
|
$ObjSearcher.PropertiesToLoad.AddRange(("distinguishedname","description","name","whencreated","whenchanged"))
|
|||
|
$ObjSearcher.SearchScope = "Subtree"
|
|||
|
|
|||
|
Try
|
|||
|
{
|
|||
|
$ADOUs = $ObjSearcher.FindAll()
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Warning "[Get-ADROU] Error while enumerating OU Objects"
|
|||
|
Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
Return $null
|
|||
|
}
|
|||
|
$ObjSearcher.dispose()
|
|||
|
|
|||
|
If ($ADOUs)
|
|||
|
{
|
|||
|
Write-Verbose "[*] Total OUs: $([ADRecon.LDAPClass]::ObjectCount($ADOUs))"
|
|||
|
$OUObj = [ADRecon.LDAPClass]::OUParser($ADOUs, $Threads)
|
|||
|
Remove-Variable ADOUs
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
If ($OUObj)
|
|||
|
{
|
|||
|
Return $OUObj
|
|||
|
}
|
|||
|
Else
|
|||
|
{
|
|||
|
Return $null
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
Function Get-ADRGPO
|
|||
|
{
|
|||
|
<#
|
|||
|
.SYNOPSIS
|
|||
|
Returns all Group Policy Objects (GPO) in the current (or specified) domain.
|
|||
|
|
|||
|
.DESCRIPTION
|
|||
|
Returns all Group Policy Objects (GPO) in the current (or specified) domain.
|
|||
|
|
|||
|
.PARAMETER Protocol
|
|||
|
[string]
|
|||
|
Which protocol to use; ADWS (default) or LDAP.
|
|||
|
|
|||
|
.PARAMETER objDomain
|
|||
|
[DirectoryServices.DirectoryEntry]
|
|||
|
Domain Directory Entry object.
|
|||
|
|
|||
|
.PARAMETER Credential
|
|||
|
[Management.Automation.PSCredential]
|
|||
|
Credentials.
|
|||
|
|
|||
|
.PARAMETER PageSize
|
|||
|
[int]
|
|||
|
The PageSize to set for the LDAP searcher object. Default 200.
|
|||
|
|
|||
|
.PARAMETER Threads
|
|||
|
[int]
|
|||
|
The number of threads to use during processing of objects. Default 10.
|
|||
|
|
|||
|
.OUTPUTS
|
|||
|
PSObject.
|
|||
|
#>
|
|||
|
param(
|
|||
|
[Parameter(Mandatory = $true)]
|
|||
|
[string] $Protocol,
|
|||
|
|
|||
|
[Parameter(Mandatory = $false)]
|
|||
|
[DirectoryServices.DirectoryEntry] $objDomain,
|
|||
|
|
|||
|
[Parameter(Mandatory = $true)]
|
|||
|
[int] $PageSize,
|
|||
|
|
|||
|
[Parameter(Mandatory = $false)]
|
|||
|
[int] $Threads = 10
|
|||
|
)
|
|||
|
|
|||
|
If ($Protocol -eq 'ADWS')
|
|||
|
{
|
|||
|
Try
|
|||
|
{
|
|||
|
$ADGPOs = @( Get-ADObject -LDAPFilter '(objectCategory=groupPolicyContainer)' -Properties DisplayName,DistinguishedName,Name,gPCFileSysPath,whenCreated,whenChanged )
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Warning "[Get-ADRGPO] Error while enumerating groupPolicyContainer Objects"
|
|||
|
Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
Return $null
|
|||
|
}
|
|||
|
|
|||
|
If ($ADGPOs)
|
|||
|
{
|
|||
|
Write-Verbose "[*] Total GPOs: $([ADRecon.ADWSClass]::ObjectCount($ADGPOs))"
|
|||
|
$GPOsObj = [ADRecon.ADWSClass]::GPOParser($ADGPOs, $Threads)
|
|||
|
Remove-Variable ADGPOs
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
If ($Protocol -eq 'LDAP')
|
|||
|
{
|
|||
|
$objSearcher = New-Object System.DirectoryServices.DirectorySearcher $objDomain
|
|||
|
$ObjSearcher.PageSize = $PageSize
|
|||
|
$ObjSearcher.Filter = "(objectCategory=groupPolicyContainer)"
|
|||
|
$ObjSearcher.SearchScope = "Subtree"
|
|||
|
|
|||
|
Try
|
|||
|
{
|
|||
|
$ADGPOs = $ObjSearcher.FindAll()
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Warning "[Get-ADRGPO] Error while enumerating groupPolicyContainer Objects"
|
|||
|
Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
Return $null
|
|||
|
}
|
|||
|
$ObjSearcher.dispose()
|
|||
|
|
|||
|
If ($ADGPOs)
|
|||
|
{
|
|||
|
Write-Verbose "[*] Total GPOs: $([ADRecon.LDAPClass]::ObjectCount($ADGPOs))"
|
|||
|
$GPOsObj = [ADRecon.LDAPClass]::GPOParser($ADGPOs, $Threads)
|
|||
|
Remove-Variable ADGPOs
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
If ($GPOsObj)
|
|||
|
{
|
|||
|
Return $GPOsObj
|
|||
|
}
|
|||
|
Else
|
|||
|
{
|
|||
|
Return $null
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
# based on https://github.com/GoateePFE/GPLinkReport/blob/master/gPLinkReport.ps1
|
|||
|
Function Get-ADRGPLink
|
|||
|
{
|
|||
|
<#
|
|||
|
.SYNOPSIS
|
|||
|
Returns all group policy links (gPLink) applied to Scope of Management (SOM) in the current (or specified) domain.
|
|||
|
|
|||
|
.DESCRIPTION
|
|||
|
Returns all group policy links (gPLink) applied to Scope of Management (SOM) in the current (or specified) domain.
|
|||
|
|
|||
|
.PARAMETER Protocol
|
|||
|
[string]
|
|||
|
Which protocol to use; ADWS (default) or LDAP.
|
|||
|
|
|||
|
.PARAMETER objDomain
|
|||
|
[DirectoryServices.DirectoryEntry]
|
|||
|
Domain Directory Entry object.
|
|||
|
|
|||
|
.PARAMETER PageSize
|
|||
|
[int]
|
|||
|
The PageSize to set for the LDAP searcher object. Default 200.
|
|||
|
|
|||
|
.PARAMETER Threads
|
|||
|
[int]
|
|||
|
The number of threads to use during processing of objects. Default 10.
|
|||
|
|
|||
|
.OUTPUTS
|
|||
|
PSObject.
|
|||
|
#>
|
|||
|
param(
|
|||
|
[Parameter(Mandatory = $true)]
|
|||
|
[string] $Protocol,
|
|||
|
|
|||
|
[Parameter(Mandatory = $false)]
|
|||
|
[DirectoryServices.DirectoryEntry] $objDomain,
|
|||
|
|
|||
|
[Parameter(Mandatory = $true)]
|
|||
|
[int] $PageSize,
|
|||
|
|
|||
|
[Parameter(Mandatory = $false)]
|
|||
|
[int] $Threads = 10
|
|||
|
)
|
|||
|
|
|||
|
If ($Protocol -eq 'ADWS')
|
|||
|
{
|
|||
|
Try
|
|||
|
{
|
|||
|
$ADSOMs = @( Get-ADObject -LDAPFilter '(|(objectclass=domain)(objectclass=organizationalUnit))' -Properties DistinguishedName,Name,gPLink,gPOptions )
|
|||
|
$ADSOMs += @( Get-ADObject -SearchBase "CN=Sites,$((Get-ADRootDSE).configurationNamingContext)" -LDAPFilter "(objectclass=site)" -Properties DistinguishedName,Name,gPLink,gPOptions )
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Warning "[Get-ADRGPLink] Error while enumerating SOM Objects"
|
|||
|
Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
Return $null
|
|||
|
}
|
|||
|
|
|||
|
Try
|
|||
|
{
|
|||
|
$ADGPOs = @( Get-ADObject -LDAPFilter '(objectCategory=groupPolicyContainer)' -Properties DisplayName,DistinguishedName )
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Warning "[Get-ADRGPLink] Error while enumerating groupPolicyContainer Objects"
|
|||
|
Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
Return $null
|
|||
|
}
|
|||
|
|
|||
|
If ( ($ADSOMs) -and ($ADGPOs) )
|
|||
|
{
|
|||
|
Write-Verbose "[*] Total SOMs: $([ADRecon.ADWSClass]::ObjectCount($ADSOMs))"
|
|||
|
$SOMObj = [ADRecon.ADWSClass]::SOMParser($ADGPOs, $ADSOMs, $Threads)
|
|||
|
Remove-Variable ADSOMs
|
|||
|
Remove-Variable ADGPOs
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
If ($Protocol -eq 'LDAP')
|
|||
|
{
|
|||
|
$ADSOMs = @()
|
|||
|
$objSearcher = New-Object System.DirectoryServices.DirectorySearcher $objDomain
|
|||
|
$ObjSearcher.PageSize = $PageSize
|
|||
|
$ObjSearcher.Filter = "(|(objectclass=domain)(objectclass=organizationalUnit))"
|
|||
|
$ObjSearcher.PropertiesToLoad.AddRange(("distinguishedname","name","gplink","gpoptions"))
|
|||
|
$ObjSearcher.SearchScope = "Subtree"
|
|||
|
|
|||
|
Try
|
|||
|
{
|
|||
|
$ADSOMs += $ObjSearcher.FindAll()
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Warning "[Get-ADRGPLink] Error while enumerating SOM Objects"
|
|||
|
Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
Return $null
|
|||
|
}
|
|||
|
$ObjSearcher.dispose()
|
|||
|
|
|||
|
$SearchPath = "CN=Sites"
|
|||
|
If ($Credential -ne [Management.Automation.PSCredential]::Empty)
|
|||
|
{
|
|||
|
$objSearchPath = New-Object System.DirectoryServices.DirectoryEntry "LDAP://$($DomainController)/$SearchPath,$($objDomainRootDSE.ConfigurationNamingContext)", $Credential.UserName,$Credential.GetNetworkCredential().Password
|
|||
|
}
|
|||
|
Else
|
|||
|
{
|
|||
|
$objSearchPath = New-Object System.DirectoryServices.DirectoryEntry "LDAP://$SearchPath,$($objDomainRootDSE.ConfigurationNamingContext)"
|
|||
|
}
|
|||
|
$objSearcher = New-Object System.DirectoryServices.DirectorySearcher $objSearchPath
|
|||
|
$ObjSearcher.Filter = "(objectclass=site)"
|
|||
|
$ObjSearcher.PropertiesToLoad.AddRange(("distinguishedname","name","gplink","gpoptions"))
|
|||
|
$ObjSearcher.SearchScope = "Subtree"
|
|||
|
|
|||
|
Try
|
|||
|
{
|
|||
|
$ADSOMs += $ObjSearcher.FindAll()
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Warning "[Get-ADRGPLink] Error while enumerating SOM Objects"
|
|||
|
Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
Return $null
|
|||
|
}
|
|||
|
$ObjSearcher.dispose()
|
|||
|
|
|||
|
$objSearcher = New-Object System.DirectoryServices.DirectorySearcher $objDomain
|
|||
|
$ObjSearcher.PageSize = $PageSize
|
|||
|
$ObjSearcher.Filter = "(objectCategory=groupPolicyContainer)"
|
|||
|
$ObjSearcher.SearchScope = "Subtree"
|
|||
|
|
|||
|
Try
|
|||
|
{
|
|||
|
$ADGPOs = $ObjSearcher.FindAll()
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Warning "[Get-ADRGPLink] Error while enumerating groupPolicyContainer Objects"
|
|||
|
Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
Return $null
|
|||
|
}
|
|||
|
$ObjSearcher.dispose()
|
|||
|
|
|||
|
If ( ($ADSOMs) -and ($ADGPOs) )
|
|||
|
{
|
|||
|
Write-Verbose "[*] Total SOMs: $([ADRecon.LDAPClass]::ObjectCount($ADSOMs))"
|
|||
|
$SOMObj = [ADRecon.LDAPClass]::SOMParser($ADGPOs, $ADSOMs, $Threads)
|
|||
|
Remove-Variable ADSOMs
|
|||
|
Remove-Variable ADGPOs
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
If ($SOMObj)
|
|||
|
{
|
|||
|
Return $SOMObj
|
|||
|
}
|
|||
|
Else
|
|||
|
{
|
|||
|
Return $null
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
# Modified Convert-DNSRecord function from https://github.com/PowerShellMafia/PowerSploit/blob/dev/Recon/PowerView.ps1
|
|||
|
Function Convert-DNSRecord
|
|||
|
{
|
|||
|
<#
|
|||
|
.SYNOPSIS
|
|||
|
|
|||
|
Helpers that decodes a binary DNS record blob.
|
|||
|
|
|||
|
Author: Michael B. Smith, Will Schroeder (@harmj0y)
|
|||
|
License: BSD 3-Clause
|
|||
|
Required Dependencies: None
|
|||
|
|
|||
|
.DESCRIPTION
|
|||
|
|
|||
|
Decodes a binary blob representing an Active Directory DNS entry.
|
|||
|
Used by Get-DomainDNSRecord.
|
|||
|
|
|||
|
Adapted/ported from Michael B. Smith's code at https://raw.githubusercontent.com/mmessano/PowerShell/master/dns-dump.ps1
|
|||
|
|
|||
|
.PARAMETER DNSRecord
|
|||
|
|
|||
|
A byte array representing the DNS record.
|
|||
|
|
|||
|
.OUTPUTS
|
|||
|
|
|||
|
System.Management.Automation.PSCustomObject
|
|||
|
|
|||
|
Outputs custom PSObjects with detailed information about the DNS record entry.
|
|||
|
|
|||
|
.LINK
|
|||
|
|
|||
|
https://raw.githubusercontent.com/mmessano/PowerShell/master/dns-dump.ps1
|
|||
|
#>
|
|||
|
|
|||
|
[OutputType('System.Management.Automation.PSCustomObject')]
|
|||
|
[CmdletBinding()]
|
|||
|
Param(
|
|||
|
[Parameter(Position = 0, Mandatory = $True, ValueFromPipelineByPropertyName = $True)]
|
|||
|
[Byte[]]
|
|||
|
$DNSRecord
|
|||
|
)
|
|||
|
|
|||
|
BEGIN {
|
|||
|
Function Get-Name
|
|||
|
{
|
|||
|
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseOutputTypeCorrectly', '')]
|
|||
|
[CmdletBinding()]
|
|||
|
Param(
|
|||
|
[Byte[]]
|
|||
|
$Raw
|
|||
|
)
|
|||
|
|
|||
|
[Int]$Length = $Raw[0]
|
|||
|
[Int]$Segments = $Raw[1]
|
|||
|
[Int]$Index = 2
|
|||
|
[String]$Name = ''
|
|||
|
|
|||
|
while ($Segments-- -gt 0)
|
|||
|
{
|
|||
|
[Int]$SegmentLength = $Raw[$Index++]
|
|||
|
while ($SegmentLength-- -gt 0)
|
|||
|
{
|
|||
|
$Name += [Char]$Raw[$Index++]
|
|||
|
}
|
|||
|
$Name += "."
|
|||
|
}
|
|||
|
$Name
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
PROCESS
|
|||
|
{
|
|||
|
# $RDataLen = [BitConverter]::ToUInt16($DNSRecord, 0)
|
|||
|
$RDataType = [BitConverter]::ToUInt16($DNSRecord, 2)
|
|||
|
$UpdatedAtSerial = [BitConverter]::ToUInt32($DNSRecord, 8)
|
|||
|
|
|||
|
$TTLRaw = $DNSRecord[12..15]
|
|||
|
|
|||
|
# reverse for big endian
|
|||
|
$Null = [array]::Reverse($TTLRaw)
|
|||
|
$TTL = [BitConverter]::ToUInt32($TTLRaw, 0)
|
|||
|
|
|||
|
$Age = [BitConverter]::ToUInt32($DNSRecord, 20)
|
|||
|
If ($Age -ne 0)
|
|||
|
{
|
|||
|
$TimeStamp = ((Get-Date -Year 1601 -Month 1 -Day 1 -Hour 0 -Minute 0 -Second 0).AddHours($age)).ToString()
|
|||
|
}
|
|||
|
Else
|
|||
|
{
|
|||
|
$TimeStamp = '[static]'
|
|||
|
}
|
|||
|
|
|||
|
$DNSRecordObject = New-Object PSObject
|
|||
|
|
|||
|
switch ($RDataType)
|
|||
|
{
|
|||
|
1
|
|||
|
{
|
|||
|
$IP = "{0}.{1}.{2}.{3}" -f $DNSRecord[24], $DNSRecord[25], $DNSRecord[26], $DNSRecord[27]
|
|||
|
$Data = $IP
|
|||
|
$DNSRecordObject | Add-Member Noteproperty 'RecordType' 'A'
|
|||
|
}
|
|||
|
|
|||
|
2
|
|||
|
{
|
|||
|
$NSName = Get-Name $DNSRecord[24..$DNSRecord.length]
|
|||
|
$Data = $NSName
|
|||
|
$DNSRecordObject | Add-Member Noteproperty 'RecordType' 'NS'
|
|||
|
}
|
|||
|
|
|||
|
5
|
|||
|
{
|
|||
|
$Alias = Get-Name $DNSRecord[24..$DNSRecord.length]
|
|||
|
$Data = $Alias
|
|||
|
$DNSRecordObject | Add-Member Noteproperty 'RecordType' 'CNAME'
|
|||
|
}
|
|||
|
|
|||
|
6
|
|||
|
{
|
|||
|
$PrimaryNS = Get-Name $DNSRecord[44..$DNSRecord.length]
|
|||
|
$ResponsibleParty = Get-Name $DNSRecord[$(46+$DNSRecord[44])..$DNSRecord.length]
|
|||
|
$SerialRaw = $DNSRecord[24..27]
|
|||
|
# reverse for big endian
|
|||
|
$Null = [array]::Reverse($SerialRaw)
|
|||
|
$Serial = [BitConverter]::ToUInt32($SerialRaw, 0)
|
|||
|
|
|||
|
$RefreshRaw = $DNSRecord[28..31]
|
|||
|
$Null = [array]::Reverse($RefreshRaw)
|
|||
|
$Refresh = [BitConverter]::ToUInt32($RefreshRaw, 0)
|
|||
|
|
|||
|
$RetryRaw = $DNSRecord[32..35]
|
|||
|
$Null = [array]::Reverse($RetryRaw)
|
|||
|
$Retry = [BitConverter]::ToUInt32($RetryRaw, 0)
|
|||
|
|
|||
|
$ExpiresRaw = $DNSRecord[36..39]
|
|||
|
$Null = [array]::Reverse($ExpiresRaw)
|
|||
|
$Expires = [BitConverter]::ToUInt32($ExpiresRaw, 0)
|
|||
|
|
|||
|
$MinTTLRaw = $DNSRecord[40..43]
|
|||
|
$Null = [array]::Reverse($MinTTLRaw)
|
|||
|
$MinTTL = [BitConverter]::ToUInt32($MinTTLRaw, 0)
|
|||
|
|
|||
|
$Data = "[" + $Serial + "][" + $PrimaryNS + "][" + $ResponsibleParty + "][" + $Refresh + "][" + $Retry + "][" + $Expires + "][" + $MinTTL + "]"
|
|||
|
$DNSRecordObject | Add-Member Noteproperty 'RecordType' 'SOA'
|
|||
|
}
|
|||
|
|
|||
|
12
|
|||
|
{
|
|||
|
$Ptr = Get-Name $DNSRecord[24..$DNSRecord.length]
|
|||
|
$Data = $Ptr
|
|||
|
$DNSRecordObject | Add-Member Noteproperty 'RecordType' 'PTR'
|
|||
|
}
|
|||
|
|
|||
|
13
|
|||
|
{
|
|||
|
[string]$CPUType = ""
|
|||
|
[string]$OSType = ""
|
|||
|
[int]$SegmentLength = $DNSRecord[24]
|
|||
|
$Index = 25
|
|||
|
while ($SegmentLength-- -gt 0)
|
|||
|
{
|
|||
|
$CPUType += [char]$DNSRecord[$Index++]
|
|||
|
}
|
|||
|
$Index = 24 + $DNSRecord[24] + 1
|
|||
|
[int]$SegmentLength = $Index++
|
|||
|
while ($SegmentLength-- -gt 0)
|
|||
|
{
|
|||
|
$OSType += [char]$DNSRecord[$Index++]
|
|||
|
}
|
|||
|
$Data = "[" + $CPUType + "][" + $OSType + "]"
|
|||
|
$DNSRecordObject | Add-Member Noteproperty 'RecordType' 'HINFO'
|
|||
|
}
|
|||
|
|
|||
|
15
|
|||
|
{
|
|||
|
$PriorityRaw = $DNSRecord[24..25]
|
|||
|
# reverse for big endian
|
|||
|
$Null = [array]::Reverse($PriorityRaw)
|
|||
|
$Priority = [BitConverter]::ToUInt16($PriorityRaw, 0)
|
|||
|
$MXHost = Get-Name $DNSRecord[26..$DNSRecord.length]
|
|||
|
$Data = "[" + $Priority + "][" + $MXHost + "]"
|
|||
|
$DNSRecordObject | Add-Member Noteproperty 'RecordType' 'MX'
|
|||
|
}
|
|||
|
|
|||
|
16
|
|||
|
{
|
|||
|
[string]$TXT = ''
|
|||
|
[int]$SegmentLength = $DNSRecord[24]
|
|||
|
$Index = 25
|
|||
|
while ($SegmentLength-- -gt 0)
|
|||
|
{
|
|||
|
$TXT += [char]$DNSRecord[$Index++]
|
|||
|
}
|
|||
|
$Data = $TXT
|
|||
|
$DNSRecordObject | Add-Member Noteproperty 'RecordType' 'TXT'
|
|||
|
}
|
|||
|
|
|||
|
28
|
|||
|
{
|
|||
|
### yeah, this doesn't do all the fancy formatting that can be done for IPv6
|
|||
|
$AAAA = ""
|
|||
|
for ($i = 24; $i -lt 40; $i+=2)
|
|||
|
{
|
|||
|
$BlockRaw = $DNSRecord[$i..$($i+1)]
|
|||
|
# reverse for big endian
|
|||
|
$Null = [array]::Reverse($BlockRaw)
|
|||
|
$Block = [BitConverter]::ToUInt16($BlockRaw, 0)
|
|||
|
$AAAA += ($Block).ToString('x4')
|
|||
|
If ($i -ne 38)
|
|||
|
{
|
|||
|
$AAAA += ':'
|
|||
|
}
|
|||
|
}
|
|||
|
$Data = $AAAA
|
|||
|
$DNSRecordObject | Add-Member Noteproperty 'RecordType' 'AAAA'
|
|||
|
}
|
|||
|
|
|||
|
33
|
|||
|
{
|
|||
|
$PriorityRaw = $DNSRecord[24..25]
|
|||
|
# reverse for big endian
|
|||
|
$Null = [array]::Reverse($PriorityRaw)
|
|||
|
$Priority = [BitConverter]::ToUInt16($PriorityRaw, 0)
|
|||
|
|
|||
|
$WeightRaw = $DNSRecord[26..27]
|
|||
|
$Null = [array]::Reverse($WeightRaw)
|
|||
|
$Weight = [BitConverter]::ToUInt16($WeightRaw, 0)
|
|||
|
|
|||
|
$PortRaw = $DNSRecord[28..29]
|
|||
|
$Null = [array]::Reverse($PortRaw)
|
|||
|
$Port = [BitConverter]::ToUInt16($PortRaw, 0)
|
|||
|
|
|||
|
$SRVHost = Get-Name $DNSRecord[30..$DNSRecord.length]
|
|||
|
$Data = "[" + $Priority + "][" + $Weight + "][" + $Port + "][" + $SRVHost + "]"
|
|||
|
$DNSRecordObject | Add-Member Noteproperty 'RecordType' 'SRV'
|
|||
|
}
|
|||
|
|
|||
|
default
|
|||
|
{
|
|||
|
$Data = $([System.Convert]::ToBase64String($DNSRecord[24..$DNSRecord.length]))
|
|||
|
$DNSRecordObject | Add-Member Noteproperty 'RecordType' 'UNKNOWN'
|
|||
|
}
|
|||
|
}
|
|||
|
$DNSRecordObject | Add-Member Noteproperty 'UpdatedAtSerial' $UpdatedAtSerial
|
|||
|
$DNSRecordObject | Add-Member Noteproperty 'TTL' $TTL
|
|||
|
$DNSRecordObject | Add-Member Noteproperty 'Age' $Age
|
|||
|
$DNSRecordObject | Add-Member Noteproperty 'TimeStamp' $TimeStamp
|
|||
|
$DNSRecordObject | Add-Member Noteproperty 'Data' $Data
|
|||
|
Return $DNSRecordObject
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
Function Get-ADRDNSZone
|
|||
|
{
|
|||
|
<#
|
|||
|
.SYNOPSIS
|
|||
|
Returns all DNS Zones and Records in the current (or specified) domain.
|
|||
|
|
|||
|
.DESCRIPTION
|
|||
|
Returns all DNS Zones and Records in the current (or specified) domain.
|
|||
|
|
|||
|
.PARAMETER Protocol
|
|||
|
[string]
|
|||
|
Which protocol to use; ADWS (default) or LDAP.
|
|||
|
|
|||
|
.PARAMETER ADROutputDir
|
|||
|
[string]
|
|||
|
Path for ADRecon output folder.
|
|||
|
|
|||
|
.PARAMETER objDomain
|
|||
|
[DirectoryServices.DirectoryEntry]
|
|||
|
Domain Directory Entry object.
|
|||
|
|
|||
|
.PARAMETER DomainController
|
|||
|
[string]
|
|||
|
IP Address of the Domain Controller.
|
|||
|
|
|||
|
.PARAMETER Credential
|
|||
|
[Management.Automation.PSCredential]
|
|||
|
Credentials.
|
|||
|
|
|||
|
.PARAMETER PageSize
|
|||
|
[int]
|
|||
|
The PageSize to set for the LDAP searcher object. Default 200.
|
|||
|
|
|||
|
.PARAMETER OutputType
|
|||
|
[array]
|
|||
|
Output Type.
|
|||
|
|
|||
|
.OUTPUTS
|
|||
|
CSV files are created in the folder specified with the information.
|
|||
|
#>
|
|||
|
param(
|
|||
|
[Parameter(Mandatory = $true)]
|
|||
|
[string] $Protocol,
|
|||
|
|
|||
|
[Parameter(Mandatory = $true)]
|
|||
|
[string] $ADROutputDir,
|
|||
|
|
|||
|
[Parameter(Mandatory = $false)]
|
|||
|
[DirectoryServices.DirectoryEntry] $objDomain,
|
|||
|
|
|||
|
[Parameter(Mandatory = $false)]
|
|||
|
[string] $DomainController,
|
|||
|
|
|||
|
[Parameter(Mandatory = $false)]
|
|||
|
[Management.Automation.PSCredential] $Credential = [Management.Automation.PSCredential]::Empty,
|
|||
|
|
|||
|
[Parameter(Mandatory = $true)]
|
|||
|
[int] $PageSize,
|
|||
|
|
|||
|
[Parameter(Mandatory = $true)]
|
|||
|
[array] $OutputType
|
|||
|
)
|
|||
|
|
|||
|
If ($Protocol -eq 'ADWS')
|
|||
|
{
|
|||
|
Try
|
|||
|
{
|
|||
|
$ADDNSZones = Get-ADObject -LDAPFilter '(objectClass=dnsZone)' -Properties Name,whenCreated,whenChanged,usncreated,usnchanged,distinguishedname
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Warning "[Get-ADRDNSZone] Error while enumerating dnsZone Objects"
|
|||
|
Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
}
|
|||
|
|
|||
|
$DNSZoneArray = @()
|
|||
|
If ($ADDNSZones)
|
|||
|
{
|
|||
|
$DNSZoneArray += $ADDNSZones
|
|||
|
Remove-Variable ADDNSZones
|
|||
|
}
|
|||
|
|
|||
|
Try
|
|||
|
{
|
|||
|
$ADDNSZones1 = Get-ADObject -LDAPFilter '(objectClass=dnsZone)' -SearchBase "DC=DomainDnsZones,$((Get-ADDomain).DistinguishedName)" -Properties Name,whenCreated,whenChanged,usncreated,usnchanged,distinguishedname
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Warning "[Get-ADRDNSZone] Error while enumerating DomainDnsZones dnsZone Objects"
|
|||
|
Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
}
|
|||
|
If ($ADDNSZones1)
|
|||
|
{
|
|||
|
$DNSZoneArray += $ADDNSZones1
|
|||
|
Remove-Variable ADDNSZones1
|
|||
|
}
|
|||
|
|
|||
|
Try
|
|||
|
{
|
|||
|
$ADDNSZones2 = Get-ADObject -LDAPFilter '(objectClass=dnsZone)' -SearchBase "DC=ForestDnsZones,$((Get-ADDomain).DistinguishedName)" -Properties Name,whenCreated,whenChanged,usncreated,usnchanged,distinguishedname
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Warning "[Get-ADRDNSZone] Error while enumerating DC=ForestDnsZones,$((Get-ADDomain).DistinguishedName) dnsZone Objects"
|
|||
|
Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
}
|
|||
|
If ($ADDNSZones2)
|
|||
|
{
|
|||
|
$DNSZoneArray += $ADDNSZones2
|
|||
|
Remove-Variable ADDNSZones2
|
|||
|
}
|
|||
|
|
|||
|
Write-Verbose "[*] Total DNS Zones: $([ADRecon.ADWSClass]::ObjectCount($DNSZoneArray))"
|
|||
|
|
|||
|
If ($DNSZoneArray)
|
|||
|
{
|
|||
|
$ADDNSZonesObj = @()
|
|||
|
$ADDNSNodesObj = @()
|
|||
|
$DNSZoneArray | ForEach-Object {
|
|||
|
# Create the object for each instance.
|
|||
|
$Obj = New-Object PSObject
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name Name -Value $([ADRecon.ADWSClass]::CleanString($_.Name))
|
|||
|
Try
|
|||
|
{
|
|||
|
$DNSNodes = Get-ADObject -SearchBase $($_.DistinguishedName) -LDAPFilter '(objectClass=dnsNode)' -Properties DistinguishedName,dnsrecord,dNSTombstoned,Name,ProtectedFromAccidentalDeletion,showInAdvancedViewOnly,whenChanged,whenCreated
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Warning "[Get-ADRDNSZone] Error while enumerating $($_.DistinguishedName) dnsNode Objects"
|
|||
|
Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
}
|
|||
|
If ($DNSNodes)
|
|||
|
{
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name RecordCount -Value $($DNSNodes | Measure-Object | Select-Object -ExpandProperty Count)
|
|||
|
$DNSNodes | ForEach-Object {
|
|||
|
$ObjNode = New-Object PSObject
|
|||
|
$ObjNode | Add-Member -MemberType NoteProperty -Name ZoneName -Value $Obj.Name
|
|||
|
$ObjNode | Add-Member -MemberType NoteProperty -Name Name -Value $_.Name
|
|||
|
Try
|
|||
|
{
|
|||
|
$DNSRecord = Convert-DNSRecord $_.dnsrecord[0]
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Warning "[Get-ADRDNSZone] Error while converting the DNSRecord"
|
|||
|
Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
}
|
|||
|
$ObjNode | Add-Member -MemberType NoteProperty -Name RecordType -Value $DNSRecord.RecordType
|
|||
|
$ObjNode | Add-Member -MemberType NoteProperty -Name Data -Value $DNSRecord.Data
|
|||
|
$ObjNode | Add-Member -MemberType NoteProperty -Name TTL -Value $DNSRecord.TTL
|
|||
|
$ObjNode | Add-Member -MemberType NoteProperty -Name Age -Value $DNSRecord.Age
|
|||
|
$ObjNode | Add-Member -MemberType NoteProperty -Name TimeStamp -Value $DNSRecord.TimeStamp
|
|||
|
$ObjNode | Add-Member -MemberType NoteProperty -Name UpdatedAtSerial -Value $DNSRecord.UpdatedAtSerial
|
|||
|
$ObjNode | Add-Member -MemberType NoteProperty -Name whenCreated -Value $_.whenCreated
|
|||
|
$ObjNode | Add-Member -MemberType NoteProperty -Name whenChanged -Value $_.whenChanged
|
|||
|
# TO DO LDAP part
|
|||
|
#$ObjNode | Add-Member -MemberType NoteProperty -Name dNSTombstoned -Value $_.dNSTombstoned
|
|||
|
#$ObjNode | Add-Member -MemberType NoteProperty -Name ProtectedFromAccidentalDeletion -Value $_.ProtectedFromAccidentalDeletion
|
|||
|
$ObjNode | Add-Member -MemberType NoteProperty -Name showInAdvancedViewOnly -Value $_.showInAdvancedViewOnly
|
|||
|
$ObjNode | Add-Member -MemberType NoteProperty -Name DistinguishedName -Value $_.DistinguishedName
|
|||
|
$ADDNSNodesObj += $ObjNode
|
|||
|
If ($DNSRecord)
|
|||
|
{
|
|||
|
Remove-Variable DNSRecord
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
Else
|
|||
|
{
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name RecordCount -Value $null
|
|||
|
}
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name USNCreated -Value $_.usncreated
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name USNChanged -Value $_.usnchanged
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name whenCreated -Value $_.whenCreated
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name whenChanged -Value $_.whenChanged
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name DistinguishedName -Value $_.DistinguishedName
|
|||
|
$ADDNSZonesObj += $Obj
|
|||
|
}
|
|||
|
Write-Verbose "[*] Total DNS Records: $([ADRecon.ADWSClass]::ObjectCount($ADDNSNodesObj))"
|
|||
|
Remove-Variable DNSZoneArray
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
If ($Protocol -eq 'LDAP')
|
|||
|
{
|
|||
|
$objSearcher = New-Object System.DirectoryServices.DirectorySearcher $objDomain
|
|||
|
$ObjSearcher.PageSize = $PageSize
|
|||
|
$ObjSearcher.PropertiesToLoad.AddRange(("name","whencreated","whenchanged","usncreated","usnchanged","distinguishedname"))
|
|||
|
$ObjSearcher.Filter = "(objectClass=dnsZone)"
|
|||
|
$ObjSearcher.SearchScope = "Subtree"
|
|||
|
|
|||
|
Try
|
|||
|
{
|
|||
|
$ADDNSZones = $ObjSearcher.FindAll()
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Warning "[Get-ADRDNSZone] Error while enumerating dnsZone Objects"
|
|||
|
Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
}
|
|||
|
$ObjSearcher.dispose()
|
|||
|
|
|||
|
$DNSZoneArray = @()
|
|||
|
If ($ADDNSZones)
|
|||
|
{
|
|||
|
$DNSZoneArray += $ADDNSZones
|
|||
|
Remove-Variable ADDNSZones
|
|||
|
}
|
|||
|
|
|||
|
$SearchPath = "DC=DomainDnsZones"
|
|||
|
If ($Credential -ne [Management.Automation.PSCredential]::Empty)
|
|||
|
{
|
|||
|
$objSearchPath = New-Object System.DirectoryServices.DirectoryEntry "LDAP://$($DomainController)/$($SearchPath),$($objDomain.distinguishedName)", $Credential.UserName,$Credential.GetNetworkCredential().Password
|
|||
|
}
|
|||
|
Else
|
|||
|
{
|
|||
|
$objSearchPath = New-Object System.DirectoryServices.DirectoryEntry "LDAP://$($SearchPath),$($objDomain.distinguishedName)"
|
|||
|
}
|
|||
|
$objSearcherPath = New-Object System.DirectoryServices.DirectorySearcher $objSearchPath
|
|||
|
$objSearcherPath.Filter = "(objectClass=dnsZone)"
|
|||
|
$objSearcherPath.PageSize = $PageSize
|
|||
|
$objSearcherPath.PropertiesToLoad.AddRange(("name","whencreated","whenchanged","usncreated","usnchanged","distinguishedname"))
|
|||
|
$objSearcherPath.SearchScope = "Subtree"
|
|||
|
|
|||
|
Try
|
|||
|
{
|
|||
|
$ADDNSZones1 = $objSearcherPath.FindAll()
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Warning "[Get-ADRDNSZone] Error while enumerating DomainDnsZones dnsZone Objects."
|
|||
|
Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
}
|
|||
|
$objSearcherPath.dispose()
|
|||
|
|
|||
|
If ($ADDNSZones1)
|
|||
|
{
|
|||
|
$DNSZoneArray += $ADDNSZones1
|
|||
|
Remove-Variable ADDNSZones1
|
|||
|
}
|
|||
|
|
|||
|
$SearchPath = "DC=ForestDnsZones"
|
|||
|
If ($Credential -ne [Management.Automation.PSCredential]::Empty)
|
|||
|
{
|
|||
|
$objSearchPath = New-Object System.DirectoryServices.DirectoryEntry "LDAP://$($DomainController)/$($SearchPath),$($objDomain.distinguishedName)", $Credential.UserName,$Credential.GetNetworkCredential().Password
|
|||
|
}
|
|||
|
Else
|
|||
|
{
|
|||
|
$objSearchPath = New-Object System.DirectoryServices.DirectoryEntry "LDAP://$($SearchPath),$($objDomain.distinguishedName)"
|
|||
|
}
|
|||
|
$objSearcherPath = New-Object System.DirectoryServices.DirectorySearcher $objSearchPath
|
|||
|
$objSearcherPath.Filter = "(objectClass=dnsZone)"
|
|||
|
$objSearcherPath.PageSize = $PageSize
|
|||
|
$objSearcherPath.PropertiesToLoad.AddRange(("name","whencreated","whenchanged","usncreated","usnchanged","distinguishedname"))
|
|||
|
$objSearcherPath.SearchScope = "Subtree"
|
|||
|
|
|||
|
Try
|
|||
|
{
|
|||
|
$ADDNSZones2 = $objSearcherPath.FindAll()
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Warning "[Get-ADRDNSZone] Error while enumerating ForestDnsZones dnsZone Objects."
|
|||
|
Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
}
|
|||
|
$objSearcherPath.dispose()
|
|||
|
|
|||
|
If ($ADDNSZones2)
|
|||
|
{
|
|||
|
$DNSZoneArray += $ADDNSZones2
|
|||
|
Remove-Variable ADDNSZones2
|
|||
|
}
|
|||
|
|
|||
|
Write-Verbose "[*] Total DNS Zones: $([ADRecon.LDAPClass]::ObjectCount($DNSZoneArray))"
|
|||
|
|
|||
|
If ($DNSZoneArray)
|
|||
|
{
|
|||
|
$ADDNSZonesObj = @()
|
|||
|
$ADDNSNodesObj = @()
|
|||
|
$DNSZoneArray | ForEach-Object {
|
|||
|
If ($Credential -ne [Management.Automation.PSCredential]::Empty)
|
|||
|
{
|
|||
|
$objSearchPath = New-Object System.DirectoryServices.DirectoryEntry "LDAP://$($DomainController)/$($_.Properties.distinguishedname)", $Credential.UserName,$Credential.GetNetworkCredential().Password
|
|||
|
}
|
|||
|
Else
|
|||
|
{
|
|||
|
$objSearchPath = New-Object System.DirectoryServices.DirectoryEntry "LDAP://$($_.Properties.distinguishedname)"
|
|||
|
}
|
|||
|
$objSearcherPath = New-Object System.DirectoryServices.DirectorySearcher $objSearchPath
|
|||
|
$objSearcherPath.Filter = "(objectClass=dnsNode)"
|
|||
|
$objSearcherPath.PageSize = $PageSize
|
|||
|
$objSearcherPath.PropertiesToLoad.AddRange(("distinguishedname","dnsrecord","name","dc","showinadvancedviewonly","whenchanged","whencreated"))
|
|||
|
Try
|
|||
|
{
|
|||
|
$DNSNodes = $objSearcherPath.FindAll()
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Warning "[Get-ADRDNSZone] Error while enumerating $($_.Properties.distinguishedname) dnsNode Objects"
|
|||
|
Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
}
|
|||
|
$objSearcherPath.dispose()
|
|||
|
Remove-Variable objSearchPath
|
|||
|
|
|||
|
# Create the object for each instance.
|
|||
|
$Obj = New-Object PSObject
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name Name -Value $([ADRecon.LDAPClass]::CleanString($_.Properties.name[0]))
|
|||
|
If ($DNSNodes)
|
|||
|
{
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name RecordCount -Value $($DNSNodes | Measure-Object | Select-Object -ExpandProperty Count)
|
|||
|
$DNSNodes | ForEach-Object {
|
|||
|
$ObjNode = New-Object PSObject
|
|||
|
$ObjNode | Add-Member -MemberType NoteProperty -Name ZoneName -Value $Obj.Name
|
|||
|
$name = ([string] $($_.Properties.name))
|
|||
|
If (-Not $name)
|
|||
|
{
|
|||
|
$name = ([string] $($_.Properties.dc))
|
|||
|
}
|
|||
|
$ObjNode | Add-Member -MemberType NoteProperty -Name Name -Value $name
|
|||
|
Try
|
|||
|
{
|
|||
|
$DNSRecord = Convert-DNSRecord $_.Properties.dnsrecord[0]
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Warning "[Get-ADRDNSZone] Error while converting the DNSRecord"
|
|||
|
Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
}
|
|||
|
$ObjNode | Add-Member -MemberType NoteProperty -Name RecordType -Value $DNSRecord.RecordType
|
|||
|
$ObjNode | Add-Member -MemberType NoteProperty -Name Data -Value $DNSRecord.Data
|
|||
|
$ObjNode | Add-Member -MemberType NoteProperty -Name TTL -Value $DNSRecord.TTL
|
|||
|
$ObjNode | Add-Member -MemberType NoteProperty -Name Age -Value $DNSRecord.Age
|
|||
|
$ObjNode | Add-Member -MemberType NoteProperty -Name TimeStamp -Value $DNSRecord.TimeStamp
|
|||
|
$ObjNode | Add-Member -MemberType NoteProperty -Name UpdatedAtSerial -Value $DNSRecord.UpdatedAtSerial
|
|||
|
$ObjNode | Add-Member -MemberType NoteProperty -Name whenCreated -Value ([DateTime] $($_.Properties.whencreated))
|
|||
|
$ObjNode | Add-Member -MemberType NoteProperty -Name whenChanged -Value ([DateTime] $($_.Properties.whenchanged))
|
|||
|
# TO DO
|
|||
|
#$ObjNode | Add-Member -MemberType NoteProperty -Name dNSTombstoned -Value $null
|
|||
|
#$ObjNode | Add-Member -MemberType NoteProperty -Name ProtectedFromAccidentalDeletion -Value $null
|
|||
|
$ObjNode | Add-Member -MemberType NoteProperty -Name showInAdvancedViewOnly -Value ([string] $($_.Properties.showinadvancedviewonly))
|
|||
|
$ObjNode | Add-Member -MemberType NoteProperty -Name DistinguishedName -Value ([string] $($_.Properties.distinguishedname))
|
|||
|
$ADDNSNodesObj += $ObjNode
|
|||
|
If ($DNSRecord)
|
|||
|
{
|
|||
|
Remove-Variable DNSRecord
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
Else
|
|||
|
{
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name RecordCount -Value $null
|
|||
|
}
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name USNCreated -Value ([string] $($_.Properties.usncreated))
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name USNChanged -Value ([string] $($_.Properties.usnchanged))
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name whenCreated -Value ([DateTime] $($_.Properties.whencreated))
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name whenChanged -Value ([DateTime] $($_.Properties.whenchanged))
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name DistinguishedName -Value ([string] $($_.Properties.distinguishedname))
|
|||
|
$ADDNSZonesObj += $Obj
|
|||
|
}
|
|||
|
Write-Verbose "[*] Total DNS Records: $([ADRecon.LDAPClass]::ObjectCount($ADDNSNodesObj))"
|
|||
|
Remove-Variable DNSZoneArray
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
If ($ADDNSZonesObj)
|
|||
|
{
|
|||
|
Export-ADR $ADDNSZonesObj $ADROutputDir $OutputType "DNSZones"
|
|||
|
Remove-Variable ADDNSZonesObj
|
|||
|
}
|
|||
|
|
|||
|
If ($ADDNSNodesObj)
|
|||
|
{
|
|||
|
Export-ADR $ADDNSNodesObj $ADROutputDir $OutputType "DNSNodes"
|
|||
|
Remove-Variable ADDNSNodesObj
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
Function Get-ADRPrinter
|
|||
|
{
|
|||
|
<#
|
|||
|
.SYNOPSIS
|
|||
|
Returns all printers in the current (or specified) domain.
|
|||
|
|
|||
|
.DESCRIPTION
|
|||
|
Returns all printers in the current (or specified) domain.
|
|||
|
|
|||
|
.PARAMETER Protocol
|
|||
|
[string]
|
|||
|
Which protocol to use; ADWS (default) or LDAP.
|
|||
|
|
|||
|
.PARAMETER objDomain
|
|||
|
[DirectoryServices.DirectoryEntry]
|
|||
|
Domain Directory Entry object.
|
|||
|
|
|||
|
.PARAMETER PageSize
|
|||
|
[int]
|
|||
|
The PageSize to set for the LDAP searcher object. Default 200.
|
|||
|
|
|||
|
.PARAMETER Threads
|
|||
|
[int]
|
|||
|
The number of threads to use during processing of objects. Default 10.
|
|||
|
|
|||
|
.OUTPUTS
|
|||
|
PSObject.
|
|||
|
#>
|
|||
|
|
|||
|
param(
|
|||
|
[Parameter(Mandatory = $true)]
|
|||
|
[string] $Protocol,
|
|||
|
|
|||
|
[Parameter(Mandatory = $false)]
|
|||
|
[DirectoryServices.DirectoryEntry] $objDomain,
|
|||
|
|
|||
|
[Parameter(Mandatory = $true)]
|
|||
|
[int] $PageSize,
|
|||
|
|
|||
|
[Parameter(Mandatory = $false)]
|
|||
|
[int] $Threads = 10
|
|||
|
)
|
|||
|
|
|||
|
If ($Protocol -eq 'ADWS')
|
|||
|
{
|
|||
|
Try
|
|||
|
{
|
|||
|
$ADPrinters = @( Get-ADObject -LDAPFilter '(objectCategory=printQueue)' -Properties driverName,driverVersion,Name,portName,printShareName,serverName,url,whenChanged,whenCreated )
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Warning "[Get-ADRPrinter] Error while enumerating printQueue Objects"
|
|||
|
Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
Return $null
|
|||
|
}
|
|||
|
|
|||
|
If ($ADPrinters)
|
|||
|
{
|
|||
|
Write-Verbose "[*] Total Printers: $([ADRecon.ADWSClass]::ObjectCount($ADPrinters))"
|
|||
|
$PrintersObj = [ADRecon.ADWSClass]::PrinterParser($ADPrinters, $Threads)
|
|||
|
Remove-Variable ADPrinters
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
If ($Protocol -eq 'LDAP')
|
|||
|
{
|
|||
|
$objSearcher = New-Object System.DirectoryServices.DirectorySearcher $objDomain
|
|||
|
$ObjSearcher.PageSize = $PageSize
|
|||
|
$ObjSearcher.Filter = "(objectCategory=printQueue)"
|
|||
|
$ObjSearcher.SearchScope = "Subtree"
|
|||
|
|
|||
|
Try
|
|||
|
{
|
|||
|
$ADPrinters = $ObjSearcher.FindAll()
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Warning "[Get-ADRPrinter] Error while enumerating printQueue Objects"
|
|||
|
Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
Return $null
|
|||
|
}
|
|||
|
$ObjSearcher.dispose()
|
|||
|
|
|||
|
If ($ADPrinters)
|
|||
|
{
|
|||
|
$cnt = $([ADRecon.LDAPClass]::ObjectCount($ADPrinters))
|
|||
|
If ($cnt -ge 1)
|
|||
|
{
|
|||
|
Write-Verbose "[*] Total Printers: $cnt"
|
|||
|
$PrintersObj = [ADRecon.LDAPClass]::PrinterParser($ADPrinters, $Threads)
|
|||
|
}
|
|||
|
Remove-Variable ADPrinters
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
If ($PrintersObj)
|
|||
|
{
|
|||
|
Return $PrintersObj
|
|||
|
}
|
|||
|
Else
|
|||
|
{
|
|||
|
Return $null
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
Function Get-ADRComputer
|
|||
|
{
|
|||
|
<#
|
|||
|
.SYNOPSIS
|
|||
|
Returns all computers in the current (or specified) domain.
|
|||
|
|
|||
|
.DESCRIPTION
|
|||
|
Returns all computers in the current (or specified) domain.
|
|||
|
|
|||
|
.PARAMETER Protocol
|
|||
|
[string]
|
|||
|
Which protocol to use; ADWS (default) or LDAP.
|
|||
|
|
|||
|
.PARAMETER date
|
|||
|
[DateTime]
|
|||
|
Date when ADRecon was executed.
|
|||
|
|
|||
|
.PARAMETER objDomain
|
|||
|
[DirectoryServices.DirectoryEntry]
|
|||
|
Domain Directory Entry object.
|
|||
|
|
|||
|
.PARAMETER DormantTimeSpan
|
|||
|
[int]
|
|||
|
Timespan for Dormant accounts. Default 90 days.
|
|||
|
|
|||
|
.PARAMTER PassMaxAge
|
|||
|
[int]
|
|||
|
Maximum machine account password age. Default 30 days
|
|||
|
https://docs.microsoft.com/en-us/windows/security/threat-protection/security-policy-settings/domain-member-maximum-machine-account-password-age
|
|||
|
|
|||
|
.PARAMETER PageSize
|
|||
|
[int]
|
|||
|
The PageSize to set for the LDAP searcher object. Default 200.
|
|||
|
|
|||
|
.PARAMETER Threads
|
|||
|
[int]
|
|||
|
The number of threads to use during processing of objects. Default 10.
|
|||
|
|
|||
|
.OUTPUTS
|
|||
|
PSObject.
|
|||
|
#>
|
|||
|
param(
|
|||
|
[Parameter(Mandatory = $true)]
|
|||
|
[string] $Protocol,
|
|||
|
|
|||
|
[Parameter(Mandatory = $true)]
|
|||
|
[DateTime] $date,
|
|||
|
|
|||
|
[Parameter(Mandatory = $false)]
|
|||
|
[DirectoryServices.DirectoryEntry] $objDomain,
|
|||
|
|
|||
|
[Parameter(Mandatory = $true)]
|
|||
|
[int] $DormantTimeSpan = 90,
|
|||
|
|
|||
|
[Parameter(Mandatory = $true)]
|
|||
|
[int] $PassMaxAge = 30,
|
|||
|
|
|||
|
[Parameter(Mandatory = $true)]
|
|||
|
[int] $PageSize,
|
|||
|
|
|||
|
[Parameter(Mandatory = $false)]
|
|||
|
[int] $Threads = 10
|
|||
|
)
|
|||
|
|
|||
|
If ($Protocol -eq 'ADWS')
|
|||
|
{
|
|||
|
Try
|
|||
|
{
|
|||
|
$ADComputers = @( Get-ADComputer -Filter * -ResultPageSize $PageSize -Properties Description,DistinguishedName,DNSHostName,Enabled,IPv4Address,LastLogonDate,'msDS-AllowedToDelegateTo','ms-ds-CreatorSid','msDS-SupportedEncryptionTypes',Name,OperatingSystem,OperatingSystemHotfix,OperatingSystemServicePack,OperatingSystemVersion,PasswordLastSet,primaryGroupID,SamAccountName,SID,SIDHistory,TrustedForDelegation,TrustedToAuthForDelegation,UserAccountControl,whenChanged,whenCreated )
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Warning "[Get-ADRComputer] Error while enumerating Computer Objects"
|
|||
|
Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
Return $null
|
|||
|
}
|
|||
|
|
|||
|
If ($ADComputers)
|
|||
|
{
|
|||
|
Write-Verbose "[*] Total Computers: $([ADRecon.ADWSClass]::ObjectCount($ADComputers))"
|
|||
|
$ComputerObj = [ADRecon.ADWSClass]::ComputerParser($ADComputers, $date, $DormantTimeSpan, $PassMaxAge, $Threads)
|
|||
|
Remove-Variable ADComputers
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
If ($Protocol -eq 'LDAP')
|
|||
|
{
|
|||
|
$objSearcher = New-Object System.DirectoryServices.DirectorySearcher $objDomain
|
|||
|
$ObjSearcher.PageSize = $PageSize
|
|||
|
$ObjSearcher.Filter = "(samAccountType=805306369)"
|
|||
|
$ObjSearcher.PropertiesToLoad.AddRange(("description","distinguishedname","dnshostname","lastlogontimestamp","msDS-AllowedToDelegateTo","ms-ds-CreatorSid","msDS-SupportedEncryptionTypes","name","objectsid","operatingsystem","operatingsystemhotfix","operatingsystemservicepack","operatingsystemversion","primarygroupid","pwdlastset","samaccountname","sidhistory","useraccountcontrol","whenchanged","whencreated"))
|
|||
|
$ObjSearcher.SearchScope = "Subtree"
|
|||
|
|
|||
|
Try
|
|||
|
{
|
|||
|
$ADComputers = $ObjSearcher.FindAll()
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Warning "[Get-ADRComputer] Error while enumerating Computer Objects"
|
|||
|
Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
Return $null
|
|||
|
}
|
|||
|
$ObjSearcher.dispose()
|
|||
|
|
|||
|
If ($ADComputers)
|
|||
|
{
|
|||
|
Write-Verbose "[*] Total Computers: $([ADRecon.LDAPClass]::ObjectCount($ADComputers))"
|
|||
|
$ComputerObj = [ADRecon.LDAPClass]::ComputerParser($ADComputers, $date, $DormantTimeSpan, $PassMaxAge, $Threads)
|
|||
|
Remove-Variable ADComputers
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
If ($ComputerObj)
|
|||
|
{
|
|||
|
Return $ComputerObj
|
|||
|
}
|
|||
|
Else
|
|||
|
{
|
|||
|
Return $null
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
Function Get-ADRComputerSPN
|
|||
|
{
|
|||
|
<#
|
|||
|
.SYNOPSIS
|
|||
|
Returns all computer service principal name (SPN) in the current (or specified) domain.
|
|||
|
|
|||
|
.DESCRIPTION
|
|||
|
Returns all computer service principal name (SPN) in the current (or specified) domain.
|
|||
|
|
|||
|
.PARAMETER Protocol
|
|||
|
[string]
|
|||
|
Which protocol to use; ADWS (default) or LDAP.
|
|||
|
|
|||
|
.PARAMETER objDomain
|
|||
|
[DirectoryServices.DirectoryEntry]
|
|||
|
Domain Directory Entry object.
|
|||
|
|
|||
|
.PARAMETER PageSize
|
|||
|
[int]
|
|||
|
The PageSize to set for the LDAP searcher object. Default 200.
|
|||
|
|
|||
|
.PARAMETER Threads
|
|||
|
[int]
|
|||
|
The number of threads to use during processing of objects. Default 10.
|
|||
|
|
|||
|
.OUTPUTS
|
|||
|
PSObject.
|
|||
|
#>
|
|||
|
param(
|
|||
|
[Parameter(Mandatory = $true)]
|
|||
|
[string] $Protocol,
|
|||
|
|
|||
|
[Parameter(Mandatory = $false)]
|
|||
|
[DirectoryServices.DirectoryEntry] $objDomain,
|
|||
|
|
|||
|
[Parameter(Mandatory = $true)]
|
|||
|
[int] $PageSize,
|
|||
|
|
|||
|
[Parameter(Mandatory = $false)]
|
|||
|
[int] $Threads = 10
|
|||
|
)
|
|||
|
|
|||
|
If ($Protocol -eq 'ADWS')
|
|||
|
{
|
|||
|
Try
|
|||
|
{
|
|||
|
$ADComputers = @( Get-ADObject -LDAPFilter "(&(samAccountType=805306369)(servicePrincipalName=*))" -Properties Name,servicePrincipalName -ResultPageSize $PageSize )
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Warning "[Get-ADRComputerSPN] Error while enumerating ComputerSPN Objects"
|
|||
|
Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
Return $null
|
|||
|
}
|
|||
|
|
|||
|
If ($ADComputers)
|
|||
|
{
|
|||
|
Write-Verbose "[*] Total ComputerSPNs: $([ADRecon.ADWSClass]::ObjectCount($ADComputers))"
|
|||
|
$ComputerSPNObj = [ADRecon.ADWSClass]::ComputerSPNParser($ADComputers, $Threads)
|
|||
|
Remove-Variable ADComputers
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
If ($Protocol -eq 'LDAP')
|
|||
|
{
|
|||
|
$objSearcher = New-Object System.DirectoryServices.DirectorySearcher $objDomain
|
|||
|
$ObjSearcher.PageSize = $PageSize
|
|||
|
$ObjSearcher.Filter = "(&(samAccountType=805306369)(servicePrincipalName=*))"
|
|||
|
$ObjSearcher.PropertiesToLoad.AddRange(("name","serviceprincipalname"))
|
|||
|
$ObjSearcher.SearchScope = "Subtree"
|
|||
|
Try
|
|||
|
{
|
|||
|
$ADComputers = $ObjSearcher.FindAll()
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Warning "[Get-ADRComputerSPN] Error while enumerating ComputerSPN Objects"
|
|||
|
Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
Return $null
|
|||
|
}
|
|||
|
$ObjSearcher.dispose()
|
|||
|
|
|||
|
If ($ADComputers)
|
|||
|
{
|
|||
|
Write-Verbose "[*] Total ComputerSPNs: $([ADRecon.LDAPClass]::ObjectCount($ADComputers))"
|
|||
|
$ComputerSPNObj = [ADRecon.LDAPClass]::ComputerSPNParser($ADComputers, $Threads)
|
|||
|
Remove-Variable ADComputers
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
If ($ComputerSPNObj)
|
|||
|
{
|
|||
|
Return $ComputerSPNObj
|
|||
|
}
|
|||
|
Else
|
|||
|
{
|
|||
|
Return $null
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
# based on https://github.com/kfosaaen/Get-LAPSPasswords/blob/master/Get-LAPSPasswords.ps1
|
|||
|
Function Get-ADRLAPSCheck
|
|||
|
{
|
|||
|
<#
|
|||
|
.SYNOPSIS
|
|||
|
Returns all LAPS (local administrator) stored passwords in the current (or specified) domain.
|
|||
|
|
|||
|
.DESCRIPTION
|
|||
|
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.
|
|||
|
|
|||
|
.PARAMETER Protocol
|
|||
|
[string]
|
|||
|
Which protocol to use; ADWS (default) or LDAP.
|
|||
|
|
|||
|
.PARAMETER objDomain
|
|||
|
[DirectoryServices.DirectoryEntry]
|
|||
|
Domain Directory Entry object.
|
|||
|
|
|||
|
.PARAMETER PageSize
|
|||
|
[int]
|
|||
|
The PageSize to set for the LDAP searcher object. Default 200.
|
|||
|
|
|||
|
.PARAMETER Threads
|
|||
|
[int]
|
|||
|
The number of threads to use during processing of objects. Default 10.
|
|||
|
|
|||
|
.OUTPUTS
|
|||
|
PSObject.
|
|||
|
#>
|
|||
|
param(
|
|||
|
[Parameter(Mandatory = $true)]
|
|||
|
[string] $Protocol,
|
|||
|
|
|||
|
[Parameter(Mandatory = $false)]
|
|||
|
[DirectoryServices.DirectoryEntry] $objDomain,
|
|||
|
|
|||
|
[Parameter(Mandatory = $true)]
|
|||
|
[int] $PageSize,
|
|||
|
|
|||
|
[Parameter(Mandatory = $false)]
|
|||
|
[int] $Threads = 10
|
|||
|
)
|
|||
|
|
|||
|
If ($Protocol -eq 'ADWS')
|
|||
|
{
|
|||
|
Try
|
|||
|
{
|
|||
|
$ADComputers = @( Get-ADObject -LDAPFilter "(samAccountType=805306369)" -Properties CN,DNSHostName,'ms-Mcs-AdmPwd','ms-Mcs-AdmPwdExpirationTime' -ResultPageSize $PageSize )
|
|||
|
}
|
|||
|
Catch [System.ArgumentException]
|
|||
|
{
|
|||
|
Write-Warning "[*] LAPS is not implemented."
|
|||
|
Return $null
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Warning "[Get-ADRLAPSCheck] Error while enumerating LAPS Objects"
|
|||
|
Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
Return $null
|
|||
|
}
|
|||
|
|
|||
|
If ($ADComputers)
|
|||
|
{
|
|||
|
Write-Verbose "[*] Total LAPS Objects: $([ADRecon.ADWSClass]::ObjectCount($ADComputers))"
|
|||
|
$LAPSObj = [ADRecon.ADWSClass]::LAPSParser($ADComputers, $Threads)
|
|||
|
Remove-Variable ADComputers
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
If ($Protocol -eq 'LDAP')
|
|||
|
{
|
|||
|
$objSearcher = New-Object System.DirectoryServices.DirectorySearcher $objDomain
|
|||
|
$ObjSearcher.PageSize = $PageSize
|
|||
|
$ObjSearcher.Filter = "(samAccountType=805306369)"
|
|||
|
$ObjSearcher.PropertiesToLoad.AddRange(("cn","dnshostname","ms-mcs-admpwd","ms-mcs-admpwdexpirationtime"))
|
|||
|
$ObjSearcher.SearchScope = "Subtree"
|
|||
|
Try
|
|||
|
{
|
|||
|
$ADComputers = $ObjSearcher.FindAll()
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Warning "[Get-ADRLAPSCheck] Error while enumerating LAPS Objects"
|
|||
|
Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
Return $null
|
|||
|
}
|
|||
|
$ObjSearcher.dispose()
|
|||
|
|
|||
|
If ($ADComputers)
|
|||
|
{
|
|||
|
$LAPSCheck = [ADRecon.LDAPClass]::LAPSCheck($ADComputers)
|
|||
|
If (-Not $LAPSCheck)
|
|||
|
{
|
|||
|
Write-Warning "[*] LAPS is not implemented."
|
|||
|
Return $null
|
|||
|
}
|
|||
|
Else
|
|||
|
{
|
|||
|
Write-Verbose "[*] Total LAPS Objects: $([ADRecon.LDAPClass]::ObjectCount($ADComputers))"
|
|||
|
$LAPSObj = [ADRecon.LDAPClass]::LAPSParser($ADComputers, $Threads)
|
|||
|
Remove-Variable ADComputers
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
If ($LAPSObj)
|
|||
|
{
|
|||
|
Return $LAPSObj
|
|||
|
}
|
|||
|
Else
|
|||
|
{
|
|||
|
Return $null
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
Function Get-ADRBitLocker
|
|||
|
{
|
|||
|
<#
|
|||
|
.SYNOPSIS
|
|||
|
Returns all BitLocker Recovery Keys stored in the current (or specified) domain.
|
|||
|
|
|||
|
.DESCRIPTION
|
|||
|
Returns all BitLocker Recovery Keys stored in the current (or specified) domain.
|
|||
|
|
|||
|
.PARAMETER Protocol
|
|||
|
[string]
|
|||
|
Which protocol to use; ADWS (default) or LDAP.
|
|||
|
|
|||
|
.PARAMETER objDomain
|
|||
|
[DirectoryServices.DirectoryEntry]
|
|||
|
Domain Directory Entry object.
|
|||
|
|
|||
|
.PARAMETER DomainController
|
|||
|
[string]
|
|||
|
IP Address of the Domain Controller.
|
|||
|
|
|||
|
.PARAMETER Credential
|
|||
|
[Management.Automation.PSCredential]
|
|||
|
Credentials.
|
|||
|
|
|||
|
.OUTPUTS
|
|||
|
PSObject.
|
|||
|
#>
|
|||
|
param(
|
|||
|
[Parameter(Mandatory = $true)]
|
|||
|
[string] $Protocol,
|
|||
|
|
|||
|
[Parameter(Mandatory = $false)]
|
|||
|
[DirectoryServices.DirectoryEntry] $objDomain,
|
|||
|
|
|||
|
[Parameter(Mandatory = $false)]
|
|||
|
[string] $DomainController,
|
|||
|
|
|||
|
[Parameter(Mandatory = $false)]
|
|||
|
[Management.Automation.PSCredential] $Credential = [Management.Automation.PSCredential]::Empty
|
|||
|
)
|
|||
|
|
|||
|
If ($Protocol -eq 'ADWS')
|
|||
|
{
|
|||
|
Try
|
|||
|
{
|
|||
|
$ADBitLockerRecoveryKeys = Get-ADObject -LDAPFilter '(objectClass=msFVE-RecoveryInformation)' -Properties distinguishedName,msFVE-RecoveryPassword,msFVE-RecoveryGuid,msFVE-VolumeGuid,Name,whenCreated
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Warning "[Get-ADRBitLocker] Error while enumerating msFVE-RecoveryInformation Objects"
|
|||
|
Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
Return $null
|
|||
|
}
|
|||
|
|
|||
|
If ($ADBitLockerRecoveryKeys)
|
|||
|
{
|
|||
|
$cnt = $([ADRecon.ADWSClass]::ObjectCount($ADBitLockerRecoveryKeys))
|
|||
|
If ($cnt -ge 1)
|
|||
|
{
|
|||
|
Write-Verbose "[*] Total BitLocker Recovery Keys: $cnt"
|
|||
|
$BitLockerObj = @()
|
|||
|
$ADBitLockerRecoveryKeys | ForEach-Object {
|
|||
|
# Create the object for each instance.
|
|||
|
$Obj = New-Object PSObject
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Distinguished Name" -Value $((($_.distinguishedName -split '}')[1]).substring(1))
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Name" -Value $_.Name
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "whenCreated" -Value $_.whenCreated
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Recovery Key ID" -Value $([GUID] $_.'msFVE-RecoveryGuid')
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Recovery Key" -Value $_.'msFVE-RecoveryPassword'
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Volume GUID" -Value $([GUID] $_.'msFVE-VolumeGuid')
|
|||
|
Try
|
|||
|
{
|
|||
|
$TempComp = Get-ADComputer -Identity $Obj.'Distinguished Name' -Properties msTPM-OwnerInformation,msTPM-TpmInformationForComputer
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Warning "[Get-ADRBitLocker] Error while enumerating $($Obj.'Distinguished Name') Computer Object"
|
|||
|
Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
}
|
|||
|
If ($TempComp)
|
|||
|
{
|
|||
|
# msTPM-OwnerInformation (Vista/7 or Server 2008/R2)
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "msTPM-OwnerInformation" -Value $TempComp.'msTPM-OwnerInformation'
|
|||
|
|
|||
|
# msTPM-TpmInformationForComputer (Windows 8/10 or Server 2012/R2)
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "msTPM-TpmInformationForComputer" -Value $TempComp.'msTPM-TpmInformationForComputer'
|
|||
|
If ($null -ne $TempComp.'msTPM-TpmInformationForComputer')
|
|||
|
{
|
|||
|
# Grab the TPM Owner Info from the msTPM-InformationObject
|
|||
|
$TPMObject = Get-ADObject -Identity $TempComp.'msTPM-TpmInformationForComputer' -Properties msTPM-OwnerInformation
|
|||
|
$TPMRecoveryInfo = $TPMObject.'msTPM-OwnerInformation'
|
|||
|
}
|
|||
|
Else
|
|||
|
{
|
|||
|
$TPMRecoveryInfo = $null
|
|||
|
}
|
|||
|
}
|
|||
|
Else
|
|||
|
{
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "msTPM-OwnerInformation" -Value $null
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "msTPM-TpmInformationForComputer" -Value $null
|
|||
|
$TPMRecoveryInfo = $null
|
|||
|
|
|||
|
}
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "TPM Owner Password" -Value $TPMRecoveryInfo
|
|||
|
$BitLockerObj += $Obj
|
|||
|
}
|
|||
|
}
|
|||
|
Remove-Variable ADBitLockerRecoveryKeys
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
If ($Protocol -eq 'LDAP')
|
|||
|
{
|
|||
|
$objSearcher = New-Object System.DirectoryServices.DirectorySearcher $objDomain
|
|||
|
$ObjSearcher.PageSize = $PageSize
|
|||
|
$ObjSearcher.Filter = "(objectClass=msFVE-RecoveryInformation)"
|
|||
|
$ObjSearcher.PropertiesToLoad.AddRange(("distinguishedName","msfve-recoverypassword","msfve-recoveryguid","msfve-volumeguid","mstpm-ownerinformation","mstpm-tpminformationforcomputer","name","whencreated"))
|
|||
|
$ObjSearcher.SearchScope = "Subtree"
|
|||
|
|
|||
|
Try
|
|||
|
{
|
|||
|
$ADBitLockerRecoveryKeys = $ObjSearcher.FindAll()
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Warning "[Get-ADRBitLocker] Error while enumerating msFVE-RecoveryInformation Objects"
|
|||
|
Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
Return $null
|
|||
|
}
|
|||
|
$ObjSearcher.dispose()
|
|||
|
|
|||
|
If ($ADBitLockerRecoveryKeys)
|
|||
|
{
|
|||
|
$cnt = $([ADRecon.LDAPClass]::ObjectCount($ADBitLockerRecoveryKeys))
|
|||
|
If ($cnt -ge 1)
|
|||
|
{
|
|||
|
Write-Verbose "[*] Total BitLocker Recovery Keys: $cnt"
|
|||
|
$BitLockerObj = @()
|
|||
|
$ADBitLockerRecoveryKeys | ForEach-Object {
|
|||
|
# Create the object for each instance.
|
|||
|
$Obj = New-Object PSObject
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Distinguished Name" -Value $((($_.Properties.distinguishedname -split '}')[1]).substring(1))
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Name" -Value ([string] ($_.Properties.name))
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "whenCreated" -Value ([DateTime] $($_.Properties.whencreated))
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Recovery Key ID" -Value $([GUID] $_.Properties.'msfve-recoveryguid'[0])
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Recovery Key" -Value ([string] ($_.Properties.'msfve-recoverypassword'))
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Volume GUID" -Value $([GUID] $_.Properties.'msfve-volumeguid'[0])
|
|||
|
|
|||
|
$objSearcher = New-Object System.DirectoryServices.DirectorySearcher $objDomain
|
|||
|
$ObjSearcher.PageSize = $PageSize
|
|||
|
$ObjSearcher.Filter = "(&(samAccountType=805306369)(distinguishedName=$($Obj.'Distinguished Name')))"
|
|||
|
$ObjSearcher.PropertiesToLoad.AddRange(("mstpm-ownerinformation","mstpm-tpminformationforcomputer"))
|
|||
|
$ObjSearcher.SearchScope = "Subtree"
|
|||
|
|
|||
|
Try
|
|||
|
{
|
|||
|
$TempComp = $ObjSearcher.FindAll()
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Warning "[Get-ADRBitLocker] Error while enumerating $($Obj.'Distinguished Name') Computer Object"
|
|||
|
Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
}
|
|||
|
$ObjSearcher.dispose()
|
|||
|
|
|||
|
If ($TempComp)
|
|||
|
{
|
|||
|
# msTPM-OwnerInformation (Vista/7 or Server 2008/R2)
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "msTPM-OwnerInformation" -Value $([string] $TempComp.Properties.'mstpm-ownerinformation')
|
|||
|
|
|||
|
# msTPM-TpmInformationForComputer (Windows 8/10 or Server 2012/R2)
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "msTPM-TpmInformationForComputer" -Value $([string] $TempComp.Properties.'mstpm-tpminformationforcomputer')
|
|||
|
If ($null -ne $TempComp.Properties.'mstpm-tpminformationforcomputer')
|
|||
|
{
|
|||
|
# Grab the TPM Owner Info from the msTPM-InformationObject
|
|||
|
If ($Credential -ne [Management.Automation.PSCredential]::Empty)
|
|||
|
{
|
|||
|
$objSearchPath = New-Object System.DirectoryServices.DirectoryEntry "LDAP://$($DomainController)/$($TempComp.Properties.'mstpm-tpminformationforcomputer')", $Credential.UserName,$Credential.GetNetworkCredential().Password
|
|||
|
$objSearcherPath = New-Object System.DirectoryServices.DirectorySearcher $objSearchPath
|
|||
|
$objSearcherPath.PropertiesToLoad.AddRange(("mstpm-ownerinformation"))
|
|||
|
Try
|
|||
|
{
|
|||
|
$TPMObject = $objSearcherPath.FindAll()
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
}
|
|||
|
$objSearcherPath.dispose()
|
|||
|
|
|||
|
If ($TPMObject)
|
|||
|
{
|
|||
|
$TPMRecoveryInfo = $([string] $TPMObject.Properties.'mstpm-ownerinformation')
|
|||
|
}
|
|||
|
Else
|
|||
|
{
|
|||
|
$TPMRecoveryInfo = $null
|
|||
|
}
|
|||
|
}
|
|||
|
Else
|
|||
|
{
|
|||
|
Try
|
|||
|
{
|
|||
|
$TPMObject = ([ADSI]"LDAP://$($TempComp.Properties.'mstpm-tpminformationforcomputer')")
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
}
|
|||
|
If ($TPMObject)
|
|||
|
{
|
|||
|
$TPMRecoveryInfo = $([string] $TPMObject.Properties.'mstpm-ownerinformation')
|
|||
|
}
|
|||
|
Else
|
|||
|
{
|
|||
|
$TPMRecoveryInfo = $null
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
Else
|
|||
|
{
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "msTPM-OwnerInformation" -Value $null
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "msTPM-TpmInformationForComputer" -Value $null
|
|||
|
$TPMRecoveryInfo = $null
|
|||
|
}
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "TPM Owner Password" -Value $TPMRecoveryInfo
|
|||
|
$BitLockerObj += $Obj
|
|||
|
}
|
|||
|
}
|
|||
|
Remove-Variable cnt
|
|||
|
Remove-Variable ADBitLockerRecoveryKeys
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
If ($BitLockerObj)
|
|||
|
{
|
|||
|
Return $BitLockerObj
|
|||
|
}
|
|||
|
Else
|
|||
|
{
|
|||
|
Return $null
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
# Modified ConvertFrom-SID function from https://github.com/PowerShellMafia/PowerSploit/blob/dev/Recon/PowerView.ps1
|
|||
|
Function ConvertFrom-SID
|
|||
|
{
|
|||
|
<#
|
|||
|
.SYNOPSIS
|
|||
|
Converts a security identifier (SID) to a group/user name.
|
|||
|
|
|||
|
Author: Will Schroeder (@harmj0y)
|
|||
|
License: BSD 3-Clause
|
|||
|
|
|||
|
.DESCRIPTION
|
|||
|
Converts a security identifier string (SID) to a group/user name using IADsNameTranslate interface.
|
|||
|
|
|||
|
.PARAMETER Protocol
|
|||
|
[string]
|
|||
|
Which protocol to use; ADWS (default) or LDAP.
|
|||
|
|
|||
|
.PARAMETER ObjectSid
|
|||
|
Specifies one or more SIDs to convert.
|
|||
|
|
|||
|
.PARAMETER DomainFQDN
|
|||
|
Specifies the FQDN of the Domain.
|
|||
|
|
|||
|
.PARAMETER Credential
|
|||
|
Specifies an alternate credential to use for the translation.
|
|||
|
|
|||
|
.PARAMETER ResolveSIDs
|
|||
|
[bool]
|
|||
|
Whether to resolve SIDs in the ACLs module. (Default False)
|
|||
|
|
|||
|
.EXAMPLE
|
|||
|
|
|||
|
ConvertFrom-SID S-1-5-21-890171859-3433809279-3366196753-1108
|
|||
|
|
|||
|
TESTLAB\harmj0y
|
|||
|
|
|||
|
.EXAMPLE
|
|||
|
|
|||
|
"S-1-5-21-890171859-3433809279-3366196753-1107", "S-1-5-21-890171859-3433809279-3366196753-1108", "S-1-5-32-562" | ConvertFrom-SID
|
|||
|
|
|||
|
TESTLAB\WINDOWS2$
|
|||
|
TESTLAB\harmj0y
|
|||
|
BUILTIN\Distributed COM Users
|
|||
|
|
|||
|
.EXAMPLE
|
|||
|
|
|||
|
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
|
|||
|
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm', $SecPassword)
|
|||
|
ConvertFrom-SID S-1-5-21-890171859-3433809279-3366196753-1108 -Credential $Cred
|
|||
|
|
|||
|
TESTLAB\harmj0y
|
|||
|
|
|||
|
.INPUTS
|
|||
|
[String]
|
|||
|
Accepts one or more SID strings on the pipeline.
|
|||
|
|
|||
|
.OUTPUTS
|
|||
|
[String]
|
|||
|
The converted DOMAIN\username.
|
|||
|
#>
|
|||
|
Param(
|
|||
|
[Parameter(Mandatory = $true)]
|
|||
|
[string] $Protocol,
|
|||
|
|
|||
|
[Parameter(Mandatory = $true)]
|
|||
|
[Alias('SID')]
|
|||
|
#[ValidatePattern('^S-1-.*')]
|
|||
|
[String]
|
|||
|
$ObjectSid,
|
|||
|
|
|||
|
[Parameter(Mandatory = $false)]
|
|||
|
[string] $DomainFQDN,
|
|||
|
|
|||
|
[Parameter(Mandatory = $false)]
|
|||
|
[Management.Automation.PSCredential] $Credential = [Management.Automation.PSCredential]::Empty,
|
|||
|
|
|||
|
[Parameter(Mandatory = $false)]
|
|||
|
[bool] $ResolveSID = $false
|
|||
|
)
|
|||
|
|
|||
|
BEGIN {
|
|||
|
# Name Translator Initialization Types
|
|||
|
# https://msdn.microsoft.com/en-us/library/aa772266%28v=vs.85%29.aspx
|
|||
|
$ADS_NAME_INITTYPE_DOMAIN = 1 # Initializes a NameTranslate object by setting the domain that the object binds to.
|
|||
|
#$ADS_NAME_INITTYPE_SERVER = 2 # Initializes a NameTranslate object by setting the server that the object binds to.
|
|||
|
$ADS_NAME_INITTYPE_GC = 3 # Initializes a NameTranslate object by locating the global catalog that the object binds to.
|
|||
|
|
|||
|
# Name Transator Name Types
|
|||
|
# https://msdn.microsoft.com/en-us/library/aa772267%28v=vs.85%29.aspx
|
|||
|
#$ADS_NAME_TYPE_1779 = 1 # Name format as specified in RFC 1779. For example, "CN=Jeff Smith,CN=users,DC=Fabrikam,DC=com".
|
|||
|
#$ADS_NAME_TYPE_CANONICAL = 2 # Canonical name format. For example, "Fabrikam.com/Users/Jeff Smith".
|
|||
|
$ADS_NAME_TYPE_NT4 = 3 # Account name format used in Windows. For example, "Fabrikam\JeffSmith".
|
|||
|
#$ADS_NAME_TYPE_DISPLAY = 4 # Display name format. For example, "Jeff Smith".
|
|||
|
#$ADS_NAME_TYPE_DOMAIN_SIMPLE = 5 # Simple domain name format. For example, "JeffSmith@Fabrikam.com".
|
|||
|
#$ADS_NAME_TYPE_ENTERPRISE_SIMPLE = 6 # Simple enterprise name format. For example, "JeffSmith@Fabrikam.com".
|
|||
|
#$ADS_NAME_TYPE_GUID = 7 # Global Unique Identifier format. For example, "{95ee9fff-3436-11d1-b2b0-d15ae3ac8436}".
|
|||
|
$ADS_NAME_TYPE_UNKNOWN = 8 # Unknown name type. The system will estimate the format. This element is a meaningful option only with the IADsNameTranslate.Set or the IADsNameTranslate.SetEx method, but not with the IADsNameTranslate.Get or IADsNameTranslate.GetEx method.
|
|||
|
#$ADS_NAME_TYPE_USER_PRINCIPAL_NAME = 9 # User principal name format. For example, "JeffSmith@Fabrikam.com".
|
|||
|
#$ADS_NAME_TYPE_CANONICAL_EX = 10 # Extended canonical name format. For example, "Fabrikam.com/Users Jeff Smith".
|
|||
|
#$ADS_NAME_TYPE_SERVICE_PRINCIPAL_NAME = 11 # Service principal name format. For example, "www/www.fabrikam.com@fabrikam.com".
|
|||
|
#$ADS_NAME_TYPE_SID_OR_SID_HISTORY_NAME = 12 # A SID string, as defined in the Security Descriptor Definition Language (SDDL), for either the SID of the current object or one from the object SID history. For example, "O:AOG:DAD:(A;;RPWPCCDCLCSWRCWDWOGA;;;S-1-0-0)"
|
|||
|
|
|||
|
# https://msdn.microsoft.com/en-us/library/aa772250.aspx
|
|||
|
#$ADS_CHASE_REFERRALS_NEVER = (0x00) # The client should never chase the referred-to server. Setting this option prevents a client from contacting other servers in a referral process.
|
|||
|
#$ADS_CHASE_REFERRALS_SUBORDINATE = (0x20) # The client chases only subordinate referrals which are a subordinate naming context in a directory tree. For example, if the base search is requested for "DC=Fabrikam,DC=Com", and the server returns a result set and a referral of "DC=Sales,DC=Fabrikam,DC=Com" on the AdbSales server, the client can contact the AdbSales server to continue the search. The ADSI LDAP provider always turns off this flag for paged searches.
|
|||
|
#$ADS_CHASE_REFERRALS_EXTERNAL = (0x40) # The client chases external referrals. For example, a client requests server A to perform a search for "DC=Fabrikam,DC=Com". However, server A does not contain the object, but knows that an independent server, B, owns it. It then refers the client to server B.
|
|||
|
$ADS_CHASE_REFERRALS_ALWAYS = (0x60) # Referrals are chased for either the subordinate or external type.
|
|||
|
}
|
|||
|
|
|||
|
PROCESS {
|
|||
|
$TargetSid = $($ObjectSid.TrimStart("O:"))
|
|||
|
$TargetSid = $($TargetSid.Trim('*'))
|
|||
|
If ($TargetSid -match '^S-1-.*')
|
|||
|
{
|
|||
|
Try
|
|||
|
{
|
|||
|
# try to resolve any built-in SIDs first - https://support.microsoft.com/en-us/kb/243330
|
|||
|
Switch ($TargetSid) {
|
|||
|
'S-1-0' { 'Null Authority' }
|
|||
|
'S-1-0-0' { 'Nobody' }
|
|||
|
'S-1-1' { 'World Authority' }
|
|||
|
'S-1-1-0' { 'Everyone' }
|
|||
|
'S-1-2' { 'Local Authority' }
|
|||
|
'S-1-2-0' { 'Local' }
|
|||
|
'S-1-2-1' { 'Console Logon ' }
|
|||
|
'S-1-3' { 'Creator Authority' }
|
|||
|
'S-1-3-0' { 'Creator Owner' }
|
|||
|
'S-1-3-1' { 'Creator Group' }
|
|||
|
'S-1-3-2' { 'Creator Owner Server' }
|
|||
|
'S-1-3-3' { 'Creator Group Server' }
|
|||
|
'S-1-3-4' { 'Owner Rights' }
|
|||
|
'S-1-4' { 'Non-unique Authority' }
|
|||
|
'S-1-5' { 'NT Authority' }
|
|||
|
'S-1-5-1' { 'Dialup' }
|
|||
|
'S-1-5-2' { 'Network' }
|
|||
|
'S-1-5-3' { 'Batch' }
|
|||
|
'S-1-5-4' { 'Interactive' }
|
|||
|
'S-1-5-6' { 'Service' }
|
|||
|
'S-1-5-7' { 'Anonymous' }
|
|||
|
'S-1-5-8' { 'Proxy' }
|
|||
|
'S-1-5-9' { 'Enterprise Domain Controllers' }
|
|||
|
'S-1-5-10' { 'Principal Self' }
|
|||
|
'S-1-5-11' { 'Authenticated Users' }
|
|||
|
'S-1-5-12' { 'Restricted Code' }
|
|||
|
'S-1-5-13' { 'Terminal Server Users' }
|
|||
|
'S-1-5-14' { 'Remote Interactive Logon' }
|
|||
|
'S-1-5-15' { 'This Organization ' }
|
|||
|
'S-1-5-17' { 'This Organization ' }
|
|||
|
'S-1-5-18' { 'Local System' }
|
|||
|
'S-1-5-19' { 'NT Authority' }
|
|||
|
'S-1-5-20' { 'NT Authority' }
|
|||
|
'S-1-5-80-0' { 'All Services ' }
|
|||
|
'S-1-5-32-544' { 'BUILTIN\Administrators' }
|
|||
|
'S-1-5-32-545' { 'BUILTIN\Users' }
|
|||
|
'S-1-5-32-546' { 'BUILTIN\Guests' }
|
|||
|
'S-1-5-32-547' { 'BUILTIN\Power Users' }
|
|||
|
'S-1-5-32-548' { 'BUILTIN\Account Operators' }
|
|||
|
'S-1-5-32-549' { 'BUILTIN\Server Operators' }
|
|||
|
'S-1-5-32-550' { 'BUILTIN\Print Operators' }
|
|||
|
'S-1-5-32-551' { 'BUILTIN\Backup Operators' }
|
|||
|
'S-1-5-32-552' { 'BUILTIN\Replicators' }
|
|||
|
'S-1-5-32-554' { 'BUILTIN\Pre-Windows 2000 Compatible Access' }
|
|||
|
'S-1-5-32-555' { 'BUILTIN\Remote Desktop Users' }
|
|||
|
'S-1-5-32-556' { 'BUILTIN\Network Configuration Operators' }
|
|||
|
'S-1-5-32-557' { 'BUILTIN\Incoming Forest Trust Builders' }
|
|||
|
'S-1-5-32-558' { 'BUILTIN\Performance Monitor Users' }
|
|||
|
'S-1-5-32-559' { 'BUILTIN\Performance Log Users' }
|
|||
|
'S-1-5-32-560' { 'BUILTIN\Windows Authorization Access Group' }
|
|||
|
'S-1-5-32-561' { 'BUILTIN\Terminal Server License Servers' }
|
|||
|
'S-1-5-32-562' { 'BUILTIN\Distributed COM Users' }
|
|||
|
'S-1-5-32-569' { 'BUILTIN\Cryptographic Operators' }
|
|||
|
'S-1-5-32-573' { 'BUILTIN\Event Log Readers' }
|
|||
|
'S-1-5-32-574' { 'BUILTIN\Certificate Service DCOM Access' }
|
|||
|
'S-1-5-32-575' { 'BUILTIN\RDS Remote Access Servers' }
|
|||
|
'S-1-5-32-576' { 'BUILTIN\RDS Endpoint Servers' }
|
|||
|
'S-1-5-32-577' { 'BUILTIN\RDS Management Servers' }
|
|||
|
'S-1-5-32-578' { 'BUILTIN\Hyper-V Administrators' }
|
|||
|
'S-1-5-32-579' { 'BUILTIN\Access Control Assistance Operators' }
|
|||
|
'S-1-5-32-580' { 'BUILTIN\Remote Management Users' }
|
|||
|
Default {
|
|||
|
# based on Convert-ADName function from https://github.com/PowerShellMafia/PowerSploit/blob/dev/Recon/PowerView.ps1
|
|||
|
If ( ($TargetSid -match '^S-1-.*') -and ($ResolveSID) )
|
|||
|
{
|
|||
|
If ($Protocol -eq 'ADWS')
|
|||
|
{
|
|||
|
Try
|
|||
|
{
|
|||
|
$ADObject = Get-ADObject -Filter "objectSid -eq '$TargetSid'" -Properties DistinguishedName,sAMAccountName
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Warning "[ConvertFrom-SID] Error while enumerating Object using SID"
|
|||
|
Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
}
|
|||
|
If ($ADObject)
|
|||
|
{
|
|||
|
$UserDomain = Get-DNtoFQDN -ADObjectDN $ADObject.DistinguishedName
|
|||
|
$ADSOutput = $UserDomain + "\" + $ADObject.sAMAccountName
|
|||
|
Remove-Variable UserDomain
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
If ($Protocol -eq 'LDAP')
|
|||
|
{
|
|||
|
If ($Credential -ne [Management.Automation.PSCredential]::Empty)
|
|||
|
{
|
|||
|
$ADObject = New-Object System.DirectoryServices.DirectoryEntry("LDAP://$DomainFQDN/<SID=$TargetSid>",($Credential.GetNetworkCredential()).UserName,($Credential.GetNetworkCredential()).Password)
|
|||
|
}
|
|||
|
Else
|
|||
|
{
|
|||
|
$ADObject = New-Object System.DirectoryServices.DirectoryEntry("LDAP://$DomainFQDN/<SID=$TargetSid>")
|
|||
|
}
|
|||
|
If ($ADObject)
|
|||
|
{
|
|||
|
If (-Not ([string]::IsNullOrEmpty($ADObject.Properties.samaccountname)) )
|
|||
|
{
|
|||
|
$UserDomain = Get-DNtoFQDN -ADObjectDN $([string] ($ADObject.Properties.distinguishedname))
|
|||
|
$ADSOutput = $UserDomain + "\" + $([string] ($ADObject.Properties.samaccountname))
|
|||
|
Remove-Variable UserDomain
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
If ( (-Not $ADSOutput) -or ([string]::IsNullOrEmpty($ADSOutput)) )
|
|||
|
{
|
|||
|
$ADSOutputType = $ADS_NAME_TYPE_NT4
|
|||
|
$Init = $true
|
|||
|
$Translate = New-Object -ComObject NameTranslate
|
|||
|
If ($Credential -ne [Management.Automation.PSCredential]::Empty)
|
|||
|
{
|
|||
|
$ADSInitType = $ADS_NAME_INITTYPE_DOMAIN
|
|||
|
Try
|
|||
|
{
|
|||
|
[System.__ComObject].InvokeMember(“InitEx”,”InvokeMethod”,$null,$Translate,$(@($ADSInitType,$DomainFQDN,($Credential.GetNetworkCredential()).UserName,$DomainFQDN,($Credential.GetNetworkCredential()).Password)))
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
$Init = $false
|
|||
|
#Write-Verbose "[ConvertFrom-SID] Error initializing translation for $($TargetSid) using alternate credentials"
|
|||
|
#Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
}
|
|||
|
}
|
|||
|
Else
|
|||
|
{
|
|||
|
$ADSInitType = $ADS_NAME_INITTYPE_GC
|
|||
|
Try
|
|||
|
{
|
|||
|
[System.__ComObject].InvokeMember(“Init”,”InvokeMethod”,$null,$Translate,($ADSInitType,$null))
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
$Init = $false
|
|||
|
#Write-Verbose "[ConvertFrom-SID] Error initializing translation for $($TargetSid)"
|
|||
|
#Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
}
|
|||
|
}
|
|||
|
If ($Init)
|
|||
|
{
|
|||
|
[System.__ComObject].InvokeMember(“ChaseReferral”,”SetProperty”,$null,$Translate,$ADS_CHASE_REFERRALS_ALWAYS)
|
|||
|
Try
|
|||
|
{
|
|||
|
[System.__ComObject].InvokeMember(“Set”,”InvokeMethod”,$null,$Translate,($ADS_NAME_TYPE_UNKNOWN, $TargetSID))
|
|||
|
$ADSOutput = [System.__ComObject].InvokeMember(“Get”,”InvokeMethod”,$null,$Translate,$ADSOutputType)
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
#Write-Verbose "[ConvertFrom-SID] Error translating $($TargetSid)"
|
|||
|
#Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
If (-Not ([string]::IsNullOrEmpty($ADSOutput)) )
|
|||
|
{
|
|||
|
Return $ADSOutput
|
|||
|
}
|
|||
|
Else
|
|||
|
{
|
|||
|
Return $TargetSid
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
#Write-Output "[ConvertFrom-SID] Error converting SID $($TargetSid)"
|
|||
|
#Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
}
|
|||
|
}
|
|||
|
Else
|
|||
|
{
|
|||
|
Return $TargetSid
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
# based on https://gallery.technet.microsoft.com/Active-Directory-OU-1d09f989
|
|||
|
Function Get-ADRACL
|
|||
|
{
|
|||
|
<#
|
|||
|
.SYNOPSIS
|
|||
|
Returns all ACLs for the Domain, OUs, Root Containers, GPO, User, Computer and Group objects in the current (or specified) domain.
|
|||
|
|
|||
|
.DESCRIPTION
|
|||
|
Returns all ACLs for the Domain, OUs, Root Containers, GPO, User, Computer and Group objects in the current (or specified) domain.
|
|||
|
|
|||
|
.PARAMETER Protocol
|
|||
|
[string]
|
|||
|
Which protocol to use; ADWS (default) or LDAP.
|
|||
|
|
|||
|
.PARAMETER objDomain
|
|||
|
[DirectoryServices.DirectoryEntry]
|
|||
|
Domain Directory Entry object.
|
|||
|
|
|||
|
.PARAMETER DomainController
|
|||
|
[string]
|
|||
|
IP Address of the Domain Controller.
|
|||
|
|
|||
|
.PARAMETER Credential
|
|||
|
[Management.Automation.PSCredential]
|
|||
|
Credentials.
|
|||
|
|
|||
|
.PARAMETER ResolveSIDs
|
|||
|
[bool]
|
|||
|
Whether to resolve SIDs in the ACLs module. (Default False)
|
|||
|
|
|||
|
.PARAMETER PageSize
|
|||
|
[int]
|
|||
|
The PageSize to set for the LDAP searcher object. Default 200.
|
|||
|
|
|||
|
.PARAMETER Threads
|
|||
|
[int]
|
|||
|
The number of threads to use during processing of objects. Default 10.
|
|||
|
|
|||
|
.OUTPUTS
|
|||
|
PSObject.
|
|||
|
|
|||
|
.LINK
|
|||
|
https://gallery.technet.microsoft.com/Active-Directory-OU-1d09f989
|
|||
|
#>
|
|||
|
param(
|
|||
|
[Parameter(Mandatory = $true)]
|
|||
|
[string] $Protocol,
|
|||
|
|
|||
|
[Parameter(Mandatory = $false)]
|
|||
|
[DirectoryServices.DirectoryEntry] $objDomain,
|
|||
|
|
|||
|
[Parameter(Mandatory = $false)]
|
|||
|
[string] $DomainController,
|
|||
|
|
|||
|
[Parameter(Mandatory = $false)]
|
|||
|
[Management.Automation.PSCredential] $Credential = [Management.Automation.PSCredential]::Empty,
|
|||
|
|
|||
|
[Parameter(Mandatory = $false)]
|
|||
|
[bool] $ResolveSID = $false,
|
|||
|
|
|||
|
[Parameter(Mandatory = $true)]
|
|||
|
[int] $PageSize,
|
|||
|
|
|||
|
[Parameter(Mandatory = $false)]
|
|||
|
[int] $Threads = 10
|
|||
|
)
|
|||
|
|
|||
|
If ($Protocol -eq 'ADWS')
|
|||
|
{
|
|||
|
If ($Credential -eq [Management.Automation.PSCredential]::Empty)
|
|||
|
{
|
|||
|
If (Test-Path AD:)
|
|||
|
{
|
|||
|
Set-Location AD:
|
|||
|
}
|
|||
|
Else
|
|||
|
{
|
|||
|
Write-Warning "Default AD drive not found ... Skipping ACL enumeration"
|
|||
|
Return $null
|
|||
|
}
|
|||
|
}
|
|||
|
$GUIDs = @{'00000000-0000-0000-0000-000000000000' = 'All'}
|
|||
|
Try
|
|||
|
{
|
|||
|
Write-Verbose "[*] Enumerating schemaIDs"
|
|||
|
$schemaIDs = Get-ADObject -SearchBase (Get-ADRootDSE).schemaNamingContext -LDAPFilter '(schemaIDGUID=*)' -Properties name, schemaIDGUID
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Warning "[Get-ADRACL] Error while enumerating schemaIDs"
|
|||
|
Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
}
|
|||
|
|
|||
|
If ($schemaIDs)
|
|||
|
{
|
|||
|
$schemaIDs | Where-Object {$_} | ForEach-Object {
|
|||
|
# convert the GUID
|
|||
|
$GUIDs[(New-Object Guid (,$_.schemaIDGUID)).Guid] = $_.name
|
|||
|
}
|
|||
|
Remove-Variable schemaIDs
|
|||
|
}
|
|||
|
|
|||
|
Try
|
|||
|
{
|
|||
|
Write-Verbose "[*] Enumerating Active Directory Rights"
|
|||
|
$schemaIDs = Get-ADObject -SearchBase "CN=Extended-Rights,$((Get-ADRootDSE).configurationNamingContext)" -LDAPFilter '(objectClass=controlAccessRight)' -Properties name, rightsGUID
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Warning "[Get-ADRACL] Error while enumerating Active Directory Rights"
|
|||
|
Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
}
|
|||
|
|
|||
|
If ($schemaIDs)
|
|||
|
{
|
|||
|
$schemaIDs | Where-Object {$_} | ForEach-Object {
|
|||
|
# convert the GUID
|
|||
|
$GUIDs[(New-Object Guid (,$_.rightsGUID)).Guid] = $_.name
|
|||
|
}
|
|||
|
Remove-Variable schemaIDs
|
|||
|
}
|
|||
|
|
|||
|
# Get the DistinguishedNames of Domain, OUs, Root Containers and GroupPolicy objects.
|
|||
|
$Objs = @()
|
|||
|
Try
|
|||
|
{
|
|||
|
$ADDomain = Get-ADDomain
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Warning "[Get-ADRACL] Error getting Domain Context"
|
|||
|
Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
}
|
|||
|
|
|||
|
Try
|
|||
|
{
|
|||
|
Write-Verbose "[*] Enumerating Domain, OU, GPO, User, Computer and Group Objects"
|
|||
|
$Objs += Get-ADObject -LDAPFilter '(|(objectClass=domain)(objectCategory=organizationalunit)(objectCategory=groupPolicyContainer)(samAccountType=805306368)(samAccountType=805306369)(samaccounttype=268435456)(samaccounttype=268435457)(samaccounttype=536870912)(samaccounttype=536870913))' -Properties DisplayName, DistinguishedName, Name, ntsecuritydescriptor, ObjectClass, objectsid
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Warning "[Get-ADRACL] Error while enumerating Domain, OU, GPO, User, Computer and Group Objects"
|
|||
|
Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
}
|
|||
|
|
|||
|
If ($ADDomain)
|
|||
|
{
|
|||
|
Try
|
|||
|
{
|
|||
|
Write-Verbose "[*] Enumerating Root Container Objects"
|
|||
|
$Objs += Get-ADObject -SearchBase $($ADDomain.DistinguishedName) -SearchScope OneLevel -LDAPFilter '(objectClass=container)' -Properties DistinguishedName, Name, ntsecuritydescriptor, ObjectClass
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Warning "[Get-ADRACL] Error while enumerating Root Container Objects"
|
|||
|
Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
If ($Objs)
|
|||
|
{
|
|||
|
$ACLObj = @()
|
|||
|
Write-Verbose "[*] Total Objects: $([ADRecon.ADWSClass]::ObjectCount($Objs))"
|
|||
|
Write-Verbose "[-] DACLs"
|
|||
|
$DACLObj = [ADRecon.ADWSClass]::DACLParser($Objs, $GUIDs, $Threads)
|
|||
|
#Write-Verbose "[-] SACLs - May need a Privileged Account"
|
|||
|
Write-Warning "[*] SACLs - Currently, the module is only supported with LDAP."
|
|||
|
#$SACLObj = [ADRecon.ADWSClass]::SACLParser($Objs, $GUIDs, $Threads)
|
|||
|
Remove-Variable Objs
|
|||
|
Remove-Variable GUIDs
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
If ($Protocol -eq 'LDAP')
|
|||
|
{
|
|||
|
$GUIDs = @{'00000000-0000-0000-0000-000000000000' = 'All'}
|
|||
|
|
|||
|
If ($Credential -ne [Management.Automation.PSCredential]::Empty)
|
|||
|
{
|
|||
|
$DomainFQDN = Get-DNtoFQDN($objDomain.distinguishedName)
|
|||
|
$DomainContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext("Domain",$($DomainFQDN),$($Credential.UserName),$($Credential.GetNetworkCredential().password))
|
|||
|
Try
|
|||
|
{
|
|||
|
$ADDomain = [System.DirectoryServices.ActiveDirectory.Domain]::GetDomain($DomainContext)
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Warning "[Get-ADRACL] Error getting Domain Context"
|
|||
|
Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
}
|
|||
|
|
|||
|
Try
|
|||
|
{
|
|||
|
$ForestContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext("Forest",$($ADDomain.Forest),$($Credential.UserName),$($Credential.GetNetworkCredential().password))
|
|||
|
$ADForest = [System.DirectoryServices.ActiveDirectory.Forest]::GetForest($ForestContext)
|
|||
|
$SchemaPath = $ADForest.Schema.Name
|
|||
|
Remove-Variable ADForest
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Warning "[Get-ADRACL] Error enumerating SchemaPath"
|
|||
|
Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
}
|
|||
|
}
|
|||
|
Else
|
|||
|
{
|
|||
|
$ADDomain = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()
|
|||
|
$ADForest = [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest()
|
|||
|
$SchemaPath = $ADForest.Schema.Name
|
|||
|
Remove-Variable ADForest
|
|||
|
}
|
|||
|
|
|||
|
If ($SchemaPath)
|
|||
|
{
|
|||
|
Write-Verbose "[*] Enumerating schemaIDs"
|
|||
|
If ($Credential -ne [Management.Automation.PSCredential]::Empty)
|
|||
|
{
|
|||
|
$objSearchPath = New-Object System.DirectoryServices.DirectoryEntry "LDAP://$($DomainController)/$($SchemaPath)", $Credential.UserName,$Credential.GetNetworkCredential().Password
|
|||
|
$objSearcherPath = New-Object System.DirectoryServices.DirectorySearcher $objSearchPath
|
|||
|
}
|
|||
|
Else
|
|||
|
{
|
|||
|
$objSearcherPath = New-Object System.DirectoryServices.DirectorySearcher ([ADSI] "LDAP://$($SchemaPath)")
|
|||
|
}
|
|||
|
$objSearcherPath.PageSize = $PageSize
|
|||
|
$objSearcherPath.filter = "(schemaIDGUID=*)"
|
|||
|
|
|||
|
Try
|
|||
|
{
|
|||
|
$SchemaSearcher = $objSearcherPath.FindAll()
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Warning "[Get-ADRACL] Error enumerating SchemaIDs"
|
|||
|
Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
}
|
|||
|
|
|||
|
If ($SchemaSearcher)
|
|||
|
{
|
|||
|
$SchemaSearcher | Where-Object {$_} | ForEach-Object {
|
|||
|
# convert the GUID
|
|||
|
$GUIDs[(New-Object Guid (,$_.properties.schemaidguid[0])).Guid] = $_.properties.name[0]
|
|||
|
}
|
|||
|
$SchemaSearcher.dispose()
|
|||
|
}
|
|||
|
$objSearcherPath.dispose()
|
|||
|
|
|||
|
Write-Verbose "[*] Enumerating Active Directory Rights"
|
|||
|
If ($Credential -ne [Management.Automation.PSCredential]::Empty)
|
|||
|
{
|
|||
|
$objSearchPath = New-Object System.DirectoryServices.DirectoryEntry "LDAP://$($DomainController)/$($SchemaPath.replace("Schema","Extended-Rights"))", $Credential.UserName,$Credential.GetNetworkCredential().Password
|
|||
|
$objSearcherPath = New-Object System.DirectoryServices.DirectorySearcher $objSearchPath
|
|||
|
}
|
|||
|
Else
|
|||
|
{
|
|||
|
$objSearcherPath = New-Object System.DirectoryServices.DirectorySearcher ([ADSI] "LDAP://$($SchemaPath.replace("Schema","Extended-Rights"))")
|
|||
|
}
|
|||
|
$objSearcherPath.PageSize = $PageSize
|
|||
|
$objSearcherPath.filter = "(objectClass=controlAccessRight)"
|
|||
|
|
|||
|
Try
|
|||
|
{
|
|||
|
$RightsSearcher = $objSearcherPath.FindAll()
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Warning "[Get-ADRACL] Error enumerating Active Directory Rights"
|
|||
|
Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
}
|
|||
|
|
|||
|
If ($RightsSearcher)
|
|||
|
{
|
|||
|
$RightsSearcher | Where-Object {$_} | ForEach-Object {
|
|||
|
# convert the GUID
|
|||
|
$GUIDs[$_.properties.rightsguid[0].toString()] = $_.properties.name[0]
|
|||
|
}
|
|||
|
$RightsSearcher.dispose()
|
|||
|
}
|
|||
|
$objSearcherPath.dispose()
|
|||
|
}
|
|||
|
|
|||
|
# Get the Domain, OUs, Root Containers, GPO, User, Computer and Group objects.
|
|||
|
$Objs = @()
|
|||
|
Write-Verbose "[*] Enumerating Domain, OU, GPO, User, Computer and Group Objects"
|
|||
|
$objSearcher = New-Object System.DirectoryServices.DirectorySearcher $objDomain
|
|||
|
$ObjSearcher.PageSize = $PageSize
|
|||
|
$ObjSearcher.Filter = "(|(objectClass=domain)(objectCategory=organizationalunit)(objectCategory=groupPolicyContainer)(samAccountType=805306368)(samAccountType=805306369)(samaccounttype=268435456)(samaccounttype=268435457)(samaccounttype=536870912)(samaccounttype=536870913))"
|
|||
|
# https://msdn.microsoft.com/en-us/library/system.directoryservices.securitymasks(v=vs.110).aspx
|
|||
|
$ObjSearcher.SecurityMasks = [System.DirectoryServices.SecurityMasks]::Dacl -bor [System.DirectoryServices.SecurityMasks]::Group -bor [System.DirectoryServices.SecurityMasks]::Owner -bor [System.DirectoryServices.SecurityMasks]::Sacl
|
|||
|
$ObjSearcher.PropertiesToLoad.AddRange(("displayname","distinguishedname","name","ntsecuritydescriptor","objectclass","objectsid"))
|
|||
|
$ObjSearcher.SearchScope = "Subtree"
|
|||
|
|
|||
|
Try
|
|||
|
{
|
|||
|
$Objs += $ObjSearcher.FindAll()
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Warning "[Get-ADRACL] Error while enumerating Domain, OU, GPO, User, Computer and Group Objects"
|
|||
|
Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
}
|
|||
|
$ObjSearcher.dispose()
|
|||
|
|
|||
|
Write-Verbose "[*] Enumerating Root Container Objects"
|
|||
|
$objSearcher = New-Object System.DirectoryServices.DirectorySearcher $objDomain
|
|||
|
$ObjSearcher.PageSize = $PageSize
|
|||
|
$ObjSearcher.Filter = "(objectClass=container)"
|
|||
|
# https://msdn.microsoft.com/en-us/library/system.directoryservices.securitymasks(v=vs.110).aspx
|
|||
|
$ObjSearcher.SecurityMasks = $ObjSearcher.SecurityMasks = [System.DirectoryServices.SecurityMasks]::Dacl -bor [System.DirectoryServices.SecurityMasks]::Group -bor [System.DirectoryServices.SecurityMasks]::Owner -bor [System.DirectoryServices.SecurityMasks]::Sacl
|
|||
|
$ObjSearcher.PropertiesToLoad.AddRange(("distinguishedname","name","ntsecuritydescriptor","objectclass"))
|
|||
|
$ObjSearcher.SearchScope = "OneLevel"
|
|||
|
|
|||
|
Try
|
|||
|
{
|
|||
|
$Objs += $ObjSearcher.FindAll()
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Warning "[Get-ADRACL] Error while enumerating Root Container Objects"
|
|||
|
Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
}
|
|||
|
$ObjSearcher.dispose()
|
|||
|
|
|||
|
If ($Objs)
|
|||
|
{
|
|||
|
Write-Verbose "[*] Total Objects: $([ADRecon.LDAPClass]::ObjectCount($Objs))"
|
|||
|
Write-Verbose "[-] DACLs"
|
|||
|
$DACLObj = [ADRecon.LDAPClass]::DACLParser($Objs, $GUIDs, $Threads)
|
|||
|
Write-Verbose "[-] SACLs - May need a Privileged Account"
|
|||
|
$SACLObj = [ADRecon.LDAPClass]::SACLParser($Objs, $GUIDs, $Threads)
|
|||
|
Remove-Variable Objs
|
|||
|
Remove-Variable GUIDs
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
If ($DACLObj)
|
|||
|
{
|
|||
|
Export-ADR $DACLObj $ADROutputDir $OutputType "DACLs"
|
|||
|
Remove-Variable DACLObj
|
|||
|
}
|
|||
|
|
|||
|
If ($SACLObj)
|
|||
|
{
|
|||
|
Export-ADR $SACLObj $ADROutputDir $OutputType "SACLs"
|
|||
|
Remove-Variable SACLObj
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
Function Get-ADRGPOReport
|
|||
|
{
|
|||
|
<#
|
|||
|
.SYNOPSIS
|
|||
|
Runs the Get-GPOReport cmdlet if available.
|
|||
|
|
|||
|
.DESCRIPTION
|
|||
|
Runs the Get-GPOReport cmdlet if available and saves in HTML and XML formats.
|
|||
|
|
|||
|
.PARAMETER Protocol
|
|||
|
[string]
|
|||
|
Which protocol to use; ADWS (default) or LDAP.
|
|||
|
|
|||
|
.PARAMETER UseAltCreds
|
|||
|
[bool]
|
|||
|
Whether to use provided credentials or not.
|
|||
|
|
|||
|
.PARAMETER ADROutputDir
|
|||
|
[string]
|
|||
|
Path for ADRecon output folder.
|
|||
|
|
|||
|
.OUTPUTS
|
|||
|
HTML and XML GPOReports are created in the folder specified.
|
|||
|
#>
|
|||
|
param(
|
|||
|
[Parameter(Mandatory = $true)]
|
|||
|
[string] $Protocol,
|
|||
|
|
|||
|
[Parameter(Mandatory = $true)]
|
|||
|
[bool] $UseAltCreds,
|
|||
|
|
|||
|
[Parameter(Mandatory = $true)]
|
|||
|
[string] $ADROutputDir
|
|||
|
)
|
|||
|
|
|||
|
If ($Protocol -eq 'ADWS')
|
|||
|
{
|
|||
|
Try
|
|||
|
{
|
|||
|
# Suppress verbose output on module import
|
|||
|
$SaveVerbosePreference = $script:VerbosePreference
|
|||
|
$script:VerbosePreference = 'SilentlyContinue'
|
|||
|
Import-Module GroupPolicy -WarningAction Stop -ErrorAction Stop | Out-Null
|
|||
|
If ($SaveVerbosePreference)
|
|||
|
{
|
|||
|
$script:VerbosePreference = $SaveVerbosePreference
|
|||
|
Remove-Variable SaveVerbosePreference
|
|||
|
}
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Warning "[Get-ADRGPOReport] Error importing the GroupPolicy Module. Skipping GPOReport"
|
|||
|
Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
If ($SaveVerbosePreference)
|
|||
|
{
|
|||
|
$script:VerbosePreference = $SaveVerbosePreference
|
|||
|
Remove-Variable SaveVerbosePreference
|
|||
|
}
|
|||
|
Return $null
|
|||
|
}
|
|||
|
Try
|
|||
|
{
|
|||
|
Write-Verbose "[*] GPOReport XML"
|
|||
|
$ADFileName = -join($ADROutputDir,'\','GPO-Report','.xml')
|
|||
|
Get-GPOReport -All -ReportType XML -Path $ADFileName
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
If ($UseAltCreds)
|
|||
|
{
|
|||
|
Write-Warning "[*] Run the tool using RUNAS."
|
|||
|
Write-Warning "[*] runas /user:<Domain FQDN>\<Username> /netonly powershell.exe"
|
|||
|
Return $null
|
|||
|
}
|
|||
|
Write-Warning "[Get-ADRGPOReport] Error getting the GPOReport in XML"
|
|||
|
Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
}
|
|||
|
Try
|
|||
|
{
|
|||
|
Write-Verbose "[*] GPOReport HTML"
|
|||
|
$ADFileName = -join($ADROutputDir,'\','GPO-Report','.html')
|
|||
|
Get-GPOReport -All -ReportType HTML -Path $ADFileName
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
If ($UseAltCreds)
|
|||
|
{
|
|||
|
Write-Warning "[*] Run the tool using RUNAS."
|
|||
|
Write-Warning "[*] runas /user:<Domain FQDN>\<Username> /netonly powershell.exe"
|
|||
|
Return $null
|
|||
|
}
|
|||
|
Write-Warning "[Get-ADRGPOReport] Error getting the GPOReport in XML"
|
|||
|
Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
}
|
|||
|
}
|
|||
|
If ($Protocol -eq 'LDAP')
|
|||
|
{
|
|||
|
Write-Warning "[*] Currently, the module is only supported with ADWS."
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
# Modified Invoke-UserImpersonation function from https://github.com/PowerShellMafia/PowerSploit/blob/dev/Recon/PowerView.ps1
|
|||
|
Function Get-ADRUserImpersonation
|
|||
|
{
|
|||
|
<#
|
|||
|
.SYNOPSIS
|
|||
|
|
|||
|
Creates a new "runas /netonly" type logon and impersonates the token.
|
|||
|
|
|||
|
Author: Will Schroeder (@harmj0y)
|
|||
|
License: BSD 3-Clause
|
|||
|
Required Dependencies: PSReflect
|
|||
|
|
|||
|
.DESCRIPTION
|
|||
|
|
|||
|
This function uses LogonUser() with the LOGON32_LOGON_NEW_CREDENTIALS LogonType
|
|||
|
to simulate "runas /netonly". The resulting token is then impersonated with
|
|||
|
ImpersonateLoggedOnUser() and the token handle is returned for later usage
|
|||
|
with Invoke-RevertToSelf.
|
|||
|
|
|||
|
.PARAMETER Credential
|
|||
|
|
|||
|
A [Management.Automation.PSCredential] object with alternate credentials
|
|||
|
to impersonate in the current thread space.
|
|||
|
|
|||
|
.PARAMETER TokenHandle
|
|||
|
|
|||
|
An IntPtr TokenHandle returned by a previous Invoke-UserImpersonation.
|
|||
|
If this is supplied, LogonUser() is skipped and only ImpersonateLoggedOnUser()
|
|||
|
is executed.
|
|||
|
|
|||
|
.PARAMETER Quiet
|
|||
|
|
|||
|
Suppress any warnings about STA vs MTA.
|
|||
|
|
|||
|
.EXAMPLE
|
|||
|
|
|||
|
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
|
|||
|
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
|
|||
|
Invoke-UserImpersonation -Credential $Cred
|
|||
|
|
|||
|
.OUTPUTS
|
|||
|
|
|||
|
IntPtr
|
|||
|
|
|||
|
The TokenHandle result from LogonUser.
|
|||
|
#>
|
|||
|
|
|||
|
[OutputType([IntPtr])]
|
|||
|
[CmdletBinding(DefaultParameterSetName = 'Credential')]
|
|||
|
Param(
|
|||
|
[Parameter(Mandatory = $True, ParameterSetName = 'Credential')]
|
|||
|
[Management.Automation.PSCredential]
|
|||
|
[Management.Automation.CredentialAttribute()]
|
|||
|
$Credential,
|
|||
|
|
|||
|
[Parameter(Mandatory = $True, ParameterSetName = 'TokenHandle')]
|
|||
|
[ValidateNotNull()]
|
|||
|
[IntPtr]
|
|||
|
$TokenHandle,
|
|||
|
|
|||
|
[Switch]
|
|||
|
$Quiet
|
|||
|
)
|
|||
|
|
|||
|
If (([System.Threading.Thread]::CurrentThread.GetApartmentState() -ne 'STA') -and (-not $PSBoundParameters['Quiet']))
|
|||
|
{
|
|||
|
Write-Warning "[Get-ADRUserImpersonation] powershell.exe is not currently in a single-threaded apartment state, token impersonation may not work."
|
|||
|
}
|
|||
|
|
|||
|
If ($PSBoundParameters['TokenHandle'])
|
|||
|
{
|
|||
|
$LogonTokenHandle = $TokenHandle
|
|||
|
}
|
|||
|
Else
|
|||
|
{
|
|||
|
$LogonTokenHandle = [IntPtr]::Zero
|
|||
|
$NetworkCredential = $Credential.GetNetworkCredential()
|
|||
|
$UserDomain = $NetworkCredential.Domain
|
|||
|
If (-Not $UserDomain)
|
|||
|
{
|
|||
|
Write-Warning "[Get-ADRUserImpersonation] Use credential with Domain FQDN. (<Domain FQDN>\<Username>)"
|
|||
|
}
|
|||
|
$UserName = $NetworkCredential.UserName
|
|||
|
Write-Warning "[Get-ADRUserImpersonation] Executing LogonUser() with user: $($UserDomain)\$($UserName)"
|
|||
|
|
|||
|
# LOGON32_LOGON_NEW_CREDENTIALS = 9, LOGON32_PROVIDER_WINNT50 = 3
|
|||
|
# this is to simulate "runas.exe /netonly" functionality
|
|||
|
$Result = $Advapi32::LogonUser($UserName, $UserDomain, $NetworkCredential.Password, 9, 3, [ref]$LogonTokenHandle)
|
|||
|
$LastError = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error();
|
|||
|
|
|||
|
If (-not $Result)
|
|||
|
{
|
|||
|
throw "[Get-ADRUserImpersonation] LogonUser() Error: $(([ComponentModel.Win32Exception] $LastError).Message)"
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
# actually impersonate the token from LogonUser()
|
|||
|
$Result = $Advapi32::ImpersonateLoggedOnUser($LogonTokenHandle)
|
|||
|
|
|||
|
If (-not $Result)
|
|||
|
{
|
|||
|
throw "[Get-ADRUserImpersonation] ImpersonateLoggedOnUser() Error: $(([ComponentModel.Win32Exception] $LastError).Message)"
|
|||
|
}
|
|||
|
|
|||
|
Write-Verbose "[Get-ADR-UserImpersonation] Alternate credentials successfully impersonated"
|
|||
|
$LogonTokenHandle
|
|||
|
}
|
|||
|
|
|||
|
# Modified Invoke-RevertToSelf function from https://github.com/PowerShellMafia/PowerSploit/blob/dev/Recon/PowerView.ps1
|
|||
|
Function Get-ADRRevertToSelf
|
|||
|
{
|
|||
|
<#
|
|||
|
.SYNOPSIS
|
|||
|
|
|||
|
Reverts any token impersonation.
|
|||
|
|
|||
|
Author: Will Schroeder (@harmj0y)
|
|||
|
License: BSD 3-Clause
|
|||
|
Required Dependencies: PSReflect
|
|||
|
|
|||
|
.DESCRIPTION
|
|||
|
|
|||
|
This function uses RevertToSelf() to revert any impersonated tokens.
|
|||
|
If -TokenHandle is passed (the token handle returned by Invoke-UserImpersonation),
|
|||
|
CloseHandle() is used to close the opened handle.
|
|||
|
|
|||
|
.PARAMETER TokenHandle
|
|||
|
|
|||
|
An optional IntPtr TokenHandle returned by Invoke-UserImpersonation.
|
|||
|
|
|||
|
.EXAMPLE
|
|||
|
|
|||
|
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
|
|||
|
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
|
|||
|
$Token = Invoke-UserImpersonation -Credential $Cred
|
|||
|
Invoke-RevertToSelf -TokenHandle $Token
|
|||
|
#>
|
|||
|
|
|||
|
[CmdletBinding()]
|
|||
|
Param(
|
|||
|
[ValidateNotNull()]
|
|||
|
[IntPtr]
|
|||
|
$TokenHandle
|
|||
|
)
|
|||
|
|
|||
|
If ($PSBoundParameters['TokenHandle'])
|
|||
|
{
|
|||
|
Write-Warning "[Get-ADRRevertToSelf] Reverting token impersonation and closing LogonUser() token handle"
|
|||
|
$Result = $Kernel32::CloseHandle($TokenHandle)
|
|||
|
}
|
|||
|
|
|||
|
$Result = $Advapi32::RevertToSelf()
|
|||
|
$LastError = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error();
|
|||
|
|
|||
|
If (-not $Result)
|
|||
|
{
|
|||
|
Write-Error "[Get-ADRRevertToSelf] RevertToSelf() Error: $(([ComponentModel.Win32Exception] $LastError).Message)"
|
|||
|
}
|
|||
|
|
|||
|
Write-Verbose "[Get-ADRRevertToSelf] Token impersonation successfully reverted"
|
|||
|
}
|
|||
|
|
|||
|
# Modified Get-DomainSPNTicket function from https://github.com/PowerShellMafia/PowerSploit/blob/dev/Recon/PowerView.ps1
|
|||
|
Function Get-ADRSPNTicket
|
|||
|
{
|
|||
|
<#
|
|||
|
<#
|
|||
|
.SYNOPSIS
|
|||
|
Request the kerberos ticket for a specified service principal name (SPN).
|
|||
|
|
|||
|
Author: machosec, Will Schroeder (@harmj0y)
|
|||
|
License: BSD 3-Clause
|
|||
|
Required Dependencies: Invoke-UserImpersonation, Invoke-RevertToSelf
|
|||
|
|
|||
|
.DESCRIPTION
|
|||
|
This function will either take one SPN strings, and will request a kerberos ticket for the given SPN using System.IdentityModel.Tokens.KerberosRequestorSecurityToken. The encrypted portion of the ticket is then extracted and output in either crackable Hashcat format.
|
|||
|
|
|||
|
.PARAMETER UserSPN
|
|||
|
[string]
|
|||
|
Service Principal Name.
|
|||
|
|
|||
|
.OUTPUTS
|
|||
|
PSObject.
|
|||
|
#>
|
|||
|
param(
|
|||
|
[Parameter(Mandatory = $true)]
|
|||
|
[string] $UserSPN
|
|||
|
)
|
|||
|
|
|||
|
Try
|
|||
|
{
|
|||
|
$Null = [Reflection.Assembly]::LoadWithPartialName('System.IdentityModel')
|
|||
|
$Ticket = New-Object System.IdentityModel.Tokens.KerberosRequestorSecurityToken -ArgumentList $UserSPN
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Warning "[Get-ADRSPNTicket] Error requesting ticket for SPN $UserSPN"
|
|||
|
Write-Warning "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
Return $null
|
|||
|
}
|
|||
|
|
|||
|
If ($Ticket)
|
|||
|
{
|
|||
|
$TicketByteStream = $Ticket.GetRequest()
|
|||
|
}
|
|||
|
|
|||
|
If ($TicketByteStream)
|
|||
|
{
|
|||
|
$TicketHexStream = [System.BitConverter]::ToString($TicketByteStream) -replace '-'
|
|||
|
|
|||
|
# TicketHexStream == GSS-API Frame (see https://tools.ietf.org/html/rfc4121#section-4.1)
|
|||
|
# No easy way to parse ASN1, so we'll try some janky regex to parse the embedded KRB_AP_REQ.Ticket object
|
|||
|
If ($TicketHexStream -match 'a382....3082....A0030201(?<EtypeLen>..)A1.{1,4}.......A282(?<CipherTextLen>....)........(?<DataToEnd>.+)')
|
|||
|
{
|
|||
|
$Etype = [Convert]::ToByte( $Matches.EtypeLen, 16 )
|
|||
|
$CipherTextLen = [Convert]::ToUInt32($Matches.CipherTextLen, 16)-4
|
|||
|
$CipherText = $Matches.DataToEnd.Substring(0,$CipherTextLen*2)
|
|||
|
|
|||
|
# Make sure the next field matches the beginning of the KRB_AP_REQ.Authenticator object
|
|||
|
If ($Matches.DataToEnd.Substring($CipherTextLen*2, 4) -ne 'A482')
|
|||
|
{
|
|||
|
Write-Warning '[Get-ADRSPNTicket] Error parsing ciphertext for the SPN $($Ticket.ServicePrincipalName).' # Use the TicketByteHexStream field and extract the hash offline with Get-KerberoastHashFromAPReq
|
|||
|
$Hash = $null
|
|||
|
}
|
|||
|
Else
|
|||
|
{
|
|||
|
$Hash = "$($CipherText.Substring(0,32))`$$($CipherText.Substring(32))"
|
|||
|
}
|
|||
|
}
|
|||
|
Else
|
|||
|
{
|
|||
|
Write-Warning "[Get-ADRSPNTicket] Unable to parse ticket structure for the SPN $($Ticket.ServicePrincipalName)." # Use the TicketByteHexStream field and extract the hash offline with Get-KerberoastHashFromAPReq
|
|||
|
$Hash = $null
|
|||
|
}
|
|||
|
}
|
|||
|
$Obj = New-Object PSObject
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "ServicePrincipalName" -Value $Ticket.ServicePrincipalName
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Etype" -Value $Etype
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Hash" -Value $Hash
|
|||
|
Return $Obj
|
|||
|
}
|
|||
|
|
|||
|
Function Get-ADRKerberoast
|
|||
|
{
|
|||
|
<#
|
|||
|
.SYNOPSIS
|
|||
|
Returns all user service principal name (SPN) hashes in the current (or specified) domain.
|
|||
|
|
|||
|
.DESCRIPTION
|
|||
|
Returns all user service principal name (SPN) hashes in the current (or specified) domain.
|
|||
|
|
|||
|
.PARAMETER Protocol
|
|||
|
[string]
|
|||
|
Which protocol to use; ADWS (default) or LDAP.
|
|||
|
|
|||
|
.PARAMETER objDomain
|
|||
|
[DirectoryServices.DirectoryEntry]
|
|||
|
Domain Directory Entry object.
|
|||
|
|
|||
|
.PARAMETER Credential
|
|||
|
[Management.Automation.PSCredential]
|
|||
|
Credentials.
|
|||
|
|
|||
|
.PARAMETER PageSize
|
|||
|
[int]
|
|||
|
The PageSize to set for the LDAP searcher object. Default 200.
|
|||
|
|
|||
|
.OUTPUTS
|
|||
|
PSObject.
|
|||
|
#>
|
|||
|
param(
|
|||
|
[Parameter(Mandatory = $true)]
|
|||
|
[string] $Protocol,
|
|||
|
|
|||
|
[Parameter(Mandatory = $false)]
|
|||
|
[DirectoryServices.DirectoryEntry] $objDomain,
|
|||
|
|
|||
|
[Parameter(Mandatory = $false)]
|
|||
|
[Management.Automation.PSCredential] $Credential = [Management.Automation.PSCredential]::Empty,
|
|||
|
|
|||
|
[Parameter(Mandatory = $true)]
|
|||
|
[int] $PageSize
|
|||
|
)
|
|||
|
|
|||
|
If ($Credential -ne [Management.Automation.PSCredential]::Empty)
|
|||
|
{
|
|||
|
$LogonToken = Get-ADRUserImpersonation -Credential $Credential
|
|||
|
}
|
|||
|
|
|||
|
If ($Protocol -eq 'ADWS')
|
|||
|
{
|
|||
|
Try
|
|||
|
{
|
|||
|
$ADUsers = Get-ADObject -LDAPFilter "(&(!objectClass=computer)(servicePrincipalName=*)(!userAccountControl:1.2.840.113556.1.4.803:=2))" -Properties sAMAccountName,servicePrincipalName,DistinguishedName -ResultPageSize $PageSize
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Warning "[Get-ADRKerberoast] Error while enumerating UserSPN Objects"
|
|||
|
Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
Return $null
|
|||
|
}
|
|||
|
|
|||
|
If ($ADUsers)
|
|||
|
{
|
|||
|
$UserSPNObj = @()
|
|||
|
$ADUsers | ForEach-Object {
|
|||
|
ForEach ($UserSPN in $_.servicePrincipalName)
|
|||
|
{
|
|||
|
$Obj = New-Object PSObject
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Username" -Value $_.sAMAccountName
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "ServicePrincipalName" -Value $UserSPN
|
|||
|
|
|||
|
$HashObj = Get-ADRSPNTicket $UserSPN
|
|||
|
If ($HashObj)
|
|||
|
{
|
|||
|
$UserDomain = $_.DistinguishedName.SubString($_.DistinguishedName.IndexOf('DC=')) -replace 'DC=','' -replace ',','.'
|
|||
|
# JohnTheRipper output format
|
|||
|
$JTRHash = "`$krb5tgs`$$($HashObj.ServicePrincipalName):$($HashObj.Hash)"
|
|||
|
# hashcat output format
|
|||
|
$HashcatHash = "`$krb5tgs`$$($HashObj.Etype)`$*$($_.SamAccountName)`$$UserDomain`$$($HashObj.ServicePrincipalName)*`$$($HashObj.Hash)"
|
|||
|
}
|
|||
|
Else
|
|||
|
{
|
|||
|
$JTRHash = $null
|
|||
|
$HashcatHash = $null
|
|||
|
}
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "John" -Value $JTRHash
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Hashcat" -Value $HashcatHash
|
|||
|
$UserSPNObj += $Obj
|
|||
|
}
|
|||
|
}
|
|||
|
Remove-Variable ADUsers
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
If ($Protocol -eq 'LDAP')
|
|||
|
{
|
|||
|
$objSearcher = New-Object System.DirectoryServices.DirectorySearcher $objDomain
|
|||
|
$ObjSearcher.PageSize = $PageSize
|
|||
|
$ObjSearcher.Filter = "(&(!objectClass=computer)(servicePrincipalName=*)(!userAccountControl:1.2.840.113556.1.4.803:=2))"
|
|||
|
$ObjSearcher.PropertiesToLoad.AddRange(("distinguishedname","samaccountname","serviceprincipalname","useraccountcontrol"))
|
|||
|
$ObjSearcher.SearchScope = "Subtree"
|
|||
|
Try
|
|||
|
{
|
|||
|
$ADUsers = $ObjSearcher.FindAll()
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Warning "[Get-ADRKerberoast] Error while enumerating UserSPN Objects"
|
|||
|
Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
Return $null
|
|||
|
}
|
|||
|
$ObjSearcher.dispose()
|
|||
|
|
|||
|
If ($ADUsers)
|
|||
|
{
|
|||
|
$UserSPNObj = @()
|
|||
|
$ADUsers | ForEach-Object {
|
|||
|
ForEach ($UserSPN in $_.Properties.serviceprincipalname)
|
|||
|
{
|
|||
|
$Obj = New-Object PSObject
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Username" -Value $_.Properties.samaccountname[0]
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "ServicePrincipalName" -Value $UserSPN
|
|||
|
|
|||
|
$HashObj = Get-ADRSPNTicket $UserSPN
|
|||
|
If ($HashObj)
|
|||
|
{
|
|||
|
$UserDomain = $_.Properties.distinguishedname[0].SubString($_.Properties.distinguishedname[0].IndexOf('DC=')) -replace 'DC=','' -replace ',','.'
|
|||
|
# JohnTheRipper output format
|
|||
|
$JTRHash = "`$krb5tgs`$$($HashObj.ServicePrincipalName):$($HashObj.Hash)"
|
|||
|
# hashcat output format
|
|||
|
$HashcatHash = "`$krb5tgs`$$($HashObj.Etype)`$*$($_.Properties.samaccountname)`$$UserDomain`$$($HashObj.ServicePrincipalName)*`$$($HashObj.Hash)"
|
|||
|
}
|
|||
|
Else
|
|||
|
{
|
|||
|
$JTRHash = $null
|
|||
|
$HashcatHash = $null
|
|||
|
}
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "John" -Value $JTRHash
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Hashcat" -Value $HashcatHash
|
|||
|
$UserSPNObj += $Obj
|
|||
|
}
|
|||
|
}
|
|||
|
Remove-Variable ADUsers
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
If ($LogonToken)
|
|||
|
{
|
|||
|
Get-ADRRevertToSelf -TokenHandle $LogonToken
|
|||
|
}
|
|||
|
|
|||
|
If ($UserSPNObj)
|
|||
|
{
|
|||
|
Return $UserSPNObj
|
|||
|
}
|
|||
|
Else
|
|||
|
{
|
|||
|
Return $null
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
# based on https://gallery.technet.microsoft.com/scriptcenter/PowerShell-script-to-find-6fc15ecb
|
|||
|
Function Get-ADRDomainAccountsusedforServiceLogon
|
|||
|
{
|
|||
|
<#
|
|||
|
.SYNOPSIS
|
|||
|
Returns all accounts used by services on computers in an Active Directory domain.
|
|||
|
|
|||
|
.DESCRIPTION
|
|||
|
Retrieves a list of all computers in the current domain and reads service configuration using Get-WmiObject.
|
|||
|
|
|||
|
.PARAMETER Protocol
|
|||
|
[string]
|
|||
|
Which protocol to use; ADWS (default) or LDAP.
|
|||
|
|
|||
|
.PARAMETER objDomain
|
|||
|
[DirectoryServices.DirectoryEntry]
|
|||
|
Domain Directory Entry object.
|
|||
|
|
|||
|
.PARAMETER PageSize
|
|||
|
[int]
|
|||
|
The PageSize to set for the LDAP searcher object. Default 200.
|
|||
|
|
|||
|
.PARAMETER Threads
|
|||
|
[int]
|
|||
|
The number of threads to use during processing of objects. Default 10.
|
|||
|
|
|||
|
.OUTPUTS
|
|||
|
PSObject.
|
|||
|
#>
|
|||
|
param(
|
|||
|
[Parameter(Mandatory = $true)]
|
|||
|
[string] $Protocol,
|
|||
|
|
|||
|
[Parameter(Mandatory = $false)]
|
|||
|
[DirectoryServices.DirectoryEntry] $objDomain,
|
|||
|
|
|||
|
[Parameter(Mandatory = $false)]
|
|||
|
[Management.Automation.PSCredential] $Credential = [Management.Automation.PSCredential]::Empty,
|
|||
|
|
|||
|
[Parameter(Mandatory = $true)]
|
|||
|
[int] $PageSize,
|
|||
|
|
|||
|
[Parameter(Mandatory = $false)]
|
|||
|
[int] $Threads = 10
|
|||
|
)
|
|||
|
|
|||
|
BEGIN {
|
|||
|
$readServiceAccounts = [scriptblock] {
|
|||
|
# scriptblock to retrieve service list form a remove machine
|
|||
|
$hostname = [string] $args[0]
|
|||
|
$OperatingSystem = [string] $args[1]
|
|||
|
#$Credential = [Management.Automation.PSCredential] $args[2]
|
|||
|
$Credential = $args[2]
|
|||
|
$timeout = 250
|
|||
|
$port = 135
|
|||
|
Try
|
|||
|
{
|
|||
|
$tcpclient = New-Object System.Net.Sockets.TcpClient
|
|||
|
$result = $tcpclient.BeginConnect($hostname,$port,$null,$null)
|
|||
|
$success = $result.AsyncWaitHandle.WaitOne($timeout,$null)
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
$warning = "$hostname ($OperatingSystem) is unreachable $($_.Exception.Message)"
|
|||
|
$success = $false
|
|||
|
$tcpclient.Close()
|
|||
|
}
|
|||
|
If ($success)
|
|||
|
{
|
|||
|
# PowerShellv2 does not support New-CimSession
|
|||
|
If ($PSVersionTable.PSVersion.Major -ne 2)
|
|||
|
{
|
|||
|
If ($Credential -ne [Management.Automation.PSCredential]::Empty)
|
|||
|
{
|
|||
|
$session = New-CimSession -ComputerName $hostname -SessionOption $(New-CimSessionOption –Protocol DCOM) -Credential $Credential
|
|||
|
If ($session)
|
|||
|
{
|
|||
|
$serviceList = @( Get-CimInstance -ClassName Win32_Service -Property Name,StartName,SystemName -CimSession $session -ErrorAction Stop)
|
|||
|
}
|
|||
|
}
|
|||
|
Else
|
|||
|
{
|
|||
|
$session = New-CimSession -ComputerName $hostname -SessionOption $(New-CimSessionOption –Protocol DCOM)
|
|||
|
If ($session)
|
|||
|
{
|
|||
|
$serviceList = @( Get-CimInstance -ClassName Win32_Service -Property Name,StartName,SystemName -CimSession $session -ErrorAction Stop )
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
Else
|
|||
|
{
|
|||
|
If ($Credential -ne [Management.Automation.PSCredential]::Empty)
|
|||
|
{
|
|||
|
$serviceList = @( Get-WmiObject -Class Win32_Service -ComputerName $hostname -Credential $Credential -Impersonation 3 -Property Name,StartName,SystemName -ErrorAction Stop )
|
|||
|
}
|
|||
|
Else
|
|||
|
{
|
|||
|
$serviceList = @( Get-WmiObject -Class Win32_Service -ComputerName $hostname -Property Name,StartName,SystemName -ErrorAction Stop )
|
|||
|
}
|
|||
|
}
|
|||
|
$serviceList
|
|||
|
}
|
|||
|
Try
|
|||
|
{
|
|||
|
If ($tcpclient) { $tcpclient.EndConnect($result) | Out-Null }
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
$warning = "$hostname ($OperatingSystem) : $($_.Exception.Message)"
|
|||
|
}
|
|||
|
$warning
|
|||
|
}
|
|||
|
|
|||
|
Function processCompletedJobs()
|
|||
|
{
|
|||
|
# reads service list from completed jobs,
|
|||
|
# updates $serviceAccount table and removes completed job
|
|||
|
|
|||
|
$jobs = Get-Job -State Completed
|
|||
|
ForEach( $job in $jobs )
|
|||
|
{
|
|||
|
If ($null -ne $job)
|
|||
|
{
|
|||
|
$data = Receive-Job $job
|
|||
|
Remove-Job $job
|
|||
|
}
|
|||
|
|
|||
|
If ($data)
|
|||
|
{
|
|||
|
If ( $data.GetType() -eq [Object[]] )
|
|||
|
{
|
|||
|
$serviceList = $data | Where-Object { if ($_.StartName) { $_ }}
|
|||
|
$serviceList | ForEach-Object {
|
|||
|
$Obj = New-Object PSObject
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Account" -Value $_.StartName
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Service Name" -Value $_.Name
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "SystemName" -Value $_.SystemName
|
|||
|
If ($_.StartName.toUpper().Contains($currentDomain))
|
|||
|
{
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Running as Domain User" -Value $true
|
|||
|
}
|
|||
|
Else
|
|||
|
{
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Running as Domain User" -Value $false
|
|||
|
}
|
|||
|
$script:serviceAccounts += $Obj
|
|||
|
}
|
|||
|
}
|
|||
|
ElseIf ( $data.GetType() -eq [String] )
|
|||
|
{
|
|||
|
$script:warnings += $data
|
|||
|
Write-Verbose $data
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
PROCESS
|
|||
|
{
|
|||
|
$script:serviceAccounts = @()
|
|||
|
[string[]] $warnings = @()
|
|||
|
If ($Protocol -eq 'ADWS')
|
|||
|
{
|
|||
|
Try
|
|||
|
{
|
|||
|
$ADDomain = Get-ADDomain
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Warning "[Get-ADRDomainAccountsusedforServiceLogon] Error getting Domain Context"
|
|||
|
Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
Return $null
|
|||
|
}
|
|||
|
If ($ADDomain)
|
|||
|
{
|
|||
|
$currentDomain = $ADDomain.NetBIOSName.toUpper()
|
|||
|
Remove-Variable ADDomain
|
|||
|
}
|
|||
|
Else
|
|||
|
{
|
|||
|
$currentDomain = ""
|
|||
|
Write-Warning "Current Domain could not be retrieved."
|
|||
|
}
|
|||
|
|
|||
|
Try
|
|||
|
{
|
|||
|
$ADComputers = Get-ADComputer -Filter { Enabled -eq $true -and OperatingSystem -Like "*Windows*" } -Properties Name,DNSHostName,OperatingSystem
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Warning "[Get-ADRDomainAccountsusedforServiceLogon] Error while enumerating Windows Computer Objects"
|
|||
|
Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
Return $null
|
|||
|
}
|
|||
|
|
|||
|
If ($ADComputers)
|
|||
|
{
|
|||
|
# start data retrieval job for each server in the list
|
|||
|
# use up to $Threads threads
|
|||
|
$cnt = $([ADRecon.ADWSClass]::ObjectCount($ADComputers))
|
|||
|
Write-Verbose "[*] Total Windows Hosts: $cnt"
|
|||
|
$icnt = 0
|
|||
|
$ADComputers | ForEach-Object {
|
|||
|
$StopWatch = [System.Diagnostics.StopWatch]::StartNew()
|
|||
|
If( $_.dnshostname )
|
|||
|
{
|
|||
|
$args = @($_.DNSHostName, $_.OperatingSystem, $Credential)
|
|||
|
Start-Job -ScriptBlock $readServiceAccounts -Name "read_$($_.name)" -ArgumentList $args | Out-Null
|
|||
|
++$icnt
|
|||
|
If ($StopWatch.Elapsed.TotalMilliseconds -ge 1000)
|
|||
|
{
|
|||
|
Write-Progress -Activity "Retrieving data from servers" -Status "$("{0:N2}" -f (($icnt/$cnt*100),2)) % Complete:" -PercentComplete 100
|
|||
|
$StopWatch.Reset()
|
|||
|
$StopWatch.Start()
|
|||
|
}
|
|||
|
while ( ( Get-Job -State Running).count -ge $Threads ) { Start-Sleep -Seconds 3 }
|
|||
|
processCompletedJobs
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
# process remaining jobs
|
|||
|
|
|||
|
Write-Progress -Activity "Retrieving data from servers" -Status "Waiting for background jobs to complete..." -PercentComplete 100
|
|||
|
Wait-Job -State Running -Timeout 30 | Out-Null
|
|||
|
Get-Job -State Running | Stop-Job
|
|||
|
processCompletedJobs
|
|||
|
Write-Progress -Activity "Retrieving data from servers" -Completed -Status "All Done"
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
If ($Protocol -eq 'LDAP')
|
|||
|
{
|
|||
|
$currentDomain = ([string]($objDomain.name)).toUpper()
|
|||
|
|
|||
|
$objSearcher = New-Object System.DirectoryServices.DirectorySearcher $objDomain
|
|||
|
$ObjSearcher.PageSize = $PageSize
|
|||
|
$ObjSearcher.Filter = "(&(samAccountType=805306369)(!userAccountControl:1.2.840.113556.1.4.803:=2)(operatingSystem=*Windows*))"
|
|||
|
$ObjSearcher.PropertiesToLoad.AddRange(("name","dnshostname","operatingsystem"))
|
|||
|
$ObjSearcher.SearchScope = "Subtree"
|
|||
|
|
|||
|
Try
|
|||
|
{
|
|||
|
$ADComputers = $ObjSearcher.FindAll()
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Warning "[Get-ADRDomainAccountsusedforServiceLogon] Error while enumerating Windows Computer Objects"
|
|||
|
Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
Return $null
|
|||
|
}
|
|||
|
$ObjSearcher.dispose()
|
|||
|
|
|||
|
If ($ADComputers)
|
|||
|
{
|
|||
|
# start data retrieval job for each server in the list
|
|||
|
# use up to $Threads threads
|
|||
|
$cnt = $([ADRecon.LDAPClass]::ObjectCount($ADComputers))
|
|||
|
Write-Verbose "[*] Total Windows Hosts: $cnt"
|
|||
|
$icnt = 0
|
|||
|
$ADComputers | ForEach-Object {
|
|||
|
If( $_.Properties.dnshostname )
|
|||
|
{
|
|||
|
$args = @($_.Properties.dnshostname, $_.Properties.operatingsystem, $Credential)
|
|||
|
Start-Job -ScriptBlock $readServiceAccounts -Name "read_$($_.Properties.name)" -ArgumentList $args | Out-Null
|
|||
|
++$icnt
|
|||
|
If ($StopWatch.Elapsed.TotalMilliseconds -ge 1000)
|
|||
|
{
|
|||
|
Write-Progress -Activity "Retrieving data from servers" -Status "$("{0:N2}" -f (($icnt/$cnt*100),2)) % Complete:" -PercentComplete 100
|
|||
|
$StopWatch.Reset()
|
|||
|
$StopWatch.Start()
|
|||
|
}
|
|||
|
while ( ( Get-Job -State Running).count -ge $Threads ) { Start-Sleep -Seconds 3 }
|
|||
|
processCompletedJobs
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
# process remaining jobs
|
|||
|
Write-Progress -Activity "Retrieving data from servers" -Status "Waiting for background jobs to complete..." -PercentComplete 100
|
|||
|
Wait-Job -State Running -Timeout 30 | Out-Null
|
|||
|
Get-Job -State Running | Stop-Job
|
|||
|
processCompletedJobs
|
|||
|
Write-Progress -Activity "Retrieving data from servers" -Completed -Status "All Done"
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
If ($script:serviceAccounts)
|
|||
|
{
|
|||
|
Return $script:serviceAccounts
|
|||
|
}
|
|||
|
Else
|
|||
|
{
|
|||
|
Return $null
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
Function Remove-EmptyADROutputDir
|
|||
|
{
|
|||
|
<#
|
|||
|
.SYNOPSIS
|
|||
|
Removes ADRecon output folder if empty.
|
|||
|
|
|||
|
.DESCRIPTION
|
|||
|
Removes ADRecon output folder if empty.
|
|||
|
|
|||
|
.PARAMETER ADROutputDir
|
|||
|
[string]
|
|||
|
Path for ADRecon output folder.
|
|||
|
|
|||
|
.PARAMETER OutputType
|
|||
|
[array]
|
|||
|
Output Type.
|
|||
|
#>
|
|||
|
param(
|
|||
|
[Parameter(Mandatory = $true)]
|
|||
|
[string] $ADROutputDir,
|
|||
|
|
|||
|
[Parameter(Mandatory = $true)]
|
|||
|
[array] $OutputType
|
|||
|
)
|
|||
|
|
|||
|
Switch ($OutputType)
|
|||
|
{
|
|||
|
'CSV'
|
|||
|
{
|
|||
|
$CSVPath = -join($ADROutputDir,'\','CSV-Files')
|
|||
|
If (!(Test-Path -Path $CSVPath\*))
|
|||
|
{
|
|||
|
Write-Verbose "Removed Empty Directory $CSVPath"
|
|||
|
Remove-Item $CSVPath
|
|||
|
}
|
|||
|
}
|
|||
|
'XML'
|
|||
|
{
|
|||
|
$XMLPath = -join($ADROutputDir,'\','XML-Files')
|
|||
|
If (!(Test-Path -Path $XMLPath\*))
|
|||
|
{
|
|||
|
Write-Verbose "Removed Empty Directory $XMLPath"
|
|||
|
Remove-Item $XMLPath
|
|||
|
}
|
|||
|
}
|
|||
|
'JSON'
|
|||
|
{
|
|||
|
$JSONPath = -join($ADROutputDir,'\','JSON-Files')
|
|||
|
If (!(Test-Path -Path $JSONPath\*))
|
|||
|
{
|
|||
|
Write-Verbose "Removed Empty Directory $JSONPath"
|
|||
|
Remove-Item $JSONPath
|
|||
|
}
|
|||
|
}
|
|||
|
'HTML'
|
|||
|
{
|
|||
|
$HTMLPath = -join($ADROutputDir,'\','HTML-Files')
|
|||
|
If (!(Test-Path -Path $HTMLPath\*))
|
|||
|
{
|
|||
|
Write-Verbose "Removed Empty Directory $HTMLPath"
|
|||
|
Remove-Item $HTMLPath
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
If (!(Test-Path -Path $ADROutputDir\*))
|
|||
|
{
|
|||
|
Remove-Item $ADROutputDir
|
|||
|
Write-Verbose "Removed Empty Directory $ADROutputDir"
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
Function Get-ADRAbout
|
|||
|
{
|
|||
|
<#
|
|||
|
.SYNOPSIS
|
|||
|
Returns information about ADRecon.
|
|||
|
|
|||
|
.DESCRIPTION
|
|||
|
Returns information about ADRecon.
|
|||
|
|
|||
|
.PARAMETER Protocol
|
|||
|
[string]
|
|||
|
Which protocol to use; ADWS (default) or LDAP.
|
|||
|
|
|||
|
.PARAMETER date
|
|||
|
[DateTime]
|
|||
|
Date
|
|||
|
|
|||
|
.PARAMETER ADReconVersion
|
|||
|
[string]
|
|||
|
ADRecon Version.
|
|||
|
|
|||
|
.PARAMETER Credential
|
|||
|
[Management.Automation.PSCredential]
|
|||
|
Credentials.
|
|||
|
|
|||
|
.PARAMETER RanonComputer
|
|||
|
[string]
|
|||
|
Details of the Computer running ADRecon.
|
|||
|
|
|||
|
.PARAMETER TotalTime
|
|||
|
[string]
|
|||
|
TotalTime.
|
|||
|
|
|||
|
.OUTPUTS
|
|||
|
PSObject.
|
|||
|
#>
|
|||
|
param(
|
|||
|
[Parameter(Mandatory = $true)]
|
|||
|
[string] $Protocol,
|
|||
|
|
|||
|
[Parameter(Mandatory = $true)]
|
|||
|
[DateTime] $date,
|
|||
|
|
|||
|
[Parameter(Mandatory = $true)]
|
|||
|
[string] $ADReconVersion,
|
|||
|
|
|||
|
[Parameter(Mandatory = $false)]
|
|||
|
[Management.Automation.PSCredential] $Credential = [Management.Automation.PSCredential]::Empty,
|
|||
|
|
|||
|
[Parameter(Mandatory = $true)]
|
|||
|
[string] $RanonComputer,
|
|||
|
|
|||
|
[Parameter(Mandatory = $true)]
|
|||
|
[string] $TotalTime
|
|||
|
)
|
|||
|
|
|||
|
$AboutADRecon = @()
|
|||
|
|
|||
|
If ($Protocol -eq 'ADWS')
|
|||
|
{
|
|||
|
$Version = "RSAT Version"
|
|||
|
}
|
|||
|
Else
|
|||
|
{
|
|||
|
$Version = "LDAP Version"
|
|||
|
}
|
|||
|
|
|||
|
If ($Credential -ne [Management.Automation.PSCredential]::Empty)
|
|||
|
{
|
|||
|
$Username = $($Credential.UserName)
|
|||
|
}
|
|||
|
Else
|
|||
|
{
|
|||
|
$Username = $([Environment]::UserName)
|
|||
|
}
|
|||
|
|
|||
|
$ObjValues = @("Date", $($date), "ADRecon", "https://github.com/sense-of-security/ADRecon", $Version, $($ADReconVersion), "Ran as user", $Username, "Ran on computer", $RanonComputer, "Execution Time (mins)", $($TotalTime))
|
|||
|
|
|||
|
For ($i = 0; $i -lt $($ObjValues.Count); $i++)
|
|||
|
{
|
|||
|
$Obj = New-Object PSObject
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Category" -Value $ObjValues[$i]
|
|||
|
$Obj | Add-Member -MemberType NoteProperty -Name "Value" -Value $ObjValues[$i+1]
|
|||
|
$i++
|
|||
|
$AboutADRecon += $Obj
|
|||
|
}
|
|||
|
Return $AboutADRecon
|
|||
|
}
|
|||
|
|
|||
|
Function Invoke-ADRecon
|
|||
|
{
|
|||
|
<#
|
|||
|
.SYNOPSIS
|
|||
|
Wrapper function to run ADRecon modules.
|
|||
|
|
|||
|
.DESCRIPTION
|
|||
|
Wrapper function to set variables, check dependencies and run ADRecon modules.
|
|||
|
|
|||
|
.PARAMETER Protocol
|
|||
|
[string]
|
|||
|
Which protocol to use; ADWS (default) or LDAP.
|
|||
|
|
|||
|
.PARAMETER Collect
|
|||
|
[array]
|
|||
|
Which modules to run; Forest, Domain, Trusts, Sites, Subnets, PasswordPolicy, FineGrainedPasswordPolicy, DomainControllers, Users, UserSPNs, PasswordAttributes, Groups, GroupMembers, OUs, GPOs, gPLinks, DNSZones, Printers, Computers, ComputerSPNs, LAPS, BitLocker, ACLs, GPOReport, Kerberoast, DomainAccountsusedforServiceLogon.
|
|||
|
|
|||
|
.PARAMETER DomainController
|
|||
|
[string]
|
|||
|
IP Address of the Domain Controller.
|
|||
|
|
|||
|
.PARAMETER Credential
|
|||
|
[Management.Automation.PSCredential]
|
|||
|
Credentials.
|
|||
|
|
|||
|
.PARAMETER OutputDir
|
|||
|
[string]
|
|||
|
Path for ADRecon output folder to save the CSV files and the ADRecon-Report.xlsx.
|
|||
|
|
|||
|
.PARAMETER DormantTimeSpan
|
|||
|
[int]
|
|||
|
Timespan for Dormant accounts. Default 90 days.
|
|||
|
|
|||
|
.PARAMTER PassMaxAge
|
|||
|
[int]
|
|||
|
Maximum machine account password age. Default 30 days
|
|||
|
|
|||
|
.PARAMETER PageSize
|
|||
|
[int]
|
|||
|
The PageSize to set for the LDAP searcher object. Default 200.
|
|||
|
|
|||
|
.PARAMETER Threads
|
|||
|
[int]
|
|||
|
The number of threads to use during processing of objects. Default 10.
|
|||
|
|
|||
|
.PARAMETER UseAltCreds
|
|||
|
[bool]
|
|||
|
Whether to use provided credentials or not.
|
|||
|
|
|||
|
.OUTPUTS
|
|||
|
STDOUT, CSV, XML, JSON, HTML and/or Excel file is created in the folder specified with the information.
|
|||
|
#>
|
|||
|
param(
|
|||
|
[Parameter(Mandatory = $false)]
|
|||
|
[string] $GenExcel,
|
|||
|
|
|||
|
[Parameter(Mandatory = $false)]
|
|||
|
[ValidateSet('ADWS', 'LDAP')]
|
|||
|
[string] $Protocol = 'ADWS',
|
|||
|
|
|||
|
[Parameter(Mandatory = $true)]
|
|||
|
[array] $Collect,
|
|||
|
|
|||
|
[Parameter(Mandatory = $false)]
|
|||
|
[string] $DomainController = '',
|
|||
|
|
|||
|
[Parameter(Mandatory = $false)]
|
|||
|
[Management.Automation.PSCredential] $Credential = [Management.Automation.PSCredential]::Empty,
|
|||
|
|
|||
|
[Parameter(Mandatory = $true)]
|
|||
|
[array] $OutputType,
|
|||
|
|
|||
|
[Parameter(Mandatory = $false)]
|
|||
|
[string] $ADROutputDir,
|
|||
|
|
|||
|
[Parameter(Mandatory = $false)]
|
|||
|
[int] $DormantTimeSpan = 90,
|
|||
|
|
|||
|
[Parameter(Mandatory = $false)]
|
|||
|
[int] $PassMaxAge = 30,
|
|||
|
|
|||
|
[Parameter(Mandatory = $false)]
|
|||
|
[int] $PageSize = 200,
|
|||
|
|
|||
|
[Parameter(Mandatory = $false)]
|
|||
|
[int] $Threads = 10,
|
|||
|
|
|||
|
[Parameter(Mandatory = $false)]
|
|||
|
[bool] $UseAltCreds = $false
|
|||
|
)
|
|||
|
|
|||
|
[string] $ADReconVersion = "v1.1"
|
|||
|
Write-Output "[*] ADRecon $ADReconVersion by Prashant Mahajan (@prashant3535)"
|
|||
|
|
|||
|
If ($GenExcel)
|
|||
|
{
|
|||
|
If (!(Test-Path $GenExcel))
|
|||
|
{
|
|||
|
Write-Output "[Invoke-ADRecon] Invalid Path ... Exiting"
|
|||
|
Return $null
|
|||
|
}
|
|||
|
Export-ADRExcel -ExcelPath $GenExcel
|
|||
|
Return $null
|
|||
|
}
|
|||
|
|
|||
|
# Suppress verbose output
|
|||
|
$SaveVerbosePreference = $script:VerbosePreference
|
|||
|
$script:VerbosePreference = 'SilentlyContinue'
|
|||
|
Try
|
|||
|
{
|
|||
|
If ($PSVersionTable.PSVersion.Major -ne 2)
|
|||
|
{
|
|||
|
$computer = Get-CimInstance -ClassName Win32_ComputerSystem
|
|||
|
$computerdomainrole = ($computer).DomainRole
|
|||
|
}
|
|||
|
Else
|
|||
|
{
|
|||
|
$computer = Get-WMIObject win32_computersystem
|
|||
|
$computerdomainrole = ($computer).DomainRole
|
|||
|
}
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Output "[Invoke-ADRecon] $($_.Exception.Message)"
|
|||
|
}
|
|||
|
If ($SaveVerbosePreference)
|
|||
|
{
|
|||
|
$script:VerbosePreference = $SaveVerbosePreference
|
|||
|
Remove-Variable SaveVerbosePreference
|
|||
|
}
|
|||
|
|
|||
|
switch ($computerdomainrole)
|
|||
|
{
|
|||
|
0
|
|||
|
{
|
|||
|
[string] $computerrole = "Standalone Workstation"
|
|||
|
$Env:ADPS_LoadDefaultDrive = 0
|
|||
|
$UseAltCreds = $true
|
|||
|
}
|
|||
|
1 { [string] $computerrole = "Member Workstation" }
|
|||
|
2
|
|||
|
{
|
|||
|
[string] $computerrole = "Standalone Server"
|
|||
|
$UseAltCreds = $true
|
|||
|
$Env:ADPS_LoadDefaultDrive = 0
|
|||
|
}
|
|||
|
3 { [string] $computerrole = "Member Server" }
|
|||
|
4 { [string] $computerrole = "Backup Domain Controller" }
|
|||
|
5 { [string] $computerrole = "Primary Domain Controller" }
|
|||
|
default { Write-Output "Computer Role could not be identified." }
|
|||
|
}
|
|||
|
|
|||
|
$RanonComputer = "$($computer.domain)\$([Environment]::MachineName) - $($computerrole)"
|
|||
|
Remove-Variable computer
|
|||
|
Remove-Variable computerdomainrole
|
|||
|
Remove-Variable computerrole
|
|||
|
|
|||
|
# If either DomainController or Credentials are provided, treat as non-member
|
|||
|
If (($DomainController -ne "") -or ($Credential -ne [Management.Automation.PSCredential]::Empty))
|
|||
|
{
|
|||
|
# Disable loading of default drive on member
|
|||
|
If (($Protocol -eq 'ADWS') -and (-Not $UseAltCreds))
|
|||
|
{
|
|||
|
$Env:ADPS_LoadDefaultDrive = 0
|
|||
|
}
|
|||
|
$UseAltCreds = $true
|
|||
|
}
|
|||
|
|
|||
|
# Import ActiveDirectory module
|
|||
|
If ($Protocol -eq 'ADWS')
|
|||
|
{
|
|||
|
Try
|
|||
|
{
|
|||
|
# Suppress verbose output on module import
|
|||
|
$SaveVerbosePreference = $script:VerbosePreference;
|
|||
|
$script:VerbosePreference = 'SilentlyContinue';
|
|||
|
Import-Module ActiveDirectory -WarningAction Stop -ErrorAction Stop | Out-Null
|
|||
|
If ($SaveVerbosePreference)
|
|||
|
{
|
|||
|
$script:VerbosePreference = $SaveVerbosePreference
|
|||
|
Remove-Variable SaveVerbosePreference
|
|||
|
}
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Warning "[Invoke-ADRecon] Error importing ActiveDirectory Module from RSAT (Remote Server Administration Tools) ... Continuing with LDAP"
|
|||
|
$Protocol = 'LDAP'
|
|||
|
If ($SaveVerbosePreference)
|
|||
|
{
|
|||
|
$script:VerbosePreference = $SaveVerbosePreference
|
|||
|
Remove-Variable SaveVerbosePreference
|
|||
|
}
|
|||
|
Write-Verbose "[EXCEPTION] $($_.Exception.Message)"
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
# Compile C# code
|
|||
|
# Suppress Debug output
|
|||
|
$SaveDebugPreference = $script:DebugPreference
|
|||
|
$script:DebugPreference = 'SilentlyContinue'
|
|||
|
Try
|
|||
|
{
|
|||
|
$Advapi32 = Add-Type -MemberDefinition $Advapi32Def -Name "Advapi32" -Namespace ADRecon -PassThru
|
|||
|
$Kernel32 = Add-Type -MemberDefinition $Kernel32Def -Name "Kernel32" -Namespace ADRecon -PassThru
|
|||
|
Add-Type -TypeDefinition $PingCastleSMBScannerSource
|
|||
|
$CLR = ([System.Reflection.Assembly]::GetExecutingAssembly().ImageRuntimeVersion)[1]
|
|||
|
If ($Protocol -eq 'ADWS')
|
|||
|
{
|
|||
|
If ($CLR -eq "4")
|
|||
|
{
|
|||
|
Add-Type -TypeDefinition $ADWSSource -ReferencedAssemblies ([System.String[]]@(([System.Reflection.Assembly]::LoadWithPartialName("Microsoft.ActiveDirectory.Management")).Location,([System.Reflection.Assembly]::LoadWithPartialName("System.DirectoryServices")).Location))
|
|||
|
}
|
|||
|
Else
|
|||
|
{
|
|||
|
Add-Type -TypeDefinition $ADWSSource -ReferencedAssemblies ([System.String[]]@(([System.Reflection.Assembly]::LoadWithPartialName("Microsoft.ActiveDirectory.Management")).Location,([System.Reflection.Assembly]::LoadWithPartialName("System.DirectoryServices")).Location)) -Language CSharpVersion3
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
If ($Protocol -eq 'LDAP')
|
|||
|
{
|
|||
|
If ($CLR -eq "4")
|
|||
|
{
|
|||
|
Add-Type -TypeDefinition $LDAPSource -ReferencedAssemblies ([System.Reflection.Assembly]::LoadWithPartialName("System.DirectoryServices")).Location
|
|||
|
}
|
|||
|
Else
|
|||
|
{
|
|||
|
Add-Type -TypeDefinition $LDAPSource -ReferencedAssemblies ([System.Reflection.Assembly]::LoadWithPartialName("System.DirectoryServices")).Location -Language CSharpVersion3
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Output "[Invoke-ADRecon] $($_.Exception.Message)"
|
|||
|
Return $null
|
|||
|
}
|
|||
|
If ($SaveDebugPreference)
|
|||
|
{
|
|||
|
$script:DebugPreference = $SaveDebugPreference
|
|||
|
Remove-Variable SaveDebugPreference
|
|||
|
}
|
|||
|
|
|||
|
# Allow running using RUNAS from a non-domain joined machine
|
|||
|
# runas /user:<Domain FQDN>\<Username> /netonly powershell.exe
|
|||
|
If (($Protocol -eq 'LDAP') -and ($UseAltCreds) -and ($DomainController -eq "") -and ($Credential -eq [Management.Automation.PSCredential]::Empty))
|
|||
|
{
|
|||
|
Try
|
|||
|
{
|
|||
|
$objDomain = [ADSI]""
|
|||
|
If(!($objDomain.name))
|
|||
|
{
|
|||
|
Write-Verbose "[Invoke-ADRecon] RUNAS Check, LDAP bind Unsuccessful"
|
|||
|
}
|
|||
|
$UseAltCreds = $false
|
|||
|
$objDomain.Dispose()
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
$UseAltCreds = $true
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
If ($UseAltCreds -and (($DomainController -eq "") -or ($Credential -eq [Management.Automation.PSCredential]::Empty)))
|
|||
|
{
|
|||
|
|
|||
|
If (($DomainController -ne "") -and ($Credential -eq [Management.Automation.PSCredential]::Empty))
|
|||
|
{
|
|||
|
Try
|
|||
|
{
|
|||
|
$Credential = Get-Credential
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Output "[Invoke-ADRecon] $($_.Exception.Message)"
|
|||
|
Return $null
|
|||
|
}
|
|||
|
}
|
|||
|
Else
|
|||
|
{
|
|||
|
Write-Output "Run Get-Help .\ADRecon.ps1 -Examples for additional information."
|
|||
|
Write-Output "[Invoke-ADRecon] Use the -DomainController and -Credential parameter."`n
|
|||
|
Return $null
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
Write-Output "[*] Running on $RanonComputer"
|
|||
|
|
|||
|
Switch ($Collect)
|
|||
|
{
|
|||
|
'Forest' { $ADRForest = $true }
|
|||
|
'Domain' {$ADRDomain = $true }
|
|||
|
'Trusts' { $ADRTrust = $true }
|
|||
|
'Sites' { $ADRSite = $true }
|
|||
|
'Subnets' { $ADRSubnet = $true }
|
|||
|
'PasswordPolicy' { $ADRPasswordPolicy = $true }
|
|||
|
'FineGrainedPasswordPolicy' { $ADRFineGrainedPasswordPolicy = $true }
|
|||
|
'DomainControllers' { $ADRDomainControllers = $true }
|
|||
|
'Users' { $ADRUsers = $true }
|
|||
|
'UserSPNs' { $ADRUserSPNs = $true }
|
|||
|
'PasswordAttributes' { $ADRPasswordAttributes = $true }
|
|||
|
'Groups' { $ADRGroups = $true }
|
|||
|
'GroupMembers' { $ADRGroupMembers = $true }
|
|||
|
'OUs' { $ADROUs = $true }
|
|||
|
'GPOs' { $ADRGPOs = $true }
|
|||
|
'gPLinks' { $ADRgPLinks = $true }
|
|||
|
'DNSZones' { $ADRDNSZones = $true }
|
|||
|
'Printers' { $ADRPrinters = $true }
|
|||
|
'Computers' { $ADRComputers = $true }
|
|||
|
'ComputerSPNs' { $ADRComputerSPNs = $true }
|
|||
|
'LAPS' { $ADRLAPS = $true }
|
|||
|
'BitLocker' { $ADRBitLocker = $true }
|
|||
|
'ACLs' { $ADRACLs = $true }
|
|||
|
'GPOReport'
|
|||
|
{
|
|||
|
$ADRGPOReport = $true
|
|||
|
$ADRCreate = $true
|
|||
|
}
|
|||
|
'Kerberoast' { $ADRKerberoast = $true }
|
|||
|
'DomainAccountsusedforServiceLogon' { $ADRDomainAccountsusedforServiceLogon = $true }
|
|||
|
'Default'
|
|||
|
{
|
|||
|
$ADRForest = $true
|
|||
|
$ADRDomain = $true
|
|||
|
$ADRTrust = $true
|
|||
|
$ADRSite = $true
|
|||
|
$ADRSubnet = $true
|
|||
|
$ADRPasswordPolicy = $true
|
|||
|
$ADRFineGrainedPasswordPolicy = $true
|
|||
|
$ADRDomainControllers = $true
|
|||
|
$ADRUsers = $true
|
|||
|
$ADRUserSPNs = $true
|
|||
|
$ADRPasswordAttributes = $true
|
|||
|
$ADRGroups = $true
|
|||
|
$ADRGroupMembers = $true
|
|||
|
$ADROUs = $true
|
|||
|
$ADRGPOs = $true
|
|||
|
$ADRgPLinks = $true
|
|||
|
$ADRDNSZones = $true
|
|||
|
$ADRPrinters = $true
|
|||
|
$ADRComputers = $true
|
|||
|
$ADRComputerSPNs = $true
|
|||
|
$ADRLAPS = $true
|
|||
|
$ADRBitLocker = $true
|
|||
|
$ADRACLs = $true
|
|||
|
$ADRGPOReport = $true
|
|||
|
#$ADRKerberoast = $true
|
|||
|
#$ADRDomainAccountsusedforServiceLogon = $true
|
|||
|
If ($OutputType -eq "Default")
|
|||
|
{
|
|||
|
[array] $OutputType = "CSV","Excel"
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
Switch ($OutputType)
|
|||
|
{
|
|||
|
'STDOUT' { $ADRSTDOUT = $true }
|
|||
|
'CSV'
|
|||
|
{
|
|||
|
$ADRCSV = $true
|
|||
|
$ADRCreate = $true
|
|||
|
}
|
|||
|
'XML'
|
|||
|
{
|
|||
|
$ADRXML = $true
|
|||
|
$ADRCreate = $true
|
|||
|
}
|
|||
|
'JSON'
|
|||
|
{
|
|||
|
$ADRJSON = $true
|
|||
|
$ADRCreate = $true
|
|||
|
}
|
|||
|
'HTML'
|
|||
|
{
|
|||
|
$ADRHTML = $true
|
|||
|
$ADRCreate = $true
|
|||
|
}
|
|||
|
'Excel'
|
|||
|
{
|
|||
|
$ADRExcel = $true
|
|||
|
$ADRCreate = $true
|
|||
|
}
|
|||
|
'All'
|
|||
|
{
|
|||
|
#$ADRSTDOUT = $true
|
|||
|
$ADRCSV = $true
|
|||
|
$ADRXML = $true
|
|||
|
$ADRJSON = $true
|
|||
|
$ADRHTML = $true
|
|||
|
$ADRExcel = $true
|
|||
|
$ADRCreate = $true
|
|||
|
[array] $OutputType = "CSV","XML","JSON","HTML","Excel"
|
|||
|
}
|
|||
|
'Default'
|
|||
|
{
|
|||
|
[array] $OutputType = "STDOUT"
|
|||
|
$ADRSTDOUT = $true
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
If ( ($ADRExcel) -and (-Not $ADRCSV) )
|
|||
|
{
|
|||
|
$ADRCSV = $true
|
|||
|
[array] $OutputType += "CSV"
|
|||
|
}
|
|||
|
|
|||
|
$returndir = Get-Location
|
|||
|
$date = Get-Date
|
|||
|
|
|||
|
# Create Output dir
|
|||
|
If ( ($ADROutputDir) -and ($ADRCreate) )
|
|||
|
{
|
|||
|
If (!(Test-Path $ADROutputDir))
|
|||
|
{
|
|||
|
New-Item $ADROutputDir -type directory | Out-Null
|
|||
|
If (!(Test-Path $ADROutputDir))
|
|||
|
{
|
|||
|
Write-Output "[Invoke-ADRecon] Error, invalid OutputDir Path ... Exiting"
|
|||
|
Return $null
|
|||
|
}
|
|||
|
}
|
|||
|
$ADROutputDir = $((Convert-Path $ADROutputDir).TrimEnd("\"))
|
|||
|
Write-Verbose "[*] Output Directory: $ADROutputDir"
|
|||
|
}
|
|||
|
ElseIf ($ADRCreate)
|
|||
|
{
|
|||
|
$ADROutputDir = -join($returndir,'\','ADRecon-Report-',$(Get-Date -UFormat %Y%m%d%H%M%S))
|
|||
|
New-Item $ADROutputDir -type directory | Out-Null
|
|||
|
If (!(Test-Path $ADROutputDir))
|
|||
|
{
|
|||
|
Write-Output "[Invoke-ADRecon] Error, could not create output directory"
|
|||
|
Return $null
|
|||
|
}
|
|||
|
$ADROutputDir = $((Convert-Path $ADROutputDir).TrimEnd("\"))
|
|||
|
Remove-Variable ADRCreate
|
|||
|
}
|
|||
|
Else
|
|||
|
{
|
|||
|
$ADROutputDir = $returndir
|
|||
|
}
|
|||
|
|
|||
|
If ($ADRCSV)
|
|||
|
{
|
|||
|
$CSVPath = [System.IO.DirectoryInfo] -join($ADROutputDir,'\','CSV-Files')
|
|||
|
New-Item $CSVPath -type directory | Out-Null
|
|||
|
If (!(Test-Path $CSVPath))
|
|||
|
{
|
|||
|
Write-Output "[Invoke-ADRecon] Error, could not create output directory"
|
|||
|
Return $null
|
|||
|
}
|
|||
|
Remove-Variable ADRCSV
|
|||
|
}
|
|||
|
|
|||
|
If ($ADRXML)
|
|||
|
{
|
|||
|
$XMLPath = [System.IO.DirectoryInfo] -join($ADROutputDir,'\','XML-Files')
|
|||
|
New-Item $XMLPath -type directory | Out-Null
|
|||
|
If (!(Test-Path $XMLPath))
|
|||
|
{
|
|||
|
Write-Output "[Invoke-ADRecon] Error, could not create output directory"
|
|||
|
Return $null
|
|||
|
}
|
|||
|
Remove-Variable ADRXML
|
|||
|
}
|
|||
|
|
|||
|
If ($ADRJSON)
|
|||
|
{
|
|||
|
$JSONPath = [System.IO.DirectoryInfo] -join($ADROutputDir,'\','JSON-Files')
|
|||
|
New-Item $JSONPath -type directory | Out-Null
|
|||
|
If (!(Test-Path $JSONPath))
|
|||
|
{
|
|||
|
Write-Output "[Invoke-ADRecon] Error, could not create output directory"
|
|||
|
Return $null
|
|||
|
}
|
|||
|
Remove-Variable ADRJSON
|
|||
|
}
|
|||
|
|
|||
|
If ($ADRHTML)
|
|||
|
{
|
|||
|
$HTMLPath = [System.IO.DirectoryInfo] -join($ADROutputDir,'\','HTML-Files')
|
|||
|
New-Item $HTMLPath -type directory | Out-Null
|
|||
|
If (!(Test-Path $HTMLPath))
|
|||
|
{
|
|||
|
Write-Output "[Invoke-ADRecon] Error, could not create output directory"
|
|||
|
Return $null
|
|||
|
}
|
|||
|
Remove-Variable ADRHTML
|
|||
|
}
|
|||
|
|
|||
|
# AD Login
|
|||
|
If ($UseAltCreds -and ($Protocol -eq 'ADWS'))
|
|||
|
{
|
|||
|
If (!(Test-Path ADR:))
|
|||
|
{
|
|||
|
Try
|
|||
|
{
|
|||
|
New-PSDrive -PSProvider ActiveDirectory -Name ADR -Root "" -Server $DomainController -Credential $Credential -ErrorAction Stop | Out-Null
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Output "[Invoke-ADRecon] $($_.Exception.Message)"
|
|||
|
If ($ADROutputDir)
|
|||
|
{
|
|||
|
Remove-EmptyADROutputDir $ADROutputDir $OutputType
|
|||
|
}
|
|||
|
Return $null
|
|||
|
}
|
|||
|
}
|
|||
|
Else
|
|||
|
{
|
|||
|
Remove-PSDrive ADR
|
|||
|
Try
|
|||
|
{
|
|||
|
New-PSDrive -PSProvider ActiveDirectory -Name ADR -Root "" -Server $DomainController -Credential $Credential -ErrorAction Stop | Out-Null
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Output "[Invoke-ADRecon] $($_.Exception.Message)"
|
|||
|
If ($ADROutputDir)
|
|||
|
{
|
|||
|
Remove-EmptyADROutputDir $ADROutputDir $OutputType
|
|||
|
}
|
|||
|
Return $null
|
|||
|
}
|
|||
|
}
|
|||
|
Set-Location ADR:
|
|||
|
Write-Debug "ADR PSDrive Created"
|
|||
|
}
|
|||
|
|
|||
|
If ($Protocol -eq 'LDAP')
|
|||
|
{
|
|||
|
If ($UseAltCreds)
|
|||
|
{
|
|||
|
Try
|
|||
|
{
|
|||
|
$objDomain = New-Object System.DirectoryServices.DirectoryEntry "LDAP://$($DomainController)", $Credential.UserName,$Credential.GetNetworkCredential().Password
|
|||
|
$objDomainRootDSE = New-Object System.DirectoryServices.DirectoryEntry "LDAP://$($DomainController)/RootDSE", $Credential.UserName,$Credential.GetNetworkCredential().Password
|
|||
|
}
|
|||
|
Catch
|
|||
|
{
|
|||
|
Write-Output "[Invoke-ADRecon] $($_.Exception.Message)"
|
|||
|
If ($ADROutputDir)
|
|||
|
{
|
|||
|
Remove-EmptyADROutputDir $ADROutputDir $OutputType
|
|||
|
}
|
|||
|
Return $null
|
|||
|
}
|
|||
|
If(!($objDomain.name))
|
|||
|
{
|
|||
|
Write-Output "[Invoke-ADRecon] LDAP bind Unsuccessful"
|
|||
|
If ($ADROutputDir)
|
|||
|
{
|
|||
|
Remove-EmptyADROutputDir $ADROutputDir $OutputType
|
|||
|
}
|
|||
|
Return $null
|
|||
|
}
|
|||
|
Else
|
|||
|
{
|
|||
|
Write-Output "[*] LDAP bind Successful"
|
|||
|
}
|
|||
|
}
|
|||
|
Else
|
|||
|
{
|
|||
|
$objDomain = [ADSI]""
|
|||
|
$objDomainRootDSE = ([ADSI] "LDAP://RootDSE")
|
|||
|
If(!($objDomain.name))
|
|||
|
{
|
|||
|
Write-Output "[Invoke-ADRecon] LDAP bind Unsuccessful"
|
|||
|
If ($ADROutputDir)
|
|||
|
{
|
|||
|
Remove-EmptyADROutputDir $ADROutputDir $OutputType
|
|||
|
}
|
|||
|
Return $null
|
|||
|
}
|
|||
|
}
|
|||
|
Write-Debug "LDAP Bing Successful"
|
|||
|
}
|
|||
|
|
|||
|
Write-Output "[*] Commencing - $date"
|
|||
|
If ($ADRDomain)
|
|||
|
{
|
|||
|
Write-Output "[-] Domain"
|
|||
|
$ADRObject = Get-ADRDomain -Protocol $Protocol -objDomain $objDomain -objDomainRootDSE $objDomainRootDSE -DomainController $DomainController -Credential $Credential
|
|||
|
If ($ADRObject)
|
|||
|
{
|
|||
|
Export-ADR -ADRObj $ADRObject -ADROutputDir $ADROutputDir -OutputType $OutputType -ADRModuleName "Domain"
|
|||
|
Remove-Variable ADRObject
|
|||
|
}
|
|||
|
Remove-Variable ADRDomain
|
|||
|
}
|
|||
|
If ($ADRForest)
|
|||
|
{
|
|||
|
Write-Output "[-] Forest"
|
|||
|
$ADRObject = Get-ADRForest -Protocol $Protocol -objDomain $objDomain -objDomainRootDSE $objDomainRootDSE -DomainController $DomainController -Credential $Credential
|
|||
|
If ($ADRObject)
|
|||
|
{
|
|||
|
Export-ADR -ADRObj $ADRObject -ADROutputDir $ADROutputDir -OutputType $OutputType -ADRModuleName "Forest"
|
|||
|
Remove-Variable ADRObject
|
|||
|
}
|
|||
|
Remove-Variable ADRForest
|
|||
|
}
|
|||
|
If ($ADRTrust)
|
|||
|
{
|
|||
|
Write-Output "[-] Trusts"
|
|||
|
$ADRObject = Get-ADRTrust -Protocol $Protocol -objDomain $objDomain
|
|||
|
If ($ADRObject)
|
|||
|
{
|
|||
|
Export-ADR -ADRObj $ADRObject -ADROutputDir $ADROutputDir -OutputType $OutputType -ADRModuleName "Trusts"
|
|||
|
Remove-Variable ADRObject
|
|||
|
}
|
|||
|
Remove-Variable ADRTrust
|
|||
|
}
|
|||
|
If ($ADRSite)
|
|||
|
{
|
|||
|
Write-Output "[-] Sites"
|
|||
|
$ADRObject = Get-ADRSite -Protocol $Protocol -objDomain $objDomain -objDomainRootDSE $objDomainRootDSE -DomainController $DomainController -Credential $Credential
|
|||
|
If ($ADRObject)
|
|||
|
{
|
|||
|
Export-ADR -ADRObj $ADRObject -ADROutputDir $ADROutputDir -OutputType $OutputType -ADRModuleName "Sites"
|
|||
|
Remove-Variable ADRObject
|
|||
|
}
|
|||
|
Remove-Variable ADRSite
|
|||
|
}
|
|||
|
If ($ADRSubnet)
|
|||
|
{
|
|||
|
Write-Output "[-] Subnets"
|
|||
|
$ADRObject = Get-ADRSubnet -Protocol $Protocol -objDomain $objDomain -objDomainRootDSE $objDomainRootDSE -DomainController $DomainController -Credential $Credential
|
|||
|
If ($ADRObject)
|
|||
|
{
|
|||
|
Export-ADR -ADRObj $ADRObject -ADROutputDir $ADROutputDir -OutputType $OutputType -ADRModuleName "Subnets"
|
|||
|
Remove-Variable ADRObject
|
|||
|
}
|
|||
|
Remove-Variable ADRSubnet
|
|||
|
}
|
|||
|
If ($ADRPasswordPolicy)
|
|||
|
{
|
|||
|
Write-Output "[-] Default Password Policy"
|
|||
|
$ADRObject = Get-ADRDefaultPasswordPolicy -Protocol $Protocol -objDomain $objDomain
|
|||
|
If ($ADRObject)
|
|||
|
{
|
|||
|
Export-ADR -ADRObj $ADRObject -ADROutputDir $ADROutputDir -OutputType $OutputType -ADRModuleName "DefaultPasswordPolicy"
|
|||
|
Remove-Variable ADRObject
|
|||
|
}
|
|||
|
Remove-Variable ADRPasswordPolicy
|
|||
|
}
|
|||
|
If ($ADRFineGrainedPasswordPolicy)
|
|||
|
{
|
|||
|
Write-Output "[-] Fine Grained Password Policy - May need a Privileged Account"
|
|||
|
$ADRObject = Get-ADRFineGrainedPasswordPolicy -Protocol $Protocol -objDomain $objDomain
|
|||
|
If ($ADRObject)
|
|||
|
{
|
|||
|
Export-ADR -ADRObj $ADRObject -ADROutputDir $ADROutputDir -OutputType $OutputType -ADRModuleName "FineGrainedPasswordPolicy"
|
|||
|
Remove-Variable ADRObject
|
|||
|
}
|
|||
|
Remove-Variable ADRFineGrainedPasswordPolicy
|
|||
|
}
|
|||
|
If ($ADRDomainControllers)
|
|||
|
{
|
|||
|
Write-Output "[-] Domain Controllers"
|
|||
|
$ADRObject = Get-ADRDomainController -Protocol $Protocol -objDomain $objDomain -Credential $Credential
|
|||
|
If ($ADRObject)
|
|||
|
{
|
|||
|
Export-ADR -ADRObj $ADRObject -ADROutputDir $ADROutputDir -OutputType $OutputType -ADRModuleName "DomainControllers"
|
|||
|
Remove-Variable ADRObject
|
|||
|
}
|
|||
|
Remove-Variable ADRDomainControllers
|
|||
|
}
|
|||
|
If ($ADRUsers)
|
|||
|
{
|
|||
|
Write-Output "[-] Users - May take some time"
|
|||
|
$ADRObject = Get-ADRUser -Protocol $Protocol -date $date -objDomain $objDomain -DormantTimeSpan $DormantTimeSpan -PageSize $PageSize -Threads $Threads
|
|||
|
If ($ADRObject)
|
|||
|
{
|
|||
|
Export-ADR -ADRObj $ADRObject -ADROutputDir $ADROutputDir -OutputType $OutputType -ADRModuleName "Users"
|
|||
|
Remove-Variable ADRObject
|
|||
|
}
|
|||
|
Remove-Variable ADRUsers
|
|||
|
}
|
|||
|
If ($ADRUserSPNs)
|
|||
|
{
|
|||
|
Write-Output "[-] User SPNs"
|
|||
|
$ADRObject = Get-ADRUserSPN -Protocol $Protocol -objDomain $objDomain -PageSize $PageSize -Threads $Threads
|
|||
|
If ($ADRObject)
|
|||
|
{
|
|||
|
Export-ADR -ADRObj $ADRObject -ADROutputDir $ADROutputDir -OutputType $OutputType -ADRModuleName "UserSPNs"
|
|||
|
Remove-Variable ADRObject
|
|||
|
}
|
|||
|
Remove-Variable ADRUserSPNs
|
|||
|
}
|
|||
|
If ($ADRPasswordAttributes)
|
|||
|
{
|
|||
|
Write-Output "[-] PasswordAttributes - Experimental"
|
|||
|
$ADRObject = Get-ADRPasswordAttributes -Protocol $Protocol -objDomain $objDomain -PageSize $PageSize
|
|||
|
If ($ADRObject)
|
|||
|
{
|
|||
|
Export-ADR -ADRObj $ADRObject -ADROutputDir $ADROutputDir -OutputType $OutputType -ADRModuleName "PasswordAttributes"
|
|||
|
Remove-Variable ADRObject
|
|||
|
}
|
|||
|
Remove-Variable ADRPasswordAttributes
|
|||
|
}
|
|||
|
If ($ADRGroups)
|
|||
|
{
|
|||
|
Write-Output "[-] Groups - May take some time"
|
|||
|
$ADRObject = Get-ADRGroup -Protocol $Protocol -objDomain $objDomain -PageSize $PageSize -Threads $Threads
|
|||
|
If ($ADRObject)
|
|||
|
{
|
|||
|
Export-ADR -ADRObj $ADRObject -ADROutputDir $ADROutputDir -OutputType $OutputType -ADRModuleName "Groups"
|
|||
|
Remove-Variable ADRObject
|
|||
|
}
|
|||
|
Remove-Variable ADRGroups
|
|||
|
}
|
|||
|
If ($ADRGroupMembers)
|
|||
|
{
|
|||
|
Write-Output "[-] Group Memberships - May take some time"
|
|||
|
|
|||
|
$ADRObject = Get-ADRGroupMember -Protocol $Protocol -objDomain $objDomain -PageSize $PageSize -Threads $Threads
|
|||
|
If ($ADRObject)
|
|||
|
{
|
|||
|
Export-ADR -ADRObj $ADRObject -ADROutputDir $ADROutputDir -OutputType $OutputType -ADRModuleName "GroupMembers"
|
|||
|
Remove-Variable ADRObject
|
|||
|
}
|
|||
|
Remove-Variable ADRGroupMembers
|
|||
|
}
|
|||
|
If ($ADROUs)
|
|||
|
{
|
|||
|
Write-Output "[-] OrganizationalUnits (OUs)"
|
|||
|
$ADRObject = Get-ADROU -Protocol $Protocol -objDomain $objDomain -PageSize $PageSize -Threads $Threads
|
|||
|
If ($ADRObject)
|
|||
|
{
|
|||
|
Export-ADR -ADRObj $ADRObject -ADROutputDir $ADROutputDir -OutputType $OutputType -ADRModuleName "OUs"
|
|||
|
Remove-Variable ADRObject
|
|||
|
}
|
|||
|
Remove-Variable ADROUs
|
|||
|
}
|
|||
|
If ($ADRGPOs)
|
|||
|
{
|
|||
|
Write-Output "[-] GPOs"
|
|||
|
$ADRObject = Get-ADRGPO -Protocol $Protocol -objDomain $objDomain -PageSize $PageSize -Threads $Threads
|
|||
|
If ($ADRObject)
|
|||
|
{
|
|||
|
Export-ADR -ADRObj $ADRObject -ADROutputDir $ADROutputDir -OutputType $OutputType -ADRModuleName "GPOs"
|
|||
|
Remove-Variable ADRObject
|
|||
|
}
|
|||
|
Remove-Variable ADRGPOs
|
|||
|
}
|
|||
|
If ($ADRgPLinks)
|
|||
|
{
|
|||
|
Write-Output "[-] gPLinks - Scope of Management (SOM)"
|
|||
|
$ADRObject = Get-ADRgPLink -Protocol $Protocol -objDomain $objDomain -PageSize $PageSize -Threads $Threads
|
|||
|
If ($ADRObject)
|
|||
|
{
|
|||
|
Export-ADR -ADRObj $ADRObject -ADROutputDir $ADROutputDir -OutputType $OutputType -ADRModuleName "gPLinks"
|
|||
|
Remove-Variable ADRObject
|
|||
|
}
|
|||
|
Remove-Variable ADRgPLinks
|
|||
|
}
|
|||
|
If ($ADRDNSZones)
|
|||
|
{
|
|||
|
Write-Output "[-] DNS Zones and Records"
|
|||
|
Get-ADRDNSZone -Protocol $Protocol -ADROutputDir $ADROutputDir -objDomain $objDomain -DomainController $DomainController -Credential $Credential -PageSize $PageSize -OutputType $OutputType
|
|||
|
Remove-Variable ADRDNSZones
|
|||
|
}
|
|||
|
If ($ADRPrinters)
|
|||
|
{
|
|||
|
Write-Output "[-] Printers"
|
|||
|
$ADRObject = Get-ADRPrinter -Protocol $Protocol -objDomain $objDomain -PageSize $PageSize -Threads $Threads
|
|||
|
If ($ADRObject)
|
|||
|
{
|
|||
|
Export-ADR -ADRObj $ADRObject -ADROutputDir $ADROutputDir -OutputType $OutputType -ADRModuleName "Printers"
|
|||
|
Remove-Variable ADRObject
|
|||
|
}
|
|||
|
Remove-Variable ADRPrinters
|
|||
|
}
|
|||
|
If ($ADRComputers)
|
|||
|
{
|
|||
|
Write-Output "[-] Computers - May take some time"
|
|||
|
$ADRObject = Get-ADRComputer -Protocol $Protocol -date $date -objDomain $objDomain -DormantTimeSpan $DormantTimeSpan -PassMaxAge $PassMaxAge -PageSize $PageSize -Threads $Threads
|
|||
|
If ($ADRObject)
|
|||
|
{
|
|||
|
Export-ADR -ADRObj $ADRObject -ADROutputDir $ADROutputDir -OutputType $OutputType -ADRModuleName "Computers"
|
|||
|
Remove-Variable ADRObject
|
|||
|
}
|
|||
|
Remove-Variable ADRComputers
|
|||
|
}
|
|||
|
If ($ADRComputerSPNs)
|
|||
|
{
|
|||
|
Write-Output "[-] Computer SPNs"
|
|||
|
$ADRObject = Get-ADRComputerSPN -Protocol $Protocol -objDomain $objDomain -PageSize $PageSize -Threads $Threads
|
|||
|
If ($ADRObject)
|
|||
|
{
|
|||
|
Export-ADR -ADRObj $ADRObject -ADROutputDir $ADROutputDir -OutputType $OutputType -ADRModuleName "ComputerSPNs"
|
|||
|
Remove-Variable ADRObject
|
|||
|
}
|
|||
|
Remove-Variable ADRComputerSPNs
|
|||
|
}
|
|||
|
If ($ADRLAPS)
|
|||
|
{
|
|||
|
Write-Output "[-] LAPS - Needs Privileged Account"
|
|||
|
$ADRObject = Get-ADRLAPSCheck -Protocol $Protocol -objDomain $objDomain -PageSize $PageSize -Threads $Threads
|
|||
|
If ($ADRObject)
|
|||
|
{
|
|||
|
Export-ADR -ADRObj $ADRObject -ADROutputDir $ADROutputDir -OutputType $OutputType -ADRModuleName "LAPS"
|
|||
|
Remove-Variable ADRObject
|
|||
|
}
|
|||
|
Remove-Variable ADRLAPS
|
|||
|
}
|
|||
|
If ($ADRBitLocker)
|
|||
|
{
|
|||
|
Write-Output "[-] BitLocker Recovery Keys - Needs Privileged Account"
|
|||
|
$ADRObject = Get-ADRBitLocker -Protocol $Protocol -objDomain $objDomain -DomainController $DomainController -Credential $Credential
|
|||
|
If ($ADRObject)
|
|||
|
{
|
|||
|
Export-ADR -ADRObj $ADRObject -ADROutputDir $ADROutputDir -OutputType $OutputType -ADRModuleName "BitLockerRecoveryKeys"
|
|||
|
Remove-Variable ADRObject
|
|||
|
}
|
|||
|
Remove-Variable ADRBitLocker
|
|||
|
}
|
|||
|
If ($ADRACLs)
|
|||
|
{
|
|||
|
Write-Output "[-] ACLs - May take some time"
|
|||
|
$ADRObject = Get-ADRACL -Protocol $Protocol -objDomain $objDomain -DomainController $DomainController -Credential $Credential -PageSize $PageSize -Threads $Threads
|
|||
|
Remove-Variable ADRACLs
|
|||
|
}
|
|||
|
If ($ADRGPOReport)
|
|||
|
{
|
|||
|
Write-Output "[-] GPOReport - May take some time"
|
|||
|
Get-ADRGPOReport -Protocol $Protocol -UseAltCreds $UseAltCreds -ADROutputDir $ADROutputDir
|
|||
|
Remove-Variable ADRGPOReport
|
|||
|
}
|
|||
|
If ($ADRKerberoast)
|
|||
|
{
|
|||
|
Write-Output "[-] Kerberoast"
|
|||
|
$ADRObject = Get-ADRKerberoast -Protocol $Protocol -objDomain $objDomain -Credential $Credential -PageSize $PageSize
|
|||
|
If ($ADRObject)
|
|||
|
{
|
|||
|
Export-ADR -ADRObj $ADRObject -ADROutputDir $ADROutputDir -OutputType $OutputType -ADRModuleName "Kerberoast"
|
|||
|
Remove-Variable ADRObject
|
|||
|
}
|
|||
|
Remove-Variable ADRKerberoast
|
|||
|
}
|
|||
|
If ($ADRDomainAccountsusedforServiceLogon)
|
|||
|
{
|
|||
|
Write-Output "[-] Domain Accounts used for Service Logon - Needs Privileged Account"
|
|||
|
$ADRObject = Get-ADRDomainAccountsusedforServiceLogon -Protocol $Protocol -objDomain $objDomain -Credential $Credential -PageSize $PageSize -Threads $Threads
|
|||
|
If ($ADRObject)
|
|||
|
{
|
|||
|
Export-ADR -ADRObj $ADRObject -ADROutputDir $ADROutputDir -OutputType $OutputType -ADRModuleName "DomainAccountsusedforServiceLogon"
|
|||
|
Remove-Variable ADRObject
|
|||
|
}
|
|||
|
Remove-Variable ADRDomainAccountsusedforServiceLogon
|
|||
|
}
|
|||
|
|
|||
|
$TotalTime = "{0:N2}" -f ((Get-DateDiff -Date1 (Get-Date) -Date2 $date).TotalMinutes)
|
|||
|
|
|||
|
$AboutADRecon = Get-ADRAbout -Protocol $Protocol -date $date -ADReconVersion $ADReconVersion -Credential $Credential -RanonComputer $RanonComputer -TotalTime $TotalTime
|
|||
|
|
|||
|
If ( ($OutputType -Contains "CSV") -or ($OutputType -Contains "XML") -or ($OutputType -Contains "JSON") -or ($OutputType -Contains "HTML") )
|
|||
|
{
|
|||
|
If ($AboutADRecon)
|
|||
|
{
|
|||
|
Export-ADR -ADRObj $AboutADRecon -ADROutputDir $ADROutputDir -OutputType $OutputType -ADRModuleName "AboutADRecon"
|
|||
|
}
|
|||
|
Write-Output "[*] Total Execution Time (mins): $($TotalTime)"
|
|||
|
Write-Output "[*] Output Directory: $ADROutputDir"
|
|||
|
$ADRSTDOUT = $false
|
|||
|
}
|
|||
|
|
|||
|
Switch ($OutputType)
|
|||
|
{
|
|||
|
'STDOUT'
|
|||
|
{
|
|||
|
If ($ADRSTDOUT)
|
|||
|
{
|
|||
|
Write-Output "[*] Total Execution Time (mins): $($TotalTime)"
|
|||
|
}
|
|||
|
}
|
|||
|
'HTML'
|
|||
|
{
|
|||
|
Export-ADR -ADRObj $(New-Object PSObject) -ADROutputDir $ADROutputDir -OutputType $([array] "HTML") -ADRModuleName "Index"
|
|||
|
}
|
|||
|
'EXCEL'
|
|||
|
{
|
|||
|
Export-ADRExcel $ADROutputDir
|
|||
|
}
|
|||
|
}
|
|||
|
Remove-Variable TotalTime
|
|||
|
Remove-Variable AboutADRecon
|
|||
|
Set-Location $returndir
|
|||
|
Remove-Variable returndir
|
|||
|
|
|||
|
If (($Protocol -eq 'ADWS') -and $UseAltCreds)
|
|||
|
{
|
|||
|
Remove-PSDrive ADR
|
|||
|
}
|
|||
|
|
|||
|
If ($Protocol -eq 'LDAP')
|
|||
|
{
|
|||
|
$objDomain.Dispose()
|
|||
|
$objDomainRootDSE.Dispose()
|
|||
|
}
|
|||
|
|
|||
|
If ($ADROutputDir)
|
|||
|
{
|
|||
|
Remove-EmptyADROutputDir $ADROutputDir $OutputType
|
|||
|
}
|
|||
|
|
|||
|
Remove-Variable ADReconVersion
|
|||
|
Remove-Variable RanonComputer
|
|||
|
}
|
|||
|
|
|||
|
If ($Log)
|
|||
|
{
|
|||
|
Start-Transcript -Path "$(Get-Location)\ADRecon-Console-Log.txt"
|
|||
|
}
|
|||
|
|
|||
|
Invoke-ADRecon -GenExcel $GenExcel -Protocol $Protocol -Collect $Collect -DomainController $DomainController -Credential $Credential -OutputType $OutputType -ADROutputDir $OutputDir -DormantTimeSpan $DormantTimeSpan -PassMaxAge $PassMaxAge -PageSize $PageSize -Threads $Threads
|
|||
|
|
|||
|
If ($Log)
|
|||
|
{
|
|||
|
Stop-Transcript
|
|||
|
}
|