aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Exfiltration/Invoke-CredentialInjection.ps16
-rw-r--r--Privesc/PowerUp.ps12
-rwxr-xr-xRecon/PowerView.ps11351
3 files changed, 1308 insertions, 51 deletions
diff --git a/Exfiltration/Invoke-CredentialInjection.ps1 b/Exfiltration/Invoke-CredentialInjection.ps1
index d6f3c4c..b025328 100644
--- a/Exfiltration/Invoke-CredentialInjection.ps1
+++ b/Exfiltration/Invoke-CredentialInjection.ps1
@@ -2416,7 +2416,7 @@ function Invoke-CredentialInjection
$PEInfo = Get-PEBasicInfo -PEBytes $PEBytes -Win32Types $Win32Types
$OriginalImageBase = $PEInfo.OriginalImageBase
$NXCompatible = $true
- if (($PEInfo.DllCharacteristics -band $Win32Constants.IMAGE_DLLCHARACTERISTICS_NX_COMPAT) -ne $Win32Constants.IMAGE_DLLCHARACTERISTICS_NX_COMPAT)
+ if (([Int] $PEInfo.DllCharacteristics -band $Win32Constants.IMAGE_DLLCHARACTERISTICS_NX_COMPAT) -ne $Win32Constants.IMAGE_DLLCHARACTERISTICS_NX_COMPAT)
{
Write-Warning "PE is not compatible with DEP, might cause issues" -WarningAction Continue
$NXCompatible = $false
@@ -2474,7 +2474,7 @@ function Invoke-CredentialInjection
Write-Verbose "Allocating memory for the PE and write its headers to memory"
[IntPtr]$LoadAddr = [IntPtr]::Zero
- if (($PEInfo.DllCharacteristics -band $Win32Constants.IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE) -ne $Win32Constants.IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE)
+ if (([Int] $PEInfo.DllCharacteristics -band $Win32Constants.IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE) -ne $Win32Constants.IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE)
{
Write-Warning "PE file being reflectively loaded is not ASLR compatible. If the loading fails, try restarting PowerShell and trying again" -WarningAction Continue
[IntPtr]$LoadAddr = $OriginalImageBase
@@ -3346,7 +3346,7 @@ function Invoke-CredentialInjection
}
elseif ($PsCmdlet.ParameterSetName -ieq "ExistingWinLogon")
{
- $WinLogonProcessId = (Get-Process -Name "winlogon")[0].Id
+ $WinLogonProcessId = (Get-Process -Name "winlogon"| Select-Object -first 1).Id
}
#Get a ushort representing the logontype
diff --git a/Privesc/PowerUp.ps1 b/Privesc/PowerUp.ps1
index 5852904..2680986 100644
--- a/Privesc/PowerUp.ps1
+++ b/Privesc/PowerUp.ps1
@@ -2698,7 +2698,7 @@ Required Dependencies: Get-ServiceDetail, Get-ModifiablePath, Write-ServiceBinar
.DESCRIPTION
-Takes a esrvice Name or a ServiceProcess.ServiceController on the pipeline where the
+Takes a service Name or a ServiceProcess.ServiceController on the pipeline where the
current user can modify the associated service binary listed in the binPath. Backs up
the original service binary to "OriginalService.exe.bak" in service binary location,
and then uses Write-ServiceBinary to create a C# service binary that either adds
diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1
index 0a4e000..40b060c 100755
--- a/Recon/PowerView.ps1
+++ b/Recon/PowerView.ps1
@@ -731,6 +731,363 @@ New-Struct. :P
#
########################################################
+Function New-DynamicParameter {
+<#
+.SYNOPSIS
+
+Helper function to simplify creating dynamic parameters.
+
+ Adapated from https://beatcracker.wordpress.com/2015/08/10/dynamic-parameters-validateset-and-enums/.
+ 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 created.
+ 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 create dynamic parameters en masse,
+with one function call. There is no need to create and maintain custom RuntimeDefinedParameterDictionary.
+
+2. Dynamic parameters are created by separate function calls and added to the RuntimeDefinedParameterDictionary you created beforehand.
+Then you output this RuntimeDefinedParameterDictionary to the pipeline. This allows more fine-grained control of the dynamic parameters,
+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-parameters/
+ http://jrich523.wordpress.com/2013/05/30/powershell-simple-way-to-add-dynamic-parameters-to-advanced-function/
+
+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 all parameters sets.
+
+.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 = $true, ParameterSetName = 'DynamicParameter')]
+ [ValidateNotNullOrEmpty()]
+ [string]$Name,
+
+ [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')]
+ [System.Type]$Type = [int],
+
+ [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')]
+ [string[]]$Alias,
+
+ [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')]
+ [switch]$Mandatory,
+
+ [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')]
+ [int]$Position,
+
+ [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')]
+ [string]$HelpMessage,
+
+ [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')]
+ [switch]$DontShow,
+
+ [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')]
+ [switch]$ValueFromPipeline,
+
+ [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')]
+ [switch]$ValueFromPipelineByPropertyName,
+
+ [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')]
+ [switch]$ValueFromRemainingArguments,
+
+ [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')]
+ [string]$ParameterSetName = '__AllParameterSets',
+
+ [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')]
+ [switch]$AllowNull,
+
+ [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')]
+ [switch]$AllowEmptyString,
+
+ [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')]
+ [switch]$AllowEmptyCollection,
+
+ [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')]
+ [switch]$ValidateNotNull,
+
+ [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')]
+ [switch]$ValidateNotNullOrEmpty,
+
+ [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')]
+ [ValidateCount(2,2)]
+ [int[]]$ValidateCount,
+
+ [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')]
+ [ValidateCount(2,2)]
+ [int[]]$ValidateRange,
+
+ [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')]
+ [ValidateCount(2,2)]
+ [int[]]$ValidateLength,
+
+ [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')]
+ [ValidateNotNullOrEmpty()]
+ [string]$ValidatePattern,
+
+ [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')]
+ [ValidateNotNullOrEmpty()]
+ [scriptblock]$ValidateScript,
+
+ [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')]
+ [ValidateNotNullOrEmpty()]
+ [string[]]$ValidateSet,
+
+ [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')]
+ [ValidateNotNullOrEmpty()]
+ [ValidateScript({
+ if(!($_ -is [System.Management.Automation.RuntimeDefinedParameterDictionary]))
+ {
+ Throw 'Dictionary must be a System.Management.Automation.RuntimeDefinedParameterDictionary object'
+ }
+ $true
+ })]
+ $Dictionary = $false,
+
+ [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'CreateVariables')]
+ [switch]$CreateVariables,
+
+ [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'CreateVariables')]
+ [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.PSBoundParametersDictionary object'
+ }
+ $true
+ })]
+ $BoundParameters
+ )
+
+ Begin {
+ $InternalDictionary = New-Object -TypeName System.Management.Automation.RuntimeDefinedParameterDictionary
+ 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 unbound parameters
+ $UnboundParameters = (Get-Command -Name ($PSCmdlet.MyInvocation.InvocationName)).Parameters.GetEnumerator() |
+ # Find parameters that are belong to the current parameter set
+ Where-Object { $_.Value.ParameterSets.Keys -contains $PsCmdlet.ParameterSetName } |
+ 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 value (if specified)
+ $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|ValueFromPipeline|ValueFromPipelineByPropertyName|ValueFromRemainingArguments)$'
+ $ValidationRegex = '^(AllowNull|AllowEmptyString|AllowEmptyCollection|ValidateCount|ValidateLength|ValidatePattern|ValidateRange|ValidateScript|ValidateSet|ValidateNotNull|ValidateNotNullOrEmpty)$'
+ $AliasRegex = '^Alias$'
+ $ParameterAttribute = New-Object -TypeName System.Management.Automation.ParameterAttribute
+
+ 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.Attribute]
+ switch -regex ($PSBoundParameters.Keys) {
+ $ValidationRegex {
+ Try {
+ $ParameterOptions = New-Object -TypeName "System.Management.Automation.${_}Attribute" -ArgumentList (. $GetVar) -ErrorAction Stop
+ $AttributeCollection.Add($ParameterOptions)
+ }
+ Catch { $_ }
+ continue
+ }
+ $AliasRegex {
+ Try {
+ $ParameterAlias = New-Object -TypeName System.Management.Automation.AliasAttribute -ArgumentList (. $GetVar) -ErrorAction Stop
+ $AttributeCollection.Add($ParameterAlias)
+ continue
+ }
+ Catch { $_ }
+ }
+ }
+ $AttributeCollection.Add($ParameterAttribute)
+ $Parameter = New-Object -TypeName System.Management.Automation.RuntimeDefinedParameter -ArgumentList @($Name, $Type, $AttributeCollection)
+ $DPDictionary.Add($Name, $Parameter)
+ }
+ }
+ }
+
+ End {
+ if(!$CreateVariables -and !$Dictionary) {
+ $DPDictionary
+ }
+ }
+}
+
+
function Get-IniContent {
<#
.SYNOPSIS
@@ -2790,8 +3147,8 @@ A custom PSObject with LDAP hashtable properties translated.
$Properties.PropertyNames | ForEach-Object {
if ($_ -ne 'adspath') {
if (($_ -eq 'objectsid') -or ($_ -eq 'sidhistory')) {
- # convert the SID to a string
- $ObjectProperties[$_] = (New-Object System.Security.Principal.SecurityIdentifier($Properties[$_][0], 0)).Value
+ # convert all listed sids (i.e. if multiple are listed in sidHistory)
+ $ObjectProperties[$_] = $Properties[$_] | ForEach-Object { (New-Object System.Security.Principal.SecurityIdentifier($_, 0)).Value }
}
elseif ($_ -eq 'grouptype') {
$ObjectProperties[$_] = $Properties[$_][0] -as $GroupTypeEnum
@@ -4234,19 +4591,16 @@ 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, Get-ForestSchemaClass
+Required Dependencies: Get-Domain, Get-DomainUser, Get-DomainGroup, Get-DomainComputer
.DESCRIPTION
-Enumerates the schema for the specified -ClassName (if passed) by using Get-ForestSchemaClass.
-If a -ReferenceObject is passed, the class is extracted from the passed object.
-A 'reference' set of property names is then calculated, either from a standard set preserved
+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. These property names are substracted
-from the master schema propertyu name list to retrieve a set of 'non-standard' properties.
-Every user/group/computer object (depending on determined class) are enumerated, and for each
-object, if the object has a 'non-standard' property set, the object samAccountName, property
-name, and property value are output to the pipeline.
+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
@@ -4302,13 +4656,13 @@ for connection to the target domain.
.EXAMPLE
-Find-DomainObjectPropertyOutlier -User
+Find-DomainObjectPropertyOutlier -ClassName 'User'
Enumerates users in the current domain with 'outlier' properties filled in.
.EXAMPLE
-Find-DomainObjectPropertyOutlier -Group -Domain external.local
+Find-DomainObjectPropertyOutlier -ClassName 'Group' -Domain external.local
Enumerates groups in the external.local forest/domain with 'outlier' properties filled in.
@@ -4428,7 +4782,7 @@ Custom PSObject with translated object property outliers.
Write-Verbose "[Find-DomainObjectPropertyOutlier] Extracting property names from -ReferenceObject to use as the reference property set"
$ReferenceObjectProperties = Get-Member -InputObject $ReferenceObject -MemberType NoteProperty | Select-Object -Expand Name
$ReferenceObjectClass = $ReferenceObject.objectclass | Select-Object -Last 1
- Write-Verbose "[Find-DomainObjectPropertyOutlier] Caldulated ReferenceObjectClass : $ReferenceObjectClass"
+ Write-Verbose "[Find-DomainObjectPropertyOutlier] Calculated ReferenceObjectClass : $ReferenceObjectClass"
}
else {
Write-Verbose "[Find-DomainObjectPropertyOutlier] Using the default reference property set for the object class '$ClassName'"
@@ -4436,34 +4790,30 @@ Custom PSObject with translated object property outliers.
if (($ClassName -eq 'User') -or ($ReferenceObjectClass -eq 'User')) {
$Objects = Get-DomainUser @SearcherArguments
- $SchemaClass = Get-ForestSchemaClass @SchemaArguments -ClassName 'User'
- $ReferenceObjectProperties = $UserReferencePropertySet
+ if (-not $ReferenceObjectProperties) {
+ $ReferenceObjectProperties = $UserReferencePropertySet
+ }
}
elseif (($ClassName -eq 'Group') -or ($ReferenceObjectClass -eq 'Group')) {
$Objects = Get-DomainGroup @SearcherArguments
- $SchemaClass = Get-ForestSchemaClass @SchemaArguments -ClassName 'Group'
- $ReferenceObjectProperties = $GroupReferencePropertySet
+ if (-not $ReferenceObjectProperties) {
+ $ReferenceObjectProperties = $GroupReferencePropertySet
+ }
}
elseif (($ClassName -eq 'Computer') -or ($ReferenceObjectClass -eq 'Computer')) {
- Write-Verbose "COMPUTER!"
$Objects = Get-DomainComputer @SearcherArguments
- $SchemaClass = Get-ForestSchemaClass @SchemaArguments -ClassName 'Computer'
- $ReferenceObjectProperties = $ComputerReferencePropertySet
+ if (-not $ReferenceObjectProperties) {
+ $ReferenceObjectProperties = $ComputerReferencePropertySet
+ }
}
else {
throw "[Find-DomainObjectPropertyOutlier] Invalid class: $ClassName"
}
- $SchemaProperties = $SchemaClass | Select-Object -ExpandProperty OptionalProperties | Select-Object -ExpandProperty name
- $SchemaProperties += $SchemaClass | Select-Object -ExpandProperty MandatoryProperties | Select-Object -ExpandProperty name
-
- # find the schema properties that are NOT in the first returned reference property set
- $NonstandardProperties = Compare-Object -ReferenceObject $ReferenceObjectProperties -DifferenceObject $SchemaProperties -PassThru
-
ForEach ($Object in $Objects) {
$ObjectProperties = Get-Member -InputObject $Object -MemberType NoteProperty | Select-Object -Expand Name
ForEach($ObjectProperty in $ObjectProperties) {
- if ($NonstandardProperties -Contains $ObjectProperty) {
+ if ($ReferenceObjectProperties -NotContains $ObjectProperty) {
$Out = New-Object PSObject
$Out | Add-Member Noteproperty 'SamAccountName' $Object.SamAccountName
$Out | Add-Member Noteproperty 'Property' $ObjectProperty
@@ -4511,6 +4861,11 @@ Wildcards accepted. Also accepts DOMAIN\user format.
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).
@@ -4623,6 +4978,12 @@ Search for users with a primary group ID other than 513 ('domain users') and onl
.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
@@ -4742,6 +5103,14 @@ The raw DirectoryServices.SearchResult object, if -Raw is enabled.
$Raw
)
+ 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 }
@@ -4758,6 +5127,11 @@ The raw DirectoryServices.SearchResult object, if -Raw is enabled.
}
PROCESS {
+ #bind dynamic parameter to a friendly variable
+ if ($PSBoundParameters -and ($PSBoundParameters.Count -ne 0)) {
+ New-DynamicParameter -CreateVariables -BoundParameters $PSBoundParameters
+ }
+
if ($UserSearcher) {
$IdentityFilter = ''
$Filter = ''
@@ -4768,6 +5142,17 @@ The raw DirectoryServices.SearchResult object, if -Raw is enabled.
}
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 'DC=','' -replace ',','.'
+ Write-Verbose "[Get-DomainUser] Extracted domain '$IdentityDomain' from '$IdentityInstance'"
+ $SearcherArguments['Domain'] = $IdentityDomain
+ $UserSearcher = Get-DomainSearcher @SearcherArguments
+ if (-not $UserSearcher) {
+ Write-Warning "[Get-DomainUser] Unable to retrieve domain searcher for '$IdentityDomain'"
+ }
+ }
}
elseif ($IdentityInstance -imatch '^[0-9A-F]{8}-([0-9A-F]{4}-){3}[0-9A-F]{12}$') {
$GuidByteString = (([Guid]$IdentityInstance).ToByteArray() | ForEach-Object { '\' + $_.ToString('X2') }) -join ''
@@ -4823,6 +5208,19 @@ The raw DirectoryServices.SearchResult object, if -Raw is enabled.
$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)"
+ }
+ }
+
$UserSearcher.filter = "(&(samAccountType=805306368)$Filter)"
Write-Verbose "[Get-DomainUser] filter string: $($UserSearcher.filter)"
@@ -5565,6 +5963,11 @@ A SamAccountName (e.g. WINDOWS10$), DistinguishedName (e.g. CN=WINDOWS10,CN=Comp
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1124), GUID (e.g. 4f16b6bc-7010-4cbf-b628-f3cfe20f6994),
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.
@@ -5666,6 +6069,12 @@ Returns all MS SQL servers in the testlab.local domain.
.EXAMPLE
+Get-DomainComputer -UACFilter TRUSTED_FOR_DELEGATION,SERVER_TRUST_ACCOUNT -Properties dnshostname
+
+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.
@@ -5779,6 +6188,14 @@ The raw DirectoryServices.SearchResult object, if -Raw is enabled.
$Raw
)
+ 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 }
@@ -5795,8 +6212,12 @@ The raw DirectoryServices.SearchResult object, if -Raw is enabled.
}
PROCESS {
- if ($CompSearcher) {
+ #bind dynamic parameter to a friendly variable
+ if ($PSBoundParameters -and ($PSBoundParameters.Count -ne 0)) {
+ New-DynamicParameter -CreateVariables -BoundParameters $PSBoundParameters
+ }
+ if ($CompSearcher) {
$IdentityFilter = ''
$Filter = ''
$Identity | Where-Object {$_} | ForEach-Object {
@@ -5806,6 +6227,17 @@ The raw DirectoryServices.SearchResult object, if -Raw is enabled.
}
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 'DC=','' -replace ',','.'
+ Write-Verbose "[Get-DomainComputer] Extracted domain '$IdentityDomain' from '$IdentityInstance'"
+ $SearcherArguments['Domain'] = $IdentityDomain
+ $CompSearcher = Get-DomainSearcher @SearcherArguments
+ if (-not $CompSearcher) {
+ Write-Warning "[Get-DomainComputer] Unable to retrieve domain searcher for '$IdentityDomain'"
+ }
+ }
}
elseif ($IdentityInstance.Contains('.')) {
$IdentityFilter += "(|(name=$IdentityInstance)(dnshostname=$IdentityInstance))"
@@ -5854,6 +6286,18 @@ The raw DirectoryServices.SearchResult object, if -Raw is enabled.
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)"
+ }
+ }
$CompSearcher.filter = "(&(samAccountType=805306369)$Filter)"
Write-Verbose "[Get-DomainComputer] Get-DomainComputer filter string: $($CompSearcher.filter)"
@@ -5914,6 +6358,11 @@ A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201).
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.
@@ -6086,6 +6535,14 @@ The raw DirectoryServices.SearchResult object, if -Raw is enabled.
$Raw
)
+ 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 }
@@ -6102,6 +6559,10 @@ The raw DirectoryServices.SearchResult object, if -Raw is enabled.
}
PROCESS {
+ #bind dynamic parameter to a friendly variable
+ if ($PSBoundParameters -and ($PSBoundParameters.Count -ne 0)) {
+ New-DynamicParameter -CreateVariables -BoundParameters $PSBoundParameters
+ }
if ($ObjectSearcher) {
$IdentityFilter = ''
$Filter = ''
@@ -6112,6 +6573,17 @@ The raw DirectoryServices.SearchResult object, if -Raw is enabled.
}
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 'DC=','' -replace ',','.'
+ Write-Verbose "[Get-DomainObject] Extracted domain '$IdentityDomain' from '$IdentityInstance'"
+ $SearcherArguments['Domain'] = $IdentityDomain
+ $ObjectSearcher = Get-DomainSearcher @SearcherArguments
+ if (-not $ObjectSearcher) {
+ Write-Warning "[Get-DomainObject] Unable to retrieve domain searcher for '$IdentityDomain'"
+ }
+ }
}
elseif ($IdentityInstance -imatch '^[0-9A-F]{8}-([0-9A-F]{4}-){3}[0-9A-F]{12}$') {
$GuidByteString = (([Guid]$IdentityInstance).ToByteArray() | ForEach-Object { '\' + $_.ToString('X2') }) -join ''
@@ -6144,6 +6616,19 @@ The raw DirectoryServices.SearchResult object, if -Raw is enabled.
$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 '') {
$ObjectSearcher.filter = "(&$Filter)"
}
@@ -6175,6 +6660,485 @@ The raw DirectoryServices.SearchResult object, if -Raw is enabled.
}
+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=local),
+SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201).
+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','94299db1-e3e7-48f9-845b-3bffef8bedbb' | Get-DomainObjectAttributeHistory -Properties objectClass | ft
+
+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-track-security-descriptor-modifications/
+#>
+
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')]
+ [OutputType('PowerView.ADObjectAttributeHistory')]
+ [CmdletBinding()]
+ Param(
+ [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
+ [Alias('DistinguishedName', 'SamAccountName', 'Name', 'MemberDistinguishedName', 'MemberName')]
+ [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'] = $ResultPageSize }
+ if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
+ 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_DATA' -ErrorAction SilentlyContinue
+ 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.ftimeLastOriginatingChange
+ $Output | Add-Member NoteProperty 'Version' $TempObject.dwVersion
+ $Output | Add-Member NoteProperty 'LastOriginatingDsaDN' $TempObject.pszLastOriginatingDsaDN
+ $Output.PSObject.TypeNames.Insert(0, 'PowerView.ADObjectAttributeHistory')
+ $Output
+ }
+ }
+ else {
+ Write-Verbose "[Get-DomainObjectAttributeHistory] Error retrieving 'msds-replattributemetadata' for '$ObjectDN'"
+ }
+ }
+ }
+ }
+}
+
+
+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=local),
+SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201).
+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-DomainObjectLinkedAttributeHistory
+
+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-the-group-membership/
+#>
+
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')]
+ [OutputType('PowerView.ADObjectLinkedAttributeHistory')]
+ [CmdletBinding()]
+ Param(
+ [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
+ [Alias('DistinguishedName', 'SamAccountName', 'Name', 'MemberDistinguishedName', 'MemberName')]
+ [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'] = $ResultPageSize }
+ if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
+ 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_DATA' -ErrorAction SilentlyContinue
+ 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.ftimeLastOriginatingChange
+ $Output | Add-Member NoteProperty 'Version' $TempObject.dwVersion
+ $Output | Add-Member NoteProperty 'LastOriginatingDsaDN' $TempObject.pszLastOriginatingDsaDN
+ $Output.PSObject.TypeNames.Insert(0, 'PowerView.ADObjectLinkedAttributeHistory')
+ $Output
+ }
+ }
+ else {
+ Write-Verbose "[Get-DomainObjectLinkedAttributeHistory] Error retrieving 'msds-replvaluemetadata' for '$ObjectDN'"
+ }
+ }
+ }
+ }
+}
+
+
function Set-DomainObject {
<#
.SYNOPSIS
@@ -6328,13 +7292,13 @@ scriptpath
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[CmdletBinding()]
Param(
- [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
+ [Parameter(Position = 0, Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('DistinguishedName', 'SamAccountName', 'Name')]
[String[]]
$Identity,
[ValidateNotNullOrEmpty()]
- [Alias('Reaplce')]
+ [Alias('Replace')]
[Hashtable]
$Set,
@@ -6966,7 +7930,8 @@ function Get-DomainObjectAcl {
<#
.SYNOPSIS
-Returns the ACLs associated with a specific active directory object.
+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
@@ -6978,6 +7943,10 @@ A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201).
Wildcards accepted.
+.PARAMETER Sacl
+
+Switch. Return the SACL instead of the DACL for the object (default behavior).
+
.PARAMETER ResolveGUIDs
Switch. Resolve GUIDs to their display names.
@@ -7039,6 +8008,12 @@ 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
@@ -7060,6 +8035,9 @@ Custom PSObject with ACL entries.
$Identity,
[Switch]
+ $Sacl,
+
+ [Switch]
$ResolveGUIDs,
[String]
@@ -7108,9 +8086,15 @@ Custom PSObject with ACL entries.
BEGIN {
$SearcherArguments = @{
- 'SecurityMasks' = 'Dacl'
'Properties' = 'samaccountname,ntsecuritydescriptor,distinguishedname,objectsid'
}
+
+ if ($PSBoundParameters['Sacl']) {
+ $SearcherArguments['SecurityMasks'] = 'Sacl'
+ }
+ else {
+ $SearcherArguments['SecurityMasks'] = 'Dacl'
+ }
if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
@@ -7145,6 +8129,17 @@ Custom PSObject with ACL entries.
}
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 'DC=','' -replace ',','.'
+ Write-Verbose "[Get-DomainObjectAcl] Extracted domain '$IdentityDomain' from '$IdentityInstance'"
+ $SearcherArguments['Domain'] = $IdentityDomain
+ $Searcher = Get-DomainSearcher @SearcherArguments
+ if (-not $Searcher) {
+ Write-Warning "[Get-DomainObjectAcl] Unable to retrieve domain searcher for '$IdentityDomain'"
+ }
+ }
}
elseif ($IdentityInstance -imatch '^[0-9A-F]{8}-([0-9A-F]{4}-){3}[0-9A-F]{12}$') {
$GuidByteString = (([Guid]$IdentityInstance).ToByteArray() | ForEach-Object { '\' + $_.ToString('X2') }) -join ''
@@ -7183,8 +8178,7 @@ Custom PSObject with ACL entries.
}
try {
- New-Object Security.AccessControl.RawSecurityDescriptor -ArgumentList $Object['ntsecuritydescriptor'][0], 0 | Select-Object -Expand DiscretionaryAcl | ForEach-Object {
-
+ New-Object Security.AccessControl.RawSecurityDescriptor -ArgumentList $Object['ntsecuritydescriptor'][0], 0 | ForEach-Object { if ($PSBoundParameters['Sacl']) {$_.SystemAcl} else {$_.DiscretionaryAcl} } | ForEach-Object {
if ($PSBoundParameters['RightsFilter']) {
$GuidFilter = Switch ($RightsFilter) {
'ResetPassword' { '00299570-246d-11d0-a768-00aa006e0529' }
@@ -7205,7 +8199,6 @@ Custom PSObject with ACL entries.
if ($Continue) {
$_ | Add-Member NoteProperty 'ActiveDirectoryRights' ([Enum]::ToObject([System.DirectoryServices.ActiveDirectoryRights], $_.AccessMask))
-
if ($GUIDs) {
# if we're resolving GUIDs, map them them to the resolved hash table
$AclProperties = @{}
@@ -8048,6 +9041,17 @@ Custom PSObject with translated OU property fields.
$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=')) -replace 'DC=','' -replace ',','.'
+ Write-Verbose "[Get-DomainOU] Extracted domain '$IdentityDomain' from '$IdentityInstance'"
+ $SearcherArguments['Domain'] = $IdentityDomain
+ $OUSearcher = Get-DomainSearcher @SearcherArguments
+ if (-not $OUSearcher) {
+ Write-Warning "[Get-DomainOU] Unable to retrieve domain searcher for '$IdentityDomain'"
+ }
+ }
}
else {
try {
@@ -8307,6 +9311,17 @@ Custom PSObject with translated site property fields.
$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=')) -replace 'DC=','' -replace ',','.'
+ Write-Verbose "[Get-DomainSite] Extracted domain '$IdentityDomain' from '$IdentityInstance'"
+ $SearcherArguments['Domain'] = $IdentityDomain
+ $SiteSearcher = Get-DomainSearcher @SearcherArguments
+ if (-not $SiteSearcher) {
+ Write-Warning "[Get-DomainSite] Unable to retrieve domain searcher for '$IdentityDomain'"
+ }
+ }
}
else {
try {
@@ -8565,6 +9580,17 @@ Custom PSObject with translated subnet property fields.
$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=')) -replace 'DC=','' -replace ',','.'
+ Write-Verbose "[Get-DomainSubnet] Extracted domain '$IdentityDomain' from '$IdentityInstance'"
+ $SearcherArguments['Domain'] = $IdentityDomain
+ $SubnetSearcher = Get-DomainSearcher @SearcherArguments
+ if (-not $SubnetSearcher) {
+ Write-Warning "[Get-DomainSubnet] Unable to retrieve domain searcher for '$IdentityDomain'"
+ }
+ }
}
else {
try {
@@ -9044,6 +10070,17 @@ Custom PSObject with translated group property fields.
}
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 'DC=','' -replace ',','.'
+ Write-Verbose "[Get-DomainGroup] Extracted domain '$IdentityDomain' from '$IdentityInstance'"
+ $SearcherArguments['Domain'] = $IdentityDomain
+ $GroupSearcher = Get-DomainSearcher @SearcherArguments
+ if (-not $GroupSearcher) {
+ Write-Warning "[Get-DomainGroup] Unable to retrieve domain searcher for '$IdentityDomain'"
+ }
+ }
}
elseif ($IdentityInstance -imatch '^[0-9A-F]{8}-([0-9A-F]{4}-){3}[0-9A-F]{12}$') {
$GuidByteString = (([Guid]$IdentityInstance).ToByteArray() | ForEach-Object { '\' + $_.ToString('X2') }) -join ''
@@ -9785,6 +10822,17 @@ http://www.powershellmagazine.com/2013/05/23/pstip-retrieve-group-membership-of-
}
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 'DC=','' -replace ',','.'
+ Write-Verbose "[Get-DomainGroupMember] Extracted domain '$IdentityDomain' from '$IdentityInstance'"
+ $SearcherArguments['Domain'] = $IdentityDomain
+ $GroupSearcher = Get-DomainSearcher @SearcherArguments
+ if (-not $GroupSearcher) {
+ Write-Warning "[Get-DomainGroupMember] Unable to retrieve domain searcher for '$IdentityDomain'"
+ }
+ }
}
elseif ($IdentityInstance -imatch '^[0-9A-F]{8}-([0-9A-F]{4}-){3}[0-9A-F]{12}$') {
$GuidByteString = (([Guid]$IdentityInstance).ToByteArray() | ForEach-Object { '\' + $_.ToString('X2') }) -join ''
@@ -9988,6 +11036,211 @@ http://www.powershellmagazine.com/2013/05/23/pstip-retrieve-group-membership-of-
}
+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=local),
+SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201).
+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-the-group-membership/
+#>
+
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')]
+ [OutputType('PowerView.DomainGroupMemberDeleted')]
+ [CmdletBinding()]
+ Param(
+ [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
+ [Alias('DistinguishedName', 'SamAccountName', 'Name', 'MemberDistinguishedName', 'MemberName')]
+ [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'] = $ResultPageSize }
+ if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
+ 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_DATA' -ErrorAction SilentlyContinue
+ if ($TempObject) {
+ if (($TempObject.pszAttributeName -Match 'member') -and (($TempObject.dwVersion % 2) -eq 0 )) {
+ $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.ftimeLastOriginatingChange
+ $Output | Add-Member NoteProperty 'TimesAdded' ($TempObject.dwVersion / 2)
+ $Output | Add-Member NoteProperty 'LastOriginatingDsaDN' $TempObject.pszLastOriginatingDsaDN
+ $Output.PSObject.TypeNames.Insert(0, 'PowerView.DomainGroupMemberDeleted')
+ $Output
+ }
+ }
+ else {
+ Write-Verbose "[Get-DomainGroupMemberDeleted] Error retrieving 'msds-replvaluemetadata' for '$ObjectDN'"
+ }
+ }
+ }
+ }
+}
+
+
function Add-DomainGroupMember {
<#
.SYNOPSIS
@@ -11386,6 +12639,17 @@ The raw DirectoryServices.SearchResult object, if -Raw is enabled.
$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=')) -replace 'DC=','' -replace ',','.'
+ Write-Verbose "[Get-DomainGPO] Extracted domain '$IdentityDomain' from '$IdentityInstance'"
+ $SearcherArguments['Domain'] = $IdentityDomain
+ $GPOSearcher = Get-DomainSearcher @SearcherArguments
+ if (-not $GPOSearcher) {
+ Write-Warning "[Get-DomainGPO] Unable to retrieve domain searcher for '$IdentityDomain'"
+ }
+ }
}
elseif ($IdentityInstance -match '{.*}') {
$IdentityFilter += "(name=$IdentityInstance)"
@@ -11850,21 +13114,14 @@ for connection to the target domain.
.EXAMPLE
-Find-GPOLocation
+Get-DomainGPOUserLocalGroupMapping
Find all user/group -> machine relationships where the user/group is a member
of the local administrators group on target machines.
.EXAMPLE
-Find-GPOLocation -UserName dfm -Domain dev.testlab.local
-
-Find all computers that dfm user has local administrator rights to in
-the dev.testlab.local domain.
-
-.EXAMPLE
-
-Find-GPOLocation -UserName dfm -Domain dev.testlab.local
+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.
@@ -18946,7 +20203,7 @@ $Mod = New-InMemoryModule -ModuleName Win32
# [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingPositionalParameters', Scope='Function', Target='psenum')]
# used to parse the 'samAccountType' property for users/computers/groups
-$SamAccountTypeEnum = psenum $Mod PowerView.GroupTypeEnum UInt32 @{
+$SamAccountTypeEnum = psenum $Mod PowerView.SamAccountTypeEnum UInt32 @{
DOMAIN_OBJECT = '0x00000000'
GROUP_OBJECT = '0x10000000'
NON_SECURITY_GROUP_OBJECT = '0x10000001'
@@ -18961,7 +20218,7 @@ $SamAccountTypeEnum = psenum $Mod PowerView.GroupTypeEnum UInt32 @{
}
# used to parse the 'grouptype' property for groups
-$GroupTypeEnum = psenum $Mod PowerView.SamAccountTypeEnum UInt32 @{
+$GroupTypeEnum = psenum $Mod PowerView.GroupTypeEnum UInt32 @{
CREATED_BY_SYSTEM = '0x00000001'
GLOBAL_SCOPE = '0x00000002'
DOMAIN_LOCAL_SCOPE = '0x00000004'