· 6 years ago · Nov 25, 2019, 12:23 PM
1function New-InMemoryModule
2{
3<#
4 .SYNOPSIS
5
6 Creates an in-memory assembly and module
7
8 Author: Matthew Graeber (@mattifestation)
9 License: BSD 3-Clause
10 Required Dependencies: None
11 Optional Dependencies: None
12
13 .DESCRIPTION
14
15 When defining custom enums, structs, and unmanaged functions, it is
16 necessary to associate to an assembly module. This helper function
17 creates an in-memory module that can be passed to the 'enum',
18 'struct', and Add-Win32Type functions.
19
20 .PARAMETER ModuleName
21
22 Specifies the desired name for the in-memory assembly and module. If
23 ModuleName is not provided, it will default to a GUID.
24
25 .EXAMPLE
26
27 $Module = New-InMemoryModule -ModuleName Win32
28#>
29
30 Param
31 (
32 [Parameter(Position = 0)]
33 [ValidateNotNullOrEmpty()]
34 [String]
35 $ModuleName = [Guid]::NewGuid().ToString()
36 )
37
38 $LoadedAssemblies = [AppDomain]::CurrentDomain.GetAssemblies()
39
40 ForEach ($Assembly in $LoadedAssemblies) {
41 if ($Assembly.FullName -and ($Assembly.FullName.Split(',')[0] -eq $ModuleName)) {
42 return $Assembly
43 }
44 }
45
46 $DynAssembly = New-Object Reflection.AssemblyName($ModuleName)
47 $Domain = [AppDomain]::CurrentDomain
48 $AssemblyBuilder = $Domain.DefineDynamicAssembly($DynAssembly, 'Run')
49 $ModuleBuilder = $AssemblyBuilder.DefineDynamicModule($ModuleName, $False)
50
51 return $ModuleBuilder
52}
53
54
55# A helper function used to reduce typing while defining function
56# prototypes for Add-Win32Type.
57function func
58{
59 Param
60 (
61 [Parameter(Position = 0, Mandatory = $True)]
62 [String]
63 $DllName,
64
65 [Parameter(Position = 1, Mandatory = $True)]
66 [String]
67 $FunctionName,
68
69 [Parameter(Position = 2, Mandatory = $True)]
70 [Type]
71 $ReturnType,
72
73 [Parameter(Position = 3)]
74 [Type[]]
75 $ParameterTypes,
76
77 [Parameter(Position = 4)]
78 [Runtime.InteropServices.CallingConvention]
79 $NativeCallingConvention,
80
81 [Parameter(Position = 5)]
82 [Runtime.InteropServices.CharSet]
83 $Charset,
84
85 [Switch]
86 $SetLastError
87 )
88
89 $Properties = @{
90 DllName = $DllName
91 FunctionName = $FunctionName
92 ReturnType = $ReturnType
93 }
94
95 if ($ParameterTypes) { $Properties['ParameterTypes'] = $ParameterTypes }
96 if ($NativeCallingConvention) { $Properties['NativeCallingConvention'] = $NativeCallingConvention }
97 if ($Charset) { $Properties['Charset'] = $Charset }
98 if ($SetLastError) { $Properties['SetLastError'] = $SetLastError }
99
100 New-Object PSObject -Property $Properties
101}
102
103
104function Add-Win32Type
105{
106<#
107 .SYNOPSIS
108
109 Creates a .NET type for an unmanaged Win32 function.
110
111 Author: Matthew Graeber (@mattifestation)
112 License: BSD 3-Clause
113 Required Dependencies: None
114 Optional Dependencies: func
115
116 .DESCRIPTION
117
118 Add-Win32Type enables you to easily interact with unmanaged (i.e.
119 Win32 unmanaged) functions in PowerShell. After providing
120 Add-Win32Type with a function signature, a .NET type is created
121 using reflection (i.e. csc.exe is never called like with Add-Type).
122
123 The 'func' helper function can be used to reduce typing when defining
124 multiple function definitions.
125
126 .PARAMETER DllName
127
128 The name of the DLL.
129
130 .PARAMETER FunctionName
131
132 The name of the target function.
133
134 .PARAMETER ReturnType
135
136 The return type of the function.
137
138 .PARAMETER ParameterTypes
139
140 The function parameters.
141
142 .PARAMETER NativeCallingConvention
143
144 Specifies the native calling convention of the function. Defaults to
145 stdcall.
146
147 .PARAMETER Charset
148
149 If you need to explicitly call an 'A' or 'W' Win32 function, you can
150 specify the character set.
151
152 .PARAMETER SetLastError
153
154 Indicates whether the callee calls the SetLastError Win32 API
155 function before returning from the attributed method.
156
157 .PARAMETER Module
158
159 The in-memory module that will host the functions. Use
160 New-InMemoryModule to define an in-memory module.
161
162 .PARAMETER Namespace
163
164 An optional namespace to prepend to the type. Add-Win32Type defaults
165 to a namespace consisting only of the name of the DLL.
166
167 .EXAMPLE
168
169 $Mod = New-InMemoryModule -ModuleName Win32
170
171 $FunctionDefinitions = @(
172 (func kernel32 GetProcAddress ([IntPtr]) @([IntPtr], [String]) -Charset Ansi -SetLastError),
173 (func kernel32 GetModuleHandle ([Intptr]) @([String]) -SetLastError),
174 (func ntdll RtlGetCurrentPeb ([IntPtr]) @())
175 )
176
177 $Types = $FunctionDefinitions | Add-Win32Type -Module $Mod -Namespace 'Win32'
178 $Kernel32 = $Types['kernel32']
179 $Ntdll = $Types['ntdll']
180 $Ntdll::RtlGetCurrentPeb()
181 $ntdllbase = $Kernel32::GetModuleHandle('ntdll')
182 $Kernel32::GetProcAddress($ntdllbase, 'RtlGetCurrentPeb')
183
184 .NOTES
185
186 Inspired by Lee Holmes' Invoke-WindowsApi http://poshcode.org/2189
187
188 When defining multiple function prototypes, it is ideal to provide
189 Add-Win32Type with an array of function signatures. That way, they
190 are all incorporated into the same in-memory module.
191#>
192
193 [OutputType([Hashtable])]
194 Param(
195 [Parameter(Mandatory = $True, ValueFromPipelineByPropertyName = $True)]
196 [String]
197 $DllName,
198
199 [Parameter(Mandatory = $True, ValueFromPipelineByPropertyName = $True)]
200 [String]
201 $FunctionName,
202
203 [Parameter(Mandatory = $True, ValueFromPipelineByPropertyName = $True)]
204 [Type]
205 $ReturnType,
206
207 [Parameter(ValueFromPipelineByPropertyName = $True)]
208 [Type[]]
209 $ParameterTypes,
210
211 [Parameter(ValueFromPipelineByPropertyName = $True)]
212 [Runtime.InteropServices.CallingConvention]
213 $NativeCallingConvention = [Runtime.InteropServices.CallingConvention]::StdCall,
214
215 [Parameter(ValueFromPipelineByPropertyName = $True)]
216 [Runtime.InteropServices.CharSet]
217 $Charset = [Runtime.InteropServices.CharSet]::Auto,
218
219 [Parameter(ValueFromPipelineByPropertyName = $True)]
220 [Switch]
221 $SetLastError,
222
223 [Parameter(Mandatory = $True)]
224 [ValidateScript({($_ -is [Reflection.Emit.ModuleBuilder]) -or ($_ -is [Reflection.Assembly])})]
225 $Module,
226
227 [ValidateNotNull()]
228 [String]
229 $Namespace = ''
230 )
231
232 BEGIN
233 {
234 $TypeHash = @{}
235 }
236
237 PROCESS
238 {
239 if ($Module -is [Reflection.Assembly])
240 {
241 if ($Namespace)
242 {
243 $TypeHash[$DllName] = $Module.GetType("$Namespace.$DllName")
244 }
245 else
246 {
247 $TypeHash[$DllName] = $Module.GetType($DllName)
248 }
249 }
250 else
251 {
252 # Define one type for each DLL
253 if (!$TypeHash.ContainsKey($DllName))
254 {
255 if ($Namespace)
256 {
257 $TypeHash[$DllName] = $Module.DefineType("$Namespace.$DllName", 'Public,BeforeFieldInit')
258 }
259 else
260 {
261 $TypeHash[$DllName] = $Module.DefineType($DllName, 'Public,BeforeFieldInit')
262 }
263 }
264
265 $Method = $TypeHash[$DllName].DefineMethod(
266 $FunctionName,
267 'Public,Static,PinvokeImpl',
268 $ReturnType,
269 $ParameterTypes)
270
271 # Make each ByRef parameter an Out parameter
272 $i = 1
273 ForEach($Parameter in $ParameterTypes)
274 {
275 if ($Parameter.IsByRef)
276 {
277 [void] $Method.DefineParameter($i, 'Out', $Null)
278 }
279
280 $i++
281 }
282
283 $DllImport = [Runtime.InteropServices.DllImportAttribute]
284 $SetLastErrorField = $DllImport.GetField('SetLastError')
285 $CallingConventionField = $DllImport.GetField('CallingConvention')
286 $CharsetField = $DllImport.GetField('CharSet')
287 if ($SetLastError) { $SLEValue = $True } else { $SLEValue = $False }
288
289 # Equivalent to C# version of [DllImport(DllName)]
290 $Constructor = [Runtime.InteropServices.DllImportAttribute].GetConstructor([String])
291 $DllImportAttribute = New-Object Reflection.Emit.CustomAttributeBuilder($Constructor,
292 $DllName, [Reflection.PropertyInfo[]] @(), [Object[]] @(),
293 [Reflection.FieldInfo[]] @($SetLastErrorField, $CallingConventionField, $CharsetField),
294 [Object[]] @($SLEValue, ([Runtime.InteropServices.CallingConvention] $NativeCallingConvention), ([Runtime.InteropServices.CharSet] $Charset)))
295
296 $Method.SetCustomAttribute($DllImportAttribute)
297 }
298 }
299
300 END
301 {
302 if ($Module -is [Reflection.Assembly])
303 {
304 return $TypeHash
305 }
306
307 $ReturnTypes = @{}
308
309 ForEach ($Key in $TypeHash.Keys)
310 {
311 $Type = $TypeHash[$Key].CreateType()
312
313 $ReturnTypes[$Key] = $Type
314 }
315
316 return $ReturnTypes
317 }
318}
319
320
321function psenum
322{
323<#
324 .SYNOPSIS
325
326 Creates an in-memory enumeration for use in your PowerShell session.
327
328 Author: Matthew Graeber (@mattifestation)
329 License: BSD 3-Clause
330 Required Dependencies: None
331 Optional Dependencies: None
332
333 .DESCRIPTION
334
335 The 'psenum' function facilitates the creation of enums entirely in
336 memory using as close to a "C style" as PowerShell will allow.
337
338 .PARAMETER Module
339
340 The in-memory module that will host the enum. Use
341 New-InMemoryModule to define an in-memory module.
342
343 .PARAMETER FullName
344
345 The fully-qualified name of the enum.
346
347 .PARAMETER Type
348
349 The type of each enum element.
350
351 .PARAMETER EnumElements
352
353 A hashtable of enum elements.
354
355 .PARAMETER Bitfield
356
357 Specifies that the enum should be treated as a bitfield.
358
359 .EXAMPLE
360
361 $Mod = New-InMemoryModule -ModuleName Win32
362
363 $ImageSubsystem = psenum $Mod PE.IMAGE_SUBSYSTEM UInt16 @{
364 UNKNOWN = 0
365 NATIVE = 1 # Image doesn't require a subsystem.
366 WINDOWS_GUI = 2 # Image runs in the Windows GUI subsystem.
367 WINDOWS_CUI = 3 # Image runs in the Windows character subsystem.
368 OS2_CUI = 5 # Image runs in the OS/2 character subsystem.
369 POSIX_CUI = 7 # Image runs in the Posix character subsystem.
370 NATIVE_WINDOWS = 8 # Image is a native Win9x driver.
371 WINDOWS_CE_GUI = 9 # Image runs in the Windows CE subsystem.
372 EFI_APPLICATION = 10
373 EFI_BOOT_SERVICE_DRIVER = 11
374 EFI_RUNTIME_DRIVER = 12
375 EFI_ROM = 13
376 XBOX = 14
377 WINDOWS_BOOT_APPLICATION = 16
378 }
379
380 .NOTES
381
382 PowerShell purists may disagree with the naming of this function but
383 again, this was developed in such a way so as to emulate a "C style"
384 definition as closely as possible. Sorry, I'm not going to name it
385 New-Enum. :P
386#>
387
388 [OutputType([Type])]
389 Param
390 (
391 [Parameter(Position = 0, Mandatory = $True)]
392 [ValidateScript({($_ -is [Reflection.Emit.ModuleBuilder]) -or ($_ -is [Reflection.Assembly])})]
393 $Module,
394
395 [Parameter(Position = 1, Mandatory = $True)]
396 [ValidateNotNullOrEmpty()]
397 [String]
398 $FullName,
399
400 [Parameter(Position = 2, Mandatory = $True)]
401 [Type]
402 $Type,
403
404 [Parameter(Position = 3, Mandatory = $True)]
405 [ValidateNotNullOrEmpty()]
406 [Hashtable]
407 $EnumElements,
408
409 [Switch]
410 $Bitfield
411 )
412
413 if ($Module -is [Reflection.Assembly])
414 {
415 return ($Module.GetType($FullName))
416 }
417
418 $EnumType = $Type -as [Type]
419
420 $EnumBuilder = $Module.DefineEnum($FullName, 'Public', $EnumType)
421
422 if ($Bitfield)
423 {
424 $FlagsConstructor = [FlagsAttribute].GetConstructor(@())
425 $FlagsCustomAttribute = New-Object Reflection.Emit.CustomAttributeBuilder($FlagsConstructor, @())
426 $EnumBuilder.SetCustomAttribute($FlagsCustomAttribute)
427 }
428
429 ForEach ($Key in $EnumElements.Keys)
430 {
431 # Apply the specified enum type to each element
432 $Null = $EnumBuilder.DefineLiteral($Key, $EnumElements[$Key] -as $EnumType)
433 }
434
435 $EnumBuilder.CreateType()
436}
437
438
439# A helper function used to reduce typing while defining struct
440# fields.
441function field
442{
443 Param
444 (
445 [Parameter(Position = 0, Mandatory = $True)]
446 [UInt16]
447 $Position,
448
449 [Parameter(Position = 1, Mandatory = $True)]
450 [Type]
451 $Type,
452
453 [Parameter(Position = 2)]
454 [UInt16]
455 $Offset,
456
457 [Object[]]
458 $MarshalAs
459 )
460
461 @{
462 Position = $Position
463 Type = $Type -as [Type]
464 Offset = $Offset
465 MarshalAs = $MarshalAs
466 }
467}
468
469
470function struct
471{
472<#
473 .SYNOPSIS
474
475 Creates an in-memory struct for use in your PowerShell session.
476
477 Author: Matthew Graeber (@mattifestation)
478 License: BSD 3-Clause
479 Required Dependencies: None
480 Optional Dependencies: field
481
482 .DESCRIPTION
483
484 The 'struct' function facilitates the creation of structs entirely in
485 memory using as close to a "C style" as PowerShell will allow. Struct
486 fields are specified using a hashtable where each field of the struct
487 is comprosed of the order in which it should be defined, its .NET
488 type, and optionally, its offset and special marshaling attributes.
489
490 One of the features of 'struct' is that after your struct is defined,
491 it will come with a built-in GetSize method as well as an explicit
492 converter so that you can easily cast an IntPtr to the struct without
493 relying upon calling SizeOf and/or PtrToStructure in the Marshal
494 class.
495
496 .PARAMETER Module
497
498 The in-memory module that will host the struct. Use
499 New-InMemoryModule to define an in-memory module.
500
501 .PARAMETER FullName
502
503 The fully-qualified name of the struct.
504
505 .PARAMETER StructFields
506
507 A hashtable of fields. Use the 'field' helper function to ease
508 defining each field.
509
510 .PARAMETER PackingSize
511
512 Specifies the memory alignment of fields.
513
514 .PARAMETER ExplicitLayout
515
516 Indicates that an explicit offset for each field will be specified.
517
518 .EXAMPLE
519
520 $Mod = New-InMemoryModule -ModuleName Win32
521
522 $ImageDosSignature = psenum $Mod PE.IMAGE_DOS_SIGNATURE UInt16 @{
523 DOS_SIGNATURE = 0x5A4D
524 OS2_SIGNATURE = 0x454E
525 OS2_SIGNATURE_LE = 0x454C
526 VXD_SIGNATURE = 0x454C
527 }
528
529 $ImageDosHeader = struct $Mod PE.IMAGE_DOS_HEADER @{
530 e_magic = field 0 $ImageDosSignature
531 e_cblp = field 1 UInt16
532 e_cp = field 2 UInt16
533 e_crlc = field 3 UInt16
534 e_cparhdr = field 4 UInt16
535 e_minalloc = field 5 UInt16
536 e_maxalloc = field 6 UInt16
537 e_ss = field 7 UInt16
538 e_sp = field 8 UInt16
539 e_csum = field 9 UInt16
540 e_ip = field 10 UInt16
541 e_cs = field 11 UInt16
542 e_lfarlc = field 12 UInt16
543 e_ovno = field 13 UInt16
544 e_res = field 14 UInt16[] -MarshalAs @('ByValArray', 4)
545 e_oemid = field 15 UInt16
546 e_oeminfo = field 16 UInt16
547 e_res2 = field 17 UInt16[] -MarshalAs @('ByValArray', 10)
548 e_lfanew = field 18 Int32
549 }
550
551 # Example of using an explicit layout in order to create a union.
552 $TestUnion = struct $Mod TestUnion @{
553 field1 = field 0 UInt32 0
554 field2 = field 1 IntPtr 0
555 } -ExplicitLayout
556
557 .NOTES
558
559 PowerShell purists may disagree with the naming of this function but
560 again, this was developed in such a way so as to emulate a "C style"
561 definition as closely as possible. Sorry, I'm not going to name it
562 New-Struct. :P
563#>
564
565 [OutputType([Type])]
566 Param
567 (
568 [Parameter(Position = 1, Mandatory = $True)]
569 [ValidateScript({($_ -is [Reflection.Emit.ModuleBuilder]) -or ($_ -is [Reflection.Assembly])})]
570 $Module,
571
572 [Parameter(Position = 2, Mandatory = $True)]
573 [ValidateNotNullOrEmpty()]
574 [String]
575 $FullName,
576
577 [Parameter(Position = 3, Mandatory = $True)]
578 [ValidateNotNullOrEmpty()]
579 [Hashtable]
580 $StructFields,
581
582 [Reflection.Emit.PackingSize]
583 $PackingSize = [Reflection.Emit.PackingSize]::Unspecified,
584
585 [Switch]
586 $ExplicitLayout
587 )
588
589 if ($Module -is [Reflection.Assembly])
590 {
591 return ($Module.GetType($FullName))
592 }
593
594 [Reflection.TypeAttributes] $StructAttributes = 'AnsiClass,
595 Class,
596 Public,
597 Sealed,
598 BeforeFieldInit'
599
600 if ($ExplicitLayout)
601 {
602 $StructAttributes = $StructAttributes -bor [Reflection.TypeAttributes]::ExplicitLayout
603 }
604 else
605 {
606 $StructAttributes = $StructAttributes -bor [Reflection.TypeAttributes]::SequentialLayout
607 }
608
609 $StructBuilder = $Module.DefineType($FullName, $StructAttributes, [ValueType], $PackingSize)
610 $ConstructorInfo = [Runtime.InteropServices.MarshalAsAttribute].GetConstructors()[0]
611 $SizeConst = @([Runtime.InteropServices.MarshalAsAttribute].GetField('SizeConst'))
612
613 $Fields = New-Object Hashtable[]($StructFields.Count)
614
615 # Sort each field according to the orders specified
616 # Unfortunately, PSv2 doesn't have the luxury of the
617 # hashtable [Ordered] accelerator.
618 ForEach ($Field in $StructFields.Keys)
619 {
620 $Index = $StructFields[$Field]['Position']
621 $Fields[$Index] = @{FieldName = $Field; Properties = $StructFields[$Field]}
622 }
623
624 ForEach ($Field in $Fields)
625 {
626 $FieldName = $Field['FieldName']
627 $FieldProp = $Field['Properties']
628
629 $Offset = $FieldProp['Offset']
630 $Type = $FieldProp['Type']
631 $MarshalAs = $FieldProp['MarshalAs']
632
633 $NewField = $StructBuilder.DefineField($FieldName, $Type, 'Public')
634
635 if ($MarshalAs)
636 {
637 $UnmanagedType = $MarshalAs[0] -as ([Runtime.InteropServices.UnmanagedType])
638 if ($MarshalAs[1])
639 {
640 $Size = $MarshalAs[1]
641 $AttribBuilder = New-Object Reflection.Emit.CustomAttributeBuilder($ConstructorInfo,
642 $UnmanagedType, $SizeConst, @($Size))
643 }
644 else
645 {
646 $AttribBuilder = New-Object Reflection.Emit.CustomAttributeBuilder($ConstructorInfo, [Object[]] @($UnmanagedType))
647 }
648
649 $NewField.SetCustomAttribute($AttribBuilder)
650 }
651
652 if ($ExplicitLayout) { $NewField.SetOffset($Offset) }
653 }
654
655 # Make the struct aware of its own size.
656 # No more having to call [Runtime.InteropServices.Marshal]::SizeOf!
657 $SizeMethod = $StructBuilder.DefineMethod('GetSize',
658 'Public, Static',
659 [Int],
660 [Type[]] @())
661 $ILGenerator = $SizeMethod.GetILGenerator()
662 # Thanks for the help, Jason Shirk!
663 $ILGenerator.Emit([Reflection.Emit.OpCodes]::Ldtoken, $StructBuilder)
664 $ILGenerator.Emit([Reflection.Emit.OpCodes]::Call,
665 [Type].GetMethod('GetTypeFromHandle'))
666 $ILGenerator.Emit([Reflection.Emit.OpCodes]::Call,
667 [Runtime.InteropServices.Marshal].GetMethod('SizeOf', [Type[]] @([Type])))
668 $ILGenerator.Emit([Reflection.Emit.OpCodes]::Ret)
669
670 # Allow for explicit casting from an IntPtr
671 # No more having to call [Runtime.InteropServices.Marshal]::PtrToStructure!
672 $ImplicitConverter = $StructBuilder.DefineMethod('op_Implicit',
673 'PrivateScope, Public, Static, HideBySig, SpecialName',
674 $StructBuilder,
675 [Type[]] @([IntPtr]))
676 $ILGenerator2 = $ImplicitConverter.GetILGenerator()
677 $ILGenerator2.Emit([Reflection.Emit.OpCodes]::Nop)
678 $ILGenerator2.Emit([Reflection.Emit.OpCodes]::Ldarg_0)
679 $ILGenerator2.Emit([Reflection.Emit.OpCodes]::Ldtoken, $StructBuilder)
680 $ILGenerator2.Emit([Reflection.Emit.OpCodes]::Call,
681 [Type].GetMethod('GetTypeFromHandle'))
682 $ILGenerator2.Emit([Reflection.Emit.OpCodes]::Call,
683 [Runtime.InteropServices.Marshal].GetMethod('PtrToStructure', [Type[]] @([IntPtr], [Type])))
684 $ILGenerator2.Emit([Reflection.Emit.OpCodes]::Unbox_Any, $StructBuilder)
685 $ILGenerator2.Emit([Reflection.Emit.OpCodes]::Ret)
686
687 $StructBuilder.CreateType()
688}
689
690
691########################################################
692#
693# Misc. helpers
694#
695########################################################
696
697filter Get-IniContent {
698<#
699 .SYNOPSIS
700
701 This helper parses an .ini file into a proper PowerShell object.
702
703 Author: 'The Scripting Guys'
704 Link: https://blogs.technet.microsoft.com/heyscriptingguy/2011/08/20/use-powershell-to-work-with-any-ini-file/
705
706 .LINK
707
708 https://blogs.technet.microsoft.com/heyscriptingguy/2011/08/20/use-powershell-to-work-with-any-ini-file/
709#>
710 [CmdletBinding()]
711 Param(
712 [Parameter(Mandatory=$True, ValueFromPipeline=$True, ValueFromPipelineByPropertyName=$True)]
713 [Alias('FullName')]
714 [ValidateScript({ Test-Path -Path $_ })]
715 [String[]]
716 $Path
717 )
718
719 ForEach($TargetPath in $Path) {
720 $IniObject = @{}
721 Switch -Regex -File $TargetPath {
722 "^\[(.+)\]" # Section
723 {
724 $Section = $matches[1].Trim()
725 $IniObject[$Section] = @{}
726 $CommentCount = 0
727 }
728 "^(;.*)$" # Comment
729 {
730 $Value = $matches[1].Trim()
731 $CommentCount = $CommentCount + 1
732 $Name = 'Comment' + $CommentCount
733 $IniObject[$Section][$Name] = $Value
734 }
735 "(.+?)\s*=(.*)" # Key
736 {
737 $Name, $Value = $matches[1..2]
738 $Name = $Name.Trim()
739 $Values = $Value.split(',') | ForEach-Object {$_.Trim()}
740 if($Values -isnot [System.Array]) {$Values = @($Values)}
741 $IniObject[$Section][$Name] = $Values
742 }
743 }
744 $IniObject
745 }
746}
747
748filter Export-PowerViewCSV {
749<#
750 .SYNOPSIS
751
752 This helper exports an -InputObject to a .csv in a thread-safe manner
753 using a mutex. This is so the various multi-threaded functions in
754 PowerView has a thread-safe way to export output to the same file.
755
756 Based partially on Dmitry Sotnikov's Export-CSV code
757 at http://poshcode.org/1590
758
759 .LINK
760
761 http://poshcode.org/1590
762 http://dmitrysotnikov.wordpress.com/2010/01/19/Export-Csv-append/
763#>
764 Param(
765 [Parameter(Mandatory=$True, ValueFromPipeline=$True, ValueFromPipelineByPropertyName=$True)]
766 [System.Management.Automation.PSObject[]]
767 $InputObject,
768
769 [Parameter(Mandatory=$True, Position=0)]
770 [String]
771 [ValidateNotNullOrEmpty()]
772 $OutFile
773 )
774
775 $ObjectCSV = $InputObject | ConvertTo-Csv -NoTypeInformation
776
777 # mutex so threaded code doesn't stomp on the output file
778 $Mutex = New-Object System.Threading.Mutex $False,'CSVMutex';
779 $Null = $Mutex.WaitOne()
780
781 if (Test-Path -Path $OutFile) {
782 # hack to skip the first line of output if the file already exists
783 $ObjectCSV | ForEach-Object { $Start=$True }{ if ($Start) {$Start=$False} else {$_} } | Out-File -Encoding 'ASCII' -Append -FilePath $OutFile
784 }
785 else {
786 $ObjectCSV | Out-File -Encoding 'ASCII' -Append -FilePath $OutFile
787 }
788
789 $Mutex.ReleaseMutex()
790}
791
792
793filter Get-IPAddress {
794<#
795 .SYNOPSIS
796
797 Resolves a given hostename to its associated IPv4 address.
798 If no hostname is provided, it defaults to returning
799 the IP address of the localhost.
800
801 .EXAMPLE
802
803 PS C:\> Get-IPAddress -ComputerName SERVER
804
805 Return the IPv4 address of 'SERVER'
806
807 .EXAMPLE
808
809 PS C:\> Get-Content .\hostnames.txt | Get-IPAddress
810
811 Get the IP addresses of all hostnames in an input file.
812#>
813
814 [CmdletBinding()]
815 param(
816 [Parameter(Position=0, ValueFromPipeline=$True)]
817 [Alias('HostName')]
818 [String]
819 $ComputerName = $Env:ComputerName
820 )
821
822 try {
823 # extract the computer name from whatever object was passed on the pipeline
824 $Computer = $ComputerName | Get-NameField
825
826 # get the IP resolution of this specified hostname
827 @(([Net.Dns]::GetHostEntry($Computer)).AddressList) | ForEach-Object {
828 if ($_.AddressFamily -eq 'InterNetwork') {
829 $Out = New-Object PSObject
830 $Out | Add-Member Noteproperty 'ComputerName' $Computer
831 $Out | Add-Member Noteproperty 'IPAddress' $_.IPAddressToString
832 $Out
833 }
834 }
835 }
836 catch {
837 Write-Verbose -Message 'Could not resolve host to an IP Address.'
838 }
839}
840
841
842filter Convert-NameToSid {
843<#
844 .SYNOPSIS
845
846 Converts a given user/group name to a security identifier (SID).
847
848 .PARAMETER ObjectName
849
850 The user/group name to convert, can be 'user' or 'DOMAIN\user' format.
851
852 .PARAMETER Domain
853
854 Specific domain for the given user account, defaults to the current domain.
855
856 .EXAMPLE
857
858 PS C:\> Convert-NameToSid 'DEV\dfm'
859#>
860 [CmdletBinding()]
861 param(
862 [Parameter(Mandatory=$True, ValueFromPipeline=$True)]
863 [String]
864 [Alias('Name')]
865 $ObjectName,
866
867 [String]
868 $Domain
869 )
870
871 $ObjectName = $ObjectName -Replace "/","\"
872
873 if($ObjectName.Contains("\")) {
874 # if we get a DOMAIN\user format, auto convert it
875 $Domain = $ObjectName.Split("\")[0]
876 $ObjectName = $ObjectName.Split("\")[1]
877 }
878 elseif(-not $Domain) {
879 $Domain = (Get-NetDomain).Name
880 }
881
882 try {
883 $Obj = (New-Object System.Security.Principal.NTAccount($Domain, $ObjectName))
884 $SID = $Obj.Translate([System.Security.Principal.SecurityIdentifier]).Value
885
886 $Out = New-Object PSObject
887 $Out | Add-Member Noteproperty 'ObjectName' $ObjectName
888 $Out | Add-Member Noteproperty 'SID' $SID
889 $Out
890 }
891 catch {
892 Write-Verbose "Invalid object/name: $Domain\$ObjectName"
893 $Null
894 }
895}
896
897
898filter Convert-SidToName {
899<#
900 .SYNOPSIS
901
902 Converts a security identifier (SID) to a group/user name.
903
904 .PARAMETER SID
905
906 The SID to convert.
907
908 .EXAMPLE
909
910 PS C:\> Convert-SidToName S-1-5-21-2620891829-2411261497-1773853088-1105
911#>
912 [CmdletBinding()]
913 param(
914 [Parameter(Mandatory=$True, ValueFromPipeline=$True)]
915 [String]
916 [ValidatePattern('^S-1-.*')]
917 $SID
918 )
919
920 try {
921 $SID2 = $SID.trim('*')
922
923 # try to resolve any built-in SIDs first
924 # from https://support.microsoft.com/en-us/kb/243330
925 Switch ($SID2) {
926 'S-1-0' { 'Null Authority' }
927 'S-1-0-0' { 'Nobody' }
928 'S-1-1' { 'World Authority' }
929 'S-1-1-0' { 'Everyone' }
930 'S-1-2' { 'Local Authority' }
931 'S-1-2-0' { 'Local' }
932 'S-1-2-1' { 'Console Logon ' }
933 'S-1-3' { 'Creator Authority' }
934 'S-1-3-0' { 'Creator Owner' }
935 'S-1-3-1' { 'Creator Group' }
936 'S-1-3-2' { 'Creator Owner Server' }
937 'S-1-3-3' { 'Creator Group Server' }
938 'S-1-3-4' { 'Owner Rights' }
939 'S-1-4' { 'Non-unique Authority' }
940 'S-1-5' { 'NT Authority' }
941 'S-1-5-1' { 'Dialup' }
942 'S-1-5-2' { 'Network' }
943 'S-1-5-3' { 'Batch' }
944 'S-1-5-4' { 'Interactive' }
945 'S-1-5-6' { 'Service' }
946 'S-1-5-7' { 'Anonymous' }
947 'S-1-5-8' { 'Proxy' }
948 'S-1-5-9' { 'Enterprise Domain Controllers' }
949 'S-1-5-10' { 'Principal Self' }
950 'S-1-5-11' { 'Authenticated Users' }
951 'S-1-5-12' { 'Restricted Code' }
952 'S-1-5-13' { 'Terminal Server Users' }
953 'S-1-5-14' { 'Remote Interactive Logon' }
954 'S-1-5-15' { 'This Organization ' }
955 'S-1-5-17' { 'This Organization ' }
956 'S-1-5-18' { 'Local System' }
957 'S-1-5-19' { 'NT Authority' }
958 'S-1-5-20' { 'NT Authority' }
959 'S-1-5-80-0' { 'All Services ' }
960 'S-1-5-32-544' { 'BUILTIN\Administrators' }
961 'S-1-5-32-545' { 'BUILTIN\Users' }
962 'S-1-5-32-546' { 'BUILTIN\Guests' }
963 'S-1-5-32-547' { 'BUILTIN\Power Users' }
964 'S-1-5-32-548' { 'BUILTIN\Account Operators' }
965 'S-1-5-32-549' { 'BUILTIN\Server Operators' }
966 'S-1-5-32-550' { 'BUILTIN\Print Operators' }
967 'S-1-5-32-551' { 'BUILTIN\Backup Operators' }
968 'S-1-5-32-552' { 'BUILTIN\Replicators' }
969 'S-1-5-32-554' { 'BUILTIN\Pre-Windows 2000 Compatible Access' }
970 'S-1-5-32-555' { 'BUILTIN\Remote Desktop Users' }
971 'S-1-5-32-556' { 'BUILTIN\Network Configuration Operators' }
972 'S-1-5-32-557' { 'BUILTIN\Incoming Forest Trust Builders' }
973 'S-1-5-32-558' { 'BUILTIN\Performance Monitor Users' }
974 'S-1-5-32-559' { 'BUILTIN\Performance Log Users' }
975 'S-1-5-32-560' { 'BUILTIN\Windows Authorization Access Group' }
976 'S-1-5-32-561' { 'BUILTIN\Terminal Server License Servers' }
977 'S-1-5-32-562' { 'BUILTIN\Distributed COM Users' }
978 'S-1-5-32-569' { 'BUILTIN\Cryptographic Operators' }
979 'S-1-5-32-573' { 'BUILTIN\Event Log Readers' }
980 'S-1-5-32-574' { 'BUILTIN\Certificate Service DCOM Access' }
981 'S-1-5-32-575' { 'BUILTIN\RDS Remote Access Servers' }
982 'S-1-5-32-576' { 'BUILTIN\RDS Endpoint Servers' }
983 'S-1-5-32-577' { 'BUILTIN\RDS Management Servers' }
984 'S-1-5-32-578' { 'BUILTIN\Hyper-V Administrators' }
985 'S-1-5-32-579' { 'BUILTIN\Access Control Assistance Operators' }
986 'S-1-5-32-580' { 'BUILTIN\Access Control Assistance Operators' }
987 Default {
988 $Obj = (New-Object System.Security.Principal.SecurityIdentifier($SID2))
989 $Obj.Translate( [System.Security.Principal.NTAccount]).Value
990 }
991 }
992 }
993 catch {
994 Write-Verbose "Invalid SID: $SID"
995 $SID
996 }
997}
998
999
1000filter Convert-ADName {
1001<#
1002 .SYNOPSIS
1003
1004 Converts user/group names from NT4 (DOMAIN\user) or domainSimple (user@domain.com)
1005 to canonical format (domain.com/Users/user) or NT4.
1006
1007 Based on Bill Stewart's code from this article:
1008 http://windowsitpro.com/active-directory/translating-active-directory-object-names-between-formats
1009
1010 .PARAMETER ObjectName
1011
1012 The user/group name to convert.
1013
1014 .PARAMETER InputType
1015
1016 The InputType of the user/group name ("NT4","Simple","Canonical").
1017
1018 .PARAMETER OutputType
1019
1020 The OutputType of the user/group name ("NT4","Simple","Canonical").
1021
1022 .EXAMPLE
1023
1024 PS C:\> Convert-ADName -ObjectName "dev\dfm"
1025
1026 Returns "dev.testlab.local/Users/Dave"
1027
1028 .EXAMPLE
1029
1030 PS C:\> Convert-SidToName "S-..." | Convert-ADName
1031
1032 Returns the canonical name for the resolved SID.
1033
1034 .LINK
1035
1036 http://windowsitpro.com/active-directory/translating-active-directory-object-names-between-formats
1037#>
1038 [CmdletBinding()]
1039 param(
1040 [Parameter(Mandatory=$True, ValueFromPipeline=$True)]
1041 [String]
1042 $ObjectName,
1043
1044 [String]
1045 [ValidateSet("NT4","Simple","Canonical")]
1046 $InputType,
1047
1048 [String]
1049 [ValidateSet("NT4","Simple","Canonical")]
1050 $OutputType
1051 )
1052
1053 $NameTypes = @{
1054 'Canonical' = 2
1055 'NT4' = 3
1056 'Simple' = 5
1057 }
1058
1059 if(-not $PSBoundParameters['InputType']) {
1060 if( ($ObjectName.split('/')).Count -eq 2 ) {
1061 $ObjectName = $ObjectName.replace('/', '\')
1062 }
1063
1064 if($ObjectName -match "^[A-Za-z]+\\[A-Za-z ]+") {
1065 $InputType = 'NT4'
1066 }
1067 elseif($ObjectName -match "^[A-Za-z ]+@[A-Za-z\.]+") {
1068 $InputType = 'Simple'
1069 }
1070 elseif($ObjectName -match "^[A-Za-z\.]+/[A-Za-z]+/[A-Za-z/ ]+") {
1071 $InputType = 'Canonical'
1072 }
1073 else {
1074 Write-Warning "Can not identify InType for $ObjectName"
1075 return $ObjectName
1076 }
1077 }
1078 elseif($InputType -eq 'NT4') {
1079 $ObjectName = $ObjectName.replace('/', '\')
1080 }
1081
1082 if(-not $PSBoundParameters['OutputType']) {
1083 $OutputType = Switch($InputType) {
1084 'NT4' {'Canonical'}
1085 'Simple' {'NT4'}
1086 'Canonical' {'NT4'}
1087 }
1088 }
1089
1090 # try to extract the domain from the given format
1091 $Domain = Switch($InputType) {
1092 'NT4' { $ObjectName.split("\")[0] }
1093 'Simple' { $ObjectName.split("@")[1] }
1094 'Canonical' { $ObjectName.split("/")[0] }
1095 }
1096
1097 # Accessor functions to simplify calls to NameTranslate
1098 function Invoke-Method([__ComObject] $Object, [String] $Method, $Parameters) {
1099 $Output = $Object.GetType().InvokeMember($Method, "InvokeMethod", $Null, $Object, $Parameters)
1100 if ( $Output ) { $Output }
1101 }
1102 function Set-Property([__ComObject] $Object, [String] $Property, $Parameters) {
1103 [Void] $Object.GetType().InvokeMember($Property, "SetProperty", $Null, $Object, $Parameters)
1104 }
1105
1106 $Translate = New-Object -ComObject NameTranslate
1107
1108 try {
1109 Invoke-Method $Translate "Init" (1, $Domain)
1110 }
1111 catch [System.Management.Automation.MethodInvocationException] {
1112 Write-Verbose "Error with translate init in Convert-ADName: $_"
1113 }
1114
1115 Set-Property $Translate "ChaseReferral" (0x60)
1116
1117 try {
1118 Invoke-Method $Translate "Set" ($NameTypes[$InputType], $ObjectName)
1119 (Invoke-Method $Translate "Get" ($NameTypes[$OutputType]))
1120 }
1121 catch [System.Management.Automation.MethodInvocationException] {
1122 Write-Verbose "Error with translate Set/Get in Convert-ADName: $_"
1123 }
1124}
1125
1126
1127function ConvertFrom-UACValue {
1128<#
1129 .SYNOPSIS
1130
1131 Converts a UAC int value to human readable form.
1132
1133 .PARAMETER Value
1134
1135 The int UAC value to convert.
1136
1137 .PARAMETER ShowAll
1138
1139 Show all UAC values, with a + indicating the value is currently set.
1140
1141 .EXAMPLE
1142
1143 PS C:\> ConvertFrom-UACValue -Value 66176
1144
1145 Convert the UAC value 66176 to human readable format.
1146
1147 .EXAMPLE
1148
1149 PS C:\> Get-NetUser jason | select useraccountcontrol | ConvertFrom-UACValue
1150
1151 Convert the UAC value for 'jason' to human readable format.
1152
1153 .EXAMPLE
1154
1155 PS C:\> Get-NetUser jason | select useraccountcontrol | ConvertFrom-UACValue -ShowAll
1156
1157 Convert the UAC value for 'jason' to human readable format, showing all
1158 possible UAC values.
1159#>
1160
1161 [CmdletBinding()]
1162 param(
1163 [Parameter(Mandatory=$True, ValueFromPipeline=$True)]
1164 $Value,
1165
1166 [Switch]
1167 $ShowAll
1168 )
1169
1170 begin {
1171 # values from https://support.microsoft.com/en-us/kb/305144
1172 $UACValues = New-Object System.Collections.Specialized.OrderedDictionary
1173 $UACValues.Add("SCRIPT", 1)
1174 $UACValues.Add("ACCOUNTDISABLE", 2)
1175 $UACValues.Add("HOMEDIR_REQUIRED", 8)
1176 $UACValues.Add("LOCKOUT", 16)
1177 $UACValues.Add("PASSWD_NOTREQD", 32)
1178 $UACValues.Add("PASSWD_CANT_CHANGE", 64)
1179 $UACValues.Add("ENCRYPTED_TEXT_PWD_ALLOWED", 128)
1180 $UACValues.Add("TEMP_DUPLICATE_ACCOUNT", 256)
1181 $UACValues.Add("NORMAL_ACCOUNT", 512)
1182 $UACValues.Add("INTERDOMAIN_TRUST_ACCOUNT", 2048)
1183 $UACValues.Add("WORKSTATION_TRUST_ACCOUNT", 4096)
1184 $UACValues.Add("SERVER_TRUST_ACCOUNT", 8192)
1185 $UACValues.Add("DONT_EXPIRE_PASSWORD", 65536)
1186 $UACValues.Add("MNS_LOGON_ACCOUNT", 131072)
1187 $UACValues.Add("SMARTCARD_REQUIRED", 262144)
1188 $UACValues.Add("TRUSTED_FOR_DELEGATION", 524288)
1189 $UACValues.Add("NOT_DELEGATED", 1048576)
1190 $UACValues.Add("USE_DES_KEY_ONLY", 2097152)
1191 $UACValues.Add("DONT_REQ_PREAUTH", 4194304)
1192 $UACValues.Add("PASSWORD_EXPIRED", 8388608)
1193 $UACValues.Add("TRUSTED_TO_AUTH_FOR_DELEGATION", 16777216)
1194 $UACValues.Add("PARTIAL_SECRETS_ACCOUNT", 67108864)
1195 }
1196
1197 process {
1198
1199 $ResultUACValues = New-Object System.Collections.Specialized.OrderedDictionary
1200
1201 if($Value -is [Int]) {
1202 $IntValue = $Value
1203 }
1204 elseif ($Value -is [PSCustomObject]) {
1205 if($Value.useraccountcontrol) {
1206 $IntValue = $Value.useraccountcontrol
1207 }
1208 }
1209 else {
1210 Write-Warning "Invalid object input for -Value : $Value"
1211 return $Null
1212 }
1213
1214 if($ShowAll) {
1215 foreach ($UACValue in $UACValues.GetEnumerator()) {
1216 if( ($IntValue -band $UACValue.Value) -eq $UACValue.Value) {
1217 $ResultUACValues.Add($UACValue.Name, "$($UACValue.Value)+")
1218 }
1219 else {
1220 $ResultUACValues.Add($UACValue.Name, "$($UACValue.Value)")
1221 }
1222 }
1223 }
1224 else {
1225 foreach ($UACValue in $UACValues.GetEnumerator()) {
1226 if( ($IntValue -band $UACValue.Value) -eq $UACValue.Value) {
1227 $ResultUACValues.Add($UACValue.Name, "$($UACValue.Value)")
1228 }
1229 }
1230 }
1231 $ResultUACValues
1232 }
1233}
1234
1235
1236filter Get-Proxy {
1237<#
1238 .SYNOPSIS
1239
1240 Enumerates the proxy server and WPAD conents for the current user.
1241
1242 .PARAMETER ComputerName
1243
1244 The computername to enumerate proxy settings on, defaults to local host.
1245
1246 .EXAMPLE
1247
1248 PS C:\> Get-Proxy
1249
1250 Returns the current proxy settings.
1251#>
1252 param(
1253 [Parameter(ValueFromPipeline=$True)]
1254 [ValidateNotNullOrEmpty()]
1255 [String]
1256 $ComputerName = $ENV:COMPUTERNAME
1257 )
1258
1259 try {
1260 $Reg = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('CurrentUser', $ComputerName)
1261 $RegKey = $Reg.OpenSubkey("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Internet Settings")
1262 $ProxyServer = $RegKey.GetValue('ProxyServer')
1263 $AutoConfigURL = $RegKey.GetValue('AutoConfigURL')
1264
1265 $Wpad = ""
1266 if($AutoConfigURL -and ($AutoConfigURL -ne "")) {
1267 try {
1268 $Wpad = (New-Object Net.Webclient).DownloadString($AutoConfigURL)
1269 }
1270 catch {
1271 Write-Warning "Error connecting to AutoConfigURL : $AutoConfigURL"
1272 }
1273 }
1274
1275 if($ProxyServer -or $AutoConfigUrl) {
1276
1277 $Properties = @{
1278 'ProxyServer' = $ProxyServer
1279 'AutoConfigURL' = $AutoConfigURL
1280 'Wpad' = $Wpad
1281 }
1282
1283 New-Object -TypeName PSObject -Property $Properties
1284 }
1285 else {
1286 Write-Warning "No proxy settings found for $ComputerName"
1287 }
1288 }
1289 catch {
1290 Write-Warning "Error enumerating proxy settings for $ComputerName : $_"
1291 }
1292}
1293
1294
1295function Request-SPNTicket {
1296<#
1297 .SYNOPSIS
1298
1299 Request the kerberos ticket for a specified service principal name (SPN).
1300
1301 .PARAMETER SPN
1302
1303 The service principal name to request the ticket for. Required.
1304
1305 .PARAMETER EncPart
1306
1307 Switch. Return the encrypted portion of the ticket (cipher).
1308
1309 .EXAMPLE
1310
1311 PS C:\> Request-SPNTicket -SPN "HTTP/web.testlab.local"
1312
1313 Request a kerberos service ticket for the specified SPN.
1314
1315 .EXAMPLE
1316
1317 PS C:\> Request-SPNTicket -SPN "HTTP/web.testlab.local" -EncPart
1318
1319 Request a kerberos service ticket for the specified SPN and return the encrypted portion of the ticket.
1320
1321 .EXAMPLE
1322
1323 PS C:\> "HTTP/web1.testlab.local","HTTP/web2.testlab.local" | Request-SPNTicket
1324
1325 Request kerberos service tickets for all SPNs passed on the pipeline.
1326
1327 .EXAMPLE
1328
1329 PS C:\> Get-NetUser -SPN | Request-SPNTicket
1330
1331 Request kerberos service tickets for all users with non-null SPNs.
1332#>
1333
1334 [CmdletBinding()]
1335 Param (
1336 [Parameter(Mandatory=$True, ValueFromPipelineByPropertyName = $True)]
1337 [Alias('ServicePrincipalName')]
1338 [String[]]
1339 $SPN,
1340
1341 [Alias('EncryptedPart')]
1342 [Switch]
1343 $EncPart
1344 )
1345
1346 begin {
1347 Add-Type -AssemblyName System.IdentityModel
1348 }
1349
1350 process {
1351 ForEach($UserSPN in $SPN) {
1352 Write-Verbose "Requesting ticket for: $UserSPN"
1353 if (!$EncPart) {
1354 New-Object System.IdentityModel.Tokens.KerberosRequestorSecurityToken -ArgumentList $UserSPN
1355 }
1356 else {
1357 $Ticket = New-Object System.IdentityModel.Tokens.KerberosRequestorSecurityToken -ArgumentList $UserSPN
1358 $TicketByteStream = $Ticket.GetRequest()
1359 if ($TicketByteStream)
1360 {
1361 $TicketHexStream = [System.BitConverter]::ToString($TicketByteStream) -replace "-"
1362 [System.Collections.ArrayList]$Parts = ($TicketHexStream -replace '^(.*?)04820...(.*)','$2') -Split "A48201"
1363 $Parts.RemoveAt($Parts.Count - 1)
1364 $Parts -join "A48201"
1365 break
1366 }
1367 }
1368 }
1369 }
1370}
1371
1372
1373function Get-PathAcl {
1374<#
1375 .SYNOPSIS
1376
1377 Enumerates the ACL for a given file path.
1378
1379 .PARAMETER Path
1380
1381 The local/remote path to enumerate the ACLs for.
1382
1383 .PARAMETER Recurse
1384
1385 If any ACL results are groups, recurse and retrieve user membership.
1386
1387 .EXAMPLE
1388
1389 PS C:\> Get-PathAcl "\\SERVER\Share\"
1390
1391 Returns ACLs for the given UNC share.
1392#>
1393 [CmdletBinding()]
1394 param(
1395 [Parameter(Mandatory=$True, ValueFromPipeline=$True)]
1396 [String]
1397 $Path,
1398
1399 [Switch]
1400 $Recurse
1401 )
1402
1403 begin {
1404
1405 function Convert-FileRight {
1406
1407 # From http://stackoverflow.com/questions/28029872/retrieving-security-descriptor-and-getting-number-for-filesystemrights
1408
1409 [CmdletBinding()]
1410 param(
1411 [Int]
1412 $FSR
1413 )
1414
1415 $AccessMask = @{
1416 [uint32]'0x80000000' = 'GenericRead'
1417 [uint32]'0x40000000' = 'GenericWrite'
1418 [uint32]'0x20000000' = 'GenericExecute'
1419 [uint32]'0x10000000' = 'GenericAll'
1420 [uint32]'0x02000000' = 'MaximumAllowed'
1421 [uint32]'0x01000000' = 'AccessSystemSecurity'
1422 [uint32]'0x00100000' = 'Synchronize'
1423 [uint32]'0x00080000' = 'WriteOwner'
1424 [uint32]'0x00040000' = 'WriteDAC'
1425 [uint32]'0x00020000' = 'ReadControl'
1426 [uint32]'0x00010000' = 'Delete'
1427 [uint32]'0x00000100' = 'WriteAttributes'
1428 [uint32]'0x00000080' = 'ReadAttributes'
1429 [uint32]'0x00000040' = 'DeleteChild'
1430 [uint32]'0x00000020' = 'Execute/Traverse'
1431 [uint32]'0x00000010' = 'WriteExtendedAttributes'
1432 [uint32]'0x00000008' = 'ReadExtendedAttributes'
1433 [uint32]'0x00000004' = 'AppendData/AddSubdirectory'
1434 [uint32]'0x00000002' = 'WriteData/AddFile'
1435 [uint32]'0x00000001' = 'ReadData/ListDirectory'
1436 }
1437
1438 $SimplePermissions = @{
1439 [uint32]'0x1f01ff' = 'FullControl'
1440 [uint32]'0x0301bf' = 'Modify'
1441 [uint32]'0x0200a9' = 'ReadAndExecute'
1442 [uint32]'0x02019f' = 'ReadAndWrite'
1443 [uint32]'0x020089' = 'Read'
1444 [uint32]'0x000116' = 'Write'
1445 }
1446
1447 $Permissions = @()
1448
1449 # get simple permission
1450 $Permissions += $SimplePermissions.Keys | % {
1451 if (($FSR -band $_) -eq $_) {
1452 $SimplePermissions[$_]
1453 $FSR = $FSR -band (-not $_)
1454 }
1455 }
1456
1457 # get remaining extended permissions
1458 $Permissions += $AccessMask.Keys |
1459 ? { $FSR -band $_ } |
1460 % { $AccessMask[$_] }
1461
1462 ($Permissions | ?{$_}) -join ","
1463 }
1464 }
1465
1466 process {
1467
1468 try {
1469 $ACL = Get-Acl -Path $Path
1470
1471 $ACL.GetAccessRules($true,$true,[System.Security.Principal.SecurityIdentifier]) | ForEach-Object {
1472
1473 $Names = @()
1474 if ($_.IdentityReference -match '^S-1-5-21-[0-9]+-[0-9]+-[0-9]+-[0-9]+') {
1475 $Object = Get-ADObject -SID $_.IdentityReference
1476 $Names = @()
1477 $SIDs = @($Object.objectsid)
1478
1479 if ($Recurse -and (@('268435456','268435457','536870912','536870913') -contains $Object.samAccountType)) {
1480 $SIDs += Get-NetGroupMember -SID $Object.objectsid | Select-Object -ExpandProperty MemberSid
1481 }
1482
1483 $SIDs | ForEach-Object {
1484 $Names += ,@($_, (Convert-SidToName $_))
1485 }
1486 }
1487 else {
1488 $Names += ,@($_.IdentityReference.Value, (Convert-SidToName $_.IdentityReference.Value))
1489 }
1490
1491 ForEach($Name in $Names) {
1492 $Out = New-Object PSObject
1493 $Out | Add-Member Noteproperty 'Path' $Path
1494 $Out | Add-Member Noteproperty 'FileSystemRights' (Convert-FileRight -FSR $_.FileSystemRights.value__)
1495 $Out | Add-Member Noteproperty 'IdentityReference' $Name[1]
1496 $Out | Add-Member Noteproperty 'IdentitySID' $Name[0]
1497 $Out | Add-Member Noteproperty 'AccessControlType' $_.AccessControlType
1498 $Out
1499 }
1500 }
1501 }
1502 catch {
1503 Write-Warning $_
1504 }
1505 }
1506}
1507
1508
1509filter Get-NameField {
1510<#
1511 .SYNOPSIS
1512
1513 Helper that attempts to extract appropriate field names from
1514 passed computer objects.
1515
1516 .PARAMETER Object
1517
1518 The passed object to extract name fields from.
1519
1520 .PARAMETER DnsHostName
1521
1522 A DnsHostName to extract through ValueFromPipelineByPropertyName.
1523
1524 .PARAMETER Name
1525
1526 A Name to extract through ValueFromPipelineByPropertyName.
1527
1528 .EXAMPLE
1529
1530 PS C:\> Get-NetComputer -FullData | Get-NameField
1531#>
1532 [CmdletBinding()]
1533 param(
1534 [Parameter(ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
1535 [Object]
1536 $Object,
1537
1538 [Parameter(ValueFromPipelineByPropertyName = $True)]
1539 [String]
1540 $DnsHostName,
1541
1542 [Parameter(ValueFromPipelineByPropertyName = $True)]
1543 [String]
1544 $Name
1545 )
1546
1547 if($PSBoundParameters['DnsHostName']) {
1548 $DnsHostName
1549 }
1550 elseif($PSBoundParameters['Name']) {
1551 $Name
1552 }
1553 elseif($Object) {
1554 if ( [bool]($Object.PSobject.Properties.name -match "dnshostname") ) {
1555 # objects from Get-NetComputer
1556 $Object.dnshostname
1557 }
1558 elseif ( [bool]($Object.PSobject.Properties.name -match "name") ) {
1559 # objects from Get-NetDomainController
1560 $Object.name
1561 }
1562 else {
1563 # strings and catch alls
1564 $Object
1565 }
1566 }
1567 else {
1568 return $Null
1569 }
1570}
1571
1572
1573function Convert-LDAPProperty {
1574<#
1575 .SYNOPSIS
1576
1577 Helper that converts specific LDAP property result fields.
1578 Used by several of the Get-Net* function.
1579
1580 .PARAMETER Properties
1581
1582 Properties object to extract out LDAP fields for display.
1583#>
1584 param(
1585 [Parameter(Mandatory=$True, ValueFromPipeline=$True)]
1586 [ValidateNotNullOrEmpty()]
1587 $Properties
1588 )
1589
1590 $ObjectProperties = @{}
1591
1592 $Properties.PropertyNames | ForEach-Object {
1593 if (($_ -eq "objectsid") -or ($_ -eq "sidhistory")) {
1594 # convert the SID to a string
1595 $ObjectProperties[$_] = (New-Object System.Security.Principal.SecurityIdentifier($Properties[$_][0],0)).Value
1596 }
1597 elseif($_ -eq "objectguid") {
1598 # convert the GUID to a string
1599 $ObjectProperties[$_] = (New-Object Guid (,$Properties[$_][0])).Guid
1600 }
1601 elseif( ($_ -eq "lastlogon") -or ($_ -eq "lastlogontimestamp") -or ($_ -eq "pwdlastset") -or ($_ -eq "lastlogoff") -or ($_ -eq "badPasswordTime") ) {
1602 # convert timestamps
1603 if ($Properties[$_][0] -is [System.MarshalByRefObject]) {
1604 # if we have a System.__ComObject
1605 $Temp = $Properties[$_][0]
1606 [Int32]$High = $Temp.GetType().InvokeMember("HighPart", [System.Reflection.BindingFlags]::GetProperty, $null, $Temp, $null)
1607 [Int32]$Low = $Temp.GetType().InvokeMember("LowPart", [System.Reflection.BindingFlags]::GetProperty, $null, $Temp, $null)
1608 $ObjectProperties[$_] = ([datetime]::FromFileTime([Int64]("0x{0:x8}{1:x8}" -f $High, $Low)))
1609 }
1610 else {
1611 $ObjectProperties[$_] = ([datetime]::FromFileTime(($Properties[$_][0])))
1612 }
1613 }
1614 elseif($Properties[$_][0] -is [System.MarshalByRefObject]) {
1615 # try to convert misc com objects
1616 $Prop = $Properties[$_]
1617 try {
1618 $Temp = $Prop[$_][0]
1619 Write-Verbose $_
1620 [Int32]$High = $Temp.GetType().InvokeMember("HighPart", [System.Reflection.BindingFlags]::GetProperty, $null, $Temp, $null)
1621 [Int32]$Low = $Temp.GetType().InvokeMember("LowPart", [System.Reflection.BindingFlags]::GetProperty, $null, $Temp, $null)
1622 $ObjectProperties[$_] = [Int64]("0x{0:x8}{1:x8}" -f $High, $Low)
1623 }
1624 catch {
1625 $ObjectProperties[$_] = $Prop[$_]
1626 }
1627 }
1628 elseif($Properties[$_].count -eq 1) {
1629 $ObjectProperties[$_] = $Properties[$_][0]
1630 }
1631 else {
1632 $ObjectProperties[$_] = $Properties[$_]
1633 }
1634 }
1635
1636 New-Object -TypeName PSObject -Property $ObjectProperties
1637}
1638
1639
1640
1641########################################################
1642#
1643# Domain info functions below.
1644#
1645########################################################
1646
1647filter Get-DomainSearcher {
1648<#
1649 .SYNOPSIS
1650
1651 Helper used by various functions that takes an ADSpath and
1652 domain specifier and builds the correct ADSI searcher object.
1653
1654 .PARAMETER Domain
1655
1656 The domain to use for the query, defaults to the current domain.
1657
1658 .PARAMETER DomainController
1659
1660 Domain controller to reflect LDAP queries through.
1661
1662 .PARAMETER ADSpath
1663
1664 The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
1665 Useful for OU queries.
1666
1667 .PARAMETER ADSprefix
1668
1669 Prefix to set for the searcher (like "CN=Sites,CN=Configuration")
1670
1671 .PARAMETER PageSize
1672
1673 The PageSize to set for the LDAP searcher object.
1674
1675 .PARAMETER Credential
1676
1677 A [Management.Automation.PSCredential] object of alternate credentials
1678 for connection to the target domain.
1679
1680 .EXAMPLE
1681
1682 PS C:\> Get-DomainSearcher -Domain testlab.local
1683
1684 .EXAMPLE
1685
1686 PS C:\> Get-DomainSearcher -Domain testlab.local -DomainController SECONDARY.dev.testlab.local
1687#>
1688
1689 param(
1690 [Parameter(ValueFromPipeline=$True)]
1691 [String]
1692 $Domain,
1693
1694 [String]
1695 $DomainController,
1696
1697 [String]
1698 $ADSpath,
1699
1700 [String]
1701 $ADSprefix,
1702
1703 [ValidateRange(1,10000)]
1704 [Int]
1705 $PageSize = 200,
1706
1707 [Management.Automation.PSCredential]
1708 $Credential
1709 )
1710
1711 if(-not $Credential) {
1712 if(-not $Domain) {
1713 $Domain = (Get-NetDomain).name
1714 }
1715 elseif(-not $DomainController) {
1716 try {
1717 # if there's no -DomainController specified, try to pull the primary DC to reflect queries through
1718 $DomainController = ((Get-NetDomain).PdcRoleOwner).Name
1719 }
1720 catch {
1721 throw "Get-DomainSearcher: Error in retrieving PDC for current domain"
1722 }
1723 }
1724 }
1725 elseif (-not $DomainController) {
1726 # if a DC isn't specified
1727 try {
1728 $DomainController = ((Get-NetDomain -Credential $Credential).PdcRoleOwner).Name
1729 }
1730 catch {
1731 throw "Get-DomainSearcher: Error in retrieving PDC for current domain"
1732 }
1733
1734 if(!$DomainController) {
1735 throw "Get-DomainSearcher: Error in retrieving PDC for current domain"
1736 }
1737 }
1738
1739 $SearchString = "LDAP://"
1740
1741 if($DomainController) {
1742 $SearchString += $DomainController
1743 if($Domain){
1744 $SearchString += '/'
1745 }
1746 }
1747
1748 if($ADSprefix) {
1749 $SearchString += $ADSprefix + ','
1750 }
1751
1752 if($ADSpath) {
1753 if($ADSpath -Match '^GC://') {
1754 # if we're searching the global catalog
1755 $DN = $AdsPath.ToUpper().Trim('/')
1756 $SearchString = ''
1757 }
1758 else {
1759 if($ADSpath -match '^LDAP://') {
1760 if($ADSpath -match "LDAP://.+/.+") {
1761 $SearchString = ''
1762 }
1763 else {
1764 $ADSpath = $ADSpath.Substring(7)
1765 }
1766 }
1767 $DN = $ADSpath
1768 }
1769 }
1770 else {
1771 if($Domain -and ($Domain.Trim() -ne "")) {
1772 $DN = "DC=$($Domain.Replace('.', ',DC='))"
1773 }
1774 }
1775
1776 $SearchString += $DN
1777 Write-Verbose "Get-DomainSearcher search string: $SearchString"
1778
1779 if($Credential) {
1780 Write-Verbose "Using alternate credentials for LDAP connection"
1781 $DomainObject = New-Object DirectoryServices.DirectoryEntry($SearchString, $Credential.UserName, $Credential.GetNetworkCredential().Password)
1782 $Searcher = New-Object System.DirectoryServices.DirectorySearcher($DomainObject)
1783 }
1784 else {
1785 $Searcher = New-Object System.DirectoryServices.DirectorySearcher([ADSI]$SearchString)
1786 }
1787
1788 $Searcher.PageSize = $PageSize
1789 $Searcher.CacheResults = $False
1790 $Searcher
1791}
1792
1793
1794filter Convert-DNSRecord {
1795<#
1796 .SYNOPSIS
1797
1798 Decodes a binary DNS record.
1799
1800 Adapted/ported from Michael B. Smith's code at https://raw.githubusercontent.com/mmessano/PowerShell/master/dns-dump.ps1
1801
1802 .PARAMETER DNSRecord
1803
1804 The domain to query for zones, defaults to the current domain.
1805
1806 .LINK
1807
1808 https://raw.githubusercontent.com/mmessano/PowerShell/master/dns-dump.ps1
1809#>
1810 param(
1811 [Parameter(Position=0, ValueFromPipelineByPropertyName=$True, Mandatory=$True)]
1812 [Byte[]]
1813 $DNSRecord
1814 )
1815
1816 function Get-Name {
1817 # modified decodeName from https://raw.githubusercontent.com/mmessano/PowerShell/master/dns-dump.ps1
1818 [CmdletBinding()]
1819 param(
1820 [Byte[]]
1821 $Raw
1822 )
1823
1824 [Int]$Length = $Raw[0]
1825 [Int]$Segments = $Raw[1]
1826 [Int]$Index = 2
1827 [String]$Name = ""
1828
1829 while ($Segments-- -gt 0)
1830 {
1831 [Int]$SegmentLength = $Raw[$Index++]
1832 while ($SegmentLength-- -gt 0) {
1833 $Name += [Char]$Raw[$Index++]
1834 }
1835 $Name += "."
1836 }
1837 $Name
1838 }
1839
1840 $RDataLen = [BitConverter]::ToUInt16($DNSRecord, 0)
1841 $RDataType = [BitConverter]::ToUInt16($DNSRecord, 2)
1842 $UpdatedAtSerial = [BitConverter]::ToUInt32($DNSRecord, 8)
1843
1844 $TTLRaw = $DNSRecord[12..15]
1845 # reverse for big endian
1846 $Null = [array]::Reverse($TTLRaw)
1847 $TTL = [BitConverter]::ToUInt32($TTLRaw, 0)
1848
1849 $Age = [BitConverter]::ToUInt32($DNSRecord, 20)
1850 if($Age -ne 0) {
1851 $TimeStamp = ((Get-Date -Year 1601 -Month 1 -Day 1 -Hour 0 -Minute 0 -Second 0).AddHours($age)).ToString()
1852 }
1853 else {
1854 $TimeStamp = "[static]"
1855 }
1856
1857 $DNSRecordObject = New-Object PSObject
1858
1859 if($RDataType -eq 1) {
1860 $IP = "{0}.{1}.{2}.{3}" -f $DNSRecord[24], $DNSRecord[25], $DNSRecord[26], $DNSRecord[27]
1861 $Data = $IP
1862 $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'A'
1863 }
1864
1865 elseif($RDataType -eq 2) {
1866 $NSName = Get-Name $DNSRecord[24..$DNSRecord.length]
1867 $Data = $NSName
1868 $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'NS'
1869 }
1870
1871 elseif($RDataType -eq 5) {
1872 $Alias = Get-Name $DNSRecord[24..$DNSRecord.length]
1873 $Data = $Alias
1874 $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'CNAME'
1875 }
1876
1877 elseif($RDataType -eq 6) {
1878 # TODO: how to implement properly? nested object?
1879 $Data = $([System.Convert]::ToBase64String($DNSRecord[24..$DNSRecord.length]))
1880 $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'SOA'
1881 }
1882
1883 elseif($RDataType -eq 12) {
1884 $Ptr = Get-Name $DNSRecord[24..$DNSRecord.length]
1885 $Data = $Ptr
1886 $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'PTR'
1887 }
1888
1889 elseif($RDataType -eq 13) {
1890 # TODO: how to implement properly? nested object?
1891 $Data = $([System.Convert]::ToBase64String($DNSRecord[24..$DNSRecord.length]))
1892 $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'HINFO'
1893 }
1894
1895 elseif($RDataType -eq 15) {
1896 # TODO: how to implement properly? nested object?
1897 $Data = $([System.Convert]::ToBase64String($DNSRecord[24..$DNSRecord.length]))
1898 $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'MX'
1899 }
1900
1901 elseif($RDataType -eq 16) {
1902
1903 [string]$TXT = ""
1904 [int]$SegmentLength = $DNSRecord[24]
1905 $Index = 25
1906 while ($SegmentLength-- -gt 0) {
1907 $TXT += [char]$DNSRecord[$index++]
1908 }
1909
1910 $Data = $TXT
1911 $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'TXT'
1912 }
1913
1914 elseif($RDataType -eq 28) {
1915 # TODO: how to implement properly? nested object?
1916 $Data = $([System.Convert]::ToBase64String($DNSRecord[24..$DNSRecord.length]))
1917 $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'AAAA'
1918 }
1919
1920 elseif($RDataType -eq 33) {
1921 # TODO: how to implement properly? nested object?
1922 $Data = $([System.Convert]::ToBase64String($DNSRecord[24..$DNSRecord.length]))
1923 $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'SRV'
1924 }
1925
1926 else {
1927 $Data = $([System.Convert]::ToBase64String($DNSRecord[24..$DNSRecord.length]))
1928 $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'UNKNOWN'
1929 }
1930
1931 $DNSRecordObject | Add-Member Noteproperty 'UpdatedAtSerial' $UpdatedAtSerial
1932 $DNSRecordObject | Add-Member Noteproperty 'TTL' $TTL
1933 $DNSRecordObject | Add-Member Noteproperty 'Age' $Age
1934 $DNSRecordObject | Add-Member Noteproperty 'TimeStamp' $TimeStamp
1935 $DNSRecordObject | Add-Member Noteproperty 'Data' $Data
1936 $DNSRecordObject
1937}
1938
1939
1940filter Get-DNSZone {
1941<#
1942 .SYNOPSIS
1943
1944 Enumerates the Active Directory DNS zones for a given domain.
1945
1946 .PARAMETER Domain
1947
1948 The domain to query for zones, defaults to the current domain.
1949
1950 .PARAMETER DomainController
1951
1952 Domain controller to reflect LDAP queries through.
1953
1954 .PARAMETER PageSize
1955
1956 The PageSize to set for the LDAP searcher object.
1957
1958 .PARAMETER Credential
1959
1960 A [Management.Automation.PSCredential] object of alternate credentials
1961 for connection to the target domain.
1962
1963 .PARAMETER FullData
1964
1965 Switch. Return full computer objects instead of just system names (the default).
1966
1967 .EXAMPLE
1968
1969 PS C:\> Get-DNSZone
1970
1971 Retrieves the DNS zones for the current domain.
1972
1973 .EXAMPLE
1974
1975 PS C:\> Get-DNSZone -Domain dev.testlab.local -DomainController primary.testlab.local
1976
1977 Retrieves the DNS zones for the dev.testlab.local domain, reflecting the LDAP queries
1978 through the primary.testlab.local domain controller.
1979#>
1980
1981 param(
1982 [Parameter(Position=0, ValueFromPipeline=$True)]
1983 [String]
1984 $Domain,
1985
1986 [String]
1987 $DomainController,
1988
1989 [ValidateRange(1,10000)]
1990 [Int]
1991 $PageSize = 200,
1992
1993 [Management.Automation.PSCredential]
1994 $Credential,
1995
1996 [Switch]
1997 $FullData
1998 )
1999
2000 $DNSSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -PageSize $PageSize -Credential $Credential
2001 $DNSSearcher.filter="(objectClass=dnsZone)"
2002
2003 if($DNSSearcher) {
2004 $Results = $DNSSearcher.FindAll()
2005 $Results | Where-Object {$_} | ForEach-Object {
2006 # convert/process the LDAP fields for each result
2007 $Properties = Convert-LDAPProperty -Properties $_.Properties
2008 $Properties | Add-Member NoteProperty 'ZoneName' $Properties.name
2009
2010 if ($FullData) {
2011 $Properties
2012 }
2013 else {
2014 $Properties | Select-Object ZoneName,distinguishedname,whencreated,whenchanged
2015 }
2016 }
2017 $Results.dispose()
2018 $DNSSearcher.dispose()
2019 }
2020
2021 $DNSSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -PageSize $PageSize -Credential $Credential -ADSprefix "CN=MicrosoftDNS,DC=DomainDnsZones"
2022 $DNSSearcher.filter="(objectClass=dnsZone)"
2023
2024 if($DNSSearcher) {
2025 $Results = $DNSSearcher.FindAll()
2026 $Results | Where-Object {$_} | ForEach-Object {
2027 # convert/process the LDAP fields for each result
2028 $Properties = Convert-LDAPProperty -Properties $_.Properties
2029 $Properties | Add-Member NoteProperty 'ZoneName' $Properties.name
2030
2031 if ($FullData) {
2032 $Properties
2033 }
2034 else {
2035 $Properties | Select-Object ZoneName,distinguishedname,whencreated,whenchanged
2036 }
2037 }
2038 $Results.dispose()
2039 $DNSSearcher.dispose()
2040 }
2041}
2042
2043
2044filter Get-DNSRecord {
2045<#
2046 .SYNOPSIS
2047
2048 Enumerates the Active Directory DNS records for a given zone.
2049
2050 .PARAMETER ZoneName
2051
2052 The zone to query for records (which can be enumearted with Get-DNSZone). Required.
2053
2054 .PARAMETER Domain
2055
2056 The domain to query for zones, defaults to the current domain.
2057
2058 .PARAMETER DomainController
2059
2060 Domain controller to reflect LDAP queries through.
2061
2062 .PARAMETER PageSize
2063
2064 The PageSize to set for the LDAP searcher object.
2065
2066 .PARAMETER Credential
2067
2068 A [Management.Automation.PSCredential] object of alternate credentials
2069 for connection to the target domain.
2070
2071 .EXAMPLE
2072
2073 PS C:\> Get-DNSRecord -ZoneName testlab.local
2074
2075 Retrieve all records for the testlab.local zone.
2076
2077 .EXAMPLE
2078
2079 PS C:\> Get-DNSZone | Get-DNSRecord
2080
2081 Retrieve all records for all zones in the current domain.
2082
2083 .EXAMPLE
2084
2085 PS C:\> Get-DNSZone -Domain dev.testlab.local | Get-DNSRecord -Domain dev.testlab.local
2086
2087 Retrieve all records for all zones in the dev.testlab.local domain.
2088#>
2089
2090 param(
2091 [Parameter(Position=0, ValueFromPipelineByPropertyName=$True, Mandatory=$True)]
2092 [String]
2093 $ZoneName,
2094
2095 [String]
2096 $Domain,
2097
2098 [String]
2099 $DomainController,
2100
2101 [ValidateRange(1,10000)]
2102 [Int]
2103 $PageSize = 200,
2104
2105 [Management.Automation.PSCredential]
2106 $Credential
2107 )
2108
2109 $DNSSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -PageSize $PageSize -Credential $Credential -ADSprefix "DC=$($ZoneName),CN=MicrosoftDNS,DC=DomainDnsZones"
2110 $DNSSearcher.filter="(objectClass=dnsNode)"
2111
2112 if($DNSSearcher) {
2113 $Results = $DNSSearcher.FindAll()
2114 $Results | Where-Object {$_} | ForEach-Object {
2115 try {
2116 # convert/process the LDAP fields for each result
2117 $Properties = Convert-LDAPProperty -Properties $_.Properties | Select-Object name,distinguishedname,dnsrecord,whencreated,whenchanged
2118 $Properties | Add-Member NoteProperty 'ZoneName' $ZoneName
2119
2120 # convert the record and extract the properties
2121 if ($Properties.dnsrecord -is [System.DirectoryServices.ResultPropertyValueCollection]) {
2122 # TODO: handle multiple nested records properly?
2123 $Record = Convert-DNSRecord -DNSRecord $Properties.dnsrecord[0]
2124 }
2125 else {
2126 $Record = Convert-DNSRecord -DNSRecord $Properties.dnsrecord
2127 }
2128
2129 if($Record) {
2130 $Record.psobject.properties | ForEach-Object {
2131 $Properties | Add-Member NoteProperty $_.Name $_.Value
2132 }
2133 }
2134
2135 $Properties
2136 }
2137 catch {
2138 Write-Warning "ERROR: $_"
2139 $Properties
2140 }
2141 }
2142 $Results.dispose()
2143 $DNSSearcher.dispose()
2144 }
2145}
2146
2147
2148filter Get-NetDomain {
2149<#
2150 .SYNOPSIS
2151
2152 Returns a given domain object.
2153
2154 .PARAMETER Domain
2155
2156 The domain name to query for, defaults to the current domain.
2157
2158 .PARAMETER Credential
2159
2160 A [Management.Automation.PSCredential] object of alternate credentials
2161 for connection to the target domain.
2162
2163 .EXAMPLE
2164
2165 PS C:\> Get-NetDomain -Domain testlab.local
2166
2167 .EXAMPLE
2168
2169 PS C:\> "testlab.local" | Get-NetDomain
2170
2171 .LINK
2172
2173 http://social.technet.microsoft.com/Forums/scriptcenter/en-US/0c5b3f83-e528-4d49-92a4-dee31f4b481c/finding-the-dn-of-the-the-domain-without-admodule-in-powershell?forum=ITCG
2174#>
2175
2176 param(
2177 [Parameter(ValueFromPipeline=$True)]
2178 [String]
2179 $Domain,
2180
2181 [Management.Automation.PSCredential]
2182 $Credential
2183 )
2184
2185 if($Credential) {
2186
2187 Write-Verbose "Using alternate credentials for Get-NetDomain"
2188
2189 if(!$Domain) {
2190 # if no domain is supplied, extract the logon domain from the PSCredential passed
2191 $Domain = $Credential.GetNetworkCredential().Domain
2192 Write-Verbose "Extracted domain '$Domain' from -Credential"
2193 }
2194
2195 $DomainContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext('Domain', $Domain, $Credential.UserName, $Credential.GetNetworkCredential().Password)
2196
2197 try {
2198 [System.DirectoryServices.ActiveDirectory.Domain]::GetDomain($DomainContext)
2199 }
2200 catch {
2201 Write-Verbose "The specified domain does '$Domain' not exist, could not be contacted, there isn't an existing trust, or the specified credentials are invalid."
2202 $Null
2203 }
2204 }
2205 elseif($Domain) {
2206 $DomainContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext('Domain', $Domain)
2207 try {
2208 [System.DirectoryServices.ActiveDirectory.Domain]::GetDomain($DomainContext)
2209 }
2210 catch {
2211 Write-Verbose "The specified domain '$Domain' does not exist, could not be contacted, or there isn't an existing trust."
2212 $Null
2213 }
2214 }
2215 else {
2216 [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()
2217 }
2218}
2219
2220
2221filter Get-NetForest {
2222<#
2223 .SYNOPSIS
2224
2225 Returns a given forest object.
2226
2227 .PARAMETER Forest
2228
2229 The forest name to query for, defaults to the current domain.
2230
2231 .PARAMETER Credential
2232
2233 A [Management.Automation.PSCredential] object of alternate credentials
2234 for connection to the target domain.
2235
2236 .EXAMPLE
2237
2238 PS C:\> Get-NetForest -Forest external.domain
2239
2240 .EXAMPLE
2241
2242 PS C:\> "external.domain" | Get-NetForest
2243#>
2244
2245 param(
2246 [Parameter(ValueFromPipeline=$True)]
2247 [String]
2248 $Forest,
2249
2250 [Management.Automation.PSCredential]
2251 $Credential
2252 )
2253
2254 if($Credential) {
2255
2256 Write-Verbose "Using alternate credentials for Get-NetForest"
2257
2258 if(!$Forest) {
2259 # if no domain is supplied, extract the logon domain from the PSCredential passed
2260 $Forest = $Credential.GetNetworkCredential().Domain
2261 Write-Verbose "Extracted domain '$Forest' from -Credential"
2262 }
2263
2264 $ForestContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext('Forest', $Forest, $Credential.UserName, $Credential.GetNetworkCredential().Password)
2265
2266 try {
2267 $ForestObject = [System.DirectoryServices.ActiveDirectory.Forest]::GetForest($ForestContext)
2268 }
2269 catch {
2270 Write-Verbose "The specified forest '$Forest' does not exist, could not be contacted, there isn't an existing trust, or the specified credentials are invalid."
2271 $Null
2272 }
2273 }
2274 elseif($Forest) {
2275 $ForestContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext('Forest', $Forest)
2276 try {
2277 $ForestObject = [System.DirectoryServices.ActiveDirectory.Forest]::GetForest($ForestContext)
2278 }
2279 catch {
2280 Write-Verbose "The specified forest '$Forest' does not exist, could not be contacted, or there isn't an existing trust."
2281 return $Null
2282 }
2283 }
2284 else {
2285 # otherwise use the current forest
2286 $ForestObject = [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest()
2287 }
2288
2289 if($ForestObject) {
2290 # get the SID of the forest root
2291 $ForestSid = (New-Object System.Security.Principal.NTAccount($ForestObject.RootDomain,"krbtgt")).Translate([System.Security.Principal.SecurityIdentifier]).Value
2292 $Parts = $ForestSid -Split "-"
2293 $ForestSid = $Parts[0..$($Parts.length-2)] -join "-"
2294 $ForestObject | Add-Member NoteProperty 'RootDomainSid' $ForestSid
2295 $ForestObject
2296 }
2297}
2298
2299
2300filter Get-NetForestDomain {
2301<#
2302 .SYNOPSIS
2303
2304 Return all domains for a given forest.
2305
2306 .PARAMETER Forest
2307
2308 The forest name to query domain for.
2309
2310 .PARAMETER Credential
2311
2312 A [Management.Automation.PSCredential] object of alternate credentials
2313 for connection to the target domain.
2314
2315 .EXAMPLE
2316
2317 PS C:\> Get-NetForestDomain
2318
2319 .EXAMPLE
2320
2321 PS C:\> Get-NetForestDomain -Forest external.local
2322#>
2323
2324 param(
2325 [Parameter(ValueFromPipeline=$True)]
2326 [String]
2327 $Forest,
2328
2329 [Management.Automation.PSCredential]
2330 $Credential
2331 )
2332
2333 $ForestObject = Get-NetForest -Forest $Forest -Credential $Credential
2334
2335 if($ForestObject) {
2336 $ForestObject.Domains
2337 }
2338}
2339
2340
2341filter Get-NetForestCatalog {
2342<#
2343 .SYNOPSIS
2344
2345 Return all global catalogs for a given forest.
2346
2347 .PARAMETER Forest
2348
2349 The forest name to query domain for.
2350
2351 .PARAMETER Credential
2352
2353 A [Management.Automation.PSCredential] object of alternate credentials
2354 for connection to the target domain.
2355
2356 .EXAMPLE
2357
2358 PS C:\> Get-NetForestCatalog
2359#>
2360
2361 param(
2362 [Parameter(ValueFromPipeline=$True)]
2363 [String]
2364 $Forest,
2365
2366 [Management.Automation.PSCredential]
2367 $Credential
2368 )
2369
2370 $ForestObject = Get-NetForest -Forest $Forest -Credential $Credential
2371
2372 if($ForestObject) {
2373 $ForestObject.FindAllGlobalCatalogs()
2374 }
2375}
2376
2377
2378filter Get-NetDomainController {
2379<#
2380 .SYNOPSIS
2381
2382 Return the current domain controllers for the active domain.
2383
2384 .PARAMETER Domain
2385
2386 The domain to query for domain controllers, defaults to the current domain.
2387
2388 .PARAMETER DomainController
2389
2390 Domain controller to reflect LDAP queries through.
2391
2392 .PARAMETER LDAP
2393
2394 Switch. Use LDAP queries to determine the domain controllers.
2395
2396 .PARAMETER Credential
2397
2398 A [Management.Automation.PSCredential] object of alternate credentials
2399 for connection to the target domain.
2400
2401 .EXAMPLE
2402
2403 PS C:\> Get-NetDomainController -Domain 'test.local'
2404
2405 Determine the domain controllers for 'test.local'.
2406
2407 .EXAMPLE
2408
2409 PS C:\> Get-NetDomainController -Domain 'test.local' -LDAP
2410
2411 Determine the domain controllers for 'test.local' using LDAP queries.
2412
2413 .EXAMPLE
2414
2415 PS C:\> 'test.local' | Get-NetDomainController
2416
2417 Determine the domain controllers for 'test.local'.
2418#>
2419
2420 [CmdletBinding()]
2421 param(
2422 [Parameter(ValueFromPipeline=$True)]
2423 [String]
2424 $Domain,
2425
2426 [String]
2427 $DomainController,
2428
2429 [Switch]
2430 $LDAP,
2431
2432 [Management.Automation.PSCredential]
2433 $Credential
2434 )
2435
2436 if($LDAP -or $DomainController) {
2437 # filter string to return all domain controllers
2438 Get-NetComputer -Domain $Domain -DomainController $DomainController -Credential $Credential -FullData -Filter '(userAccountControl:1.2.840.113556.1.4.803:=8192)'
2439 }
2440 else {
2441 $FoundDomain = Get-NetDomain -Domain $Domain -Credential $Credential
2442 if($FoundDomain) {
2443 $Founddomain.DomainControllers
2444 }
2445 }
2446}
2447
2448
2449########################################################
2450#
2451# "net *" replacements and other fun start below
2452#
2453########################################################
2454
2455function Get-NetUser {
2456<#
2457 .SYNOPSIS
2458
2459 Query information for a given user or users in the domain
2460 using ADSI and LDAP. Another -Domain can be specified to
2461 query for users across a trust.
2462 Replacement for "net users /domain"
2463
2464 .PARAMETER UserName
2465
2466 Username filter string, wildcards accepted.
2467
2468 .PARAMETER Domain
2469
2470 The domain to query for users, defaults to the current domain.
2471
2472 .PARAMETER DomainController
2473
2474 Domain controller to reflect LDAP queries through.
2475
2476 .PARAMETER ADSpath
2477
2478 The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
2479 Useful for OU queries.
2480
2481 .PARAMETER Filter
2482
2483 A customized ldap filter string to use, e.g. "(description=*admin*)"
2484
2485 .PARAMETER AdminCount
2486
2487 Switch. Return users with adminCount=1.
2488
2489 .PARAMETER SPN
2490
2491 Switch. Only return user objects with non-null service principal names.
2492
2493 .PARAMETER Unconstrained
2494
2495 Switch. Return users that have unconstrained delegation.
2496
2497 .PARAMETER AllowDelegation
2498
2499 Switch. Return user accounts that are not marked as 'sensitive and not allowed for delegation'
2500
2501 .PARAMETER PageSize
2502
2503 The PageSize to set for the LDAP searcher object.
2504
2505 .PARAMETER Credential
2506
2507 A [Management.Automation.PSCredential] object of alternate credentials
2508 for connection to the target domain.
2509
2510 .EXAMPLE
2511
2512 PS C:\> Get-NetUser -Domain testing
2513
2514 .EXAMPLE
2515
2516 PS C:\> Get-NetUser -ADSpath "LDAP://OU=secret,DC=testlab,DC=local"
2517#>
2518
2519 param(
2520 [Parameter(Position=0, ValueFromPipeline=$True)]
2521 [String]
2522 $UserName,
2523
2524 [String]
2525 $Domain,
2526
2527 [String]
2528 $DomainController,
2529
2530 [String]
2531 $ADSpath,
2532
2533 [String]
2534 $Filter,
2535
2536 [Switch]
2537 $SPN,
2538
2539 [Switch]
2540 $AdminCount,
2541
2542 [Switch]
2543 $Unconstrained,
2544
2545 [Switch]
2546 $AllowDelegation,
2547
2548 [ValidateRange(1,10000)]
2549 [Int]
2550 $PageSize = 200,
2551
2552 [Management.Automation.PSCredential]
2553 $Credential
2554 )
2555
2556 begin {
2557 # so this isn't repeated if users are passed on the pipeline
2558 $UserSearcher = Get-DomainSearcher -Domain $Domain -ADSpath $ADSpath -DomainController $DomainController -PageSize $PageSize -Credential $Credential
2559 }
2560
2561 process {
2562 if($UserSearcher) {
2563
2564 # if we're checking for unconstrained delegation
2565 if($Unconstrained) {
2566 Write-Verbose "Checking for unconstrained delegation"
2567 $Filter += "(userAccountControl:1.2.840.113556.1.4.803:=524288)"
2568 }
2569 if($AllowDelegation) {
2570 Write-Verbose "Checking for users who can be delegated"
2571 # negation of "Accounts that are sensitive and not trusted for delegation"
2572 $Filter += "(!(userAccountControl:1.2.840.113556.1.4.803:=1048574))"
2573 }
2574 if($AdminCount) {
2575 Write-Verbose "Checking for adminCount=1"
2576 $Filter += "(admincount=1)"
2577 }
2578
2579 # check if we're using a username filter or not
2580 if($UserName) {
2581 # samAccountType=805306368 indicates user objects
2582 $UserSearcher.filter="(&(samAccountType=805306368)(samAccountName=$UserName)$Filter)"
2583 }
2584 elseif($SPN) {
2585 $UserSearcher.filter="(&(samAccountType=805306368)(servicePrincipalName=*)$Filter)"
2586 }
2587 else {
2588 # filter is something like "(samAccountName=*blah*)" if specified
2589 $UserSearcher.filter="(&(samAccountType=805306368)$Filter)"
2590 }
2591
2592 $Results = $UserSearcher.FindAll()
2593 $Results | Where-Object {$_} | ForEach-Object {
2594 # convert/process the LDAP fields for each result
2595 $User = Convert-LDAPProperty -Properties $_.Properties
2596 $User.PSObject.TypeNames.Add('PowerView.User')
2597 $User
2598 }
2599 $Results.dispose()
2600 $UserSearcher.dispose()
2601 }
2602 }
2603}
2604
2605
2606function Add-NetUser {
2607<#
2608 .SYNOPSIS
2609
2610 Adds a domain user or a local user to the current (or remote) machine,
2611 if permissions allow, utilizing the WinNT service provider and
2612 DirectoryServices.AccountManagement, respectively.
2613
2614 The default behavior is to add a user to the local machine.
2615 An optional group name to add the user to can be specified.
2616
2617 .PARAMETER UserName
2618
2619 The username to add. If not given, it defaults to 'backdoor'
2620
2621 .PARAMETER Password
2622
2623 The password to set for the added user. If not given, it defaults to 'Password123!'
2624
2625 .PARAMETER GroupName
2626
2627 Group to optionally add the user to.
2628
2629 .PARAMETER ComputerName
2630
2631 Hostname to add the local user to, defaults to 'localhost'
2632
2633 .PARAMETER Domain
2634
2635 Specified domain to add the user to.
2636
2637 .EXAMPLE
2638
2639 PS C:\> Add-NetUser -UserName john -Password 'Password123!'
2640
2641 Adds a localuser 'john' to the local machine with password of 'Password123!'
2642
2643 .EXAMPLE
2644
2645 PS C:\> Add-NetUser -UserName john -Password 'Password123!' -ComputerName server.testlab.local
2646
2647 Adds a localuser 'john' with password of 'Password123!' to server.testlab.local's local Administrators group.
2648
2649 .EXAMPLE
2650
2651 PS C:\> Add-NetUser -UserName john -Password password -GroupName "Domain Admins" -Domain ''
2652
2653 Adds the user "john" with password "password" to the current domain and adds
2654 the user to the domain group "Domain Admins"
2655
2656 .EXAMPLE
2657
2658 PS C:\> Add-NetUser -UserName john -Password password -GroupName "Domain Admins" -Domain 'testing'
2659
2660 Adds the user "john" with password "password" to the 'testing' domain and adds
2661 the user to the domain group "Domain Admins"
2662
2663 .Link
2664
2665 http://blogs.technet.com/b/heyscriptingguy/archive/2010/11/23/use-powershell-to-create-local-user-accounts.aspx
2666#>
2667
2668 [CmdletBinding()]
2669 Param (
2670 [ValidateNotNullOrEmpty()]
2671 [String]
2672 $UserName = 'backdoor',
2673
2674 [ValidateNotNullOrEmpty()]
2675 [String]
2676 $Password = 'Password123!',
2677
2678 [ValidateNotNullOrEmpty()]
2679 [String]
2680 $GroupName,
2681
2682 [ValidateNotNullOrEmpty()]
2683 [Alias('HostName')]
2684 [String]
2685 $ComputerName = 'localhost',
2686
2687 [ValidateNotNullOrEmpty()]
2688 [String]
2689 $Domain
2690 )
2691
2692 if ($Domain) {
2693
2694 $DomainObject = Get-NetDomain -Domain $Domain
2695 if(-not $DomainObject) {
2696 Write-Warning "Error in grabbing $Domain object"
2697 return $Null
2698 }
2699
2700 # add the assembly we need
2701 Add-Type -AssemblyName System.DirectoryServices.AccountManagement
2702
2703 # http://richardspowershellblog.wordpress.com/2008/05/25/system-directoryservices-accountmanagement/
2704 # get the domain context
2705 $Context = New-Object -TypeName System.DirectoryServices.AccountManagement.PrincipalContext -ArgumentList ([System.DirectoryServices.AccountManagement.ContextType]::Domain), $DomainObject
2706
2707 # create the user object
2708 $User = New-Object -TypeName System.DirectoryServices.AccountManagement.UserPrincipal -ArgumentList $Context
2709
2710 # set user properties
2711 $User.Name = $UserName
2712 $User.SamAccountName = $UserName
2713 $User.PasswordNotRequired = $False
2714 $User.SetPassword($Password)
2715 $User.Enabled = $True
2716
2717 Write-Verbose "Creating user $UserName to with password '$Password' in domain $Domain"
2718
2719 try {
2720 # commit the user
2721 $User.Save()
2722 "[*] User $UserName successfully created in domain $Domain"
2723 }
2724 catch {
2725 Write-Warning '[!] User already exists!'
2726 return
2727 }
2728 }
2729 else {
2730
2731 Write-Verbose "Creating user $UserName to with password '$Password' on $ComputerName"
2732
2733 # if it's not a domain add, it's a local machine add
2734 $ObjOu = [ADSI]"WinNT://$ComputerName"
2735 $ObjUser = $ObjOu.Create('User', $UserName)
2736 $ObjUser.SetPassword($Password)
2737
2738 # commit the changes to the local machine
2739 try {
2740 $Null = $ObjUser.SetInfo()
2741 "[*] User $UserName successfully created on host $ComputerName"
2742 }
2743 catch {
2744 Write-Warning '[!] Account already exists!'
2745 return
2746 }
2747 }
2748
2749 # if a group is specified, invoke Add-NetGroupUser and return its value
2750 if ($GroupName) {
2751 # if we're adding the user to a domain
2752 if ($Domain) {
2753 Add-NetGroupUser -UserName $UserName -GroupName $GroupName -Domain $Domain
2754 "[*] User $UserName successfully added to group $GroupName in domain $Domain"
2755 }
2756 # otherwise, we're adding to a local group
2757 else {
2758 Add-NetGroupUser -UserName $UserName -GroupName $GroupName -ComputerName $ComputerName
2759 "[*] User $UserName successfully added to group $GroupName on host $ComputerName"
2760 }
2761 }
2762}
2763
2764
2765function Add-NetGroupUser {
2766<#
2767 .SYNOPSIS
2768
2769 Adds a user to a domain group or a local group on the current (or remote) machine,
2770 if permissions allow, utilizing the WinNT service provider and
2771 DirectoryServices.AccountManagement, respectively.
2772
2773 .PARAMETER UserName
2774
2775 The domain username to query for.
2776
2777 .PARAMETER GroupName
2778
2779 Group to add the user to.
2780
2781 .PARAMETER ComputerName
2782
2783 Hostname to add the user to, defaults to localhost.
2784
2785 .PARAMETER Domain
2786
2787 Domain to add the user to.
2788
2789 .EXAMPLE
2790
2791 PS C:\> Add-NetGroupUser -UserName john -GroupName Administrators
2792
2793 Adds a localuser "john" to the local group "Administrators"
2794
2795 .EXAMPLE
2796
2797 PS C:\> Add-NetGroupUser -UserName john -GroupName "Domain Admins" -Domain dev.local
2798
2799 Adds the existing user "john" to the domain group "Domain Admins" in "dev.local"
2800#>
2801
2802 [CmdletBinding()]
2803 param(
2804 [Parameter(Mandatory = $True)]
2805 [ValidateNotNullOrEmpty()]
2806 [String]
2807 $UserName,
2808
2809 [Parameter(Mandatory = $True)]
2810 [ValidateNotNullOrEmpty()]
2811 [String]
2812 $GroupName,
2813
2814 [ValidateNotNullOrEmpty()]
2815 [Alias('HostName')]
2816 [String]
2817 $ComputerName,
2818
2819 [String]
2820 $Domain
2821 )
2822
2823 # add the assembly if we need it
2824 Add-Type -AssemblyName System.DirectoryServices.AccountManagement
2825
2826 # if we're adding to a remote host's local group, use the WinNT provider
2827 if($ComputerName -and ($ComputerName -ne "localhost")) {
2828 try {
2829 Write-Verbose "Adding user $UserName to $GroupName on host $ComputerName"
2830 ([ADSI]"WinNT://$ComputerName/$GroupName,group").add("WinNT://$ComputerName/$UserName,user")
2831 "[*] User $UserName successfully added to group $GroupName on $ComputerName"
2832 }
2833 catch {
2834 Write-Warning "[!] Error adding user $UserName to group $GroupName on $ComputerName"
2835 return
2836 }
2837 }
2838
2839 # otherwise it's a local machine or domain add
2840 else {
2841 try {
2842 if ($Domain) {
2843 Write-Verbose "Adding user $UserName to $GroupName on domain $Domain"
2844 $CT = [System.DirectoryServices.AccountManagement.ContextType]::Domain
2845 $DomainObject = Get-NetDomain -Domain $Domain
2846 if(-not $DomainObject) {
2847 return $Null
2848 }
2849 # get the full principal context
2850 $Context = New-Object -TypeName System.DirectoryServices.AccountManagement.PrincipalContext -ArgumentList $CT, $DomainObject
2851 }
2852 else {
2853 # otherwise, get the local machine context
2854 Write-Verbose "Adding user $UserName to $GroupName on localhost"
2855 $Context = New-Object System.DirectoryServices.AccountManagement.PrincipalContext([System.DirectoryServices.AccountManagement.ContextType]::Machine, $Env:ComputerName)
2856 }
2857
2858 # find the particular group
2859 $Group = [System.DirectoryServices.AccountManagement.GroupPrincipal]::FindByIdentity($Context,$GroupName)
2860
2861 # add the particular user to the group
2862 $Group.Members.add($Context, [System.DirectoryServices.AccountManagement.IdentityType]::SamAccountName, $UserName)
2863
2864 # commit the changes
2865 $Group.Save()
2866 }
2867 catch {
2868 Write-Warning "Error adding $UserName to $GroupName : $_"
2869 }
2870 }
2871}
2872
2873
2874function Get-UserProperty {
2875<#
2876 .SYNOPSIS
2877
2878 Returns a list of all user object properties. If a property
2879 name is specified, it returns all [user:property] values.
2880
2881 Taken directly from @obscuresec's post:
2882 http://obscuresecurity.blogspot.com/2014/04/ADSISearcher.html
2883
2884 .PARAMETER Properties
2885
2886 Property names to extract for users.
2887
2888 .PARAMETER Domain
2889
2890 The domain to query for user properties, defaults to the current domain.
2891
2892 .PARAMETER DomainController
2893
2894 Domain controller to reflect LDAP queries through.
2895
2896 .PARAMETER PageSize
2897
2898 The PageSize to set for the LDAP searcher object.
2899
2900 .PARAMETER Credential
2901
2902 A [Management.Automation.PSCredential] object of alternate credentials
2903 for connection to the target domain.
2904
2905 .EXAMPLE
2906
2907 PS C:\> Get-UserProperty -Domain testing
2908
2909 Returns all user properties for users in the 'testing' domain.
2910
2911 .EXAMPLE
2912
2913 PS C:\> Get-UserProperty -Properties ssn,lastlogon,location
2914
2915 Returns all an array of user/ssn/lastlogin/location combinations
2916 for users in the current domain.
2917
2918 .LINK
2919
2920 http://obscuresecurity.blogspot.com/2014/04/ADSISearcher.html
2921#>
2922
2923 [CmdletBinding()]
2924 param(
2925 [String[]]
2926 $Properties,
2927
2928 [String]
2929 $Domain,
2930
2931 [String]
2932 $DomainController,
2933
2934 [ValidateRange(1,10000)]
2935 [Int]
2936 $PageSize = 200,
2937
2938 [Management.Automation.PSCredential]
2939 $Credential
2940 )
2941
2942 if($Properties) {
2943 # extract out the set of all properties for each object
2944 $Properties = ,"name" + $Properties
2945 Get-NetUser -Domain $Domain -DomainController $DomainController -PageSize $PageSize -Credential $Credential | Select-Object -Property $Properties
2946 }
2947 else {
2948 # extract out just the property names
2949 Get-NetUser -Domain $Domain -DomainController $DomainController -PageSize $PageSize -Credential $Credential | Select-Object -First 1 | Get-Member -MemberType *Property | Select-Object -Property 'Name'
2950 }
2951}
2952
2953
2954filter Find-UserField {
2955<#
2956 .SYNOPSIS
2957
2958 Searches user object fields for a given word (default *pass*). Default
2959 field being searched is 'description'.
2960
2961 Taken directly from @obscuresec's post:
2962 http://obscuresecurity.blogspot.com/2014/04/ADSISearcher.html
2963
2964 .PARAMETER SearchTerm
2965
2966 Term to search for, default of "pass".
2967
2968 .PARAMETER SearchField
2969
2970 User field to search, default of "description".
2971
2972 .PARAMETER ADSpath
2973
2974 The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
2975 Useful for OU queries.
2976
2977 .PARAMETER Domain
2978
2979 Domain to search computer fields for, defaults to the current domain.
2980
2981 .PARAMETER DomainController
2982
2983 Domain controller to reflect LDAP queries through.
2984
2985 .PARAMETER PageSize
2986
2987 The PageSize to set for the LDAP searcher object.
2988
2989 .PARAMETER Credential
2990
2991 A [Management.Automation.PSCredential] object of alternate credentials
2992 for connection to the target domain.
2993
2994 .EXAMPLE
2995
2996 PS C:\> Find-UserField -SearchField info -SearchTerm backup
2997
2998 Find user accounts with "backup" in the "info" field.
2999#>
3000
3001 [CmdletBinding()]
3002 param(
3003 [Parameter(Position=0,ValueFromPipeline=$True)]
3004 [String]
3005 $SearchTerm = 'pass',
3006
3007 [String]
3008 $SearchField = 'description',
3009
3010 [String]
3011 $ADSpath,
3012
3013 [String]
3014 $Domain,
3015
3016 [String]
3017 $DomainController,
3018
3019 [ValidateRange(1,10000)]
3020 [Int]
3021 $PageSize = 200,
3022
3023 [Management.Automation.PSCredential]
3024 $Credential
3025 )
3026
3027 Get-NetUser -ADSpath $ADSpath -Domain $Domain -DomainController $DomainController -Credential $Credential -Filter "($SearchField=*$SearchTerm*)" -PageSize $PageSize | Select-Object samaccountname,$SearchField
3028}
3029
3030
3031filter Get-UserEvent {
3032<#
3033 .SYNOPSIS
3034
3035 Dump and parse security events relating to an account logon (ID 4624)
3036 or a TGT request event (ID 4768). Intended to be used and tested on
3037 Windows 2008 Domain Controllers.
3038 Admin Reqd? YES
3039
3040 Author: @sixdub
3041
3042 .PARAMETER ComputerName
3043
3044 The computer to get events from. Default: Localhost
3045
3046 .PARAMETER EventType
3047
3048 Either 'logon', 'tgt', or 'all'. Defaults: 'logon'
3049
3050 .PARAMETER DateStart
3051
3052 Filter out all events before this date. Default: 5 days
3053
3054 .PARAMETER Credential
3055
3056 A [Management.Automation.PSCredential] object of alternate credentials
3057 for connection to the target domain.
3058
3059 .EXAMPLE
3060
3061 PS C:\> Get-UserEvent -ComputerName DomainController.testlab.local
3062
3063 .LINK
3064
3065 http://www.sixdub.net/2014/11/07/offensive-event-parsing-bringing-home-trophies/
3066#>
3067
3068 Param(
3069 [Parameter(ValueFromPipeline=$True)]
3070 [String]
3071 $ComputerName = $Env:ComputerName,
3072
3073 [String]
3074 [ValidateSet("logon","tgt","all")]
3075 $EventType = "logon",
3076
3077 [DateTime]
3078 $DateStart = [DateTime]::Today.AddDays(-5),
3079
3080 [Management.Automation.PSCredential]
3081 $Credential
3082 )
3083
3084 if($EventType.ToLower() -like "logon") {
3085 [Int32[]]$ID = @(4624)
3086 }
3087 elseif($EventType.ToLower() -like "tgt") {
3088 [Int32[]]$ID = @(4768)
3089 }
3090 else {
3091 [Int32[]]$ID = @(4624, 4768)
3092 }
3093
3094 if($Credential) {
3095 Write-Verbose "Using alternative credentials"
3096 $Arguments = @{
3097 'ComputerName' = $ComputerName;
3098 'Credential' = $Credential;
3099 'FilterHashTable' = @{ LogName = 'Security'; ID=$ID; StartTime=$DateStart};
3100 'ErrorAction' = 'SilentlyContinue';
3101 }
3102 }
3103 else {
3104 $Arguments = @{
3105 'ComputerName' = $ComputerName;
3106 'FilterHashTable' = @{ LogName = 'Security'; ID=$ID; StartTime=$DateStart};
3107 'ErrorAction' = 'SilentlyContinue';
3108 }
3109 }
3110
3111 # grab all events matching our filter for the specified host
3112 Get-WinEvent @Arguments | ForEach-Object {
3113
3114 if($ID -contains 4624) {
3115 # first parse and check the logon event type. This could be later adapted and tested for RDP logons (type 10)
3116 if($_.message -match '(?s)(?<=Logon Type:).*?(?=(Impersonation Level:|New Logon:))') {
3117 if($Matches) {
3118 $LogonType = $Matches[0].trim()
3119 $Matches = $Null
3120 }
3121 }
3122 else {
3123 $LogonType = ""
3124 }
3125
3126 # interactive logons or domain logons
3127 if (($LogonType -eq 2) -or ($LogonType -eq 3)) {
3128 try {
3129 # parse and store the account used and the address they came from
3130 if($_.message -match '(?s)(?<=New Logon:).*?(?=Process Information:)') {
3131 if($Matches) {
3132 $UserName = $Matches[0].split("`n")[2].split(":")[1].trim()
3133 $Domain = $Matches[0].split("`n")[3].split(":")[1].trim()
3134 $Matches = $Null
3135 }
3136 }
3137 if($_.message -match '(?s)(?<=Network Information:).*?(?=Source Port:)') {
3138 if($Matches) {
3139 $Address = $Matches[0].split("`n")[2].split(":")[1].trim()
3140 $Matches = $Null
3141 }
3142 }
3143
3144 # only add if there was account information not for a machine or anonymous logon
3145 if ($UserName -and (-not $UserName.endsWith('$')) -and ($UserName -ne 'ANONYMOUS LOGON')) {
3146 $LogonEventProperties = @{
3147 'Domain' = $Domain
3148 'ComputerName' = $ComputerName
3149 'Username' = $UserName
3150 'Address' = $Address
3151 'ID' = '4624'
3152 'LogonType' = $LogonType
3153 'Time' = $_.TimeCreated
3154 }
3155 New-Object -TypeName PSObject -Property $LogonEventProperties
3156 }
3157 }
3158 catch {
3159 Write-Verbose "Error parsing event logs: $_"
3160 }
3161 }
3162 }
3163 if($ID -contains 4768) {
3164 # the TGT event type
3165 try {
3166 if($_.message -match '(?s)(?<=Account Information:).*?(?=Service Information:)') {
3167 if($Matches) {
3168 $Username = $Matches[0].split("`n")[1].split(":")[1].trim()
3169 $Domain = $Matches[0].split("`n")[2].split(":")[1].trim()
3170 $Matches = $Null
3171 }
3172 }
3173
3174 if($_.message -match '(?s)(?<=Network Information:).*?(?=Additional Information:)') {
3175 if($Matches) {
3176 $Address = $Matches[0].split("`n")[1].split(":")[-1].trim()
3177 $Matches = $Null
3178 }
3179 }
3180
3181 $LogonEventProperties = @{
3182 'Domain' = $Domain
3183 'ComputerName' = $ComputerName
3184 'Username' = $UserName
3185 'Address' = $Address
3186 'ID' = '4768'
3187 'LogonType' = ''
3188 'Time' = $_.TimeCreated
3189 }
3190
3191 New-Object -TypeName PSObject -Property $LogonEventProperties
3192 }
3193 catch {
3194 Write-Verbose "Error parsing event logs: $_"
3195 }
3196 }
3197 }
3198}
3199
3200
3201function Get-ObjectAcl {
3202<#
3203 .SYNOPSIS
3204 Returns the ACLs associated with a specific active directory object.
3205
3206 Thanks Sean Metcalf (@pyrotek3) for the idea and guidance.
3207
3208 .PARAMETER SamAccountName
3209
3210 Object name to filter for.
3211
3212 .PARAMETER Name
3213
3214 Object name to filter for.
3215
3216 .PARAMETER DistinguishedName
3217
3218 Object distinguished name to filter for.
3219
3220 .PARAMETER ResolveGUIDs
3221
3222 Switch. Resolve GUIDs to their display names.
3223
3224 .PARAMETER Filter
3225
3226 A customized ldap filter string to use, e.g. "(description=*admin*)"
3227
3228 .PARAMETER ADSpath
3229
3230 The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
3231 Useful for OU queries.
3232
3233 .PARAMETER ADSprefix
3234
3235 Prefix to set for the searcher (like "CN=Sites,CN=Configuration")
3236
3237 .PARAMETER RightsFilter
3238
3239 Only return results with the associated rights, "All", "ResetPassword","WriteMembers"
3240
3241 .PARAMETER Domain
3242
3243 The domain to use for the query, defaults to the current domain.
3244
3245 .PARAMETER DomainController
3246
3247 Domain controller to reflect LDAP queries through.
3248
3249 .PARAMETER PageSize
3250
3251 The PageSize to set for the LDAP searcher object.
3252
3253 .EXAMPLE
3254
3255 PS C:\> Get-ObjectAcl -SamAccountName matt.admin -domain testlab.local
3256
3257 Get the ACLs for the matt.admin user in the testlab.local domain
3258
3259 .EXAMPLE
3260
3261 PS C:\> Get-ObjectAcl -SamAccountName matt.admin -domain testlab.local -ResolveGUIDs
3262
3263 Get the ACLs for the matt.admin user in the testlab.local domain and
3264 resolve relevant GUIDs to their display names.
3265
3266 .EXAMPLE
3267
3268 PS C:\> Get-NetOU -FullData | Get-ObjectAcl -ResolveGUIDs
3269
3270 Enumerate the ACL permissions for all OUs in the domain.
3271#>
3272
3273 [CmdletBinding()]
3274 Param (
3275 [Parameter(ValueFromPipelineByPropertyName=$True)]
3276 [String]
3277 $SamAccountName,
3278
3279 [Parameter(ValueFromPipelineByPropertyName=$True)]
3280 [String]
3281 $Name = "*",
3282
3283 [Parameter(ValueFromPipelineByPropertyName=$True)]
3284 [String]
3285 $DistinguishedName = "*",
3286
3287 [Switch]
3288 $ResolveGUIDs,
3289
3290 [String]
3291 $Filter,
3292
3293 [String]
3294 $ADSpath,
3295
3296 [String]
3297 $ADSprefix,
3298
3299 [String]
3300 [ValidateSet("All","ResetPassword","WriteMembers")]
3301 $RightsFilter,
3302
3303 [String]
3304 $Domain,
3305
3306 [String]
3307 $DomainController,
3308
3309 [ValidateRange(1,10000)]
3310 [Int]
3311 $PageSize = 200
3312 )
3313
3314 begin {
3315 $Searcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -ADSpath $ADSpath -ADSprefix $ADSprefix -PageSize $PageSize
3316
3317 # get a GUID -> name mapping
3318 if($ResolveGUIDs) {
3319 $GUIDs = Get-GUIDMap -Domain $Domain -DomainController $DomainController -PageSize $PageSize
3320 }
3321 }
3322
3323 process {
3324
3325 if ($Searcher) {
3326
3327 if($SamAccountName) {
3328 $Searcher.filter="(&(samaccountname=$SamAccountName)(name=$Name)(distinguishedname=$DistinguishedName)$Filter)"
3329 }
3330 else {
3331 $Searcher.filter="(&(name=$Name)(distinguishedname=$DistinguishedName)$Filter)"
3332 }
3333
3334 try {
3335 $Results = $Searcher.FindAll()
3336 $Results | Where-Object {$_} | ForEach-Object {
3337 $Object = [adsi]($_.path)
3338
3339 if($Object.distinguishedname) {
3340 $Access = $Object.PsBase.ObjectSecurity.access
3341 $Access | ForEach-Object {
3342 $_ | Add-Member NoteProperty 'ObjectDN' $Object.distinguishedname[0]
3343
3344 if($Object.objectsid[0]){
3345 $S = (New-Object System.Security.Principal.SecurityIdentifier($Object.objectsid[0],0)).Value
3346 }
3347 else {
3348 $S = $Null
3349 }
3350
3351 $_ | Add-Member NoteProperty 'ObjectSID' $S
3352 $_
3353 }
3354 }
3355 } | ForEach-Object {
3356 if($RightsFilter) {
3357 $GuidFilter = Switch ($RightsFilter) {
3358 "ResetPassword" { "00299570-246d-11d0-a768-00aa006e0529" }
3359 "WriteMembers" { "bf9679c0-0de6-11d0-a285-00aa003049e2" }
3360 Default { "00000000-0000-0000-0000-000000000000"}
3361 }
3362 if($_.ObjectType -eq $GuidFilter) { $_ }
3363 }
3364 else {
3365 $_
3366 }
3367 } | ForEach-Object {
3368 if($GUIDs) {
3369 # if we're resolving GUIDs, map them them to the resolved hash table
3370 $AclProperties = @{}
3371 $_.psobject.properties | ForEach-Object {
3372 if( ($_.Name -eq 'ObjectType') -or ($_.Name -eq 'InheritedObjectType') ) {
3373 try {
3374 $AclProperties[$_.Name] = $GUIDS[$_.Value.toString()]
3375 }
3376 catch {
3377 $AclProperties[$_.Name] = $_.Value
3378 }
3379 }
3380 else {
3381 $AclProperties[$_.Name] = $_.Value
3382 }
3383 }
3384 New-Object -TypeName PSObject -Property $AclProperties
3385 }
3386 else { $_ }
3387 }
3388 $Results.dispose()
3389 $Searcher.dispose()
3390 }
3391 catch {
3392 Write-Warning $_
3393 }
3394 }
3395 }
3396}
3397
3398
3399function Add-ObjectAcl {
3400<#
3401 .SYNOPSIS
3402
3403 Adds an ACL for a specific active directory object.
3404
3405 AdminSDHolder ACL approach from Sean Metcalf (@pyrotek3)
3406 https://adsecurity.org/?p=1906
3407
3408 ACE setting method adapted from https://social.technet.microsoft.com/Forums/windowsserver/en-US/df3bfd33-c070-4a9c-be98-c4da6e591a0a/forum-faq-using-powershell-to-assign-permissions-on-active-directory-objects.
3409
3410 'ResetPassword' doesn't need to know the user's current password
3411 'WriteMembers' allows for the modification of group membership
3412
3413 .PARAMETER TargetSamAccountName
3414
3415 Target object name to filter for.
3416
3417 .PARAMETER TargetName
3418
3419 Target object name to filter for.
3420
3421 .PARAMETER TargetDistinguishedName
3422
3423 Target object distinguished name to filter for.
3424
3425 .PARAMETER TargetFilter
3426
3427 A customized ldap filter string to use to find a target, e.g. "(description=*admin*)"
3428
3429 .PARAMETER TargetADSpath
3430
3431 The LDAP source for the target, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
3432
3433 .PARAMETER TargetADSprefix
3434
3435 Prefix to set for the target searcher (like "CN=Sites,CN=Configuration")
3436
3437 .PARAMETER PrincipalSID
3438
3439 The SID of the principal object to add for access.
3440
3441 .PARAMETER PrincipalName
3442
3443 The name of the principal object to add for access.
3444
3445 .PARAMETER PrincipalSamAccountName
3446
3447 The samAccountName of the principal object to add for access.
3448
3449 .PARAMETER Rights
3450
3451 Rights to add for the principal, "All","ResetPassword","WriteMembers","DCSync"
3452
3453 .PARAMETER Domain
3454
3455 The domain to use for the target query, defaults to the current domain.
3456
3457 .PARAMETER DomainController
3458
3459 Domain controller to reflect LDAP queries through.
3460
3461 .PARAMETER PageSize
3462
3463 The PageSize to set for the LDAP searcher object.
3464
3465 .EXAMPLE
3466
3467 Add-ObjectAcl -TargetSamAccountName matt -PrincipalSamAccountName john
3468
3469 Grants 'john' all full access rights to the 'matt' account.
3470
3471 .EXAMPLE
3472
3473 Add-ObjectAcl -TargetSamAccountName matt -PrincipalSamAccountName john -Rights ResetPassword
3474
3475 Grants 'john' the right to reset the password for the 'matt' account.
3476
3477 .LINK
3478
3479 https://adsecurity.org/?p=1906
3480
3481 https://social.technet.microsoft.com/Forums/windowsserver/en-US/df3bfd33-c070-4a9c-be98-c4da6e591a0a/forum-faq-using-powershell-to-assign-permissions-on-active-directory-objects?forum=winserverpowershell
3482#>
3483
3484 [CmdletBinding()]
3485 Param (
3486 [String]
3487 $TargetSamAccountName,
3488
3489 [String]
3490 $TargetName = "*",
3491
3492 [Alias('DN')]
3493 [String]
3494 $TargetDistinguishedName = "*",
3495
3496 [String]
3497 $TargetFilter,
3498
3499 [String]
3500 $TargetADSpath,
3501
3502 [String]
3503 $TargetADSprefix,
3504
3505 [String]
3506 [ValidatePattern('^S-1-5-21-[0-9]+-[0-9]+-[0-9]+-[0-9]+')]
3507 $PrincipalSID,
3508
3509 [String]
3510 $PrincipalName,
3511
3512 [String]
3513 $PrincipalSamAccountName,
3514
3515 [String]
3516 [ValidateSet("All","ResetPassword","WriteMembers","DCSync")]
3517 $Rights = "All",
3518
3519 [String]
3520 $RightsGUID,
3521
3522 [String]
3523 $Domain,
3524
3525 [String]
3526 $DomainController,
3527
3528 [ValidateRange(1,10000)]
3529 [Int]
3530 $PageSize = 200
3531 )
3532
3533 begin {
3534 $Searcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -ADSpath $TargetADSpath -ADSprefix $TargetADSprefix -PageSize $PageSize
3535
3536 if($PrincipalSID) {
3537 $ResolvedPrincipalSID = $PrincipalSID
3538 }
3539 else {
3540 $Principal = Get-ADObject -Domain $Domain -DomainController $DomainController -Name $PrincipalName -SamAccountName $PrincipalSamAccountName -PageSize $PageSize
3541
3542 if(!$Principal) {
3543 throw "Error resolving principal"
3544 }
3545 $ResolvedPrincipalSID = $Principal.objectsid
3546 }
3547 if(!$ResolvedPrincipalSID) {
3548 throw "Error resolving principal"
3549 }
3550 }
3551
3552 process {
3553
3554 if ($Searcher) {
3555
3556 if($TargetSamAccountName) {
3557 $Searcher.filter="(&(samaccountname=$TargetSamAccountName)(name=$TargetName)(distinguishedname=$TargetDistinguishedName)$TargetFilter)"
3558 }
3559 else {
3560 $Searcher.filter="(&(name=$TargetName)(distinguishedname=$TargetDistinguishedName)$TargetFilter)"
3561 }
3562
3563 try {
3564 $Results = $Searcher.FindAll()
3565 $Results | Where-Object {$_} | ForEach-Object {
3566
3567 # adapted from https://social.technet.microsoft.com/Forums/windowsserver/en-US/df3bfd33-c070-4a9c-be98-c4da6e591a0a/forum-faq-using-powershell-to-assign-permissions-on-active-directory-objects
3568
3569 $TargetDN = $_.Properties.distinguishedname
3570
3571 $Identity = [System.Security.Principal.IdentityReference] ([System.Security.Principal.SecurityIdentifier]$ResolvedPrincipalSID)
3572 $InheritanceType = [System.DirectoryServices.ActiveDirectorySecurityInheritance] "None"
3573 $ControlType = [System.Security.AccessControl.AccessControlType] "Allow"
3574 $ACEs = @()
3575
3576 if($RightsGUID) {
3577 $GUIDs = @($RightsGUID)
3578 }
3579 else {
3580 $GUIDs = Switch ($Rights) {
3581 # ResetPassword doesn't need to know the user's current password
3582 "ResetPassword" { "00299570-246d-11d0-a768-00aa006e0529" }
3583 # allows for the modification of group membership
3584 "WriteMembers" { "bf9679c0-0de6-11d0-a285-00aa003049e2" }
3585 # 'DS-Replication-Get-Changes' = 1131f6aa-9c07-11d1-f79f-00c04fc2dcd2
3586 # 'DS-Replication-Get-Changes-All' = 1131f6ad-9c07-11d1-f79f-00c04fc2dcd2
3587 # 'DS-Replication-Get-Changes-In-Filtered-Set' = 89e95b76-444d-4c62-991a-0facbeda640c
3588 # when applied to a domain's ACL, allows for the use of DCSync
3589 "DCSync" { "1131f6aa-9c07-11d1-f79f-00c04fc2dcd2", "1131f6ad-9c07-11d1-f79f-00c04fc2dcd2", "89e95b76-444d-4c62-991a-0facbeda640c"}
3590 }
3591 }
3592
3593 if($GUIDs) {
3594 foreach($GUID in $GUIDs) {
3595 $NewGUID = New-Object Guid $GUID
3596 $ADRights = [System.DirectoryServices.ActiveDirectoryRights] "ExtendedRight"
3597 $ACEs += New-Object System.DirectoryServices.ActiveDirectoryAccessRule $Identity,$ADRights,$ControlType,$NewGUID,$InheritanceType
3598 }
3599 }
3600 else {
3601 # deault to GenericAll rights
3602 $ADRights = [System.DirectoryServices.ActiveDirectoryRights] "GenericAll"
3603 $ACEs += New-Object System.DirectoryServices.ActiveDirectoryAccessRule $Identity,$ADRights,$ControlType,$InheritanceType
3604 }
3605
3606 Write-Verbose "Granting principal $ResolvedPrincipalSID '$Rights' on $($_.Properties.distinguishedname)"
3607
3608 try {
3609 # add all the new ACEs to the specified object
3610 ForEach ($ACE in $ACEs) {
3611 Write-Verbose "Granting principal $ResolvedPrincipalSID '$($ACE.ObjectType)' rights on $($_.Properties.distinguishedname)"
3612 $Object = [adsi]($_.path)
3613 $Object.PsBase.ObjectSecurity.AddAccessRule($ACE)
3614 $Object.PsBase.commitchanges()
3615 }
3616 }
3617 catch {
3618 Write-Warning "Error granting principal $ResolvedPrincipalSID '$Rights' on $TargetDN : $_"
3619 }
3620 }
3621 $Results.dispose()
3622 $Searcher.dispose()
3623 }
3624 catch {
3625 Write-Warning "Error: $_"
3626 }
3627 }
3628 }
3629}
3630
3631
3632function Invoke-ACLScanner {
3633<#
3634 .SYNOPSIS
3635 Searches for ACLs for specifable AD objects (default to all domain objects)
3636 with a domain sid of > -1000, and have modifiable rights.
3637
3638 Thanks Sean Metcalf (@pyrotek3) for the idea and guidance.
3639
3640 .PARAMETER SamAccountName
3641
3642 Object name to filter for.
3643
3644 .PARAMETER Name
3645
3646 Object name to filter for.
3647
3648 .PARAMETER DistinguishedName
3649
3650 Object distinguished name to filter for.
3651
3652 .PARAMETER Filter
3653
3654 A customized ldap filter string to use, e.g. "(description=*admin*)"
3655
3656 .PARAMETER ADSpath
3657
3658 The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
3659 Useful for OU queries.
3660
3661 .PARAMETER ADSprefix
3662
3663 Prefix to set for the searcher (like "CN=Sites,CN=Configuration")
3664
3665 .PARAMETER Domain
3666
3667 The domain to use for the query, defaults to the current domain.
3668
3669 .PARAMETER DomainController
3670
3671 Domain controller to reflect LDAP queries through.
3672
3673 .PARAMETER ResolveGUIDs
3674
3675 Switch. Resolve GUIDs to their display names.
3676
3677 .PARAMETER PageSize
3678
3679 The PageSize to set for the LDAP searcher object.
3680
3681 .EXAMPLE
3682
3683 PS C:\> Invoke-ACLScanner -ResolveGUIDs | Export-CSV -NoTypeInformation acls.csv
3684
3685 Enumerate all modifable ACLs in the current domain, resolving GUIDs to display
3686 names, and export everything to a .csv
3687#>
3688
3689 [CmdletBinding()]
3690 Param (
3691 [Parameter(ValueFromPipeline=$True)]
3692 [String]
3693 $SamAccountName,
3694
3695 [String]
3696 $Name = "*",
3697
3698 [Alias('DN')]
3699 [String]
3700 $DistinguishedName = "*",
3701
3702 [String]
3703 $Filter,
3704
3705 [String]
3706 $ADSpath,
3707
3708 [String]
3709 $ADSprefix,
3710
3711 [String]
3712 $Domain,
3713
3714 [String]
3715 $DomainController,
3716
3717 [Switch]
3718 $ResolveGUIDs,
3719
3720 [ValidateRange(1,10000)]
3721 [Int]
3722 $PageSize = 200
3723 )
3724
3725 # Get all domain ACLs with the appropriate parameters
3726 Get-ObjectACL @PSBoundParameters | ForEach-Object {
3727 # add in the translated SID for the object identity
3728 $_ | Add-Member Noteproperty 'IdentitySID' ($_.IdentityReference.Translate([System.Security.Principal.SecurityIdentifier]).Value)
3729 $_
3730 } | Where-Object {
3731 # check for any ACLs with SIDs > -1000
3732 try {
3733 # TODO: change this to a regex for speedup?
3734 [int]($_.IdentitySid.split("-")[-1]) -ge 1000
3735 }
3736 catch {}
3737 } | Where-Object {
3738 # filter for modifiable rights
3739 ($_.ActiveDirectoryRights -eq "GenericAll") -or ($_.ActiveDirectoryRights -match "Write") -or ($_.ActiveDirectoryRights -match "Create") -or ($_.ActiveDirectoryRights -match "Delete") -or (($_.ActiveDirectoryRights -match "ExtendedRight") -and ($_.AccessControlType -eq "Allow"))
3740 }
3741}
3742
3743
3744filter Get-GUIDMap {
3745<#
3746 .SYNOPSIS
3747
3748 Helper to build a hash table of [GUID] -> resolved names
3749
3750 Heavily adapted from http://blogs.technet.com/b/ashleymcglone/archive/2013/03/25/active-directory-ou-permissions-report-free-powershell-script-download.aspx
3751
3752 .PARAMETER Domain
3753
3754 The domain to use for the query, defaults to the current domain.
3755
3756 .PARAMETER DomainController
3757
3758 Domain controller to reflect LDAP queries through.
3759
3760 .PARAMETER PageSize
3761
3762 The PageSize to set for the LDAP searcher object.
3763
3764 .LINK
3765
3766 http://blogs.technet.com/b/ashleymcglone/archive/2013/03/25/active-directory-ou-permissions-report-free-powershell-script-download.aspx
3767#>
3768
3769 [CmdletBinding()]
3770 Param (
3771 [Parameter(ValueFromPipeline=$True)]
3772 [String]
3773 $Domain,
3774
3775 [String]
3776 $DomainController,
3777
3778 [ValidateRange(1,10000)]
3779 [Int]
3780 $PageSize = 200
3781 )
3782
3783 $GUIDs = @{'00000000-0000-0000-0000-000000000000' = 'All'}
3784
3785 $SchemaPath = (Get-NetForest).schema.name
3786
3787 $SchemaSearcher = Get-DomainSearcher -ADSpath $SchemaPath -DomainController $DomainController -PageSize $PageSize
3788 if($SchemaSearcher) {
3789 $SchemaSearcher.filter = "(schemaIDGUID=*)"
3790 try {
3791 $Results = $SchemaSearcher.FindAll()
3792 $Results | Where-Object {$_} | ForEach-Object {
3793 # convert the GUID
3794 $GUIDs[(New-Object Guid (,$_.properties.schemaidguid[0])).Guid] = $_.properties.name[0]
3795 }
3796 $Results.dispose()
3797 $SchemaSearcher.dispose()
3798 }
3799 catch {
3800 Write-Verbose "Error in building GUID map: $_"
3801 }
3802 }
3803
3804 $RightsSearcher = Get-DomainSearcher -ADSpath $SchemaPath.replace("Schema","Extended-Rights") -DomainController $DomainController -PageSize $PageSize -Credential $Credential
3805 if ($RightsSearcher) {
3806 $RightsSearcher.filter = "(objectClass=controlAccessRight)"
3807 try {
3808 $Results = $RightsSearcher.FindAll()
3809 $Results | Where-Object {$_} | ForEach-Object {
3810 # convert the GUID
3811 $GUIDs[$_.properties.rightsguid[0].toString()] = $_.properties.name[0]
3812 }
3813 $Results.dispose()
3814 $RightsSearcher.dispose()
3815 }
3816 catch {
3817 Write-Verbose "Error in building GUID map: $_"
3818 }
3819 }
3820
3821 $GUIDs
3822}
3823
3824
3825function Get-NetComputer {
3826<#
3827 .SYNOPSIS
3828
3829 This function utilizes adsisearcher to query the current AD context
3830 for current computer objects. Based off of Carlos Perez's Audit.psm1
3831 script in Posh-SecMod (link below).
3832
3833 .PARAMETER ComputerName
3834
3835 Return computers with a specific name, wildcards accepted.
3836
3837 .PARAMETER SPN
3838
3839 Return computers with a specific service principal name, wildcards accepted.
3840
3841 .PARAMETER OperatingSystem
3842
3843 Return computers with a specific operating system, wildcards accepted.
3844
3845 .PARAMETER ServicePack
3846
3847 Return computers with a specific service pack, wildcards accepted.
3848
3849 .PARAMETER Filter
3850
3851 A customized ldap filter string to use, e.g. "(description=*admin*)"
3852
3853 .PARAMETER Printers
3854
3855 Switch. Return only printers.
3856
3857 .PARAMETER Ping
3858
3859 Switch. Ping each host to ensure it's up before enumerating.
3860
3861 .PARAMETER FullData
3862
3863 Switch. Return full computer objects instead of just system names (the default).
3864
3865 .PARAMETER Domain
3866
3867 The domain to query for computers, defaults to the current domain.
3868
3869 .PARAMETER DomainController
3870
3871 Domain controller to reflect LDAP queries through.
3872
3873 .PARAMETER ADSpath
3874
3875 The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
3876 Useful for OU queries.
3877
3878 .PARAMETER SiteName
3879
3880 The AD Site name to search for computers.
3881
3882 .PARAMETER Unconstrained
3883
3884 Switch. Return computer objects that have unconstrained delegation.
3885
3886 .PARAMETER PageSize
3887
3888 The PageSize to set for the LDAP searcher object.
3889
3890 .PARAMETER Credential
3891
3892 A [Management.Automation.PSCredential] object of alternate credentials
3893 for connection to the target domain.
3894
3895 .EXAMPLE
3896
3897 PS C:\> Get-NetComputer
3898
3899 Returns the current computers in current domain.
3900
3901 .EXAMPLE
3902
3903 PS C:\> Get-NetComputer -SPN mssql*
3904
3905 Returns all MS SQL servers on the domain.
3906
3907 .EXAMPLE
3908
3909 PS C:\> Get-NetComputer -Domain testing
3910
3911 Returns the current computers in 'testing' domain.
3912
3913 .EXAMPLE
3914
3915 PS C:\> Get-NetComputer -Domain testing -FullData
3916
3917 Returns full computer objects in the 'testing' domain.
3918
3919 .LINK
3920
3921 https://github.com/darkoperator/Posh-SecMod/blob/master/Audit/Audit.psm1
3922#>
3923
3924 [CmdletBinding()]
3925 Param (
3926 [Parameter(ValueFromPipeline=$True)]
3927 [Alias('HostName')]
3928 [String]
3929 $ComputerName = '*',
3930
3931 [String]
3932 $SPN,
3933
3934 [String]
3935 $OperatingSystem,
3936
3937 [String]
3938 $ServicePack,
3939
3940 [String]
3941 $Filter,
3942
3943 [Switch]
3944 $Printers,
3945
3946 [Switch]
3947 $Ping,
3948
3949 [Switch]
3950 $FullData,
3951
3952 [String]
3953 $Domain,
3954
3955 [String]
3956 $DomainController,
3957
3958 [String]
3959 $ADSpath,
3960
3961 [String]
3962 $SiteName,
3963
3964 [Switch]
3965 $Unconstrained,
3966
3967 [ValidateRange(1,10000)]
3968 [Int]
3969 $PageSize = 200,
3970
3971 [Management.Automation.PSCredential]
3972 $Credential
3973 )
3974
3975 begin {
3976 # so this isn't repeated if multiple computer names are passed on the pipeline
3977 $CompSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -ADSpath $ADSpath -PageSize $PageSize -Credential $Credential
3978 }
3979
3980 process {
3981
3982 if ($CompSearcher) {
3983
3984 # if we're checking for unconstrained delegation
3985 if($Unconstrained) {
3986 Write-Verbose "Searching for computers with for unconstrained delegation"
3987 $Filter += "(userAccountControl:1.2.840.113556.1.4.803:=524288)"
3988 }
3989 # set the filters for the seracher if it exists
3990 if($Printers) {
3991 Write-Verbose "Searching for printers"
3992 # $CompSearcher.filter="(&(objectCategory=printQueue)$Filter)"
3993 $Filter += "(objectCategory=printQueue)"
3994 }
3995 if($SPN) {
3996 Write-Verbose "Searching for computers with SPN: $SPN"
3997 $Filter += "(servicePrincipalName=$SPN)"
3998 }
3999 if($OperatingSystem) {
4000 $Filter += "(operatingsystem=$OperatingSystem)"
4001 }
4002 if($ServicePack) {
4003 $Filter += "(operatingsystemservicepack=$ServicePack)"
4004 }
4005 if($SiteName) {
4006 $Filter += "(serverreferencebl=$SiteName)"
4007 }
4008
4009 $CompFilter = "(&(sAMAccountType=805306369)(dnshostname=$ComputerName)$Filter)"
4010 Write-Verbose "Get-NetComputer filter : '$CompFilter'"
4011 $CompSearcher.filter = $CompFilter
4012
4013 try {
4014 $Results = $CompSearcher.FindAll()
4015 $Results | Where-Object {$_} | ForEach-Object {
4016 $Up = $True
4017 if($Ping) {
4018 # TODO: how can these results be piped to ping for a speedup?
4019 $Up = Test-Connection -Count 1 -Quiet -ComputerName $_.properties.dnshostname
4020 }
4021 if($Up) {
4022 # return full data objects
4023 if ($FullData) {
4024 # convert/process the LDAP fields for each result
4025 $Computer = Convert-LDAPProperty -Properties $_.Properties
4026 $Computer.PSObject.TypeNames.Add('PowerView.Computer')
4027 $Computer
4028 }
4029 else {
4030 # otherwise we're just returning the DNS host name
4031 $_.properties.dnshostname
4032 }
4033 }
4034 }
4035 $Results.dispose()
4036 $CompSearcher.dispose()
4037 }
4038 catch {
4039 Write-Warning "Error: $_"
4040 }
4041 }
4042 }
4043}
4044
4045
4046function Get-ADObject {
4047<#
4048 .SYNOPSIS
4049
4050 Takes a domain SID and returns the user, group, or computer object
4051 associated with it.
4052
4053 .PARAMETER SID
4054
4055 The SID of the domain object you're querying for.
4056
4057 .PARAMETER Name
4058
4059 The Name of the domain object you're querying for.
4060
4061 .PARAMETER SamAccountName
4062
4063 The SamAccountName of the domain object you're querying for.
4064
4065 .PARAMETER Domain
4066
4067 The domain to query for objects, defaults to the current domain.
4068
4069 .PARAMETER DomainController
4070
4071 Domain controller to reflect LDAP queries through.
4072
4073 .PARAMETER ADSpath
4074
4075 The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
4076 Useful for OU queries.
4077
4078 .PARAMETER Filter
4079
4080 Additional LDAP filter string for the query.
4081
4082 .PARAMETER ReturnRaw
4083
4084 Switch. Return the raw object instead of translating its properties.
4085 Used by Set-ADObject to modify object properties.
4086
4087 .PARAMETER PageSize
4088
4089 The PageSize to set for the LDAP searcher object.
4090
4091 .PARAMETER Credential
4092
4093 A [Management.Automation.PSCredential] object of alternate credentials
4094 for connection to the target domain.
4095
4096 .EXAMPLE
4097
4098 PS C:\> Get-ADObject -SID "S-1-5-21-2620891829-2411261497-1773853088-1110"
4099
4100 Get the domain object associated with the specified SID.
4101
4102 .EXAMPLE
4103
4104 PS C:\> Get-ADObject -ADSpath "CN=AdminSDHolder,CN=System,DC=testlab,DC=local"
4105
4106 Get the AdminSDHolder object for the testlab.local domain.
4107#>
4108
4109 [CmdletBinding()]
4110 Param (
4111 [Parameter(ValueFromPipeline=$True)]
4112 [String]
4113 $SID,
4114
4115 [String]
4116 $Name,
4117
4118 [String]
4119 $SamAccountName,
4120
4121 [String]
4122 $Domain,
4123
4124 [String]
4125 $DomainController,
4126
4127 [String]
4128 $ADSpath,
4129
4130 [String]
4131 $Filter,
4132
4133 [Switch]
4134 $ReturnRaw,
4135
4136 [ValidateRange(1,10000)]
4137 [Int]
4138 $PageSize = 200,
4139
4140 [Management.Automation.PSCredential]
4141 $Credential
4142 )
4143 process {
4144 if($SID) {
4145 # if a SID is passed, try to resolve it to a reachable domain name for the searcher
4146 try {
4147 $Name = Convert-SidToName $SID
4148 if($Name) {
4149 $Canonical = Convert-ADName -ObjectName $Name -InputType NT4 -OutputType Canonical
4150 if($Canonical) {
4151 $Domain = $Canonical.split("/")[0]
4152 }
4153 else {
4154 Write-Warning "Error resolving SID '$SID'"
4155 return $Null
4156 }
4157 }
4158 }
4159 catch {
4160 Write-Warning "Error resolving SID '$SID' : $_"
4161 return $Null
4162 }
4163 }
4164
4165 $ObjectSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $ADSpath -PageSize $PageSize
4166
4167 if($ObjectSearcher) {
4168 if($SID) {
4169 $ObjectSearcher.filter = "(&(objectsid=$SID)$Filter)"
4170 }
4171 elseif($Name) {
4172 $ObjectSearcher.filter = "(&(name=$Name)$Filter)"
4173 }
4174 elseif($SamAccountName) {
4175 $ObjectSearcher.filter = "(&(samAccountName=$SamAccountName)$Filter)"
4176 }
4177
4178 $Results = $ObjectSearcher.FindAll()
4179 $Results | Where-Object {$_} | ForEach-Object {
4180 if($ReturnRaw) {
4181 $_
4182 }
4183 else {
4184 # convert/process the LDAP fields for each result
4185 Convert-LDAPProperty -Properties $_.Properties
4186 }
4187 }
4188 $Results.dispose()
4189 $ObjectSearcher.dispose()
4190 }
4191 }
4192}
4193
4194
4195function Set-ADObject {
4196<#
4197 .SYNOPSIS
4198
4199 Takes a SID, name, or SamAccountName to query for a specified
4200 domain object, and then sets a specified 'PropertyName' to a
4201 specified 'PropertyValue'.
4202
4203 .PARAMETER SID
4204
4205 The SID of the domain object you're querying for.
4206
4207 .PARAMETER Name
4208
4209 The Name of the domain object you're querying for.
4210
4211 .PARAMETER SamAccountName
4212
4213 The SamAccountName of the domain object you're querying for.
4214
4215 .PARAMETER Domain
4216
4217 The domain to query for objects, defaults to the current domain.
4218
4219 .PARAMETER DomainController
4220
4221 Domain controller to reflect LDAP queries through.
4222
4223 .PARAMETER Filter
4224
4225 Additional LDAP filter string for the query.
4226
4227 .PARAMETER PropertyName
4228
4229 The property name to set.
4230
4231 .PARAMETER PropertyValue
4232
4233 The value to set for PropertyName
4234
4235 .PARAMETER PropertyXorValue
4236
4237 Integer value to binary xor (-bxor) with the current int value.
4238
4239 .PARAMETER ClearValue
4240
4241 Switch. Clear the value of PropertyName
4242
4243 .PARAMETER PageSize
4244
4245 The PageSize to set for the LDAP searcher object.
4246
4247 .PARAMETER Credential
4248
4249 A [Management.Automation.PSCredential] object of alternate credentials
4250 for connection to the target domain.
4251
4252 .EXAMPLE
4253
4254 PS C:\> Set-ADObject -SamAccountName matt.admin -PropertyName countrycode -PropertyValue 0
4255
4256 Set the countrycode for matt.admin to 0
4257
4258 .EXAMPLE
4259
4260 PS C:\> Set-ADObject -SamAccountName matt.admin -PropertyName useraccountcontrol -PropertyXorValue 65536
4261
4262 Set the password not to expire on matt.admin
4263#>
4264
4265 [CmdletBinding()]
4266 Param (
4267 [String]
4268 $SID,
4269
4270 [String]
4271 $Name,
4272
4273 [String]
4274 $SamAccountName,
4275
4276 [String]
4277 $Domain,
4278
4279 [String]
4280 $DomainController,
4281
4282 [String]
4283 $Filter,
4284
4285 [Parameter(Mandatory = $True)]
4286 [String]
4287 $PropertyName,
4288
4289 $PropertyValue,
4290
4291 [Int]
4292 $PropertyXorValue,
4293
4294 [Switch]
4295 $ClearValue,
4296
4297 [ValidateRange(1,10000)]
4298 [Int]
4299 $PageSize = 200,
4300
4301 [Management.Automation.PSCredential]
4302 $Credential
4303 )
4304
4305 $Arguments = @{
4306 'SID' = $SID
4307 'Name' = $Name
4308 'SamAccountName' = $SamAccountName
4309 'Domain' = $Domain
4310 'DomainController' = $DomainController
4311 'Filter' = $Filter
4312 'PageSize' = $PageSize
4313 'Credential' = $Credential
4314 }
4315 # splat the appropriate arguments to Get-ADObject
4316 $RawObject = Get-ADObject -ReturnRaw @Arguments
4317
4318 try {
4319 # get the modifiable object for this search result
4320 $Entry = $RawObject.GetDirectoryEntry()
4321
4322 if($ClearValue) {
4323 Write-Verbose "Clearing value"
4324 $Entry.$PropertyName.clear()
4325 $Entry.commitchanges()
4326 }
4327
4328 elseif($PropertyXorValue) {
4329 $TypeName = $Entry.$PropertyName[0].GetType().name
4330
4331 # UAC value references- https://support.microsoft.com/en-us/kb/305144
4332 $PropertyValue = $($Entry.$PropertyName) -bxor $PropertyXorValue
4333 $Entry.$PropertyName = $PropertyValue -as $TypeName
4334 $Entry.commitchanges()
4335 }
4336
4337 else {
4338 $Entry.put($PropertyName, $PropertyValue)
4339 $Entry.setinfo()
4340 }
4341 }
4342 catch {
4343 Write-Warning "Error setting property $PropertyName to value '$PropertyValue' for object $($RawObject.Properties.samaccountname) : $_"
4344 }
4345}
4346
4347
4348function Invoke-DowngradeAccount {
4349<#
4350 .SYNOPSIS
4351
4352 Set reversible encryption on a given account and then force the password
4353 to be set on next user login. To repair use "-Repair".
4354
4355 .PARAMETER SamAccountName
4356
4357 The SamAccountName of the domain object you're querying for.
4358
4359 .PARAMETER Name
4360
4361 The Name of the domain object you're querying for.
4362
4363 .PARAMETER Domain
4364
4365 The domain to query for objects, defaults to the current domain.
4366
4367 .PARAMETER DomainController
4368
4369 Domain controller to reflect LDAP queries through.
4370
4371 .PARAMETER Filter
4372
4373 Additional LDAP filter string for the query.
4374
4375 .PARAMETER Repair
4376
4377 Switch. Unset the reversible encryption flag and force password reset flag.
4378
4379 .PARAMETER Credential
4380
4381 A [Management.Automation.PSCredential] object of alternate credentials
4382 for connection to the target domain.
4383
4384 .EXAMPLE
4385
4386 PS> Invoke-DowngradeAccount -SamAccountName jason
4387
4388 Set reversible encryption on the 'jason' account and force the password to be changed.
4389
4390 .EXAMPLE
4391
4392 PS> Invoke-DowngradeAccount -SamAccountName jason -Repair
4393
4394 Unset reversible encryption on the 'jason' account and remove the forced password change.
4395#>
4396
4397 [CmdletBinding()]
4398 Param (
4399 [Parameter(ParameterSetName = 'SamAccountName', Position=0, ValueFromPipeline=$True)]
4400 [String]
4401 $SamAccountName,
4402
4403 [Parameter(ParameterSetName = 'Name')]
4404 [String]
4405 $Name,
4406
4407 [String]
4408 $Domain,
4409
4410 [String]
4411 $DomainController,
4412
4413 [String]
4414 $Filter,
4415
4416 [Switch]
4417 $Repair,
4418
4419 [Management.Automation.PSCredential]
4420 $Credential
4421 )
4422
4423 process {
4424 $Arguments = @{
4425 'SamAccountName' = $SamAccountName
4426 'Name' = $Name
4427 'Domain' = $Domain
4428 'DomainController' = $DomainController
4429 'Filter' = $Filter
4430 'Credential' = $Credential
4431 }
4432
4433 # splat the appropriate arguments to Get-ADObject
4434 $UACValues = Get-ADObject @Arguments | select useraccountcontrol | ConvertFrom-UACValue
4435
4436 if($Repair) {
4437
4438 if($UACValues.Keys -contains "ENCRYPTED_TEXT_PWD_ALLOWED") {
4439 # if reversible encryption is set, unset it
4440 Set-ADObject @Arguments -PropertyName useraccountcontrol -PropertyXorValue 128
4441 }
4442
4443 # unset the forced password change
4444 Set-ADObject @Arguments -PropertyName pwdlastset -PropertyValue -1
4445 }
4446
4447 else {
4448
4449 if($UACValues.Keys -contains "DONT_EXPIRE_PASSWORD") {
4450 # if the password is set to never expire, unset
4451 Set-ADObject @Arguments -PropertyName useraccountcontrol -PropertyXorValue 65536
4452 }
4453
4454 if($UACValues.Keys -notcontains "ENCRYPTED_TEXT_PWD_ALLOWED") {
4455 # if reversible encryption is not set, set it
4456 Set-ADObject @Arguments -PropertyName useraccountcontrol -PropertyXorValue 128
4457 }
4458
4459 # force the password to be changed on next login
4460 Set-ADObject @Arguments -PropertyName pwdlastset -PropertyValue 0
4461 }
4462 }
4463}
4464
4465
4466function Get-ComputerProperty {
4467<#
4468 .SYNOPSIS
4469
4470 Returns a list of all computer object properties. If a property
4471 name is specified, it returns all [computer:property] values.
4472
4473 Taken directly from @obscuresec's post:
4474 http://obscuresecurity.blogspot.com/2014/04/ADSISearcher.html
4475
4476 .PARAMETER Properties
4477
4478 Return property names for computers.
4479
4480 .PARAMETER Domain
4481
4482 The domain to query for computer properties, defaults to the current domain.
4483
4484 .PARAMETER DomainController
4485
4486 Domain controller to reflect LDAP queries through.
4487
4488 .PARAMETER PageSize
4489
4490 The PageSize to set for the LDAP searcher object.
4491
4492 .PARAMETER Credential
4493
4494 A [Management.Automation.PSCredential] object of alternate credentials
4495 for connection to the target domain.
4496
4497 .EXAMPLE
4498
4499 PS C:\> Get-ComputerProperty -Domain testing
4500
4501 Returns all user properties for computers in the 'testing' domain.
4502
4503 .EXAMPLE
4504
4505 PS C:\> Get-ComputerProperty -Properties ssn,lastlogon,location
4506
4507 Returns all an array of computer/ssn/lastlogin/location combinations
4508 for computers in the current domain.
4509
4510 .LINK
4511
4512 http://obscuresecurity.blogspot.com/2014/04/ADSISearcher.html
4513#>
4514
4515 [CmdletBinding()]
4516 param(
4517 [String[]]
4518 $Properties,
4519
4520 [String]
4521 $Domain,
4522
4523 [String]
4524 $DomainController,
4525
4526 [ValidateRange(1,10000)]
4527 [Int]
4528 $PageSize = 200,
4529
4530 [Management.Automation.PSCredential]
4531 $Credential
4532 )
4533
4534 if($Properties) {
4535 # extract out the set of all properties for each object
4536 $Properties = ,"name" + $Properties | Sort-Object -Unique
4537 Get-NetComputer -Domain $Domain -DomainController $DomainController -Credential $Credential -FullData -PageSize $PageSize | Select-Object -Property $Properties
4538 }
4539 else {
4540 # extract out just the property names
4541 Get-NetComputer -Domain $Domain -DomainController $DomainController -Credential $Credential -FullData -PageSize $PageSize | Select-Object -first 1 | Get-Member -MemberType *Property | Select-Object -Property "Name"
4542 }
4543}
4544
4545
4546function Find-ComputerField {
4547<#
4548 .SYNOPSIS
4549
4550 Searches computer object fields for a given word (default *pass*). Default
4551 field being searched is 'description'.
4552
4553 Taken directly from @obscuresec's post:
4554 http://obscuresecurity.blogspot.com/2014/04/ADSISearcher.html
4555
4556 .PARAMETER SearchTerm
4557
4558 Term to search for, default of "pass".
4559
4560 .PARAMETER SearchField
4561
4562 User field to search in, default of "description".
4563
4564 .PARAMETER ADSpath
4565
4566 The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
4567 Useful for OU queries.
4568
4569 .PARAMETER Domain
4570
4571 Domain to search computer fields for, defaults to the current domain.
4572
4573 .PARAMETER DomainController
4574
4575 Domain controller to reflect LDAP queries through.
4576
4577 .PARAMETER PageSize
4578
4579 The PageSize to set for the LDAP searcher object.
4580
4581 .PARAMETER Credential
4582
4583 A [Management.Automation.PSCredential] object of alternate credentials
4584 for connection to the target domain.
4585
4586 .EXAMPLE
4587
4588 PS C:\> Find-ComputerField -SearchTerm backup -SearchField info
4589
4590 Find computer accounts with "backup" in the "info" field.
4591#>
4592
4593 [CmdletBinding()]
4594 param(
4595 [Parameter(Position=0,ValueFromPipeline=$True)]
4596 [Alias('Term')]
4597 [String]
4598 $SearchTerm = 'pass',
4599
4600 [Alias('Field')]
4601 [String]
4602 $SearchField = 'description',
4603
4604 [String]
4605 $ADSpath,
4606
4607 [String]
4608 $Domain,
4609
4610 [String]
4611 $DomainController,
4612
4613 [ValidateRange(1,10000)]
4614 [Int]
4615 $PageSize = 200,
4616
4617 [Management.Automation.PSCredential]
4618 $Credential
4619 )
4620
4621 process {
4622 Get-NetComputer -ADSpath $ADSpath -Domain $Domain -DomainController $DomainController -Credential $Credential -FullData -Filter "($SearchField=*$SearchTerm*)" -PageSize $PageSize | Select-Object samaccountname,$SearchField
4623 }
4624}
4625
4626
4627function Get-NetOU {
4628<#
4629 .SYNOPSIS
4630
4631 Gets a list of all current OUs in a domain.
4632
4633 .PARAMETER OUName
4634
4635 The OU name to query for, wildcards accepted.
4636
4637 .PARAMETER GUID
4638
4639 Only return OUs with the specified GUID in their gplink property.
4640
4641 .PARAMETER Domain
4642
4643 The domain to query for OUs, defaults to the current domain.
4644
4645 .PARAMETER DomainController
4646
4647 Domain controller to reflect LDAP queries through.
4648
4649 .PARAMETER ADSpath
4650
4651 The LDAP source to search through.
4652
4653 .PARAMETER FullData
4654
4655 Switch. Return full OU objects instead of just object names (the default).
4656
4657 .PARAMETER PageSize
4658
4659 The PageSize to set for the LDAP searcher object.
4660
4661 .PARAMETER Credential
4662
4663 A [Management.Automation.PSCredential] object of alternate credentials
4664 for connection to the target domain.
4665
4666 .EXAMPLE
4667
4668 PS C:\> Get-NetOU
4669
4670 Returns the current OUs in the domain.
4671
4672 .EXAMPLE
4673
4674 PS C:\> Get-NetOU -OUName *admin* -Domain testlab.local
4675
4676 Returns all OUs with "admin" in their name in the testlab.local domain.
4677
4678 .EXAMPLE
4679
4680 PS C:\> Get-NetOU -GUID 123-...
4681
4682 Returns all OUs with linked to the specified group policy object.
4683
4684 .EXAMPLE
4685
4686 PS C:\> "*admin*","*server*" | Get-NetOU
4687
4688 Get the full OU names for the given search terms piped on the pipeline.
4689#>
4690
4691 [CmdletBinding()]
4692 Param (
4693 [Parameter(ValueFromPipeline=$True)]
4694 [String]
4695 $OUName = '*',
4696
4697 [String]
4698 $GUID,
4699
4700 [String]
4701 $Domain,
4702
4703 [String]
4704 $DomainController,
4705
4706 [String]
4707 $ADSpath,
4708
4709 [Switch]
4710 $FullData,
4711
4712 [ValidateRange(1,10000)]
4713 [Int]
4714 $PageSize = 200,
4715
4716 [Management.Automation.PSCredential]
4717 $Credential
4718 )
4719
4720 begin {
4721 $OUSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $ADSpath -PageSize $PageSize
4722 }
4723 process {
4724 if ($OUSearcher) {
4725 if ($GUID) {
4726 # if we're filtering for a GUID in .gplink
4727 $OUSearcher.filter="(&(objectCategory=organizationalUnit)(name=$OUName)(gplink=*$GUID*))"
4728 }
4729 else {
4730 $OUSearcher.filter="(&(objectCategory=organizationalUnit)(name=$OUName))"
4731 }
4732
4733 try {
4734 $Results = $OUSearcher.FindAll()
4735 $Results | Where-Object {$_} | ForEach-Object {
4736 if ($FullData) {
4737 # convert/process the LDAP fields for each result
4738 $OU = Convert-LDAPProperty -Properties $_.Properties
4739 $OU.PSObject.TypeNames.Add('PowerView.OU')
4740 $OU
4741 }
4742 else {
4743 # otherwise just returning the ADS paths of the OUs
4744 $_.properties.adspath
4745 }
4746 }
4747 $Results.dispose()
4748 $OUSearcher.dispose()
4749 }
4750 catch {
4751 Write-Warning $_
4752 }
4753 }
4754 }
4755}
4756
4757
4758function Get-NetSite {
4759<#
4760 .SYNOPSIS
4761
4762 Gets a list of all current sites in a domain.
4763
4764 .PARAMETER SiteName
4765
4766 Site filter string, wildcards accepted.
4767
4768 .PARAMETER Domain
4769
4770 The domain to query for sites, defaults to the current domain.
4771
4772 .PARAMETER DomainController
4773
4774 Domain controller to reflect LDAP queries through.
4775
4776 .PARAMETER ADSpath
4777
4778 The LDAP source to search through.
4779
4780 .PARAMETER GUID
4781
4782 Only return site with the specified GUID in their gplink property.
4783
4784 .PARAMETER FullData
4785
4786 Switch. Return full site objects instead of just object names (the default).
4787
4788 .PARAMETER PageSize
4789
4790 The PageSize to set for the LDAP searcher object.
4791
4792 .PARAMETER Credential
4793
4794 A [Management.Automation.PSCredential] object of alternate credentials
4795 for connection to the target domain.
4796
4797 .EXAMPLE
4798
4799 PS C:\> Get-NetSite -Domain testlab.local -FullData
4800
4801 Returns the full data objects for all sites in testlab.local
4802#>
4803
4804 [CmdletBinding()]
4805 Param (
4806 [Parameter(ValueFromPipeline=$True)]
4807 [String]
4808 $SiteName = "*",
4809
4810 [String]
4811 $Domain,
4812
4813 [String]
4814 $DomainController,
4815
4816 [String]
4817 $ADSpath,
4818
4819 [String]
4820 $GUID,
4821
4822 [Switch]
4823 $FullData,
4824
4825 [ValidateRange(1,10000)]
4826 [Int]
4827 $PageSize = 200,
4828
4829 [Management.Automation.PSCredential]
4830 $Credential
4831 )
4832
4833 begin {
4834 $SiteSearcher = Get-DomainSearcher -ADSpath $ADSpath -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSprefix "CN=Sites,CN=Configuration" -PageSize $PageSize
4835 }
4836 process {
4837 if($SiteSearcher) {
4838
4839 if ($GUID) {
4840 # if we're filtering for a GUID in .gplink
4841 $SiteSearcher.filter="(&(objectCategory=site)(name=$SiteName)(gplink=*$GUID*))"
4842 }
4843 else {
4844 $SiteSearcher.filter="(&(objectCategory=site)(name=$SiteName))"
4845 }
4846
4847 try {
4848 $Results = $SiteSearcher.FindAll()
4849 $Results | Where-Object {$_} | ForEach-Object {
4850 if ($FullData) {
4851 # convert/process the LDAP fields for each result
4852 $Site = Convert-LDAPProperty -Properties $_.Properties
4853 $Site.PSObject.TypeNames.Add('PowerView.Site')
4854 $Site
4855 }
4856 else {
4857 # otherwise just return the site name
4858 $_.properties.name
4859 }
4860 }
4861 $Results.dispose()
4862 $SiteSearcher.dispose()
4863 }
4864 catch {
4865 Write-Verbose $_
4866 }
4867 }
4868 }
4869}
4870
4871
4872function Get-NetSubnet {
4873<#
4874 .SYNOPSIS
4875
4876 Gets a list of all current subnets in a domain.
4877
4878 .PARAMETER SiteName
4879
4880 Only return subnets from the specified SiteName.
4881
4882 .PARAMETER Domain
4883
4884 The domain to query for subnets, defaults to the current domain.
4885
4886 .PARAMETER DomainController
4887
4888 Domain controller to reflect LDAP queries through.
4889
4890 .PARAMETER ADSpath
4891
4892 The LDAP source to search through.
4893
4894 .PARAMETER FullData
4895
4896 Switch. Return full subnet objects instead of just object names (the default).
4897
4898 .PARAMETER PageSize
4899
4900 The PageSize to set for the LDAP searcher object.
4901
4902 .PARAMETER Credential
4903
4904 A [Management.Automation.PSCredential] object of alternate credentials
4905 for connection to the target domain.
4906
4907 .EXAMPLE
4908
4909 PS C:\> Get-NetSubnet
4910
4911 Returns all subnet names in the current domain.
4912
4913 .EXAMPLE
4914
4915 PS C:\> Get-NetSubnet -Domain testlab.local -FullData
4916
4917 Returns the full data objects for all subnets in testlab.local
4918#>
4919
4920 [CmdletBinding()]
4921 Param (
4922 [Parameter(ValueFromPipeline=$True)]
4923 [String]
4924 $SiteName = "*",
4925
4926 [String]
4927 $Domain,
4928
4929 [String]
4930 $ADSpath,
4931
4932 [String]
4933 $DomainController,
4934
4935 [Switch]
4936 $FullData,
4937
4938 [ValidateRange(1,10000)]
4939 [Int]
4940 $PageSize = 200,
4941
4942 [Management.Automation.PSCredential]
4943 $Credential
4944 )
4945
4946 begin {
4947 $SubnetSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $ADSpath -ADSprefix "CN=Subnets,CN=Sites,CN=Configuration" -PageSize $PageSize
4948 }
4949
4950 process {
4951 if($SubnetSearcher) {
4952
4953 $SubnetSearcher.filter="(&(objectCategory=subnet))"
4954
4955 try {
4956 $Results = $SubnetSearcher.FindAll()
4957 $Results | Where-Object {$_} | ForEach-Object {
4958 if ($FullData) {
4959 # convert/process the LDAP fields for each result
4960 Convert-LDAPProperty -Properties $_.Properties | Where-Object { $_.siteobject -match "CN=$SiteName" }
4961 }
4962 else {
4963 # otherwise just return the subnet name and site name
4964 if ( ($SiteName -and ($_.properties.siteobject -match "CN=$SiteName,")) -or ($SiteName -eq '*')) {
4965
4966 $SubnetProperties = @{
4967 'Subnet' = $_.properties.name[0]
4968 }
4969 try {
4970 $SubnetProperties['Site'] = ($_.properties.siteobject[0]).split(",")[0]
4971 }
4972 catch {
4973 $SubnetProperties['Site'] = 'Error'
4974 }
4975
4976 New-Object -TypeName PSObject -Property $SubnetProperties
4977 }
4978 }
4979 }
4980 $Results.dispose()
4981 $SubnetSearcher.dispose()
4982 }
4983 catch {
4984 Write-Warning $_
4985 }
4986 }
4987 }
4988}
4989
4990
4991function Get-DomainSID {
4992<#
4993 .SYNOPSIS
4994
4995 Gets the SID for the domain.
4996
4997 .PARAMETER Domain
4998
4999 The domain to query, defaults to the current domain.
5000
5001 .PARAMETER DomainController
5002
5003 Domain controller to reflect LDAP queries through.
5004
5005 .EXAMPLE
5006
5007 C:\> Get-DomainSID -Domain TEST
5008
5009 Returns SID for the domain 'TEST'
5010#>
5011
5012 param(
5013 [String]
5014 $Domain,
5015
5016 [String]
5017 $DomainController
5018 )
5019
5020 $DCSID = Get-NetComputer -Domain $Domain -DomainController $DomainController -FullData -Filter '(userAccountControl:1.2.840.113556.1.4.803:=8192)' | Select-Object -First 1 -ExpandProperty objectsid
5021 if($DCSID) {
5022 $DCSID.Substring(0, $DCSID.LastIndexOf('-'))
5023 }
5024 else {
5025 Write-Verbose "Error extracting domain SID for $Domain"
5026 }
5027}
5028
5029
5030function Get-NetGroup {
5031<#
5032 .SYNOPSIS
5033
5034 Gets a list of all current groups in a domain, or all
5035 the groups a given user/group object belongs to.
5036
5037 .PARAMETER GroupName
5038
5039 The group name to query for, wildcards accepted.
5040
5041 .PARAMETER SID
5042
5043 The group SID to query for.
5044
5045 .PARAMETER UserName
5046
5047 The user name (or group name) to query for all effective
5048 groups of.
5049
5050 .PARAMETER Filter
5051
5052 A customized ldap filter string to use, e.g. "(description=*admin*)"
5053
5054 .PARAMETER Domain
5055
5056 The domain to query for groups, defaults to the current domain.
5057
5058 .PARAMETER DomainController
5059
5060 Domain controller to reflect LDAP queries through.
5061
5062 .PARAMETER ADSpath
5063
5064 The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
5065 Useful for OU queries.
5066
5067 .PARAMETER AdminCount
5068
5069 Switch. Return group with adminCount=1.
5070
5071 .PARAMETER FullData
5072
5073 Switch. Return full group objects instead of just object names (the default).
5074
5075 .PARAMETER RawSids
5076
5077 Switch. Return raw SIDs when using "Get-NetGroup -UserName X"
5078
5079 .PARAMETER PageSize
5080
5081 The PageSize to set for the LDAP searcher object.
5082
5083 .PARAMETER Credential
5084
5085 A [Management.Automation.PSCredential] object of alternate credentials
5086 for connection to the target domain.
5087
5088 .PARAMETER AllTypes
5089
5090 By default we will retrieve only Security, not Distribution Groups.
5091
5092 .EXAMPLE
5093
5094 PS C:\> Get-NetGroup
5095
5096 Returns the current security groups in the domain.
5097
5098 .EXAMPLE
5099
5100 PS C:\> Get-NetGroup -GroupName *admin*
5101
5102 Returns all groups with "admin" in their group name.
5103
5104 .EXAMPLE
5105
5106 PS C:\> Get-NetGroup -Domain testing -FullData
5107
5108 Returns full group data objects in the 'testing' domain
5109#>
5110
5111 [CmdletBinding()]
5112 param(
5113 [Parameter(ValueFromPipeline=$True)]
5114 [String]
5115 $GroupName = '*',
5116
5117 [String]
5118 $SID,
5119
5120 [String]
5121 $UserName,
5122
5123 [String]
5124 $Filter,
5125
5126 [String]
5127 $Domain,
5128
5129 [String]
5130 $DomainController,
5131
5132 [String]
5133 $ADSpath,
5134
5135 [Switch]
5136 $AdminCount,
5137
5138 [Switch]
5139 $FullData,
5140
5141 [Switch]
5142 $RawSids,
5143
5144 [Switch]
5145 $AllTypes,
5146
5147 [ValidateRange(1,10000)]
5148 [Int]
5149 $PageSize = 200,
5150
5151 [Management.Automation.PSCredential]
5152 $Credential
5153 )
5154
5155 begin {
5156 $GroupSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $ADSpath -PageSize $PageSize
5157 if (!$AllTypes)
5158 {
5159 $Filter += "(groupType:1.2.840.113556.1.4.803:=2147483648)"
5160 }
5161 }
5162
5163 process {
5164 if($GroupSearcher) {
5165
5166 if($AdminCount) {
5167 Write-Verbose "Checking for adminCount=1"
5168 $Filter += "(admincount=1)"
5169 }
5170
5171 if ($UserName) {
5172 # get the raw user object
5173 $User = Get-ADObject -SamAccountName $UserName -Domain $Domain -DomainController $DomainController -Credential $Credential -ReturnRaw -PageSize $PageSize | Select-Object -First 1
5174
5175 if($User) {
5176 # convert the user to a directory entry
5177 $UserDirectoryEntry = $User.GetDirectoryEntry()
5178
5179 # cause the cache to calculate the token groups for the user
5180 $UserDirectoryEntry.RefreshCache("tokenGroups")
5181
5182 $UserDirectoryEntry.TokenGroups | ForEach-Object {
5183 # convert the token group sid
5184 $GroupSid = (New-Object System.Security.Principal.SecurityIdentifier($_,0)).Value
5185
5186 # ignore the built in groups
5187 if($GroupSid -notmatch '^S-1-5-32-.*') {
5188 if($FullData) {
5189 $Group = Get-ADObject -SID $GroupSid -PageSize $PageSize -Domain $Domain -DomainController $DomainController -Credential $Credential
5190 $Group.PSObject.TypeNames.Add('PowerView.Group')
5191 $Group
5192 }
5193 else {
5194 if($RawSids) {
5195 $GroupSid
5196 }
5197 else {
5198 Convert-SidToName -SID $GroupSid
5199 }
5200 }
5201 }
5202 }
5203 }
5204 else {
5205 Write-Warning "UserName '$UserName' failed to resolve."
5206 }
5207 }
5208 else {
5209 if ($SID) {
5210 $GroupSearcher.filter = "(&(objectCategory=group)(objectSID=$SID)$Filter)"
5211 }
5212 else {
5213 $GroupSearcher.filter = "(&(objectCategory=group)(samaccountname=$GroupName)$Filter)"
5214 }
5215
5216 $Results = $GroupSearcher.FindAll()
5217 $Results | Where-Object {$_} | ForEach-Object {
5218 # if we're returning full data objects
5219 if ($FullData) {
5220 # convert/process the LDAP fields for each result
5221 $Group = Convert-LDAPProperty -Properties $_.Properties
5222 $Group.PSObject.TypeNames.Add('PowerView.Group')
5223 $Group
5224 }
5225 else {
5226 # otherwise we're just returning the group name
5227 $_.properties.samaccountname
5228 }
5229 }
5230 $Results.dispose()
5231 $GroupSearcher.dispose()
5232 }
5233 }
5234 }
5235}
5236
5237
5238function Get-NetGroupMember {
5239<#
5240 .SYNOPSIS
5241
5242 This function users [ADSI] and LDAP to query the current AD context
5243 or trusted domain for users in a specified group. If no GroupName is
5244 specified, it defaults to querying the "Domain Admins" group.
5245 This is a replacement for "net group 'name' /domain"
5246
5247 .PARAMETER GroupName
5248
5249 The group name to query for users.
5250
5251 .PARAMETER SID
5252
5253 The Group SID to query for users. If not given, it defaults to 512 "Domain Admins"
5254
5255 .PARAMETER Filter
5256
5257 A customized ldap filter string to use, e.g. "(description=*admin*)"
5258
5259 .PARAMETER Domain
5260
5261 The domain to query for group users, defaults to the current domain.
5262
5263 .PARAMETER DomainController
5264
5265 Domain controller to reflect LDAP queries through.
5266
5267 .PARAMETER ADSpath
5268
5269 The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
5270 Useful for OU queries.
5271
5272 .PARAMETER FullData
5273
5274 Switch. Returns full data objects instead of just group/users.
5275
5276 .PARAMETER Recurse
5277
5278 Switch. If the group member is a group, recursively try to query its members as well.
5279
5280 .PARAMETER UseMatchingRule
5281
5282 Switch. Use LDAP_MATCHING_RULE_IN_CHAIN in the LDAP search query when -Recurse is specified.
5283 Much faster than manual recursion, but doesn't reveal cross-domain groups.
5284
5285 .PARAMETER PageSize
5286
5287 The PageSize to set for the LDAP searcher object.
5288
5289 .PARAMETER Credential
5290
5291 A [Management.Automation.PSCredential] object of alternate credentials
5292 for connection to the target domain.
5293
5294 .EXAMPLE
5295
5296 PS C:\> Get-NetGroupMember
5297
5298 Returns the usernames that of members of the "Domain Admins" domain group.
5299
5300 .EXAMPLE
5301
5302 PS C:\> Get-NetGroupMember -Domain testing -GroupName "Power Users"
5303
5304 Returns the usernames that of members of the "Power Users" group in the 'testing' domain.
5305
5306 .LINK
5307
5308 http://www.powershellmagazine.com/2013/05/23/pstip-retrieve-group-membership-of-an-active-directory-group-recursively/
5309#>
5310
5311 [CmdletBinding()]
5312 param(
5313 [Parameter(ValueFromPipeline=$True)]
5314 [String]
5315 $GroupName,
5316
5317 [String]
5318 $SID,
5319
5320 [String]
5321 $Domain,
5322
5323 [String]
5324 $DomainController,
5325
5326 [String]
5327 $ADSpath,
5328
5329 [Switch]
5330 $FullData,
5331
5332 [Switch]
5333 $Recurse,
5334
5335 [Switch]
5336 $UseMatchingRule,
5337
5338 [ValidateRange(1,10000)]
5339 [Int]
5340 $PageSize = 200,
5341
5342 [Management.Automation.PSCredential]
5343 $Credential
5344 )
5345
5346 begin {
5347 if($DomainController) {
5348 $TargetDomainController = $DomainController
5349 }
5350 else {
5351 $TargetDomainController = ((Get-NetDomain -Credential $Credential).PdcRoleOwner).Name
5352 }
5353
5354 if($Domain) {
5355 $TargetDomain = $Domain
5356 }
5357 else {
5358 $TargetDomain = Get-NetDomain -Credential $Credential | Select-Object -ExpandProperty name
5359 }
5360
5361 # so this isn't repeated if users are passed on the pipeline
5362 $GroupSearcher = Get-DomainSearcher -Domain $TargetDomain -DomainController $TargetDomainController -Credential $Credential -ADSpath $ADSpath -PageSize $PageSize
5363 }
5364
5365 process {
5366 if ($GroupSearcher) {
5367 if ($Recurse -and $UseMatchingRule) {
5368 # resolve the group to a distinguishedname
5369 if ($GroupName) {
5370 $Group = Get-NetGroup -AllTypes -GroupName $GroupName -Domain $TargetDomain -DomainController $TargetDomainController -Credential $Credential -FullData -PageSize $PageSize
5371 }
5372 elseif ($SID) {
5373 $Group = Get-NetGroup -AllTypes -SID $SID -Domain $TargetDomain -DomainController $TargetDomainController -Credential $Credential -FullData -PageSize $PageSize
5374 }
5375 else {
5376 # default to domain admins
5377 $SID = (Get-DomainSID -Domain $TargetDomain -DomainController $TargetDomainController) + "-512"
5378 $Group = Get-NetGroup -AllTypes -SID $SID -Domain $TargetDomain -DomainController $TargetDomainController -Credential $Credential -FullData -PageSize $PageSize
5379 }
5380 $GroupDN = $Group.distinguishedname
5381 $GroupFoundName = $Group.samaccountname
5382
5383 if ($GroupDN) {
5384 $GroupSearcher.filter = "(&(samAccountType=805306368)(memberof:1.2.840.113556.1.4.1941:=$GroupDN)$Filter)"
5385 $GroupSearcher.PropertiesToLoad.AddRange(('distinguishedName','samaccounttype','lastlogon','lastlogontimestamp','dscorepropagationdata','objectsid','whencreated','badpasswordtime','accountexpires','iscriticalsystemobject','name','usnchanged','objectcategory','description','codepage','instancetype','countrycode','distinguishedname','cn','admincount','logonhours','objectclass','logoncount','usncreated','useraccountcontrol','objectguid','primarygroupid','lastlogoff','samaccountname','badpwdcount','whenchanged','memberof','pwdlastset','adspath'))
5386
5387 $Members = $GroupSearcher.FindAll()
5388 $GroupFoundName = $GroupName
5389 }
5390 else {
5391 Write-Error "Unable to find Group"
5392 }
5393 }
5394 else {
5395 if ($GroupName) {
5396 $GroupSearcher.filter = "(&(objectCategory=group)(samaccountname=$GroupName)$Filter)"
5397 }
5398 elseif ($SID) {
5399 $GroupSearcher.filter = "(&(objectCategory=group)(objectSID=$SID)$Filter)"
5400 }
5401 else {
5402 # default to domain admins
5403 $SID = (Get-DomainSID -Domain $TargetDomain -DomainController $TargetDomainController) + "-512"
5404 $GroupSearcher.filter = "(&(objectCategory=group)(objectSID=$SID)$Filter)"
5405 }
5406
5407 try {
5408 $Result = $GroupSearcher.FindOne()
5409 }
5410 catch {
5411 $Members = @()
5412 }
5413
5414 $GroupFoundName = ''
5415
5416 if ($Result) {
5417 $Members = $Result.properties.item("member")
5418
5419 if($Members.count -eq 0) {
5420
5421 $Finished = $False
5422 $Bottom = 0
5423 $Top = 0
5424
5425 while(!$Finished) {
5426 $Top = $Bottom + 1499
5427 $MemberRange="member;range=$Bottom-$Top"
5428 $Bottom += 1500
5429
5430 $GroupSearcher.PropertiesToLoad.Clear()
5431 [void]$GroupSearcher.PropertiesToLoad.Add("$MemberRange")
5432 [void]$GroupSearcher.PropertiesToLoad.Add("samaccountname")
5433 try {
5434 $Result = $GroupSearcher.FindOne()
5435 $RangedProperty = $Result.Properties.PropertyNames -like "member;range=*"
5436 $Members += $Result.Properties.item($RangedProperty)
5437 $GroupFoundName = $Result.properties.item("samaccountname")[0]
5438
5439 if ($Members.count -eq 0) {
5440 $Finished = $True
5441 }
5442 }
5443 catch [System.Management.Automation.MethodInvocationException] {
5444 $Finished = $True
5445 }
5446 }
5447 }
5448 else {
5449 $GroupFoundName = $Result.properties.item("samaccountname")[0]
5450 $Members += $Result.Properties.item($RangedProperty)
5451 }
5452 }
5453 $GroupSearcher.dispose()
5454 }
5455
5456 $Members | Where-Object {$_} | ForEach-Object {
5457 # if we're doing the LDAP_MATCHING_RULE_IN_CHAIN recursion
5458 if ($Recurse -and $UseMatchingRule) {
5459 $Properties = $_.Properties
5460 }
5461 else {
5462 if($TargetDomainController) {
5463 $Result = [adsi]"LDAP://$TargetDomainController/$_"
5464 }
5465 else {
5466 $Result = [adsi]"LDAP://$_"
5467 }
5468 if($Result){
5469 $Properties = $Result.Properties
5470 }
5471 }
5472
5473 if($Properties) {
5474
5475 $IsGroup = @('268435456','268435457','536870912','536870913') -contains $Properties.samaccounttype
5476
5477 if ($FullData) {
5478 $GroupMember = Convert-LDAPProperty -Properties $Properties
5479 }
5480 else {
5481 $GroupMember = New-Object PSObject
5482 }
5483
5484 $GroupMember | Add-Member Noteproperty 'GroupDomain' $TargetDomain
5485 $GroupMember | Add-Member Noteproperty 'GroupName' $GroupFoundName
5486
5487 if($Properties.objectSid) {
5488 $MemberSID = ((New-Object System.Security.Principal.SecurityIdentifier $Properties.objectSid[0],0).Value)
5489 }
5490 else {
5491 $MemberSID = $Null
5492 }
5493
5494 try {
5495 $MemberDN = $Properties.distinguishedname[0]
5496
5497 if (($MemberDN -match 'ForeignSecurityPrincipals') -and ($MemberDN -match 'S-1-5-21')) {
5498 try {
5499 if(-not $MemberSID) {
5500 $MemberSID = $Properties.cn[0]
5501 }
5502 $MemberSimpleName = Convert-SidToName -SID $MemberSID | Convert-ADName -InputType 'NT4' -OutputType 'Simple'
5503 if($MemberSimpleName) {
5504 $MemberDomain = $MemberSimpleName.Split('@')[1]
5505 }
5506 else {
5507 Write-Warning "Error converting $MemberDN"
5508 $MemberDomain = $Null
5509 }
5510 }
5511 catch {
5512 Write-Warning "Error converting $MemberDN"
5513 $MemberDomain = $Null
5514 }
5515 }
5516 else {
5517 # extract the FQDN from the Distinguished Name
5518 $MemberDomain = $MemberDN.subString($MemberDN.IndexOf("DC=")) -replace 'DC=','' -replace ',','.'
5519 }
5520 }
5521 catch {
5522 $MemberDN = $Null
5523 $MemberDomain = $Null
5524 }
5525
5526 if ($Properties.samaccountname) {
5527 # forest users have the samAccountName set
5528 $MemberName = $Properties.samaccountname[0]
5529 }
5530 else {
5531 # external trust users have a SID, so convert it
5532 try {
5533 $MemberName = Convert-SidToName $Properties.cn[0]
5534 }
5535 catch {
5536 # if there's a problem contacting the domain to resolve the SID
5537 $MemberName = $Properties.cn
5538 }
5539 }
5540
5541 $GroupMember | Add-Member Noteproperty 'MemberDomain' $MemberDomain
5542 $GroupMember | Add-Member Noteproperty 'MemberName' $MemberName
5543 $GroupMember | Add-Member Noteproperty 'MemberSID' $MemberSID
5544 $GroupMember | Add-Member Noteproperty 'IsGroup' $IsGroup
5545 $GroupMember | Add-Member Noteproperty 'MemberDN' $MemberDN
5546 $GroupMember.PSObject.TypeNames.Add('PowerView.GroupMember')
5547 $GroupMember
5548
5549 # if we're doing manual recursion
5550 if ($Recurse -and !$UseMatchingRule -and $IsGroup -and $MemberName) {
5551 if($FullData) {
5552 Get-NetGroupMember -FullData -Domain $MemberDomain -DomainController $TargetDomainController -Credential $Credential -GroupName $MemberName -Recurse -PageSize $PageSize
5553 }
5554 else {
5555 Get-NetGroupMember -Domain $MemberDomain -DomainController $TargetDomainController -Credential $Credential -GroupName $MemberName -Recurse -PageSize $PageSize
5556 }
5557 }
5558 }
5559 }
5560 }
5561 }
5562}
5563
5564
5565function Get-NetFileServer {
5566<#
5567 .SYNOPSIS
5568
5569 Returns a list of all file servers extracted from user
5570 homedirectory, scriptpath, and profilepath fields.
5571
5572 .PARAMETER Domain
5573
5574 The domain to query for user file servers, defaults to the current domain.
5575
5576 .PARAMETER DomainController
5577
5578 Domain controller to reflect LDAP queries through.
5579
5580 .PARAMETER TargetUsers
5581
5582 An array of users to query for file servers.
5583
5584 .PARAMETER PageSize
5585
5586 The PageSize to set for the LDAP searcher object.
5587
5588 .PARAMETER Credential
5589
5590 A [Management.Automation.PSCredential] object of alternate credentials
5591 for connection to the target domain.
5592
5593 .EXAMPLE
5594
5595 PS C:\> Get-NetFileServer
5596
5597 Returns active file servers.
5598
5599 .EXAMPLE
5600
5601 PS C:\> Get-NetFileServer -Domain testing
5602
5603 Returns active file servers for the 'testing' domain.
5604#>
5605
5606 [CmdletBinding()]
5607 param(
5608 [String]
5609 $Domain,
5610
5611 [String]
5612 $DomainController,
5613
5614 [String[]]
5615 $TargetUsers,
5616
5617 [ValidateRange(1,10000)]
5618 [Int]
5619 $PageSize = 200,
5620
5621 [Management.Automation.PSCredential]
5622 $Credential
5623 )
5624
5625 function SplitPath {
5626 # short internal helper to split UNC server paths
5627 param([String]$Path)
5628
5629 if ($Path -and ($Path.split("\\").Count -ge 3)) {
5630 $Temp = $Path.split("\\")[2]
5631 if($Temp -and ($Temp -ne '')) {
5632 $Temp
5633 }
5634 }
5635 }
5636 $filter = "(!(userAccountControl:1.2.840.113556.1.4.803:=2))(|(scriptpath=*)(homedirectory=*)(profilepath=*))"
5637 Get-NetUser -Domain $Domain -DomainController $DomainController -Credential $Credential -PageSize $PageSize -Filter $filter | Where-Object {$_} | Where-Object {
5638 # filter for any target users
5639 if($TargetUsers) {
5640 $TargetUsers -Match $_.samAccountName
5641 }
5642 else { $True }
5643 } | ForEach-Object {
5644 # split out every potential file server path
5645 if($_.homedirectory) {
5646 SplitPath($_.homedirectory)
5647 }
5648 if($_.scriptpath) {
5649 SplitPath($_.scriptpath)
5650 }
5651 if($_.profilepath) {
5652 SplitPath($_.profilepath)
5653 }
5654
5655 } | Where-Object {$_} | Sort-Object -Unique
5656}
5657
5658
5659function Get-DFSshare {
5660<#
5661 .SYNOPSIS
5662
5663 Returns a list of all fault-tolerant distributed file
5664 systems for a given domain.
5665
5666 .PARAMETER Version
5667
5668 The version of DFS to query for servers.
5669 1/v1, 2/v2, or all
5670
5671 .PARAMETER Domain
5672
5673 The domain to query for user DFS shares, defaults to the current domain.
5674
5675 .PARAMETER DomainController
5676
5677 Domain controller to reflect LDAP queries through.
5678
5679 .PARAMETER ADSpath
5680
5681 The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
5682 Useful for OU queries.
5683
5684 .PARAMETER PageSize
5685
5686 The PageSize to set for the LDAP searcher object.
5687
5688 .PARAMETER Credential
5689
5690 A [Management.Automation.PSCredential] object of alternate credentials
5691 for connection to the target domain.
5692
5693 .EXAMPLE
5694
5695 PS C:\> Get-DFSshare
5696
5697 Returns all distributed file system shares for the current domain.
5698
5699 .EXAMPLE
5700
5701 PS C:\> Get-DFSshare -Domain test
5702
5703 Returns all distributed file system shares for the 'test' domain.
5704#>
5705
5706 [CmdletBinding()]
5707 param(
5708 [String]
5709 [ValidateSet("All","V1","1","V2","2")]
5710 $Version = "All",
5711
5712 [String]
5713 $Domain,
5714
5715 [String]
5716 $DomainController,
5717
5718 [String]
5719 $ADSpath,
5720
5721 [ValidateRange(1,10000)]
5722 [Int]
5723 $PageSize = 200,
5724
5725 [Management.Automation.PSCredential]
5726 $Credential
5727 )
5728
5729 function Parse-Pkt {
5730 [CmdletBinding()]
5731 param(
5732 [byte[]]
5733 $Pkt
5734 )
5735
5736 $bin = $Pkt
5737 $blob_version = [bitconverter]::ToUInt32($bin[0..3],0)
5738 $blob_element_count = [bitconverter]::ToUInt32($bin[4..7],0)
5739 $offset = 8
5740 #https://msdn.microsoft.com/en-us/library/cc227147.aspx
5741 $object_list = @()
5742 for($i=1; $i -le $blob_element_count; $i++){
5743 $blob_name_size_start = $offset
5744 $blob_name_size_end = $offset + 1
5745 $blob_name_size = [bitconverter]::ToUInt16($bin[$blob_name_size_start..$blob_name_size_end],0)
5746
5747 $blob_name_start = $blob_name_size_end + 1
5748 $blob_name_end = $blob_name_start + $blob_name_size - 1
5749 $blob_name = [System.Text.Encoding]::Unicode.GetString($bin[$blob_name_start..$blob_name_end])
5750
5751 $blob_data_size_start = $blob_name_end + 1
5752 $blob_data_size_end = $blob_data_size_start + 3
5753 $blob_data_size = [bitconverter]::ToUInt32($bin[$blob_data_size_start..$blob_data_size_end],0)
5754
5755 $blob_data_start = $blob_data_size_end + 1
5756 $blob_data_end = $blob_data_start + $blob_data_size - 1
5757 $blob_data = $bin[$blob_data_start..$blob_data_end]
5758 switch -wildcard ($blob_name) {
5759 "\siteroot" { }
5760 "\domainroot*" {
5761 # Parse DFSNamespaceRootOrLinkBlob object. Starts with variable length DFSRootOrLinkIDBlob which we parse first...
5762 # DFSRootOrLinkIDBlob
5763 $root_or_link_guid_start = 0
5764 $root_or_link_guid_end = 15
5765 $root_or_link_guid = [byte[]]$blob_data[$root_or_link_guid_start..$root_or_link_guid_end]
5766 $guid = New-Object Guid(,$root_or_link_guid) # should match $guid_str
5767 $prefix_size_start = $root_or_link_guid_end + 1
5768 $prefix_size_end = $prefix_size_start + 1
5769 $prefix_size = [bitconverter]::ToUInt16($blob_data[$prefix_size_start..$prefix_size_end],0)
5770 $prefix_start = $prefix_size_end + 1
5771 $prefix_end = $prefix_start + $prefix_size - 1
5772 $prefix = [System.Text.Encoding]::Unicode.GetString($blob_data[$prefix_start..$prefix_end])
5773
5774 $short_prefix_size_start = $prefix_end + 1
5775 $short_prefix_size_end = $short_prefix_size_start + 1
5776 $short_prefix_size = [bitconverter]::ToUInt16($blob_data[$short_prefix_size_start..$short_prefix_size_end],0)
5777 $short_prefix_start = $short_prefix_size_end + 1
5778 $short_prefix_end = $short_prefix_start + $short_prefix_size - 1
5779 $short_prefix = [System.Text.Encoding]::Unicode.GetString($blob_data[$short_prefix_start..$short_prefix_end])
5780
5781 $type_start = $short_prefix_end + 1
5782 $type_end = $type_start + 3
5783 $type = [bitconverter]::ToUInt32($blob_data[$type_start..$type_end],0)
5784
5785 $state_start = $type_end + 1
5786 $state_end = $state_start + 3
5787 $state = [bitconverter]::ToUInt32($blob_data[$state_start..$state_end],0)
5788
5789 $comment_size_start = $state_end + 1
5790 $comment_size_end = $comment_size_start + 1
5791 $comment_size = [bitconverter]::ToUInt16($blob_data[$comment_size_start..$comment_size_end],0)
5792 $comment_start = $comment_size_end + 1
5793 $comment_end = $comment_start + $comment_size - 1
5794 if ($comment_size -gt 0) {
5795 $comment = [System.Text.Encoding]::Unicode.GetString($blob_data[$comment_start..$comment_end])
5796 }
5797 $prefix_timestamp_start = $comment_end + 1
5798 $prefix_timestamp_end = $prefix_timestamp_start + 7
5799 # https://msdn.microsoft.com/en-us/library/cc230324.aspx FILETIME
5800 $prefix_timestamp = $blob_data[$prefix_timestamp_start..$prefix_timestamp_end] #dword lowDateTime #dword highdatetime
5801 $state_timestamp_start = $prefix_timestamp_end + 1
5802 $state_timestamp_end = $state_timestamp_start + 7
5803 $state_timestamp = $blob_data[$state_timestamp_start..$state_timestamp_end]
5804 $comment_timestamp_start = $state_timestamp_end + 1
5805 $comment_timestamp_end = $comment_timestamp_start + 7
5806 $comment_timestamp = $blob_data[$comment_timestamp_start..$comment_timestamp_end]
5807 $version_start = $comment_timestamp_end + 1
5808 $version_end = $version_start + 3
5809 $version = [bitconverter]::ToUInt32($blob_data[$version_start..$version_end],0)
5810
5811 # Parse rest of DFSNamespaceRootOrLinkBlob here
5812 $dfs_targetlist_blob_size_start = $version_end + 1
5813 $dfs_targetlist_blob_size_end = $dfs_targetlist_blob_size_start + 3
5814 $dfs_targetlist_blob_size = [bitconverter]::ToUInt32($blob_data[$dfs_targetlist_blob_size_start..$dfs_targetlist_blob_size_end],0)
5815
5816 $dfs_targetlist_blob_start = $dfs_targetlist_blob_size_end + 1
5817 $dfs_targetlist_blob_end = $dfs_targetlist_blob_start + $dfs_targetlist_blob_size - 1
5818 $dfs_targetlist_blob = $blob_data[$dfs_targetlist_blob_start..$dfs_targetlist_blob_end]
5819 $reserved_blob_size_start = $dfs_targetlist_blob_end + 1
5820 $reserved_blob_size_end = $reserved_blob_size_start + 3
5821 $reserved_blob_size = [bitconverter]::ToUInt32($blob_data[$reserved_blob_size_start..$reserved_blob_size_end],0)
5822
5823 $reserved_blob_start = $reserved_blob_size_end + 1
5824 $reserved_blob_end = $reserved_blob_start + $reserved_blob_size - 1
5825 $reserved_blob = $blob_data[$reserved_blob_start..$reserved_blob_end]
5826 $referral_ttl_start = $reserved_blob_end + 1
5827 $referral_ttl_end = $referral_ttl_start + 3
5828 $referral_ttl = [bitconverter]::ToUInt32($blob_data[$referral_ttl_start..$referral_ttl_end],0)
5829
5830 #Parse DFSTargetListBlob
5831 $target_count_start = 0
5832 $target_count_end = $target_count_start + 3
5833 $target_count = [bitconverter]::ToUInt32($dfs_targetlist_blob[$target_count_start..$target_count_end],0)
5834 $t_offset = $target_count_end + 1
5835
5836 for($j=1; $j -le $target_count; $j++){
5837 $target_entry_size_start = $t_offset
5838 $target_entry_size_end = $target_entry_size_start + 3
5839 $target_entry_size = [bitconverter]::ToUInt32($dfs_targetlist_blob[$target_entry_size_start..$target_entry_size_end],0)
5840 $target_time_stamp_start = $target_entry_size_end + 1
5841 $target_time_stamp_end = $target_time_stamp_start + 7
5842 # FILETIME again or special if priority rank and priority class 0
5843 $target_time_stamp = $dfs_targetlist_blob[$target_time_stamp_start..$target_time_stamp_end]
5844 $target_state_start = $target_time_stamp_end + 1
5845 $target_state_end = $target_state_start + 3
5846 $target_state = [bitconverter]::ToUInt32($dfs_targetlist_blob[$target_state_start..$target_state_end],0)
5847
5848 $target_type_start = $target_state_end + 1
5849 $target_type_end = $target_type_start + 3
5850 $target_type = [bitconverter]::ToUInt32($dfs_targetlist_blob[$target_type_start..$target_type_end],0)
5851
5852 $server_name_size_start = $target_type_end + 1
5853 $server_name_size_end = $server_name_size_start + 1
5854 $server_name_size = [bitconverter]::ToUInt16($dfs_targetlist_blob[$server_name_size_start..$server_name_size_end],0)
5855
5856 $server_name_start = $server_name_size_end + 1
5857 $server_name_end = $server_name_start + $server_name_size - 1
5858 $server_name = [System.Text.Encoding]::Unicode.GetString($dfs_targetlist_blob[$server_name_start..$server_name_end])
5859
5860 $share_name_size_start = $server_name_end + 1
5861 $share_name_size_end = $share_name_size_start + 1
5862 $share_name_size = [bitconverter]::ToUInt16($dfs_targetlist_blob[$share_name_size_start..$share_name_size_end],0)
5863 $share_name_start = $share_name_size_end + 1
5864 $share_name_end = $share_name_start + $share_name_size - 1
5865 $share_name = [System.Text.Encoding]::Unicode.GetString($dfs_targetlist_blob[$share_name_start..$share_name_end])
5866
5867 $target_list += "\\$server_name\$share_name"
5868 $t_offset = $share_name_end + 1
5869 }
5870 }
5871 }
5872 $offset = $blob_data_end + 1
5873 $dfs_pkt_properties = @{
5874 'Name' = $blob_name
5875 'Prefix' = $prefix
5876 'TargetList' = $target_list
5877 }
5878 $object_list += New-Object -TypeName PSObject -Property $dfs_pkt_properties
5879 $prefix = $null
5880 $blob_name = $null
5881 $target_list = $null
5882 }
5883
5884 $servers = @()
5885 $object_list | ForEach-Object {
5886 if ($_.TargetList) {
5887 $_.TargetList | ForEach-Object {
5888 $servers += $_.split("\")[2]
5889 }
5890 }
5891 }
5892
5893 $servers
5894 }
5895
5896 function Get-DFSshareV1 {
5897 [CmdletBinding()]
5898 param(
5899 [String]
5900 $Domain,
5901
5902 [String]
5903 $DomainController,
5904
5905 [String]
5906 $ADSpath,
5907
5908 [ValidateRange(1,10000)]
5909 [Int]
5910 $PageSize = 200,
5911
5912 [Management.Automation.PSCredential]
5913 $Credential
5914 )
5915
5916 $DFSsearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $ADSpath -PageSize $PageSize
5917
5918 if($DFSsearcher) {
5919 $DFSshares = @()
5920 $DFSsearcher.filter = "(&(objectClass=fTDfs))"
5921
5922 try {
5923 $Results = $DFSSearcher.FindAll()
5924 $Results | Where-Object {$_} | ForEach-Object {
5925 $Properties = $_.Properties
5926 $RemoteNames = $Properties.remoteservername
5927 $Pkt = $Properties.pkt
5928
5929 $DFSshares += $RemoteNames | ForEach-Object {
5930 try {
5931 if ( $_.Contains('\') ) {
5932 New-Object -TypeName PSObject -Property @{'Name'=$Properties.name[0];'RemoteServerName'=$_.split("\")[2]}
5933 }
5934 }
5935 catch {
5936 Write-Verbose "Error in parsing DFS share : $_"
5937 }
5938 }
5939 }
5940 $Results.dispose()
5941 $DFSSearcher.dispose()
5942
5943 if($pkt -and $pkt[0]) {
5944 Parse-Pkt $pkt[0] | ForEach-Object {
5945 # If a folder doesn't have a redirection it will
5946 # have a target like
5947 # \\null\TestNameSpace\folder\.DFSFolderLink so we
5948 # do actually want to match on "null" rather than
5949 # $null
5950 if ($_ -ne "null") {
5951 New-Object -TypeName PSObject -Property @{'Name'=$Properties.name[0];'RemoteServerName'=$_}
5952 }
5953 }
5954 }
5955 }
5956 catch {
5957 Write-Warning "Get-DFSshareV1 error : $_"
5958 }
5959 $DFSshares | Sort-Object -Property "RemoteServerName"
5960 }
5961 }
5962
5963 function Get-DFSshareV2 {
5964 [CmdletBinding()]
5965 param(
5966 [String]
5967 $Domain,
5968
5969 [String]
5970 $DomainController,
5971
5972 [String]
5973 $ADSpath,
5974
5975 [ValidateRange(1,10000)]
5976 [Int]
5977 $PageSize = 200,
5978
5979 [Management.Automation.PSCredential]
5980 $Credential
5981 )
5982
5983 $DFSsearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $ADSpath -PageSize $PageSize
5984
5985 if($DFSsearcher) {
5986 $DFSshares = @()
5987 $DFSsearcher.filter = "(&(objectClass=msDFS-Linkv2))"
5988 $DFSSearcher.PropertiesToLoad.AddRange(('msdfs-linkpathv2','msDFS-TargetListv2'))
5989
5990 try {
5991 $Results = $DFSSearcher.FindAll()
5992 $Results | Where-Object {$_} | ForEach-Object {
5993 $Properties = $_.Properties
5994 $target_list = $Properties.'msdfs-targetlistv2'[0]
5995 $xml = [xml][System.Text.Encoding]::Unicode.GetString($target_list[2..($target_list.Length-1)])
5996 $DFSshares += $xml.targets.ChildNodes | ForEach-Object {
5997 try {
5998 $Target = $_.InnerText
5999 if ( $Target.Contains('\') ) {
6000 $DFSroot = $Target.split("\")[3]
6001 $ShareName = $Properties.'msdfs-linkpathv2'[0]
6002 New-Object -TypeName PSObject -Property @{'Name'="$DFSroot$ShareName";'RemoteServerName'=$Target.split("\")[2]}
6003 }
6004 }
6005 catch {
6006 Write-Verbose "Error in parsing target : $_"
6007 }
6008 }
6009 }
6010 $Results.dispose()
6011 $DFSSearcher.dispose()
6012 }
6013 catch {
6014 Write-Warning "Get-DFSshareV2 error : $_"
6015 }
6016 $DFSshares | Sort-Object -Unique -Property "RemoteServerName"
6017 }
6018 }
6019
6020 $DFSshares = @()
6021
6022 if ( ($Version -eq "all") -or ($Version.endsWith("1")) ) {
6023 $DFSshares += Get-DFSshareV1 -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $ADSpath -PageSize $PageSize
6024 }
6025 if ( ($Version -eq "all") -or ($Version.endsWith("2")) ) {
6026 $DFSshares += Get-DFSshareV2 -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $ADSpath -PageSize $PageSize
6027 }
6028
6029 $DFSshares | Sort-Object -Property ("RemoteServerName","Name") -Unique
6030}
6031
6032
6033########################################################
6034#
6035# GPO related functions.
6036#
6037########################################################
6038
6039
6040filter Get-GptTmpl {
6041<#
6042 .SYNOPSIS
6043
6044 Helper to parse a GptTmpl.inf policy file path into a custom object.
6045
6046 .PARAMETER GptTmplPath
6047
6048 The GptTmpl.inf file path name to parse.
6049
6050 .PARAMETER UsePSDrive
6051
6052 Switch. Mount the target GptTmpl folder path as a temporary PSDrive.
6053
6054 .EXAMPLE
6055
6056 PS C:\> Get-GptTmpl -GptTmplPath "\\dev.testlab.local\sysvol\dev.testlab.local\Policies\{31B2F340-016D-11D2-945F-00C04FB984F9}\MACHINE\Microsoft\Windows NT\SecEdit\GptTmpl.inf"
6057
6058 Parse the default domain policy .inf for dev.testlab.local
6059#>
6060
6061 [CmdletBinding()]
6062 Param (
6063 [Parameter(Mandatory=$True, ValueFromPipeline=$True)]
6064 [String]
6065 $GptTmplPath,
6066
6067 [Switch]
6068 $UsePSDrive
6069 )
6070
6071 if($UsePSDrive) {
6072 # if we're PSDrives, create a temporary mount point
6073 $Parts = $GptTmplPath.split('\')
6074 $FolderPath = $Parts[0..($Parts.length-2)] -join '\'
6075 $FilePath = $Parts[-1]
6076 $RandDrive = ("abcdefghijklmnopqrstuvwxyz".ToCharArray() | Get-Random -Count 7) -join ''
6077
6078 Write-Verbose "Mounting path $GptTmplPath using a temp PSDrive at $RandDrive"
6079
6080 try {
6081 $Null = New-PSDrive -Name $RandDrive -PSProvider FileSystem -Root $FolderPath -ErrorAction Stop
6082 }
6083 catch {
6084 Write-Verbose "Error mounting path $GptTmplPath : $_"
6085 return $Null
6086 }
6087
6088 # so we can cd/dir the new drive
6089 $TargetGptTmplPath = $RandDrive + ":\" + $FilePath
6090 }
6091 else {
6092 $TargetGptTmplPath = $GptTmplPath
6093 }
6094
6095 Write-Verbose "GptTmplPath: $GptTmplPath"
6096
6097 try {
6098 Write-Verbose "Parsing $TargetGptTmplPath"
6099 $TargetGptTmplPath | Get-IniContent -ErrorAction SilentlyContinue
6100 }
6101 catch {
6102 Write-Verbose "Error parsing $TargetGptTmplPath : $_"
6103 }
6104
6105 if($UsePSDrive -and $RandDrive) {
6106 Write-Verbose "Removing temp PSDrive $RandDrive"
6107 Get-PSDrive -Name $RandDrive -ErrorAction SilentlyContinue | Remove-PSDrive -Force
6108 }
6109}
6110
6111
6112filter Get-GroupsXML {
6113<#
6114 .SYNOPSIS
6115
6116 Helper to parse a groups.xml file path into a custom object.
6117
6118 .PARAMETER GroupsXMLpath
6119
6120 The groups.xml file path name to parse.
6121
6122 .PARAMETER UsePSDrive
6123
6124 Switch. Mount the target groups.xml folder path as a temporary PSDrive.
6125#>
6126
6127 [CmdletBinding()]
6128 Param (
6129 [Parameter(Mandatory=$True, ValueFromPipeline=$True)]
6130 [String]
6131 $GroupsXMLPath,
6132
6133 [Switch]
6134 $UsePSDrive
6135 )
6136
6137 if($UsePSDrive) {
6138 # if we're PSDrives, create a temporary mount point
6139 $Parts = $GroupsXMLPath.split('\')
6140 $FolderPath = $Parts[0..($Parts.length-2)] -join '\'
6141 $FilePath = $Parts[-1]
6142 $RandDrive = ("abcdefghijklmnopqrstuvwxyz".ToCharArray() | Get-Random -Count 7) -join ''
6143
6144 Write-Verbose "Mounting path $GroupsXMLPath using a temp PSDrive at $RandDrive"
6145
6146 try {
6147 $Null = New-PSDrive -Name $RandDrive -PSProvider FileSystem -Root $FolderPath -ErrorAction Stop
6148 }
6149 catch {
6150 Write-Verbose "Error mounting path $GroupsXMLPath : $_"
6151 return $Null
6152 }
6153
6154 # so we can cd/dir the new drive
6155 $TargetGroupsXMLPath = $RandDrive + ":\" + $FilePath
6156 }
6157 else {
6158 $TargetGroupsXMLPath = $GroupsXMLPath
6159 }
6160
6161 try {
6162 [XML]$GroupsXMLcontent = Get-Content $TargetGroupsXMLPath -ErrorAction Stop
6163
6164 # process all group properties in the XML
6165 $GroupsXMLcontent | Select-Xml "/Groups/Group" | Select-Object -ExpandProperty node | ForEach-Object {
6166
6167 $Groupname = $_.Properties.groupName
6168
6169 # extract the localgroup sid for memberof
6170 $GroupSID = $_.Properties.groupSid
6171 if(-not $GroupSID) {
6172 if($Groupname -match 'Administrators') {
6173 $GroupSID = 'S-1-5-32-544'
6174 }
6175 elseif($Groupname -match 'Remote Desktop') {
6176 $GroupSID = 'S-1-5-32-555'
6177 }
6178 elseif($Groupname -match 'Guests') {
6179 $GroupSID = 'S-1-5-32-546'
6180 }
6181 else {
6182 $GroupSID = Convert-NameToSid -ObjectName $Groupname | Select-Object -ExpandProperty SID
6183 }
6184 }
6185
6186 # extract out members added to this group
6187 $Members = $_.Properties.members | Select-Object -ExpandProperty Member | Where-Object { $_.action -match 'ADD' } | ForEach-Object {
6188 if($_.sid) { $_.sid }
6189 else { $_.name }
6190 }
6191
6192 if ($Members) {
6193
6194 # extract out any/all filters...I hate you GPP
6195 if($_.filters) {
6196 $Filters = $_.filters.GetEnumerator() | ForEach-Object {
6197 New-Object -TypeName PSObject -Property @{'Type' = $_.LocalName;'Value' = $_.name}
6198 }
6199 }
6200 else {
6201 $Filters = $Null
6202 }
6203
6204 if($Members -isnot [System.Array]) { $Members = @($Members) }
6205
6206 $GPOGroup = New-Object PSObject
6207 $GPOGroup | Add-Member Noteproperty 'GPOPath' $TargetGroupsXMLPath
6208 $GPOGroup | Add-Member Noteproperty 'Filters' $Filters
6209 $GPOGroup | Add-Member Noteproperty 'GroupName' $GroupName
6210 $GPOGroup | Add-Member Noteproperty 'GroupSID' $GroupSID
6211 $GPOGroup | Add-Member Noteproperty 'GroupMemberOf' $Null
6212 $GPOGroup | Add-Member Noteproperty 'GroupMembers' $Members
6213 $GPOGroup
6214 }
6215 }
6216 }
6217 catch {
6218 Write-Verbose "Error parsing $TargetGroupsXMLPath : $_"
6219 }
6220
6221 if($UsePSDrive -and $RandDrive) {
6222 Write-Verbose "Removing temp PSDrive $RandDrive"
6223 Get-PSDrive -Name $RandDrive -ErrorAction SilentlyContinue | Remove-PSDrive -Force
6224 }
6225}
6226
6227
6228function Get-NetGPO {
6229<#
6230 .SYNOPSIS
6231
6232 Gets a list of all current GPOs in a domain.
6233
6234 .PARAMETER GPOname
6235
6236 The GPO name to query for, wildcards accepted.
6237
6238 .PARAMETER DisplayName
6239
6240 The GPO display name to query for, wildcards accepted.
6241
6242 .PARAMETER ComputerName
6243
6244 Return all GPO objects applied to a given computer (FQDN).
6245
6246 .PARAMETER Domain
6247
6248 The domain to query for GPOs, defaults to the current domain.
6249
6250 .PARAMETER DomainController
6251
6252 Domain controller to reflect LDAP queries through.
6253
6254 .PARAMETER ADSpath
6255
6256 The LDAP source to search through
6257 e.g. "LDAP://cn={8FF59D28-15D7-422A-BCB7-2AE45724125A},cn=policies,cn=system,DC=dev,DC=testlab,DC=local"
6258
6259 .PARAMETER PageSize
6260
6261 The PageSize to set for the LDAP searcher object.
6262
6263 .PARAMETER Credential
6264
6265 A [Management.Automation.PSCredential] object of alternate credentials
6266 for connection to the target domain.
6267
6268 .EXAMPLE
6269
6270 PS C:\> Get-NetGPO -Domain testlab.local
6271
6272 Returns the GPOs in the 'testlab.local' domain.
6273#>
6274 [CmdletBinding()]
6275 Param (
6276 [Parameter(ValueFromPipeline=$True)]
6277 [String]
6278 $GPOname = '*',
6279
6280 [String]
6281 $DisplayName,
6282
6283 [String]
6284 $ComputerName,
6285
6286 [String]
6287 $Domain,
6288
6289 [String]
6290 $DomainController,
6291
6292 [String]
6293 $ADSpath,
6294
6295 [ValidateRange(1,10000)]
6296 [Int]
6297 $PageSize = 200,
6298
6299 [Management.Automation.PSCredential]
6300 $Credential
6301 )
6302
6303 begin {
6304 $GPOSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $ADSpath -PageSize $PageSize
6305 }
6306
6307 process {
6308 if ($GPOSearcher) {
6309
6310 if($ComputerName) {
6311 $GPONames = @()
6312 $Computers = Get-NetComputer -ComputerName $ComputerName -Domain $Domain -DomainController $DomainController -FullData -PageSize $PageSize
6313
6314 if(!$Computers) {
6315 throw "Computer $ComputerName in domain '$Domain' not found! Try a fully qualified host name"
6316 }
6317
6318 # get the given computer's OU
6319 $ComputerOUs = @()
6320 ForEach($Computer in $Computers) {
6321 # extract all OUs a computer is a part of
6322 $DN = $Computer.distinguishedname
6323
6324 $ComputerOUs += $DN.split(",") | ForEach-Object {
6325 if($_.startswith("OU=")) {
6326 $DN.substring($DN.indexof($_))
6327 }
6328 }
6329 }
6330
6331 Write-Verbose "ComputerOUs: $ComputerOUs"
6332
6333 # find all the GPOs linked to the computer's OU
6334 ForEach($ComputerOU in $ComputerOUs) {
6335 $GPONames += Get-NetOU -Domain $Domain -DomainController $DomainController -ADSpath $ComputerOU -FullData -PageSize $PageSize | ForEach-Object {
6336 # get any GPO links
6337 write-verbose "blah: $($_.name)"
6338 $_.gplink.split("][") | ForEach-Object {
6339 if ($_.startswith("LDAP")) {
6340 $_.split(";")[0]
6341 }
6342 }
6343 }
6344 }
6345
6346 Write-Verbose "GPONames: $GPONames"
6347
6348 # find any GPOs linked to the site for the given computer
6349 $ComputerSite = (Get-SiteName -ComputerName $ComputerName).SiteName
6350 if($ComputerSite -and ($ComputerSite -notlike 'Error*')) {
6351 $GPONames += Get-NetSite -SiteName $ComputerSite -FullData | ForEach-Object {
6352 if($_.gplink) {
6353 $_.gplink.split("][") | ForEach-Object {
6354 if ($_.startswith("LDAP")) {
6355 $_.split(";")[0]
6356 }
6357 }
6358 }
6359 }
6360 }
6361
6362 $GPONames | Where-Object{$_ -and ($_ -ne '')} | ForEach-Object {
6363
6364 # use the gplink as an ADS path to enumerate all GPOs for the computer
6365 $GPOSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $_ -PageSize $PageSize
6366 $GPOSearcher.filter="(&(objectCategory=groupPolicyContainer)(name=$GPOname))"
6367
6368 try {
6369 $Results = $GPOSearcher.FindAll()
6370 $Results | Where-Object {$_} | ForEach-Object {
6371 $Out = Convert-LDAPProperty -Properties $_.Properties
6372 $Out | Add-Member Noteproperty 'ComputerName' $ComputerName
6373 $Out
6374 }
6375 $Results.dispose()
6376 $GPOSearcher.dispose()
6377 }
6378 catch {
6379 Write-Warning $_
6380 }
6381 }
6382 }
6383
6384 else {
6385 if($DisplayName) {
6386 $GPOSearcher.filter="(&(objectCategory=groupPolicyContainer)(displayname=$DisplayName))"
6387 }
6388 else {
6389 $GPOSearcher.filter="(&(objectCategory=groupPolicyContainer)(name=$GPOname))"
6390 }
6391
6392 try {
6393 $Results = $GPOSearcher.FindAll()
6394 $Results | Where-Object {$_} | ForEach-Object {
6395 if($ADSPath -and ($ADSpath -Match '^GC://')) {
6396 $Properties = Convert-LDAPProperty -Properties $_.Properties
6397 try {
6398 $GPODN = $Properties.distinguishedname
6399 $GPODomain = $GPODN.subString($GPODN.IndexOf("DC=")) -replace 'DC=','' -replace ',','.'
6400 $gpcfilesyspath = "\\$GPODomain\SysVol\$GPODomain\Policies\$($Properties.cn)"
6401 $Properties | Add-Member Noteproperty 'gpcfilesyspath' $gpcfilesyspath
6402 $Properties
6403 }
6404 catch {
6405 $Properties
6406 }
6407 }
6408 else {
6409 # convert/process the LDAP fields for each result
6410 Convert-LDAPProperty -Properties $_.Properties
6411 }
6412 }
6413 $Results.dispose()
6414 $GPOSearcher.dispose()
6415 }
6416 catch {
6417 Write-Warning $_
6418 }
6419 }
6420 }
6421 }
6422}
6423
6424
6425function New-GPOImmediateTask {
6426<#
6427 .SYNOPSIS
6428
6429 Builds an 'Immediate' schtask to push out through a specified GPO.
6430
6431 .PARAMETER TaskName
6432
6433 Name for the schtask to recreate. Required.
6434
6435 .PARAMETER Command
6436
6437 The command to execute with the task, defaults to 'powershell'
6438
6439 .PARAMETER CommandArguments
6440
6441 The arguments to supply to the -Command being launched.
6442
6443 .PARAMETER TaskDescription
6444
6445 An optional description for the task.
6446
6447 .PARAMETER TaskAuthor
6448
6449 The displayed author of the task, defaults to ''NT AUTHORITY\System'
6450
6451 .PARAMETER TaskModifiedDate
6452
6453 The displayed modified date for the task, defaults to 30 days ago.
6454
6455 .PARAMETER GPOname
6456
6457 The GPO name to build the task for.
6458
6459 .PARAMETER GPODisplayName
6460
6461 The GPO display name to build the task for.
6462
6463 .PARAMETER Domain
6464
6465 The domain to query for the GPOs, defaults to the current domain.
6466
6467 .PARAMETER DomainController
6468
6469 Domain controller to reflect LDAP queries through.
6470
6471 .PARAMETER ADSpath
6472
6473 The LDAP source to search through
6474 e.g. "LDAP://cn={8FF59D28-15D7-422A-BCB7-2AE45724125A},cn=policies,cn=system,DC=dev,DC=testlab,DC=local"
6475
6476 .PARAMETER Credential
6477
6478 A [Management.Automation.PSCredential] object of alternate credentials
6479 for connection to the target.
6480
6481 .EXAMPLE
6482
6483 PS> New-GPOImmediateTask -TaskName Debugging -GPODisplayName SecurePolicy -CommandArguments '-c "123 | Out-File C:\Temp\debug.txt"' -Force
6484
6485 Create an immediate schtask that executes the specified PowerShell arguments and
6486 push it out to the 'SecurePolicy' GPO, skipping the confirmation prompt.
6487
6488 .EXAMPLE
6489
6490 PS> New-GPOImmediateTask -GPODisplayName SecurePolicy -Remove -Force
6491
6492 Remove all schtasks from the 'SecurePolicy' GPO, skipping the confirmation prompt.
6493#>
6494 [CmdletBinding(DefaultParameterSetName = 'Create')]
6495 Param (
6496 [Parameter(ParameterSetName = 'Create', Mandatory = $True)]
6497 [String]
6498 [ValidateNotNullOrEmpty()]
6499 $TaskName,
6500
6501 [Parameter(ParameterSetName = 'Create')]
6502 [String]
6503 [ValidateNotNullOrEmpty()]
6504 $Command = 'powershell',
6505
6506 [Parameter(ParameterSetName = 'Create')]
6507 [String]
6508 [ValidateNotNullOrEmpty()]
6509 $CommandArguments,
6510
6511 [Parameter(ParameterSetName = 'Create')]
6512 [String]
6513 [ValidateNotNullOrEmpty()]
6514 $TaskDescription = '',
6515
6516 [Parameter(ParameterSetName = 'Create')]
6517 [String]
6518 [ValidateNotNullOrEmpty()]
6519 $TaskAuthor = 'NT AUTHORITY\System',
6520
6521 [Parameter(ParameterSetName = 'Create')]
6522 [String]
6523 [ValidateNotNullOrEmpty()]
6524 $TaskModifiedDate = (Get-Date (Get-Date).AddDays(-30) -Format u).trim("Z"),
6525
6526 [Parameter(ParameterSetName = 'Create')]
6527 [Parameter(ParameterSetName = 'Remove')]
6528 [String]
6529 $GPOname,
6530
6531 [Parameter(ParameterSetName = 'Create')]
6532 [Parameter(ParameterSetName = 'Remove')]
6533 [String]
6534 $GPODisplayName,
6535
6536 [Parameter(ParameterSetName = 'Create')]
6537 [Parameter(ParameterSetName = 'Remove')]
6538 [String]
6539 $Domain,
6540
6541 [Parameter(ParameterSetName = 'Create')]
6542 [Parameter(ParameterSetName = 'Remove')]
6543 [String]
6544 $DomainController,
6545
6546 [Parameter(ParameterSetName = 'Create')]
6547 [Parameter(ParameterSetName = 'Remove')]
6548 [String]
6549 $ADSpath,
6550
6551 [Parameter(ParameterSetName = 'Create')]
6552 [Parameter(ParameterSetName = 'Remove')]
6553 [Switch]
6554 $Force,
6555
6556 [Parameter(ParameterSetName = 'Remove')]
6557 [Switch]
6558 $Remove,
6559
6560 [Parameter(ParameterSetName = 'Create')]
6561 [Parameter(ParameterSetName = 'Remove')]
6562 [Management.Automation.PSCredential]
6563 $Credential
6564 )
6565
6566 # build the XML spec for our 'immediate' scheduled task
6567 $TaskXML = '<?xml version="1.0" encoding="utf-8"?><ScheduledTasks clsid="{CC63F200-7309-4ba0-B154-A71CD118DBCC}"><ImmediateTaskV2 clsid="{9756B581-76EC-4169-9AFC-0CA8D43ADB5F}" name="'+$TaskName+'" image="0" changed="'+$TaskModifiedDate+'" uid="{'+$([guid]::NewGuid())+'}" userContext="0" removePolicy="0"><Properties action="C" name="'+$TaskName+'" runAs="NT AUTHORITY\System" logonType="S4U"><Task version="1.3"><RegistrationInfo><Author>'+$TaskAuthor+'</Author><Description>'+$TaskDescription+'</Description></RegistrationInfo><Principals><Principal id="Author"><UserId>NT AUTHORITY\System</UserId><RunLevel>HighestAvailable</RunLevel><LogonType>S4U</LogonType></Principal></Principals><Settings><IdleSettings><Duration>PT10M</Duration><WaitTimeout>PT1H</WaitTimeout><StopOnIdleEnd>true</StopOnIdleEnd><RestartOnIdle>false</RestartOnIdle></IdleSettings><MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy><DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries><StopIfGoingOnBatteries>true</StopIfGoingOnBatteries><AllowHardTerminate>false</AllowHardTerminate><StartWhenAvailable>true</StartWhenAvailable><AllowStartOnDemand>false</AllowStartOnDemand><Enabled>true</Enabled><Hidden>true</Hidden><ExecutionTimeLimit>PT0S</ExecutionTimeLimit><Priority>7</Priority><DeleteExpiredTaskAfter>PT0S</DeleteExpiredTaskAfter><RestartOnFailure><Interval>PT15M</Interval><Count>3</Count></RestartOnFailure></Settings><Actions Context="Author"><Exec><Command>'+$Command+'</Command><Arguments>'+$CommandArguments+'</Arguments></Exec></Actions><Triggers><TimeTrigger><StartBoundary>%LocalTimeXmlEx%</StartBoundary><EndBoundary>%LocalTimeXmlEx%</EndBoundary><Enabled>true</Enabled></TimeTrigger></Triggers></Task></Properties></ImmediateTaskV2></ScheduledTasks>'
6568
6569 if (!$PSBoundParameters['GPOname'] -and !$PSBoundParameters['GPODisplayName']) {
6570 Write-Warning 'Either -GPOName or -GPODisplayName must be specified'
6571 return
6572 }
6573
6574 # eunmerate the specified GPO(s)
6575 $GPOs = Get-NetGPO -GPOname $GPOname -DisplayName $GPODisplayName -Domain $Domain -DomainController $DomainController -ADSpath $ADSpath -Credential $Credential
6576
6577 if(!$GPOs) {
6578 Write-Warning 'No GPO found.'
6579 return
6580 }
6581
6582 $GPOs | ForEach-Object {
6583 $ProcessedGPOName = $_.Name
6584 try {
6585 Write-Verbose "Trying to weaponize GPO: $ProcessedGPOName"
6586
6587 # map a network drive as New-PSDrive/New-Item/etc. don't accept -Credential properly :(
6588 if($Credential) {
6589 Write-Verbose "Mapping '$($_.gpcfilesyspath)' to network drive N:\"
6590 $Path = $_.gpcfilesyspath.TrimEnd('\')
6591 $Net = New-Object -ComObject WScript.Network
6592 $Net.MapNetworkDrive("N:", $Path, $False, $Credential.UserName, $Credential.GetNetworkCredential().Password)
6593 $TaskPath = "N:\Machine\Preferences\ScheduledTasks\"
6594 }
6595 else {
6596 $TaskPath = $_.gpcfilesyspath + "\Machine\Preferences\ScheduledTasks\"
6597 }
6598
6599 if($Remove) {
6600 if(!(Test-Path "$TaskPath\ScheduledTasks.xml")) {
6601 Throw "Scheduled task doesn't exist at $TaskPath\ScheduledTasks.xml"
6602 }
6603
6604 if (!$Force -and !$psCmdlet.ShouldContinue('Do you want to continue?',"Removing schtask at $TaskPath\ScheduledTasks.xml")) {
6605 return
6606 }
6607
6608 Remove-Item -Path "$TaskPath\ScheduledTasks.xml" -Force
6609 }
6610 else {
6611 if (!$Force -and !$psCmdlet.ShouldContinue('Do you want to continue?',"Creating schtask at $TaskPath\ScheduledTasks.xml")) {
6612 return
6613 }
6614
6615 # create the folder if it doesn't exist
6616 $Null = New-Item -ItemType Directory -Force -Path $TaskPath
6617
6618 if(Test-Path "$TaskPath\ScheduledTasks.xml") {
6619 Throw "Scheduled task already exists at $TaskPath\ScheduledTasks.xml !"
6620 }
6621
6622 $TaskXML | Set-Content -Encoding ASCII -Path "$TaskPath\ScheduledTasks.xml"
6623 }
6624
6625 if($Credential) {
6626 Write-Verbose "Removing mounted drive at N:\"
6627 $Net = New-Object -ComObject WScript.Network
6628 $Net.RemoveNetworkDrive("N:")
6629 }
6630 }
6631 catch {
6632 Write-Warning "Error for GPO $ProcessedGPOName : $_"
6633 if($Credential) {
6634 Write-Verbose "Removing mounted drive at N:\"
6635 $Net = New-Object -ComObject WScript.Network
6636 $Net.RemoveNetworkDrive("N:")
6637 }
6638 }
6639 }
6640}
6641
6642
6643function Get-NetGPOGroup {
6644<#
6645 .SYNOPSIS
6646
6647 Returns all GPOs in a domain that set "Restricted Groups" or use groups.xml on on target machines.
6648
6649 Author: @harmj0y
6650 License: BSD 3-Clause
6651 Required Dependencies: Get-NetGPO, Get-GptTmpl, Get-GroupsXML, Convert-NameToSid, Convert-SidToName
6652 Optional Dependencies: None
6653
6654 .DESCRIPTION
6655
6656 First enumerates all GPOs in the current/target domain using Get-NetGPO with passed
6657 arguments, and for each GPO checks if 'Restricted Groups' are set with GptTmpl.inf or
6658 group membership is set through Group Policy Preferences groups.xml files. For any
6659 GptTmpl.inf files found, the file is parsed with Get-GptTmpl and any 'Group Membership'
6660 section data is processed if present. Any found Groups.xml files are parsed with
6661 Get-GroupsXML and those memberships are returned as well.
6662
6663 .PARAMETER GPOname
6664
6665 The GPO name (GUID) to query for, wildcards accepted.
6666
6667 .PARAMETER DisplayName
6668
6669 The GPO display name to query for, wildcards accepted.
6670
6671 .PARAMETER Domain
6672
6673 The domain to query for GPOs, defaults to the current domain.
6674
6675 .PARAMETER DomainController
6676
6677 Domain controller to reflect LDAP queries through.
6678
6679 .PARAMETER ADSpath
6680
6681 The LDAP source to search through for GPOs.
6682 e.g. "LDAP://cn={8FF59D28-15D7-422A-BCB7-2AE45724125A},cn=policies,cn=system,DC=dev,DC=testlab,DC=local"
6683
6684 .PARAMETER ResolveMemberSIDs
6685
6686 Switch. Try to resolve the SIDs of all found group members.
6687
6688 .PARAMETER UsePSDrive
6689
6690 Switch. Mount any found policy files with temporary PSDrives.
6691
6692 .PARAMETER PageSize
6693
6694 The PageSize to set for the LDAP searcher object.
6695
6696 .EXAMPLE
6697
6698 PS C:\> Get-NetGPOGroup
6699
6700 Returns all local groups set by GPO along with their members and memberof.
6701
6702 .EXAMPLE
6703
6704 PS C:\> Get-NetGPOGroup -ResolveMemberSIDs
6705
6706 Returns all local groups set by GPO along with their members and memberof,
6707 and resolve any members to their domain SIDs.
6708
6709 .EXAMPLE
6710
6711 PS C:\> Get-NetGPOGroup -GPOName '{0847C615-6C4E-4D45-A064-6001040CC21C}'
6712
6713 Return any GPO-set groups for the GPO with the given name/GUID.
6714
6715 .EXAMPLE
6716
6717 PS C:\> Get-NetGPOGroup -DisplayName 'Desktops'
6718
6719 Return any GPO-set groups for the GPO with the given display name.
6720
6721 .LINK
6722
6723 https://morgansimonsenblog.azurewebsites.net/tag/groups/
6724#>
6725
6726 [CmdletBinding()]
6727 Param (
6728 [String]
6729 $GPOname = '*',
6730
6731 [String]
6732 $DisplayName,
6733
6734 [String]
6735 $Domain,
6736
6737 [String]
6738 $DomainController,
6739
6740 [String]
6741 $ADSpath,
6742
6743 [Switch]
6744 $ResolveMemberSIDs,
6745
6746 [Switch]
6747 $UsePSDrive,
6748
6749 [ValidateRange(1,10000)]
6750 [Int]
6751 $PageSize = 200
6752 )
6753
6754 $Option = [System.StringSplitOptions]::RemoveEmptyEntries
6755
6756 # get every GPO from the specified domain with restricted groups set
6757 Get-NetGPO -GPOName $GPOname -DisplayName $DisplayName -Domain $Domain -DomainController $DomainController -ADSpath $ADSpath -PageSize $PageSize | ForEach-Object {
6758
6759 $GPOdisplayName = $_.displayname
6760 $GPOname = $_.name
6761 $GPOPath = $_.gpcfilesyspath
6762
6763 $ParseArgs = @{
6764 'GptTmplPath' = "$GPOPath\MACHINE\Microsoft\Windows NT\SecEdit\GptTmpl.inf"
6765 'UsePSDrive' = $UsePSDrive
6766 }
6767
6768 # parse the GptTmpl.inf 'Restricted Groups' file if it exists
6769 $Inf = Get-GptTmpl @ParseArgs
6770
6771 if($Inf -and ($Inf.psbase.Keys -contains 'Group Membership')) {
6772
6773 $Memberships = @{}
6774
6775 # group the members/memberof fields for each entry
6776 ForEach ($Membership in $Inf.'Group Membership'.GetEnumerator()) {
6777 $Group, $Relation = $Membership.Key.Split('__', $Option) | ForEach-Object {$_.Trim()}
6778
6779 # extract out ALL members
6780 $MembershipValue = $Membership.Value | Where-Object {$_} | ForEach-Object { $_.Trim('*') } | Where-Object {$_}
6781
6782 if($ResolveMemberSIDs) {
6783 # if the resulting member is username and not a SID, attempt to resolve it
6784 $GroupMembers = @()
6785 ForEach($Member in $MembershipValue) {
6786 if($Member -and ($Member.Trim() -ne '')) {
6787 if($Member -notmatch '^S-1-.*') {
6788 $MemberSID = Convert-NameToSid -Domain $Domain -ObjectName $Member | Select-Object -ExpandProperty SID
6789 if($MemberSID) {
6790 $GroupMembers += $MemberSID
6791 }
6792 else {
6793 $GroupMembers += $Member
6794 }
6795 }
6796 else {
6797 $GroupMembers += $Member
6798 }
6799 }
6800 }
6801 $MembershipValue = $GroupMembers
6802 }
6803
6804 if(-not $Memberships[$Group]) {
6805 $Memberships[$Group] = @{}
6806 }
6807 if($MembershipValue -isnot [System.Array]) {$MembershipValue = @($MembershipValue)}
6808 $Memberships[$Group].Add($Relation, $MembershipValue)
6809 }
6810
6811 ForEach ($Membership in $Memberships.GetEnumerator()) {
6812 if($Membership -and $Membership.Key -and ($Membership.Key -match '^\*')) {
6813 # if the SID is already resolved (i.e. begins with *) try to resolve SID to a name
6814 $GroupSID = $Membership.Key.Trim('*')
6815 if($GroupSID -and ($GroupSID.Trim() -ne '')) {
6816 $GroupName = Convert-SidToName -SID $GroupSID
6817 }
6818 else {
6819 $GroupName = $False
6820 }
6821 }
6822 else {
6823 $GroupName = $Membership.Key
6824
6825 if($GroupName -and ($GroupName.Trim() -ne '')) {
6826 if($Groupname -match 'Administrators') {
6827 $GroupSID = 'S-1-5-32-544'
6828 }
6829 elseif($Groupname -match 'Remote Desktop') {
6830 $GroupSID = 'S-1-5-32-555'
6831 }
6832 elseif($Groupname -match 'Guests') {
6833 $GroupSID = 'S-1-5-32-546'
6834 }
6835 elseif($GroupName.Trim() -ne '') {
6836 $GroupSID = Convert-NameToSid -Domain $Domain -ObjectName $Groupname | Select-Object -ExpandProperty SID
6837 }
6838 else {
6839 $GroupSID = $Null
6840 }
6841 }
6842 }
6843
6844 $GPOGroup = New-Object PSObject
6845 $GPOGroup | Add-Member Noteproperty 'GPODisplayName' $GPODisplayName
6846 $GPOGroup | Add-Member Noteproperty 'GPOName' $GPOName
6847 $GPOGroup | Add-Member Noteproperty 'GPOPath' $GPOPath
6848 $GPOGroup | Add-Member Noteproperty 'GPOType' 'RestrictedGroups'
6849 $GPOGroup | Add-Member Noteproperty 'Filters' $Null
6850 $GPOGroup | Add-Member Noteproperty 'GroupName' $GroupName
6851 $GPOGroup | Add-Member Noteproperty 'GroupSID' $GroupSID
6852 $GPOGroup | Add-Member Noteproperty 'GroupMemberOf' $Membership.Value.Memberof
6853 $GPOGroup | Add-Member Noteproperty 'GroupMembers' $Membership.Value.Members
6854 $GPOGroup
6855 }
6856 }
6857
6858 $ParseArgs = @{
6859 'GroupsXMLpath' = "$GPOPath\MACHINE\Preferences\Groups\Groups.xml"
6860 'UsePSDrive' = $UsePSDrive
6861 }
6862
6863 Get-GroupsXML @ParseArgs | ForEach-Object {
6864 if($ResolveMemberSIDs) {
6865 $GroupMembers = @()
6866 ForEach($Member in $_.GroupMembers) {
6867 if($Member -and ($Member.Trim() -ne '')) {
6868 if($Member -notmatch '^S-1-.*') {
6869 # if the resulting member is username and not a SID, attempt to resolve it
6870 $MemberSID = Convert-NameToSid -Domain $Domain -ObjectName $Member | Select-Object -ExpandProperty SID
6871 if($MemberSID) {
6872 $GroupMembers += $MemberSID
6873 }
6874 else {
6875 $GroupMembers += $Member
6876 }
6877 }
6878 else {
6879 $GroupMembers += $Member
6880 }
6881 }
6882 }
6883 $_.GroupMembers = $GroupMembers
6884 }
6885
6886 $_ | Add-Member Noteproperty 'GPODisplayName' $GPODisplayName
6887 $_ | Add-Member Noteproperty 'GPOName' $GPOName
6888 $_ | Add-Member Noteproperty 'GPOType' 'GroupPolicyPreferences'
6889 $_
6890 }
6891 }
6892}
6893
6894
6895function Find-GPOLocation {
6896<#
6897 .SYNOPSIS
6898
6899 Enumerates the machines where a specific user/group is a member of a specific
6900 local group, all through GPO correlation.
6901
6902 Author: @harmj0y
6903 License: BSD 3-Clause
6904 Required Dependencies: Get-NetUser, Get-NetGroup, Get-NetGPOGroup, Get-NetOU, Get-NetComputer, Get-ADObject, Get-NetSite
6905 Optional Dependencies: None
6906
6907 .DESCRIPTION
6908
6909 Takes a user/group name and optional domain, and determines the computers in the domain
6910 the user/group has local admin (or RDP) rights to.
6911
6912 It does this by:
6913 1. resolving the user/group to its proper SID
6914 2. enumerating all groups the user/group is a current part of
6915 and extracting all target SIDs to build a target SID list
6916 3. pulling all GPOs that set 'Restricted Groups' or Groups.xml by calling
6917 Get-NetGPOGroup
6918 4. matching the target SID list to the queried GPO SID list
6919 to enumerate all GPO the user is effectively applied with
6920 5. enumerating all OUs and sites and applicable GPO GUIs are
6921 applied to through gplink enumerating
6922 6. querying for all computers under the given OUs or sites
6923
6924 If no user/group is specified, all user/group -> machine mappings discovered through
6925 GPO relationships are returned.
6926
6927 .PARAMETER UserName
6928
6929 A (single) user name name to query for access.
6930
6931 .PARAMETER GroupName
6932
6933 A (single) group name name to query for access.
6934
6935 .PARAMETER Domain
6936
6937 Optional domain the user exists in for querying, defaults to the current domain.
6938
6939 .PARAMETER DomainController
6940
6941 Domain controller to reflect LDAP queries through.
6942
6943 .PARAMETER LocalGroup
6944
6945 The local group to check access against.
6946 Can be "Administrators" (S-1-5-32-544), "RDP/Remote Desktop Users" (S-1-5-32-555),
6947 or a custom local SID. Defaults to local 'Administrators'.
6948
6949 .PARAMETER UsePSDrive
6950
6951 Switch. Mount any found policy files with temporary PSDrives.
6952
6953 .PARAMETER PageSize
6954
6955 The PageSize to set for the LDAP searcher object.
6956
6957 .EXAMPLE
6958
6959 PS C:\> Find-GPOLocation
6960
6961 Find all user/group -> machine relationships where the user/group is a member
6962 of the local administrators group on target machines.
6963
6964 .EXAMPLE
6965
6966 PS C:\> Find-GPOLocation -UserName dfm
6967
6968 Find all computers that dfm user has local administrator rights to in
6969 the current domain.
6970
6971 .EXAMPLE
6972
6973 PS C:\> Find-GPOLocation -UserName dfm -Domain dev.testlab.local
6974
6975 Find all computers that dfm user has local administrator rights to in
6976 the dev.testlab.local domain.
6977
6978 .EXAMPLE
6979
6980 PS C:\> Find-GPOLocation -UserName jason -LocalGroup RDP
6981
6982 Find all computers that jason has local RDP access rights to in the domain.
6983#>
6984
6985 [CmdletBinding()]
6986 Param (
6987 [String]
6988 $UserName,
6989
6990 [String]
6991 $GroupName,
6992
6993 [String]
6994 $Domain,
6995
6996 [String]
6997 $DomainController,
6998
6999 [String]
7000 $LocalGroup = 'Administrators',
7001
7002 [Switch]
7003 $UsePSDrive,
7004
7005 [ValidateRange(1,10000)]
7006 [Int]
7007 $PageSize = 200
7008 )
7009
7010 if($UserName) {
7011 # if a group name is specified, get that user object so we can extract the target SID
7012 $User = Get-NetUser -UserName $UserName -Domain $Domain -DomainController $DomainController -PageSize $PageSize | Select-Object -First 1
7013 $UserSid = $User.objectsid
7014
7015 if(-not $UserSid) {
7016 Throw "User '$UserName' not found!"
7017 }
7018
7019 $TargetSIDs = @($UserSid)
7020 $ObjectSamAccountName = $User.samaccountname
7021 $TargetObject = $UserSid
7022 }
7023 elseif($GroupName) {
7024 # if a group name is specified, get that group object so we can extract the target SID
7025 $Group = Get-NetGroup -GroupName $GroupName -Domain $Domain -DomainController $DomainController -FullData -PageSize $PageSize | Select-Object -First 1
7026 $GroupSid = $Group.objectsid
7027
7028 if(-not $GroupSid) {
7029 Throw "Group '$GroupName' not found!"
7030 }
7031
7032 $TargetSIDs = @($GroupSid)
7033 $ObjectSamAccountName = $Group.samaccountname
7034 $TargetObject = $GroupSid
7035 }
7036 else {
7037 $TargetSIDs = @('*')
7038 }
7039
7040 # figure out what the SID is of the target local group we're checking for membership in
7041 if($LocalGroup -like "*Admin*") {
7042 $TargetLocalSID = 'S-1-5-32-544'
7043 }
7044 elseif ( ($LocalGroup -like "*RDP*") -or ($LocalGroup -like "*Remote*") ) {
7045 $TargetLocalSID = 'S-1-5-32-555'
7046 }
7047 elseif ($LocalGroup -like "S-1-5-*") {
7048 $TargetLocalSID = $LocalGroup
7049 }
7050 else {
7051 throw "LocalGroup must be 'Administrators', 'RDP', or a 'S-1-5-X' SID format."
7052 }
7053
7054 # if we're not listing all relationships, use the tokenGroups approach from Get-NetGroup to
7055 # get all effective security SIDs this object is a part of
7056 if($TargetSIDs[0] -and ($TargetSIDs[0] -ne '*')) {
7057 $TargetSIDs += Get-NetGroup -Domain $Domain -DomainController $DomainController -PageSize $PageSize -UserName $ObjectSamAccountName -RawSids
7058 }
7059
7060 if(-not $TargetSIDs) {
7061 throw "No effective target SIDs!"
7062 }
7063
7064 Write-Verbose "TargetLocalSID: $TargetLocalSID"
7065 Write-Verbose "Effective target SIDs: $TargetSIDs"
7066
7067 $GPOGroupArgs = @{
7068 'Domain' = $Domain
7069 'DomainController' = $DomainController
7070 'UsePSDrive' = $UsePSDrive
7071 'ResolveMemberSIDs' = $True
7072 'PageSize' = $PageSize
7073 }
7074
7075 # enumerate all GPO group mappings for the target domain that involve our target SID set
7076 $GPOgroups = Get-NetGPOGroup @GPOGroupArgs | ForEach-Object {
7077
7078 $GPOgroup = $_
7079
7080 # if the locally set group is what we're looking for, check the GroupMembers ('members')
7081 # for our target SID
7082 if($GPOgroup.GroupSID -match $TargetLocalSID) {
7083 $GPOgroup.GroupMembers | Where-Object {$_} | ForEach-Object {
7084 if ( ($TargetSIDs[0] -eq '*') -or ($TargetSIDs -Contains $_) ) {
7085 $GPOgroup
7086 }
7087 }
7088 }
7089 # if the group is a 'memberof' the group we're looking for, check GroupSID against the targt SIDs
7090 if( ($GPOgroup.GroupMemberOf -contains $TargetLocalSID) ) {
7091 if( ($TargetSIDs[0] -eq '*') -or ($TargetSIDs -Contains $GPOgroup.GroupSID) ) {
7092 $GPOgroup
7093 }
7094 }
7095 } | Sort-Object -Property GPOName -Unique
7096
7097 $GPOgroups | ForEach-Object {
7098
7099 $GPOname = $_.GPODisplayName
7100 $GPOguid = $_.GPOName
7101 $GPOPath = $_.GPOPath
7102 $GPOType = $_.GPOType
7103 if($_.GroupMembers) {
7104 $GPOMembers = $_.GroupMembers
7105 }
7106 else {
7107 $GPOMembers = $_.GroupSID
7108 }
7109
7110 $Filters = $_.Filters
7111
7112 if(-not $TargetObject) {
7113 # if the * wildcard was used, set the ObjectDistName as the GPO member SID set
7114 # so all relationship mappings are output
7115 $TargetObjectSIDs = $GPOMembers
7116 }
7117 else {
7118 $TargetObjectSIDs = $TargetObject
7119 }
7120
7121 # find any OUs that have this GUID applied and then retrieve any computers from the OU
7122 Get-NetOU -Domain $Domain -DomainController $DomainController -GUID $GPOguid -FullData -PageSize $PageSize | ForEach-Object {
7123 if($Filters) {
7124 # filter for computer name/org unit if a filter is specified
7125 # TODO: handle other filters (i.e. OU filters?) again, I hate you GPP...
7126 $OUComputers = Get-NetComputer -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $_.ADSpath -FullData -PageSize $PageSize | Where-Object {
7127 $_.adspath -match ($Filters.Value)
7128 } | ForEach-Object { $_.dnshostname }
7129 }
7130 else {
7131 $OUComputers = Get-NetComputer -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $_.ADSpath -PageSize $PageSize
7132 }
7133
7134 if($OUComputers) {
7135 if($OUComputers -isnot [System.Array]) {$OUComputers = @($OUComputers)}
7136
7137 ForEach ($TargetSid in $TargetObjectSIDs) {
7138 $Object = Get-ADObject -SID $TargetSid -Domain $Domain -DomainController $DomainController -Credential $Credential -PageSize $PageSize
7139
7140 $IsGroup = @('268435456','268435457','536870912','536870913') -contains $Object.samaccounttype
7141
7142 $GPOLocation = New-Object PSObject
7143 $GPOLocation | Add-Member Noteproperty 'ObjectName' $Object.samaccountname
7144 $GPOLocation | Add-Member Noteproperty 'ObjectDN' $Object.distinguishedname
7145 $GPOLocation | Add-Member Noteproperty 'ObjectSID' $Object.objectsid
7146 $GPOLocation | Add-Member Noteproperty 'Domain' $Domain
7147 $GPOLocation | Add-Member Noteproperty 'IsGroup' $IsGroup
7148 $GPOLocation | Add-Member Noteproperty 'GPODisplayName' $GPOname
7149 $GPOLocation | Add-Member Noteproperty 'GPOGuid' $GPOGuid
7150 $GPOLocation | Add-Member Noteproperty 'GPOPath' $GPOPath
7151 $GPOLocation | Add-Member Noteproperty 'GPOType' $GPOType
7152 $GPOLocation | Add-Member Noteproperty 'ContainerName' $_.distinguishedname
7153 $GPOLocation | Add-Member Noteproperty 'ComputerName' $OUComputers
7154 $GPOLocation.PSObject.TypeNames.Add('PowerView.GPOLocalGroup')
7155 $GPOLocation
7156 }
7157 }
7158 }
7159
7160 # find any sites that have this GUID applied
7161 Get-NetSite -Domain $Domain -DomainController $DomainController -GUID $GPOguid -PageSize $PageSize -FullData | ForEach-Object {
7162
7163 ForEach ($TargetSid in $TargetObjectSIDs) {
7164 $Object = Get-ADObject -SID $TargetSid -Domain $Domain -DomainController $DomainController -Credential $Credential -PageSize $PageSize
7165
7166 $IsGroup = @('268435456','268435457','536870912','536870913') -contains $Object.samaccounttype
7167
7168 $AppliedSite = New-Object PSObject
7169 $AppliedSite | Add-Member Noteproperty 'ObjectName' $Object.samaccountname
7170 $AppliedSite | Add-Member Noteproperty 'ObjectDN' $Object.distinguishedname
7171 $AppliedSite | Add-Member Noteproperty 'ObjectSID' $Object.objectsid
7172 $AppliedSite | Add-Member Noteproperty 'IsGroup' $IsGroup
7173 $AppliedSite | Add-Member Noteproperty 'Domain' $Domain
7174 $AppliedSite | Add-Member Noteproperty 'GPODisplayName' $GPOname
7175 $AppliedSite | Add-Member Noteproperty 'GPOGuid' $GPOGuid
7176 $AppliedSite | Add-Member Noteproperty 'GPOPath' $GPOPath
7177 $AppliedSite | Add-Member Noteproperty 'GPOType' $GPOType
7178 $AppliedSite | Add-Member Noteproperty 'ContainerName' $_.distinguishedname
7179 $AppliedSite | Add-Member Noteproperty 'ComputerName' $_.siteobjectbl
7180 $AppliedSite.PSObject.TypeNames.Add('PowerView.GPOLocalGroup')
7181 $AppliedSite
7182 }
7183 }
7184 }
7185}
7186
7187
7188function Find-GPOComputerAdmin {
7189<#
7190 .SYNOPSIS
7191
7192 Takes a computer (or GPO) object and determines what users/groups are in the specified
7193 local group for the machine.
7194
7195 Author: @harmj0y
7196 License: BSD 3-Clause
7197 Required Dependencies: Get-NetComputer, Get-SiteName, Get-NetSite, Get-NetGPOGroup, Get-ADObject, Get-NetGroupMember, Convert-SidToName
7198 Optional Dependencies: None
7199
7200 .DESCRIPTION
7201
7202 If a -ComputerName is specified, retrieve the complete computer object, attempt to
7203 determine the OU the computer is a part of. Then resolve the computer's site name with
7204 Get-SiteName and retrieve all sites object Get-NetSite. For those results, attempt to
7205 enumerate all linked GPOs and associated local group settings with Get-NetGPOGroup. For
7206 each resulting GPO group, resolve the resulting user/group name to a full AD object and
7207 return the results. This will return the domain objects that are members of the specified
7208 -LocalGroup for the given computer.
7209
7210 Inverse of Find-GPOLocation.
7211
7212 .PARAMETER ComputerName
7213
7214 The computer to determine local administrative access to.
7215
7216 .PARAMETER OUName
7217
7218 OU name to determine who has local adminisrtative acess to computers
7219 within it.
7220
7221 .PARAMETER Domain
7222
7223 Optional domain the computer/OU exists in, defaults to the current domain.
7224
7225 .PARAMETER DomainController
7226
7227 Domain controller to reflect LDAP queries through.
7228
7229 .PARAMETER Recurse
7230
7231 Switch. If a returned member is a group, recurse and get all members.
7232
7233 .PARAMETER LocalGroup
7234
7235 The local group to check access against.
7236 Can be "Administrators" (S-1-5-32-544), "RDP/Remote Desktop Users" (S-1-5-32-555),
7237 or a custom local SID.
7238 Defaults to local 'Administrators'.
7239
7240 .PARAMETER UsePSDrive
7241
7242 Switch. Mount any found policy files with temporary PSDrives.
7243
7244 .PARAMETER PageSize
7245
7246 The PageSize to set for the LDAP searcher object.
7247
7248 .EXAMPLE
7249
7250 PS C:\> Find-GPOComputerAdmin -ComputerName WINDOWS3.dev.testlab.local
7251
7252 Finds users who have local admin rights over WINDOWS3 through GPO correlation.
7253
7254 .EXAMPLE
7255
7256 PS C:\> Find-GPOComputerAdmin -ComputerName WINDOWS3.dev.testlab.local -LocalGroup RDP
7257
7258 Finds users who have RDP rights over WINDOWS3 through GPO correlation.
7259#>
7260
7261 [CmdletBinding()]
7262 Param (
7263 [Parameter(ValueFromPipeline=$True)]
7264 [String]
7265 $ComputerName,
7266
7267 [String]
7268 $OUName,
7269
7270 [String]
7271 $Domain,
7272
7273 [String]
7274 $DomainController,
7275
7276 [Switch]
7277 $Recurse,
7278
7279 [String]
7280 $LocalGroup = 'Administrators',
7281
7282 [Switch]
7283 $UsePSDrive,
7284
7285 [ValidateRange(1,10000)]
7286 [Int]
7287 $PageSize = 200
7288 )
7289
7290 process {
7291
7292 if(!$ComputerName -and !$OUName) {
7293 Throw "-ComputerName or -OUName must be provided"
7294 }
7295
7296 $GPOGroups = @()
7297
7298 if($ComputerName) {
7299 $Computers = Get-NetComputer -ComputerName $ComputerName -Domain $Domain -DomainController $DomainController -FullData -PageSize $PageSize
7300
7301 if(!$Computers) {
7302 throw "Computer $ComputerName in domain '$Domain' not found! Try a fully qualified host name"
7303 }
7304
7305 $TargetOUs = @()
7306 ForEach($Computer in $Computers) {
7307 # extract all OUs a computer is a part of
7308 $DN = $Computer.distinguishedname
7309
7310 $TargetOUs += $DN.split(",") | ForEach-Object {
7311 if($_.startswith("OU=")) {
7312 $DN.substring($DN.indexof($_))
7313 }
7314 }
7315 }
7316
7317 # enumerate any linked GPOs for the computer's site
7318 $ComputerSite = (Get-SiteName -ComputerName $ComputerName).SiteName
7319 if($ComputerSite -and ($ComputerSite -notlike 'Error*')) {
7320 $GPOGroups += Get-NetSite -SiteName $ComputerSite -FullData | ForEach-Object {
7321 if($_.gplink) {
7322 $_.gplink.split("][") | ForEach-Object {
7323 if ($_.startswith("LDAP")) {
7324 $_.split(";")[0]
7325 }
7326 }
7327 }
7328 } | ForEach-Object {
7329 $GPOGroupArgs = @{
7330 'Domain' = $Domain
7331 'DomainController' = $DomainController
7332 'ResolveMemberSIDs' = $True
7333 'UsePSDrive' = $UsePSDrive
7334 'PageSize' = $PageSize
7335 }
7336
7337 # for each GPO link, get any locally set user/group SIDs
7338 Get-NetGPOGroup @GPOGroupArgs
7339 }
7340 }
7341 }
7342 else {
7343 $TargetOUs = @($OUName)
7344 }
7345
7346 Write-Verbose "Target OUs: $TargetOUs"
7347
7348 $TargetOUs | Where-Object {$_} | ForEach-Object {
7349
7350 $GPOLinks = Get-NetOU -Domain $Domain -DomainController $DomainController -ADSpath $_ -FullData -PageSize $PageSize | ForEach-Object {
7351 # and then get any GPO links
7352 if($_.gplink) {
7353 $_.gplink.split("][") | ForEach-Object {
7354 if ($_.startswith("LDAP")) {
7355 $_.split(";")[0]
7356 }
7357 }
7358 }
7359 }
7360
7361 $GPOGroupArgs = @{
7362 'Domain' = $Domain
7363 'DomainController' = $DomainController
7364 'UsePSDrive' = $UsePSDrive
7365 'ResolveMemberSIDs' = $True
7366 'PageSize' = $PageSize
7367 }
7368
7369 # extract GPO groups that are set through any gPlink for this OU
7370 $GPOGroups += Get-NetGPOGroup @GPOGroupArgs | ForEach-Object {
7371 ForEach($GPOLink in $GPOLinks) {
7372 $Name = $_.GPOName
7373 if($GPOLink -like "*$Name*") {
7374 $_
7375 }
7376 }
7377 }
7378 }
7379
7380 # for each found GPO group, resolve the SIDs of the members
7381 $GPOgroups | Sort-Object -Property GPOName -Unique | ForEach-Object {
7382 $GPOGroup = $_
7383
7384 if($GPOGroup.GroupMembers) {
7385 $GPOMembers = $GPOGroup.GroupMembers
7386 }
7387 else {
7388 $GPOMembers = $GPOGroup.GroupSID
7389 }
7390
7391 $GPOMembers | ForEach-Object {
7392 # resolve this SID to a domain object
7393 $Object = Get-ADObject -Domain $Domain -DomainController $DomainController -PageSize $PageSize -SID $_
7394
7395 $IsGroup = @('268435456','268435457','536870912','536870913') -contains $Object.samaccounttype
7396
7397 $GPOComputerAdmin = New-Object PSObject
7398 $GPOComputerAdmin | Add-Member Noteproperty 'ComputerName' $ComputerName
7399 $GPOComputerAdmin | Add-Member Noteproperty 'ObjectName' $Object.samaccountname
7400 $GPOComputerAdmin | Add-Member Noteproperty 'ObjectDN' $Object.distinguishedname
7401 $GPOComputerAdmin | Add-Member Noteproperty 'ObjectSID' $_
7402 $GPOComputerAdmin | Add-Member Noteproperty 'IsGroup' $IsGroup
7403 $GPOComputerAdmin | Add-Member Noteproperty 'GPODisplayName' $GPOGroup.GPODisplayName
7404 $GPOComputerAdmin | Add-Member Noteproperty 'GPOGuid' $GPOGroup.GPOName
7405 $GPOComputerAdmin | Add-Member Noteproperty 'GPOPath' $GPOGroup.GPOPath
7406 $GPOComputerAdmin | Add-Member Noteproperty 'GPOType' $GPOGroup.GPOType
7407 $GPOComputerAdmin
7408
7409 # if we're recursing and the current result object is a group
7410 if($Recurse -and $GPOComputerAdmin.isGroup) {
7411
7412 Get-NetGroupMember -Domain $Domain -DomainController $DomainController -SID $_ -FullData -Recurse -PageSize $PageSize | ForEach-Object {
7413
7414 $MemberDN = $_.distinguishedName
7415
7416 # extract the FQDN from the Distinguished Name
7417 $MemberDomain = $MemberDN.subString($MemberDN.IndexOf("DC=")) -replace 'DC=','' -replace ',','.'
7418
7419 $MemberIsGroup = @('268435456','268435457','536870912','536870913') -contains $_.samaccounttype
7420
7421 if ($_.samAccountName) {
7422 # forest users have the samAccountName set
7423 $MemberName = $_.samAccountName
7424 }
7425 else {
7426 # external trust users have a SID, so convert it
7427 try {
7428 $MemberName = Convert-SidToName $_.cn
7429 }
7430 catch {
7431 # if there's a problem contacting the domain to resolve the SID
7432 $MemberName = $_.cn
7433 }
7434 }
7435
7436 $GPOComputerAdmin = New-Object PSObject
7437 $GPOComputerAdmin | Add-Member Noteproperty 'ComputerName' $ComputerName
7438 $GPOComputerAdmin | Add-Member Noteproperty 'ObjectName' $MemberName
7439 $GPOComputerAdmin | Add-Member Noteproperty 'ObjectDN' $MemberDN
7440 $GPOComputerAdmin | Add-Member Noteproperty 'ObjectSID' $_.objectsid
7441 $GPOComputerAdmin | Add-Member Noteproperty 'IsGroup' $MemberIsGrou
7442 $GPOComputerAdmin | Add-Member Noteproperty 'GPODisplayName' $GPOGroup.GPODisplayName
7443 $GPOComputerAdmin | Add-Member Noteproperty 'GPOGuid' $GPOGroup.GPOName
7444 $GPOComputerAdmin | Add-Member Noteproperty 'GPOPath' $GPOGroup.GPOPath
7445 $GPOComputerAdmin | Add-Member Noteproperty 'GPOType' $GPOTypep
7446 $GPOComputerAdmin
7447 }
7448 }
7449 }
7450 }
7451 }
7452}
7453
7454
7455function Get-DomainPolicy {
7456<#
7457 .SYNOPSIS
7458
7459 Returns the default domain or DC policy for a given
7460 domain or domain controller.
7461
7462 Thanks Sean Metacalf (@pyrotek3) for the idea and guidance.
7463
7464 .PARAMETER Source
7465
7466 Extract Domain or DC (domain controller) policies.
7467
7468 .PARAMETER Domain
7469
7470 The domain to query for default policies, defaults to the current domain.
7471
7472 .PARAMETER DomainController
7473
7474 Domain controller to reflect LDAP queries through.
7475
7476 .PARAMETER ResolveSids
7477
7478 Switch. Resolve Sids from a DC policy to object names.
7479
7480 .PARAMETER UsePSDrive
7481
7482 Switch. Mount any found policy files with temporary PSDrives.
7483
7484 .EXAMPLE
7485
7486 PS C:\> Get-DomainPolicy
7487
7488 Returns the domain policy for the current domain.
7489
7490 .EXAMPLE
7491
7492 PS C:\> Get-DomainPolicy -Source DC -DomainController MASTER.testlab.local
7493
7494 Returns the policy for the MASTER.testlab.local domain controller.
7495#>
7496
7497 [CmdletBinding()]
7498 Param (
7499 [String]
7500 [ValidateSet("Domain","DC")]
7501 $Source ="Domain",
7502
7503 [String]
7504 $Domain,
7505
7506 [String]
7507 $DomainController,
7508
7509 [Switch]
7510 $ResolveSids,
7511
7512 [Switch]
7513 $UsePSDrive
7514 )
7515
7516 if($Source -eq "Domain") {
7517 # query the given domain for the default domain policy object
7518 $GPO = Get-NetGPO -Domain $Domain -DomainController $DomainController -GPOname "{31B2F340-016D-11D2-945F-00C04FB984F9}"
7519
7520 if($GPO) {
7521 # grab the GptTmpl.inf file and parse it
7522 $GptTmplPath = $GPO.gpcfilesyspath + "\MACHINE\Microsoft\Windows NT\SecEdit\GptTmpl.inf"
7523
7524 $ParseArgs = @{
7525 'GptTmplPath' = $GptTmplPath
7526 'UsePSDrive' = $UsePSDrive
7527 }
7528
7529 # parse the GptTmpl.inf
7530 Get-GptTmpl @ParseArgs
7531 }
7532
7533 }
7534 elseif($Source -eq "DC") {
7535 # query the given domain/dc for the default domain controller policy object
7536 $GPO = Get-NetGPO -Domain $Domain -DomainController $DomainController -GPOname "{6AC1786C-016F-11D2-945F-00C04FB984F9}"
7537
7538 if($GPO) {
7539 # grab the GptTmpl.inf file and parse it
7540 $GptTmplPath = $GPO.gpcfilesyspath + "\MACHINE\Microsoft\Windows NT\SecEdit\GptTmpl.inf"
7541
7542 $ParseArgs = @{
7543 'GptTmplPath' = $GptTmplPath
7544 'UsePSDrive' = $UsePSDrive
7545 }
7546
7547 # parse the GptTmpl.inf
7548 Get-GptTmpl @ParseArgs | ForEach-Object {
7549 if($ResolveSids) {
7550 # if we're resolving sids in PrivilegeRights to names
7551 $Policy = New-Object PSObject
7552 $_.psobject.properties | ForEach-Object {
7553 if( $_.Name -eq 'PrivilegeRights') {
7554
7555 $PrivilegeRights = New-Object PSObject
7556 # for every nested SID member of PrivilegeRights, try to unpack everything and resolve the SIDs as appropriate
7557 $_.Value.psobject.properties | ForEach-Object {
7558
7559 $Sids = $_.Value | ForEach-Object {
7560 try {
7561 if($_ -isnot [System.Array]) {
7562 Convert-SidToName $_
7563 }
7564 else {
7565 $_ | ForEach-Object { Convert-SidToName $_ }
7566 }
7567 }
7568 catch {
7569 Write-Verbose "Error resolving SID : $_"
7570 }
7571 }
7572
7573 $PrivilegeRights | Add-Member Noteproperty $_.Name $Sids
7574 }
7575
7576 $Policy | Add-Member Noteproperty 'PrivilegeRights' $PrivilegeRights
7577 }
7578 else {
7579 $Policy | Add-Member Noteproperty $_.Name $_.Value
7580 }
7581 }
7582 $Policy
7583 }
7584 else { $_ }
7585 }
7586 }
7587 }
7588}
7589
7590
7591
7592########################################################
7593#
7594# Functions that enumerate a single host, either through
7595# WinNT, WMI, remote registry, or API calls
7596# (with PSReflect).
7597#
7598########################################################
7599
7600function Get-NetLocalGroup {
7601<#
7602 .SYNOPSIS
7603
7604 Gets a list of all current users in a specified local group,
7605 or returns the names of all local groups with -ListGroups.
7606
7607 .PARAMETER ComputerName
7608
7609 The hostname or IP to query for local group users.
7610
7611 .PARAMETER ComputerFile
7612
7613 File of hostnames/IPs to query for local group users.
7614
7615 .PARAMETER GroupName
7616
7617 The local group name to query for users. If not given, it defaults to "Administrators"
7618
7619 .PARAMETER ListGroups
7620
7621 Switch. List all the local groups instead of their members.
7622 Old Get-NetLocalGroups functionality.
7623
7624 .PARAMETER Recurse
7625
7626 Switch. If the local member member is a domain group, recursively try to resolve its members to get a list of domain users who can access this machine.
7627
7628 .PARAMETER API
7629
7630 Switch. Use API calls instead of the WinNT service provider. Less information,
7631 but the results are faster.
7632
7633 .EXAMPLE
7634
7635 PS C:\> Get-NetLocalGroup
7636
7637 Returns the usernames that of members of localgroup "Administrators" on the local host.
7638
7639 .EXAMPLE
7640
7641 PS C:\> Get-NetLocalGroup -ComputerName WINDOWSXP
7642
7643 Returns all the local administrator accounts for WINDOWSXP
7644
7645 .EXAMPLE
7646
7647 PS C:\> Get-NetLocalGroup -ComputerName WINDOWS7 -Recurse
7648
7649 Returns all effective local/domain users/groups that can access WINDOWS7 with
7650 local administrative privileges.
7651
7652 .EXAMPLE
7653
7654 PS C:\> Get-NetLocalGroup -ComputerName WINDOWS7 -ListGroups
7655
7656 Returns all local groups on the WINDOWS7 host.
7657
7658 .EXAMPLE
7659
7660 PS C:\> "WINDOWS7", "WINDOWSSP" | Get-NetLocalGroup -API
7661
7662 Returns all local groups on the the passed hosts using API calls instead of the
7663 WinNT service provider.
7664
7665 .LINK
7666
7667 http://stackoverflow.com/questions/21288220/get-all-local-members-and-groups-displayed-together
7668 http://msdn.microsoft.com/en-us/library/aa772211(VS.85).aspx
7669#>
7670
7671 [CmdletBinding(DefaultParameterSetName = 'WinNT')]
7672 param(
7673 [Parameter(ParameterSetName = 'API', Position=0, ValueFromPipeline=$True)]
7674 [Parameter(ParameterSetName = 'WinNT', Position=0, ValueFromPipeline=$True)]
7675 [Alias('HostName')]
7676 [String[]]
7677 $ComputerName = $Env:ComputerName,
7678
7679 [Parameter(ParameterSetName = 'WinNT')]
7680 [Parameter(ParameterSetName = 'API')]
7681 [ValidateScript({Test-Path -Path $_ })]
7682 [Alias('HostList')]
7683 [String]
7684 $ComputerFile,
7685
7686 [Parameter(ParameterSetName = 'WinNT')]
7687 [Parameter(ParameterSetName = 'API')]
7688 [String]
7689 $GroupName = 'Administrators',
7690
7691 [Parameter(ParameterSetName = 'WinNT')]
7692 [Switch]
7693 $ListGroups,
7694
7695 [Parameter(ParameterSetName = 'WinNT')]
7696 [Switch]
7697 $Recurse,
7698
7699 [Parameter(ParameterSetName = 'API')]
7700 [Switch]
7701 $API
7702 )
7703
7704 process {
7705
7706 $Servers = @()
7707
7708 # if we have a host list passed, grab it
7709 if($ComputerFile) {
7710 $Servers = Get-Content -Path $ComputerFile
7711 }
7712 else {
7713 # otherwise assume a single host name
7714 $Servers += $ComputerName | Get-NameField
7715 }
7716
7717 # query the specified group using the WINNT provider, and
7718 # extract fields as appropriate from the results
7719 ForEach($Server in $Servers) {
7720
7721 if($API) {
7722 # if we're using the Netapi32 NetLocalGroupGetMembers API call to get the local group information
7723 # arguments for NetLocalGroupGetMembers
7724 $QueryLevel = 2
7725 $PtrInfo = [IntPtr]::Zero
7726 $EntriesRead = 0
7727 $TotalRead = 0
7728 $ResumeHandle = 0
7729
7730 # get the local user information
7731 $Result = $Netapi32::NetLocalGroupGetMembers($Server, $GroupName, $QueryLevel, [ref]$PtrInfo, -1, [ref]$EntriesRead, [ref]$TotalRead, [ref]$ResumeHandle)
7732
7733 # Locate the offset of the initial intPtr
7734 $Offset = $PtrInfo.ToInt64()
7735
7736 $LocalUsers = @()
7737
7738 # 0 = success
7739 if (($Result -eq 0) -and ($Offset -gt 0)) {
7740
7741 # Work out how much to increment the pointer by finding out the size of the structure
7742 $Increment = $LOCALGROUP_MEMBERS_INFO_2::GetSize()
7743
7744 # parse all the result structures
7745 for ($i = 0; ($i -lt $EntriesRead); $i++) {
7746 # create a new int ptr at the given offset and cast the pointer as our result structure
7747 $NewIntPtr = New-Object System.Intptr -ArgumentList $Offset
7748 $Info = $NewIntPtr -as $LOCALGROUP_MEMBERS_INFO_2
7749
7750 $Offset = $NewIntPtr.ToInt64()
7751 $Offset += $Increment
7752
7753 $SidString = ""
7754 $Result2 = $Advapi32::ConvertSidToStringSid($Info.lgrmi2_sid, [ref]$SidString);$LastError = [Runtime.InteropServices.Marshal]::GetLastWin32Error()
7755
7756 if($Result2 -eq 0) {
7757 Write-Verbose "Error: $(([ComponentModel.Win32Exception] $LastError).Message)"
7758 }
7759 else {
7760 $LocalUser = New-Object PSObject
7761 $LocalUser | Add-Member Noteproperty 'ComputerName' $Server
7762 $LocalUser | Add-Member Noteproperty 'AccountName' $Info.lgrmi2_domainandname
7763 $LocalUser | Add-Member Noteproperty 'SID' $SidString
7764
7765 $IsGroup = $($Info.lgrmi2_sidusage -eq 'SidTypeGroup')
7766 $LocalUser | Add-Member Noteproperty 'IsGroup' $IsGroup
7767 $LocalUser.PSObject.TypeNames.Add('PowerView.LocalUserAPI')
7768
7769 $LocalUsers += $LocalUser
7770 }
7771 }
7772
7773 # free up the result buffer
7774 $Null = $Netapi32::NetApiBufferFree($PtrInfo)
7775
7776 # try to extract out the machine SID by using the -500 account as a reference
7777 $MachineSid = $LocalUsers | Where-Object {$_.SID -like '*-500'}
7778 $Parts = $MachineSid.SID.Split('-')
7779 $MachineSid = $Parts[0..($Parts.Length -2)] -join '-'
7780
7781 $LocalUsers | ForEach-Object {
7782 if($_.SID -match $MachineSid) {
7783 $_ | Add-Member Noteproperty 'IsDomain' $False
7784 }
7785 else {
7786 $_ | Add-Member Noteproperty 'IsDomain' $True
7787 }
7788 }
7789 $LocalUsers
7790 }
7791 else {
7792 Write-Verbose "Error: $(([ComponentModel.Win32Exception] $Result).Message)"
7793 }
7794 }
7795
7796 else {
7797 # otherwise we're using the WinNT service provider
7798 try {
7799 if($ListGroups) {
7800 # if we're listing the group names on a remote server
7801 $Computer = [ADSI]"WinNT://$Server,computer"
7802
7803 $Computer.psbase.children | Where-Object { $_.psbase.schemaClassName -eq 'group' } | ForEach-Object {
7804 $Group = New-Object PSObject
7805 $Group | Add-Member Noteproperty 'Server' $Server
7806 $Group | Add-Member Noteproperty 'Group' ($_.name[0])
7807 $Group | Add-Member Noteproperty 'SID' ((New-Object System.Security.Principal.SecurityIdentifier $_.objectsid[0],0).Value)
7808 $Group | Add-Member Noteproperty 'Description' ($_.Description[0])
7809 $Group.PSObject.TypeNames.Add('PowerView.LocalGroup')
7810 $Group
7811 }
7812 }
7813 else {
7814 # otherwise we're listing the group members
7815 $Members = @($([ADSI]"WinNT://$Server/$GroupName,group").psbase.Invoke('Members'))
7816
7817 $Members | ForEach-Object {
7818
7819 $Member = New-Object PSObject
7820 $Member | Add-Member Noteproperty 'ComputerName' $Server
7821
7822 $AdsPath = ($_.GetType().InvokeMember('Adspath', 'GetProperty', $Null, $_, $Null)).Replace('WinNT://', '')
7823 $Class = $_.GetType().InvokeMember('Class', 'GetProperty', $Null, $_, $Null)
7824
7825 # try to translate the NT4 domain to a FQDN if possible
7826 $Name = Convert-ADName -ObjectName $AdsPath -InputType 'NT4' -OutputType 'Canonical'
7827 $IsGroup = $Class -eq "Group"
7828
7829 if($Name) {
7830 $FQDN = $Name.split("/")[0]
7831 $ObjName = $AdsPath.split("/")[-1]
7832 $Name = "$FQDN/$ObjName"
7833 $IsDomain = $True
7834 }
7835 else {
7836 $ObjName = $AdsPath.split("/")[-1]
7837 $Name = $AdsPath
7838 $IsDomain = $False
7839 }
7840
7841 $Member | Add-Member Noteproperty 'AccountName' $Name
7842 $Member | Add-Member Noteproperty 'IsDomain' $IsDomain
7843 $Member | Add-Member Noteproperty 'IsGroup' $IsGroup
7844
7845 if($IsDomain) {
7846 # translate the binary sid to a string
7847 $Member | Add-Member Noteproperty 'SID' ((New-Object System.Security.Principal.SecurityIdentifier($_.GetType().InvokeMember('ObjectSID', 'GetProperty', $Null, $_, $Null),0)).Value)
7848 $Member | Add-Member Noteproperty 'Description' ""
7849 $Member | Add-Member Noteproperty 'Disabled' ""
7850
7851 if($IsGroup) {
7852 $Member | Add-Member Noteproperty 'LastLogin' ""
7853 }
7854 else {
7855 try {
7856 $Member | Add-Member Noteproperty 'LastLogin' ( $_.GetType().InvokeMember('LastLogin', 'GetProperty', $Null, $_, $Null))
7857 }
7858 catch {
7859 $Member | Add-Member Noteproperty 'LastLogin' ""
7860 }
7861 }
7862 $Member | Add-Member Noteproperty 'PwdLastSet' ""
7863 $Member | Add-Member Noteproperty 'PwdExpired' ""
7864 $Member | Add-Member Noteproperty 'UserFlags' ""
7865 }
7866 else {
7867 # repull this user object so we can ensure correct information
7868 $LocalUser = $([ADSI] "WinNT://$AdsPath")
7869
7870 # translate the binary sid to a string
7871 $Member | Add-Member Noteproperty 'SID' ((New-Object System.Security.Principal.SecurityIdentifier($LocalUser.objectSid.value,0)).Value)
7872 $Member | Add-Member Noteproperty 'Description' ($LocalUser.Description[0])
7873
7874 if($IsGroup) {
7875 $Member | Add-Member Noteproperty 'PwdLastSet' ""
7876 $Member | Add-Member Noteproperty 'PwdExpired' ""
7877 $Member | Add-Member Noteproperty 'UserFlags' ""
7878 $Member | Add-Member Noteproperty 'Disabled' ""
7879 $Member | Add-Member Noteproperty 'LastLogin' ""
7880 }
7881 else {
7882 $Member | Add-Member Noteproperty 'PwdLastSet' ( (Get-Date).AddSeconds(-$LocalUser.PasswordAge[0]))
7883 $Member | Add-Member Noteproperty 'PwdExpired' ( $LocalUser.PasswordExpired[0] -eq '1')
7884 $Member | Add-Member Noteproperty 'UserFlags' ( $LocalUser.UserFlags[0] )
7885 # UAC flags of 0x2 mean the account is disabled
7886 $Member | Add-Member Noteproperty 'Disabled' $(($LocalUser.userFlags.value -band 2) -eq 2)
7887 try {
7888 $Member | Add-Member Noteproperty 'LastLogin' ( $LocalUser.LastLogin[0])
7889 }
7890 catch {
7891 $Member | Add-Member Noteproperty 'LastLogin' ""
7892 }
7893 }
7894 }
7895 $Member.PSObject.TypeNames.Add('PowerView.LocalUser')
7896 $Member
7897
7898 # if the result is a group domain object and we're recursing,
7899 # try to resolve all the group member results
7900 if($Recurse -and $IsGroup) {
7901 if($IsDomain) {
7902 $FQDN = $Name.split("/")[0]
7903 $GroupName = $Name.split("/")[1].trim()
7904
7905 Get-NetGroupMember -GroupName $GroupName -Domain $FQDN -FullData -Recurse | ForEach-Object {
7906
7907 $Member = New-Object PSObject
7908 $Member | Add-Member Noteproperty 'ComputerName' "$FQDN/$($_.GroupName)"
7909
7910 $MemberDN = $_.distinguishedName
7911 # extract the FQDN from the Distinguished Name
7912 $MemberDomain = $MemberDN.subString($MemberDN.IndexOf("DC=")) -replace 'DC=','' -replace ',','.'
7913
7914 $MemberIsGroup = @('268435456','268435457','536870912','536870913') -contains $_.samaccounttype
7915
7916 if ($_.samAccountName) {
7917 # forest users have the samAccountName set
7918 $MemberName = $_.samAccountName
7919 }
7920 else {
7921 try {
7922 # external trust users have a SID, so convert it
7923 try {
7924 $MemberName = Convert-SidToName $_.cn
7925 }
7926 catch {
7927 # if there's a problem contacting the domain to resolve the SID
7928 $MemberName = $_.cn
7929 }
7930 }
7931 catch {
7932 Write-Debug "Error resolving SID : $_"
7933 }
7934 }
7935
7936 $Member | Add-Member Noteproperty 'AccountName' "$MemberDomain/$MemberName"
7937 $Member | Add-Member Noteproperty 'SID' $_.objectsid
7938 $Member | Add-Member Noteproperty 'Description' $_.description
7939 $Member | Add-Member Noteproperty 'Disabled' $False
7940 $Member | Add-Member Noteproperty 'IsGroup' $MemberIsGroup
7941 $Member | Add-Member Noteproperty 'IsDomain' $True
7942 $Member | Add-Member Noteproperty 'LastLogin' ''
7943 $Member | Add-Member Noteproperty 'PwdLastSet' $_.pwdLastSet
7944 $Member | Add-Member Noteproperty 'PwdExpired' ''
7945 $Member | Add-Member Noteproperty 'UserFlags' $_.userAccountControl
7946 $Member.PSObject.TypeNames.Add('PowerView.LocalUser')
7947 $Member
7948 }
7949 } else {
7950 Get-NetLocalGroup -ComputerName $Server -GroupName $ObjName -Recurse
7951 }
7952 }
7953 }
7954 }
7955 }
7956 catch {
7957 Write-Warning "[!] Error: $_"
7958 }
7959 }
7960 }
7961 }
7962}
7963
7964filter Get-NetShare {
7965<#
7966 .SYNOPSIS
7967
7968 This function will execute the NetShareEnum Win32API call to query
7969 a given host for open shares. This is a replacement for
7970 "net share \\hostname"
7971
7972 .PARAMETER ComputerName
7973
7974 The hostname to query for shares. Also accepts IP addresses.
7975
7976 .OUTPUTS
7977
7978 SHARE_INFO_1 structure. A representation of the SHARE_INFO_1
7979 result structure which includes the name and note for each share,
7980 with the ComputerName added.
7981
7982 .EXAMPLE
7983
7984 PS C:\> Get-NetShare
7985
7986 Returns active shares on the local host.
7987
7988 .EXAMPLE
7989
7990 PS C:\> Get-NetShare -ComputerName sqlserver
7991
7992 Returns active shares on the 'sqlserver' host
7993
7994 .EXAMPLE
7995
7996 PS C:\> Get-NetComputer | Get-NetShare
7997
7998 Returns all shares for all computers in the domain.
7999
8000 .LINK
8001
8002 http://www.powershellmagazine.com/2014/09/25/easily-defining-enums-structs-and-win32-functions-in-memory/
8003#>
8004
8005 [CmdletBinding()]
8006 param(
8007 [Parameter(ValueFromPipeline=$True)]
8008 [Alias('HostName')]
8009 [Object[]]
8010 [ValidateNotNullOrEmpty()]
8011 $ComputerName = 'localhost'
8012 )
8013
8014 # extract the computer name from whatever object was passed on the pipeline
8015 $Computer = $ComputerName | Get-NameField
8016
8017 # arguments for NetShareEnum
8018 $QueryLevel = 1
8019 $PtrInfo = [IntPtr]::Zero
8020 $EntriesRead = 0
8021 $TotalRead = 0
8022 $ResumeHandle = 0
8023
8024 # get the share information
8025 $Result = $Netapi32::NetShareEnum($Computer, $QueryLevel, [ref]$PtrInfo, -1, [ref]$EntriesRead, [ref]$TotalRead, [ref]$ResumeHandle)
8026
8027 # Locate the offset of the initial intPtr
8028 $Offset = $PtrInfo.ToInt64()
8029
8030 # 0 = success
8031 if (($Result -eq 0) -and ($Offset -gt 0)) {
8032
8033 # Work out how much to increment the pointer by finding out the size of the structure
8034 $Increment = $SHARE_INFO_1::GetSize()
8035
8036 # parse all the result structures
8037 for ($i = 0; ($i -lt $EntriesRead); $i++) {
8038 # create a new int ptr at the given offset and cast the pointer as our result structure
8039 $NewIntPtr = New-Object System.Intptr -ArgumentList $Offset
8040 $Info = $NewIntPtr -as $SHARE_INFO_1
8041
8042 # return all the sections of the structure
8043 $Shares = $Info | Select-Object *
8044 $Shares | Add-Member Noteproperty 'ComputerName' $Computer
8045 $Offset = $NewIntPtr.ToInt64()
8046 $Offset += $Increment
8047 $Shares
8048 }
8049
8050 # free up the result buffer
8051 $Null = $Netapi32::NetApiBufferFree($PtrInfo)
8052 }
8053 else {
8054 Write-Verbose "Error: $(([ComponentModel.Win32Exception] $Result).Message)"
8055 }
8056}
8057
8058
8059filter Get-NetLoggedon {
8060<#
8061 .SYNOPSIS
8062
8063 This function will execute the NetWkstaUserEnum Win32API call to query
8064 a given host for actively logged on users.
8065
8066 .PARAMETER ComputerName
8067
8068 The hostname to query for logged on users.
8069
8070 .OUTPUTS
8071
8072 WKSTA_USER_INFO_1 structure. A representation of the WKSTA_USER_INFO_1
8073 result structure which includes the username and domain of logged on users,
8074 with the ComputerName added.
8075
8076 .EXAMPLE
8077
8078 PS C:\> Get-NetLoggedon
8079
8080 Returns users actively logged onto the local host.
8081
8082 .EXAMPLE
8083
8084 PS C:\> Get-NetLoggedon -ComputerName sqlserver
8085
8086 Returns users actively logged onto the 'sqlserver' host.
8087
8088 .EXAMPLE
8089
8090 PS C:\> Get-NetComputer | Get-NetLoggedon
8091
8092 Returns all logged on userse for all computers in the domain.
8093
8094 .LINK
8095
8096 http://www.powershellmagazine.com/2014/09/25/easily-defining-enums-structs-and-win32-functions-in-memory/
8097#>
8098
8099 [CmdletBinding()]
8100 param(
8101 [Parameter(ValueFromPipeline=$True)]
8102 [Alias('HostName')]
8103 [Object[]]
8104 [ValidateNotNullOrEmpty()]
8105 $ComputerName = 'localhost'
8106 )
8107
8108 # extract the computer name from whatever object was passed on the pipeline
8109 $Computer = $ComputerName | Get-NameField
8110
8111 # Declare the reference variables
8112 $QueryLevel = 1
8113 $PtrInfo = [IntPtr]::Zero
8114 $EntriesRead = 0
8115 $TotalRead = 0
8116 $ResumeHandle = 0
8117
8118 # get logged on user information
8119 $Result = $Netapi32::NetWkstaUserEnum($Computer, $QueryLevel, [ref]$PtrInfo, -1, [ref]$EntriesRead, [ref]$TotalRead, [ref]$ResumeHandle)
8120
8121 # Locate the offset of the initial intPtr
8122 $Offset = $PtrInfo.ToInt64()
8123
8124 # 0 = success
8125 if (($Result -eq 0) -and ($Offset -gt 0)) {
8126
8127 # Work out how much to increment the pointer by finding out the size of the structure
8128 $Increment = $WKSTA_USER_INFO_1::GetSize()
8129
8130 # parse all the result structures
8131 for ($i = 0; ($i -lt $EntriesRead); $i++) {
8132 # create a new int ptr at the given offset and cast the pointer as our result structure
8133 $NewIntPtr = New-Object System.Intptr -ArgumentList $Offset
8134 $Info = $NewIntPtr -as $WKSTA_USER_INFO_1
8135
8136 # return all the sections of the structure
8137 $LoggedOn = $Info | Select-Object *
8138 $LoggedOn | Add-Member Noteproperty 'ComputerName' $Computer
8139 $Offset = $NewIntPtr.ToInt64()
8140 $Offset += $Increment
8141 $LoggedOn
8142 }
8143
8144 # free up the result buffer
8145 $Null = $Netapi32::NetApiBufferFree($PtrInfo)
8146 }
8147 else {
8148 Write-Verbose "Error: $(([ComponentModel.Win32Exception] $Result).Message)"
8149 }
8150}
8151
8152
8153filter Get-NetSession {
8154<#
8155 .SYNOPSIS
8156
8157 This function will execute the NetSessionEnum Win32API call to query
8158 a given host for active sessions on the host.
8159 Heavily adapted from dunedinite's post on stackoverflow (see LINK below)
8160
8161 .PARAMETER ComputerName
8162
8163 The ComputerName to query for active sessions.
8164
8165 .PARAMETER UserName
8166
8167 The user name to filter for active sessions.
8168
8169 .OUTPUTS
8170
8171 SESSION_INFO_10 structure. A representation of the SESSION_INFO_10
8172 result structure which includes the host and username associated
8173 with active sessions, with the ComputerName added.
8174
8175 .EXAMPLE
8176
8177 PS C:\> Get-NetSession
8178
8179 Returns active sessions on the local host.
8180
8181 .EXAMPLE
8182
8183 PS C:\> Get-NetSession -ComputerName sqlserver
8184
8185 Returns active sessions on the 'sqlserver' host.
8186
8187 .EXAMPLE
8188
8189 PS C:\> Get-NetDomainController | Get-NetSession
8190
8191 Returns active sessions on all domain controllers.
8192
8193 .LINK
8194
8195 http://www.powershellmagazine.com/2014/09/25/easily-defining-enums-structs-and-win32-functions-in-memory/
8196#>
8197
8198 [CmdletBinding()]
8199 param(
8200 [Parameter(ValueFromPipeline=$True)]
8201 [Alias('HostName')]
8202 [Object[]]
8203 [ValidateNotNullOrEmpty()]
8204 $ComputerName = 'localhost',
8205
8206 [String]
8207 $UserName = ''
8208 )
8209
8210 # extract the computer name from whatever object was passed on the pipeline
8211 $Computer = $ComputerName | Get-NameField
8212
8213 # arguments for NetSessionEnum
8214 $QueryLevel = 10
8215 $PtrInfo = [IntPtr]::Zero
8216 $EntriesRead = 0
8217 $TotalRead = 0
8218 $ResumeHandle = 0
8219
8220 # get session information
8221 $Result = $Netapi32::NetSessionEnum($Computer, '', $UserName, $QueryLevel, [ref]$PtrInfo, -1, [ref]$EntriesRead, [ref]$TotalRead, [ref]$ResumeHandle)
8222
8223 # Locate the offset of the initial intPtr
8224 $Offset = $PtrInfo.ToInt64()
8225
8226 # 0 = success
8227 if (($Result -eq 0) -and ($Offset -gt 0)) {
8228
8229 # Work out how much to increment the pointer by finding out the size of the structure
8230 $Increment = $SESSION_INFO_10::GetSize()
8231
8232 # parse all the result structures
8233 for ($i = 0; ($i -lt $EntriesRead); $i++) {
8234 # create a new int ptr at the given offset and cast the pointer as our result structure
8235 $NewIntPtr = New-Object System.Intptr -ArgumentList $Offset
8236 $Info = $NewIntPtr -as $SESSION_INFO_10
8237
8238 # return all the sections of the structure
8239 $Sessions = $Info | Select-Object *
8240 $Sessions | Add-Member Noteproperty 'ComputerName' $Computer
8241 $Offset = $NewIntPtr.ToInt64()
8242 $Offset += $Increment
8243 $Sessions
8244 }
8245 # free up the result buffer
8246 $Null = $Netapi32::NetApiBufferFree($PtrInfo)
8247 }
8248 else {
8249 Write-Verbose "Error: $(([ComponentModel.Win32Exception] $Result).Message)"
8250 }
8251}
8252
8253
8254filter Get-LoggedOnLocal {
8255<#
8256 .SYNOPSIS
8257
8258 This function will query the HKU registry values to retrieve the local
8259 logged on users SID and then attempt and reverse it.
8260 Adapted technique from Sysinternal's PSLoggedOn script. Benefit over
8261 using the NetWkstaUserEnum API (Get-NetLoggedon) of less user privileges
8262 required (NetWkstaUserEnum requires remote admin access).
8263
8264 Note: This function requires only domain user rights on the
8265 machine you're enumerating, but remote registry must be enabled.
8266
8267 Function: Get-LoggedOnLocal
8268 Author: Matt Kelly, @BreakersAll
8269
8270 .PARAMETER ComputerName
8271
8272 The ComputerName to query for active sessions.
8273
8274 .EXAMPLE
8275
8276 PS C:\> Get-LoggedOnLocal
8277
8278 Returns active sessions on the local host.
8279
8280 .EXAMPLE
8281
8282 PS C:\> Get-LoggedOnLocal -ComputerName sqlserver
8283
8284 Returns active sessions on the 'sqlserver' host.
8285
8286#>
8287
8288 [CmdletBinding()]
8289 param(
8290 [Parameter(ValueFromPipeline=$True)]
8291 [Alias('HostName')]
8292 [Object[]]
8293 [ValidateNotNullOrEmpty()]
8294 $ComputerName = 'localhost'
8295 )
8296
8297 # process multiple host object types from the pipeline
8298 $ComputerName = Get-NameField -Object $ComputerName
8299
8300 try {
8301 # retrieve HKU remote registry values
8302 $Reg = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('Users', "$ComputerName")
8303
8304 # sort out bogus sid's like _class
8305 $Reg.GetSubKeyNames() | Where-Object { $_ -match 'S-1-5-21-[0-9]+-[0-9]+-[0-9]+-[0-9]+$' } | ForEach-Object {
8306 $UserName = Convert-SidToName $_
8307
8308 $Parts = $UserName.Split('\')
8309 $UserDomain = $Null
8310 $UserName = $Parts[-1]
8311 if ($Parts.Length -eq 2) {
8312 $UserDomain = $Parts[0]
8313 }
8314
8315 $LocalLoggedOnUser = New-Object PSObject
8316 $LocalLoggedOnUser | Add-Member Noteproperty 'ComputerName' "$ComputerName"
8317 $LocalLoggedOnUser | Add-Member Noteproperty 'UserDomain' $UserDomain
8318 $LocalLoggedOnUser | Add-Member Noteproperty 'UserName' $UserName
8319 $LocalLoggedOnUser | Add-Member Noteproperty 'UserSID' $_
8320 $LocalLoggedOnUser
8321 }
8322 }
8323 catch {
8324 Write-Verbose "Error opening remote registry on '$ComputerName'"
8325 }
8326}
8327
8328
8329filter Get-NetRDPSession {
8330<#
8331 .SYNOPSIS
8332
8333 This function will execute the WTSEnumerateSessionsEx and
8334 WTSQuerySessionInformation Win32API calls to query a given
8335 RDP remote service for active sessions and originating IPs.
8336 This is a replacement for qwinsta.
8337
8338 Note: only members of the Administrators or Account Operators local group
8339 can successfully execute this functionality on a remote target.
8340
8341 .PARAMETER ComputerName
8342
8343 The hostname to query for active RDP sessions.
8344
8345 .EXAMPLE
8346
8347 PS C:\> Get-NetRDPSession
8348
8349 Returns active RDP/terminal sessions on the local host.
8350
8351 .EXAMPLE
8352
8353 PS C:\> Get-NetRDPSession -ComputerName "sqlserver"
8354
8355 Returns active RDP/terminal sessions on the 'sqlserver' host.
8356
8357 .EXAMPLE
8358
8359 PS C:\> Get-NetDomainController | Get-NetRDPSession
8360
8361 Returns active RDP/terminal sessions on all domain controllers.
8362#>
8363
8364 [CmdletBinding()]
8365 param(
8366 [Parameter(ValueFromPipeline=$True)]
8367 [Alias('HostName')]
8368 [Object[]]
8369 [ValidateNotNullOrEmpty()]
8370 $ComputerName = 'localhost'
8371 )
8372
8373 # extract the computer name from whatever object was passed on the pipeline
8374 $Computer = $ComputerName | Get-NameField
8375
8376 # open up a handle to the Remote Desktop Session host
8377 $Handle = $Wtsapi32::WTSOpenServerEx($Computer)
8378
8379 # if we get a non-zero handle back, everything was successful
8380 if ($Handle -ne 0) {
8381
8382 # arguments for WTSEnumerateSessionsEx
8383 $ppSessionInfo = [IntPtr]::Zero
8384 $pCount = 0
8385
8386 # get information on all current sessions
8387 $Result = $Wtsapi32::WTSEnumerateSessionsEx($Handle, [ref]1, 0, [ref]$ppSessionInfo, [ref]$pCount);$LastError = [Runtime.InteropServices.Marshal]::GetLastWin32Error()
8388
8389 # Locate the offset of the initial intPtr
8390 $Offset = $ppSessionInfo.ToInt64()
8391
8392 if (($Result -ne 0) -and ($Offset -gt 0)) {
8393
8394 # Work out how much to increment the pointer by finding out the size of the structure
8395 $Increment = $WTS_SESSION_INFO_1::GetSize()
8396
8397 # parse all the result structures
8398 for ($i = 0; ($i -lt $pCount); $i++) {
8399
8400 # create a new int ptr at the given offset and cast the pointer as our result structure
8401 $NewIntPtr = New-Object System.Intptr -ArgumentList $Offset
8402 $Info = $NewIntPtr -as $WTS_SESSION_INFO_1
8403
8404 $RDPSession = New-Object PSObject
8405
8406 if ($Info.pHostName) {
8407 $RDPSession | Add-Member Noteproperty 'ComputerName' $Info.pHostName
8408 }
8409 else {
8410 # if no hostname returned, use the specified hostname
8411 $RDPSession | Add-Member Noteproperty 'ComputerName' $Computer
8412 }
8413
8414 $RDPSession | Add-Member Noteproperty 'SessionName' $Info.pSessionName
8415
8416 if ($(-not $Info.pDomainName) -or ($Info.pDomainName -eq '')) {
8417 # if a domain isn't returned just use the username
8418 $RDPSession | Add-Member Noteproperty 'UserName' "$($Info.pUserName)"
8419 }
8420 else {
8421 $RDPSession | Add-Member Noteproperty 'UserName' "$($Info.pDomainName)\$($Info.pUserName)"
8422 }
8423
8424 $RDPSession | Add-Member Noteproperty 'ID' $Info.SessionID
8425 $RDPSession | Add-Member Noteproperty 'State' $Info.State
8426
8427 $ppBuffer = [IntPtr]::Zero
8428 $pBytesReturned = 0
8429
8430 # query for the source client IP with WTSQuerySessionInformation
8431 # https://msdn.microsoft.com/en-us/library/aa383861(v=vs.85).aspx
8432 $Result2 = $Wtsapi32::WTSQuerySessionInformation($Handle, $Info.SessionID, 14, [ref]$ppBuffer, [ref]$pBytesReturned);$LastError2 = [Runtime.InteropServices.Marshal]::GetLastWin32Error()
8433
8434 if($Result -eq 0) {
8435 Write-Verbose "Error: $(([ComponentModel.Win32Exception] $LastError2).Message)"
8436 }
8437 else {
8438 $Offset2 = $ppBuffer.ToInt64()
8439 $NewIntPtr2 = New-Object System.Intptr -ArgumentList $Offset2
8440 $Info2 = $NewIntPtr2 -as $WTS_CLIENT_ADDRESS
8441
8442 $SourceIP = $Info2.Address
8443 if($SourceIP[2] -ne 0) {
8444 $SourceIP = [String]$SourceIP[2]+"."+[String]$SourceIP[3]+"."+[String]$SourceIP[4]+"."+[String]$SourceIP[5]
8445 }
8446 else {
8447 $SourceIP = $Null
8448 }
8449
8450 $RDPSession | Add-Member Noteproperty 'SourceIP' $SourceIP
8451 $RDPSession
8452
8453 # free up the memory buffer
8454 $Null = $Wtsapi32::WTSFreeMemory($ppBuffer)
8455
8456 $Offset += $Increment
8457 }
8458 }
8459 # free up the memory result buffer
8460 $Null = $Wtsapi32::WTSFreeMemoryEx(2, $ppSessionInfo, $pCount)
8461 }
8462 else {
8463 Write-Verbose "Error: $(([ComponentModel.Win32Exception] $LastError).Message)"
8464 }
8465 # Close off the service handle
8466 $Null = $Wtsapi32::WTSCloseServer($Handle)
8467 }
8468 else {
8469 Write-Verbose "Error opening the Remote Desktop Session Host (RD Session Host) server for: $ComputerName"
8470 }
8471}
8472
8473
8474filter Invoke-CheckLocalAdminAccess {
8475<#
8476 .SYNOPSIS
8477
8478 This function will use the OpenSCManagerW Win32API call to establish
8479 a handle to the remote host. If this succeeds, the current user context
8480 has local administrator acess to the target.
8481
8482 Idea stolen from the local_admin_search_enum post module in Metasploit written by:
8483 'Brandon McCann "zeknox" <bmccann[at]accuvant.com>'
8484 'Thomas McCarthy "smilingraccoon" <smilingraccoon[at]gmail.com>'
8485 'Royce Davis "r3dy" <rdavis[at]accuvant.com>'
8486
8487 .PARAMETER ComputerName
8488
8489 The hostname to query for active sessions.
8490
8491 .OUTPUTS
8492
8493 $True if the current user has local admin access to the hostname, $False otherwise
8494
8495 .EXAMPLE
8496
8497 PS C:\> Invoke-CheckLocalAdminAccess -ComputerName sqlserver
8498
8499 Returns active sessions on the local host.
8500
8501 .EXAMPLE
8502
8503 PS C:\> Get-NetComputer | Invoke-CheckLocalAdminAccess
8504
8505 Sees what machines in the domain the current user has access to.
8506
8507 .LINK
8508
8509 https://github.com/rapid7/metasploit-framework/blob/master/modules/post/windows/gather/local_admin_search_enum.rb
8510 http://www.powershellmagazine.com/2014/09/25/easily-defining-enums-structs-and-win32-functions-in-memory/
8511#>
8512
8513 [CmdletBinding()]
8514 param(
8515 [Parameter(ValueFromPipeline=$True)]
8516 [Alias('HostName')]
8517 [Object[]]
8518 [ValidateNotNullOrEmpty()]
8519 $ComputerName = 'localhost'
8520 )
8521
8522 # extract the computer name from whatever object was passed on the pipeline
8523 $Computer = $ComputerName | Get-NameField
8524
8525 # 0xF003F - SC_MANAGER_ALL_ACCESS
8526 # http://msdn.microsoft.com/en-us/library/windows/desktop/ms685981(v=vs.85).aspx
8527 $Handle = $Advapi32::OpenSCManagerW("\\$Computer", 'ServicesActive', 0xF003F);$LastError = [Runtime.InteropServices.Marshal]::GetLastWin32Error()
8528
8529 Write-Verbose "Invoke-CheckLocalAdminAccess handle: $Handle"
8530
8531 $IsAdmin = New-Object PSObject
8532 $IsAdmin | Add-Member Noteproperty 'ComputerName' $Computer
8533
8534 # if we get a non-zero handle back, everything was successful
8535 if ($Handle -ne 0) {
8536 $Null = $Advapi32::CloseServiceHandle($Handle)
8537 $IsAdmin | Add-Member Noteproperty 'IsAdmin' $True
8538 }
8539 else {
8540 Write-Verbose "Error: $(([ComponentModel.Win32Exception] $LastError).Message)"
8541 $IsAdmin | Add-Member Noteproperty 'IsAdmin' $False
8542 }
8543
8544 $IsAdmin
8545}
8546
8547
8548filter Get-SiteName {
8549<#
8550 .SYNOPSIS
8551
8552 This function will use the DsGetSiteName Win32API call to look up the
8553 name of the site where a specified computer resides.
8554
8555 .PARAMETER ComputerName
8556
8557 The hostname to look the site up for, default to localhost.
8558
8559 .EXAMPLE
8560
8561 PS C:\> Get-SiteName -ComputerName WINDOWS1
8562
8563 Returns the site for WINDOWS1.testlab.local.
8564
8565 .EXAMPLE
8566
8567 PS C:\> Get-NetComputer | Get-SiteName
8568
8569 Returns the sites for every machine in AD.
8570#>
8571 [CmdletBinding()]
8572 param(
8573 [Parameter(ValueFromPipeline=$True)]
8574 [Alias('HostName')]
8575 [Object[]]
8576 [ValidateNotNullOrEmpty()]
8577 $ComputerName = $Env:ComputerName
8578 )
8579
8580 # extract the computer name from whatever object was passed on the pipeline
8581 $Computer = $ComputerName | Get-NameField
8582
8583 # if we get an IP address, try to resolve the IP to a hostname
8584 if($Computer -match '^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$') {
8585 $IPAddress = $Computer
8586 $Computer = [System.Net.Dns]::GetHostByAddress($Computer)
8587 }
8588 else {
8589 $IPAddress = @(Get-IPAddress -ComputerName $Computer)[0].IPAddress
8590 }
8591
8592 $PtrInfo = [IntPtr]::Zero
8593
8594 $Result = $Netapi32::DsGetSiteName($Computer, [ref]$PtrInfo)
8595
8596 $ComputerSite = New-Object PSObject
8597 $ComputerSite | Add-Member Noteproperty 'ComputerName' $Computer
8598 $ComputerSite | Add-Member Noteproperty 'IPAddress' $IPAddress
8599
8600 if ($Result -eq 0) {
8601 $Sitename = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($PtrInfo)
8602 $ComputerSite | Add-Member Noteproperty 'SiteName' $Sitename
8603 }
8604 else {
8605 $ErrorMessage = "Error: $(([ComponentModel.Win32Exception] $Result).Message)"
8606 $ComputerSite | Add-Member Noteproperty 'SiteName' $ErrorMessage
8607 }
8608
8609 $Null = $Netapi32::NetApiBufferFree($PtrInfo)
8610
8611 $ComputerSite
8612}
8613
8614
8615filter Get-LastLoggedOn {
8616<#
8617 .SYNOPSIS
8618
8619 This function uses remote registry functionality to return
8620 the last user logged onto a target machine.
8621
8622 Note: This function requires administrative rights on the
8623 machine you're enumerating.
8624
8625 .PARAMETER ComputerName
8626
8627 The hostname to query for the last logged on user.
8628 Defaults to the localhost.
8629
8630 .PARAMETER Credential
8631
8632 A [Management.Automation.PSCredential] object for the remote connection.
8633
8634 .EXAMPLE
8635
8636 PS C:\> Get-LastLoggedOn
8637
8638 Returns the last user logged onto the local machine.
8639
8640 .EXAMPLE
8641
8642 PS C:\> Get-LastLoggedOn -ComputerName WINDOWS1
8643
8644 Returns the last user logged onto WINDOWS1
8645
8646 .EXAMPLE
8647
8648 PS C:\> Get-NetComputer | Get-LastLoggedOn
8649
8650 Returns the last user logged onto all machines in the domain.
8651#>
8652
8653 [CmdletBinding()]
8654 param(
8655 [Parameter(ValueFromPipeline=$True)]
8656 [Alias('HostName')]
8657 [Object[]]
8658 [ValidateNotNullOrEmpty()]
8659 $ComputerName = 'localhost',
8660
8661 [Management.Automation.PSCredential]
8662 $Credential
8663 )
8664
8665 # extract the computer name from whatever object was passed on the pipeline
8666 $Computer = $ComputerName | Get-NameField
8667
8668 # HKEY_LOCAL_MACHINE
8669 $HKLM = 2147483650
8670
8671 # try to open up the remote registry key to grab the last logged on user
8672 try {
8673
8674 if($Credential) {
8675 $Reg = Get-WmiObject -List 'StdRegProv' -Namespace root\default -Computername $Computer -Credential $Credential -ErrorAction SilentlyContinue
8676 }
8677 else {
8678 $Reg = Get-WmiObject -List 'StdRegProv' -Namespace root\default -Computername $Computer -ErrorAction SilentlyContinue
8679 }
8680
8681 $Key = "SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\LogonUI"
8682 $Value = "LastLoggedOnUser"
8683 $LastUser = $Reg.GetStringValue($HKLM, $Key, $Value).sValue
8684
8685 $LastLoggedOn = New-Object PSObject
8686 $LastLoggedOn | Add-Member Noteproperty 'ComputerName' $Computer
8687 $LastLoggedOn | Add-Member Noteproperty 'LastLoggedOn' $LastUser
8688 $LastLoggedOn
8689 }
8690 catch {
8691 Write-Warning "[!] Error opening remote registry on $Computer. Remote registry likely not enabled."
8692 }
8693}
8694
8695
8696filter Get-CachedRDPConnection {
8697<#
8698 .SYNOPSIS
8699
8700 Uses remote registry functionality to query all entries for the
8701 "Windows Remote Desktop Connection Client" on a machine, separated by
8702 user and target server.
8703
8704 Note: This function requires administrative rights on the
8705 machine you're enumerating.
8706
8707 .PARAMETER ComputerName
8708
8709 The hostname to query for RDP client information.
8710 Defaults to localhost.
8711
8712 .PARAMETER Credential
8713
8714 A [Management.Automation.PSCredential] object for the remote connection.
8715
8716 .EXAMPLE
8717
8718 PS C:\> Get-CachedRDPConnection
8719
8720 Returns the RDP connection client information for the local machine.
8721
8722 .EXAMPLE
8723
8724 PS C:\> Get-CachedRDPConnection -ComputerName WINDOWS2.testlab.local
8725
8726 Returns the RDP connection client information for the WINDOWS2.testlab.local machine
8727
8728 .EXAMPLE
8729
8730 PS C:\> Get-CachedRDPConnection -ComputerName WINDOWS2.testlab.local -Credential $Cred
8731
8732 Returns the RDP connection client information for the WINDOWS2.testlab.local machine using alternate credentials.
8733
8734 .EXAMPLE
8735
8736 PS C:\> Get-NetComputer | Get-CachedRDPConnection
8737
8738 Get cached RDP information for all machines in the domain.
8739#>
8740
8741 [CmdletBinding()]
8742 param(
8743 [Parameter(ValueFromPipeline=$True)]
8744 [Alias('HostName')]
8745 [Object[]]
8746 [ValidateNotNullOrEmpty()]
8747 $ComputerName = 'localhost',
8748
8749 [Management.Automation.PSCredential]
8750 $Credential
8751 )
8752
8753 # extract the computer name from whatever object was passed on the pipeline
8754 $Computer = $ComputerName | Get-NameField
8755
8756 # HKEY_USERS
8757 $HKU = 2147483651
8758
8759 try {
8760 if($Credential) {
8761 $Reg = Get-WmiObject -List 'StdRegProv' -Namespace root\default -Computername $Computer -Credential $Credential -ErrorAction SilentlyContinue
8762 }
8763 else {
8764 $Reg = Get-WmiObject -List 'StdRegProv' -Namespace root\default -Computername $Computer -ErrorAction SilentlyContinue
8765 }
8766
8767 # extract out the SIDs of domain users in this hive
8768 $UserSIDs = ($Reg.EnumKey($HKU, "")).sNames | ? { $_ -match 'S-1-5-21-[0-9]+-[0-9]+-[0-9]+-[0-9]+$' }
8769
8770 foreach ($UserSID in $UserSIDs) {
8771
8772 try {
8773 $UserName = Convert-SidToName $UserSID
8774
8775 # pull out all the cached RDP connections
8776 $ConnectionKeys = $Reg.EnumValues($HKU,"$UserSID\Software\Microsoft\Terminal Server Client\Default").sNames
8777
8778 foreach ($Connection in $ConnectionKeys) {
8779 # make sure this key is a cached connection
8780 if($Connection -match 'MRU.*') {
8781 $TargetServer = $Reg.GetStringValue($HKU, "$UserSID\Software\Microsoft\Terminal Server Client\Default", $Connection).sValue
8782
8783 $FoundConnection = New-Object PSObject
8784 $FoundConnection | Add-Member Noteproperty 'ComputerName' $Computer
8785 $FoundConnection | Add-Member Noteproperty 'UserName' $UserName
8786 $FoundConnection | Add-Member Noteproperty 'UserSID' $UserSID
8787 $FoundConnection | Add-Member Noteproperty 'TargetServer' $TargetServer
8788 $FoundConnection | Add-Member Noteproperty 'UsernameHint' $Null
8789 $FoundConnection
8790 }
8791 }
8792
8793 # pull out all the cached server info with username hints
8794 $ServerKeys = $Reg.EnumKey($HKU,"$UserSID\Software\Microsoft\Terminal Server Client\Servers").sNames
8795
8796 foreach ($Server in $ServerKeys) {
8797
8798 $UsernameHint = $Reg.GetStringValue($HKU, "$UserSID\Software\Microsoft\Terminal Server Client\Servers\$Server", 'UsernameHint').sValue
8799
8800 $FoundConnection = New-Object PSObject
8801 $FoundConnection | Add-Member Noteproperty 'ComputerName' $Computer
8802 $FoundConnection | Add-Member Noteproperty 'UserName' $UserName
8803 $FoundConnection | Add-Member Noteproperty 'UserSID' $UserSID
8804 $FoundConnection | Add-Member Noteproperty 'TargetServer' $Server
8805 $FoundConnection | Add-Member Noteproperty 'UsernameHint' $UsernameHint
8806 $FoundConnection
8807 }
8808
8809 }
8810 catch {
8811 Write-Verbose "Error: $_"
8812 }
8813 }
8814
8815 }
8816 catch {
8817 Write-Warning "Error accessing $Computer, likely insufficient permissions or firewall rules on host: $_"
8818 }
8819}
8820
8821
8822filter Get-RegistryMountedDrive {
8823<#
8824 .SYNOPSIS
8825
8826 Uses remote registry functionality to query all entries for the
8827 the saved network mounted drive on a machine, separated by
8828 user and target server.
8829
8830 Note: This function requires administrative rights on the
8831 machine you're enumerating.
8832
8833 .PARAMETER ComputerName
8834
8835 The hostname to query for RDP client information.
8836 Defaults to localhost.
8837
8838 .PARAMETER Credential
8839
8840 A [Management.Automation.PSCredential] object for the remote connection.
8841
8842 .EXAMPLE
8843
8844 PS C:\> Get-RegistryMountedDrive
8845
8846 Returns the saved network mounted drives for the local machine.
8847
8848 .EXAMPLE
8849
8850 PS C:\> Get-RegistryMountedDrive -ComputerName WINDOWS2.testlab.local
8851
8852 Returns the saved network mounted drives for the WINDOWS2.testlab.local machine
8853
8854 .EXAMPLE
8855
8856 PS C:\> Get-RegistryMountedDrive -ComputerName WINDOWS2.testlab.local -Credential $Cred
8857
8858 Returns the saved network mounted drives for the WINDOWS2.testlab.local machine using alternate credentials.
8859
8860 .EXAMPLE
8861
8862 PS C:\> Get-NetComputer | Get-RegistryMountedDrive
8863
8864 Get the saved network mounted drives for all machines in the domain.
8865#>
8866
8867 [CmdletBinding()]
8868 param(
8869 [Parameter(ValueFromPipeline=$True)]
8870 [Alias('HostName')]
8871 [Object[]]
8872 [ValidateNotNullOrEmpty()]
8873 $ComputerName = 'localhost',
8874
8875 [Management.Automation.PSCredential]
8876 $Credential
8877 )
8878
8879 # extract the computer name from whatever object was passed on the pipeline
8880 $Computer = $ComputerName | Get-NameField
8881
8882 # HKEY_USERS
8883 $HKU = 2147483651
8884
8885 try {
8886 if($Credential) {
8887 $Reg = Get-WmiObject -List 'StdRegProv' -Namespace root\default -Computername $Computer -Credential $Credential -ErrorAction SilentlyContinue
8888 }
8889 else {
8890 $Reg = Get-WmiObject -List 'StdRegProv' -Namespace root\default -Computername $Computer -ErrorAction SilentlyContinue
8891 }
8892
8893 # extract out the SIDs of domain users in this hive
8894 $UserSIDs = ($Reg.EnumKey($HKU, "")).sNames | ? { $_ -match 'S-1-5-21-[0-9]+-[0-9]+-[0-9]+-[0-9]+$' }
8895
8896 foreach ($UserSID in $UserSIDs) {
8897
8898 try {
8899 $UserName = Convert-SidToName $UserSID
8900
8901 $DriveLetters = ($Reg.EnumKey($HKU, "$UserSID\Network")).sNames
8902
8903 ForEach($DriveLetter in $DriveLetters) {
8904 $ProviderName = $Reg.GetStringValue($HKU, "$UserSID\Network\$DriveLetter", 'ProviderName').sValue
8905 $RemotePath = $Reg.GetStringValue($HKU, "$UserSID\Network\$DriveLetter", 'RemotePath').sValue
8906 $DriveUserName = $Reg.GetStringValue($HKU, "$UserSID\Network\$DriveLetter", 'UserName').sValue
8907 if(-not $UserName) { $UserName = '' }
8908
8909 if($RemotePath -and ($RemotePath -ne '')) {
8910 $MountedDrive = New-Object PSObject
8911 $MountedDrive | Add-Member Noteproperty 'ComputerName' $Computer
8912 $MountedDrive | Add-Member Noteproperty 'UserName' $UserName
8913 $MountedDrive | Add-Member Noteproperty 'UserSID' $UserSID
8914 $MountedDrive | Add-Member Noteproperty 'DriveLetter' $DriveLetter
8915 $MountedDrive | Add-Member Noteproperty 'ProviderName' $ProviderName
8916 $MountedDrive | Add-Member Noteproperty 'RemotePath' $RemotePath
8917 $MountedDrive | Add-Member Noteproperty 'DriveUserName' $DriveUserName
8918 $MountedDrive
8919 }
8920 }
8921 }
8922 catch {
8923 Write-Verbose "Error: $_"
8924 }
8925 }
8926 }
8927 catch {
8928 Write-Warning "Error accessing $Computer, likely insufficient permissions or firewall rules on host: $_"
8929 }
8930}
8931
8932
8933filter Get-NetProcess {
8934<#
8935 .SYNOPSIS
8936
8937 Gets a list of processes/owners on a remote machine.
8938
8939 .PARAMETER ComputerName
8940
8941 The hostname to query processes. Defaults to the local host name.
8942
8943 .PARAMETER Credential
8944
8945 A [Management.Automation.PSCredential] object for the remote connection.
8946
8947 .EXAMPLE
8948
8949 PS C:\> Get-NetProcess -ComputerName WINDOWS1
8950
8951 Returns the current processes for WINDOWS1
8952#>
8953
8954 [CmdletBinding()]
8955 param(
8956 [Parameter(ValueFromPipeline=$True)]
8957 [Alias('HostName')]
8958 [Object[]]
8959 [ValidateNotNullOrEmpty()]
8960 $ComputerName = [System.Net.Dns]::GetHostName(),
8961
8962 [Management.Automation.PSCredential]
8963 $Credential
8964 )
8965
8966 # extract the computer name from whatever object was passed on the pipeline
8967 $Computer = $ComputerName | Get-NameField
8968
8969 try {
8970 if($Credential) {
8971 $Processes = Get-WMIobject -Class Win32_process -ComputerName $ComputerName -Credential $Credential
8972 }
8973 else {
8974 $Processes = Get-WMIobject -Class Win32_process -ComputerName $ComputerName
8975 }
8976
8977 $Processes | ForEach-Object {
8978 $Owner = $_.getowner();
8979 $Process = New-Object PSObject
8980 $Process | Add-Member Noteproperty 'ComputerName' $Computer
8981 $Process | Add-Member Noteproperty 'ProcessName' $_.ProcessName
8982 $Process | Add-Member Noteproperty 'ProcessID' $_.ProcessID
8983 $Process | Add-Member Noteproperty 'Domain' $Owner.Domain
8984 $Process | Add-Member Noteproperty 'User' $Owner.User
8985 $Process
8986 }
8987 }
8988 catch {
8989 Write-Verbose "[!] Error enumerating remote processes on $Computer, access likely denied: $_"
8990 }
8991}
8992
8993
8994function Find-InterestingFile {
8995<#
8996 .SYNOPSIS
8997
8998 This function recursively searches a given UNC path for files with
8999 specific keywords in the name (default of pass, sensitive, secret, admin,
9000 login and unattend*.xml). The output can be piped out to a csv with the
9001 -OutFile flag. By default, hidden files/folders are included in search results.
9002
9003 .PARAMETER Path
9004
9005 UNC/local path to recursively search.
9006
9007 .PARAMETER Terms
9008
9009 Terms to search for.
9010
9011 .PARAMETER OfficeDocs
9012
9013 Switch. Search for office documents (*.doc*, *.xls*, *.ppt*)
9014
9015 .PARAMETER FreshEXEs
9016
9017 Switch. Find .EXEs accessed within the last week.
9018
9019 .PARAMETER LastAccessTime
9020
9021 Only return files with a LastAccessTime greater than this date value.
9022
9023 .PARAMETER LastWriteTime
9024
9025 Only return files with a LastWriteTime greater than this date value.
9026
9027 .PARAMETER CreationTime
9028
9029 Only return files with a CreationTime greater than this date value.
9030
9031 .PARAMETER ExcludeFolders
9032
9033 Switch. Exclude folders from the search results.
9034
9035 .PARAMETER ExcludeHidden
9036
9037 Switch. Exclude hidden files and folders from the search results.
9038
9039 .PARAMETER CheckWriteAccess
9040
9041 Switch. Only returns files the current user has write access to.
9042
9043 .PARAMETER OutFile
9044
9045 Output results to a specified csv output file.
9046
9047 .PARAMETER UsePSDrive
9048
9049 Switch. Mount target remote path with temporary PSDrives.
9050
9051 .OUTPUTS
9052
9053 The full path, owner, lastaccess time, lastwrite time, and size for each found file.
9054
9055 .EXAMPLE
9056
9057 PS C:\> Find-InterestingFile -Path C:\Backup\
9058
9059 Returns any files on the local path C:\Backup\ that have the default
9060 search term set in the title.
9061
9062 .EXAMPLE
9063
9064 PS C:\> Find-InterestingFile -Path \\WINDOWS7\Users\ -Terms salaries,email -OutFile out.csv
9065
9066 Returns any files on the remote path \\WINDOWS7\Users\ that have 'salaries'
9067 or 'email' in the title, and writes the results out to a csv file
9068 named 'out.csv'
9069
9070 .EXAMPLE
9071
9072 PS C:\> Find-InterestingFile -Path \\WINDOWS7\Users\ -LastAccessTime (Get-Date).AddDays(-7)
9073
9074 Returns any files on the remote path \\WINDOWS7\Users\ that have the default
9075 search term set in the title and were accessed within the last week.
9076
9077 .LINK
9078
9079 http://www.harmj0y.net/blog/redteaming/file-server-triage-on-red-team-engagements/
9080#>
9081
9082 param(
9083 [Parameter(ValueFromPipeline=$True)]
9084 [String]
9085 $Path = '.\',
9086
9087 [Alias('Terms')]
9088 [String[]]
9089 $SearchTerms = @('pass', 'sensitive', 'admin', 'login', 'secret', 'unattend*.xml', '.vmdk', 'creds', 'credential', '.config'),
9090
9091 [Switch]
9092 $OfficeDocs,
9093
9094 [Switch]
9095 $FreshEXEs,
9096
9097 [String]
9098 $LastAccessTime,
9099
9100 [String]
9101 $LastWriteTime,
9102
9103 [String]
9104 $CreationTime,
9105
9106 [Switch]
9107 $ExcludeFolders,
9108
9109 [Switch]
9110 $ExcludeHidden,
9111
9112 [Switch]
9113 $CheckWriteAccess,
9114
9115 [String]
9116 $OutFile,
9117
9118 [Switch]
9119 $UsePSDrive
9120 )
9121
9122 begin {
9123
9124 $Path += if(!$Path.EndsWith('\')) {"\"}
9125
9126 if ($Credential) {
9127 $UsePSDrive = $True
9128 }
9129
9130 # append wildcards to the front and back of all search terms
9131 $SearchTerms = $SearchTerms | ForEach-Object { if($_ -notmatch '^\*.*\*$') {"*$($_)*"} else{$_} }
9132
9133 # search just for office documents if specified
9134 if ($OfficeDocs) {
9135 $SearchTerms = @('*.doc', '*.docx', '*.xls', '*.xlsx', '*.ppt', '*.pptx')
9136 }
9137
9138 # find .exe's accessed within the last 7 days
9139 if($FreshEXEs) {
9140 # get an access time limit of 7 days ago
9141 $LastAccessTime = (Get-Date).AddDays(-7).ToString('MM/dd/yyyy')
9142 $SearchTerms = '*.exe'
9143 }
9144
9145 if($UsePSDrive) {
9146 # if we're PSDrives, create a temporary mount point
9147
9148 $Parts = $Path.split('\')
9149 $FolderPath = $Parts[0..($Parts.length-2)] -join '\'
9150 $FilePath = $Parts[-1]
9151
9152 $RandDrive = ("abcdefghijklmnopqrstuvwxyz".ToCharArray() | Get-Random -Count 7) -join ''
9153
9154 Write-Verbose "Mounting path '$Path' using a temp PSDrive at $RandDrive"
9155
9156 try {
9157 $Null = New-PSDrive -Name $RandDrive -PSProvider FileSystem -Root $FolderPath -ErrorAction Stop
9158 }
9159 catch {
9160 Write-Verbose "Error mounting path '$Path' : $_"
9161 return $Null
9162 }
9163
9164 # so we can cd/dir the new drive
9165 $Path = "${RandDrive}:\${FilePath}"
9166 }
9167 }
9168
9169 process {
9170
9171 Write-Verbose "[*] Search path $Path"
9172
9173 function Invoke-CheckWrite {
9174 # short helper to check is the current user can write to a file
9175 [CmdletBinding()]param([String]$Path)
9176 try {
9177 $Filetest = [IO.FILE]::OpenWrite($Path)
9178 $Filetest.Close()
9179 $True
9180 }
9181 catch {
9182 Write-Verbose -Message $Error[0]
9183 $False
9184 }
9185 }
9186
9187 $SearchArgs = @{
9188 'Path' = $Path
9189 'Recurse' = $True
9190 'Force' = $(-not $ExcludeHidden)
9191 'Include' = $SearchTerms
9192 'ErrorAction' = 'SilentlyContinue'
9193 }
9194
9195 Get-ChildItem @SearchArgs | ForEach-Object {
9196 Write-Verbose $_
9197 # check if we're excluding folders
9198 if(!$ExcludeFolders -or !$_.PSIsContainer) {$_}
9199 } | ForEach-Object {
9200 if($LastAccessTime -or $LastWriteTime -or $CreationTime) {
9201 if($LastAccessTime -and ($_.LastAccessTime -gt $LastAccessTime)) {$_}
9202 elseif($LastWriteTime -and ($_.LastWriteTime -gt $LastWriteTime)) {$_}
9203 elseif($CreationTime -and ($_.CreationTime -gt $CreationTime)) {$_}
9204 }
9205 else {$_}
9206 } | ForEach-Object {
9207 # filter for write access (if applicable)
9208 if((-not $CheckWriteAccess) -or (Invoke-CheckWrite -Path $_.FullName)) {$_}
9209 } | Select-Object FullName,@{Name='Owner';Expression={(Get-Acl $_.FullName).Owner}},LastAccessTime,LastWriteTime,CreationTime,Length | ForEach-Object {
9210 # check if we're outputting to the pipeline or an output file
9211 if($OutFile) {Export-PowerViewCSV -InputObject $_ -OutFile $OutFile}
9212 else {$_}
9213 }
9214 }
9215
9216 end {
9217 if($UsePSDrive -and $RandDrive) {
9218 Write-Verbose "Removing temp PSDrive $RandDrive"
9219 Get-PSDrive -Name $RandDrive -ErrorAction SilentlyContinue | Remove-PSDrive -Force
9220 }
9221 }
9222}
9223
9224
9225########################################################
9226#
9227# 'Meta'-functions start below
9228#
9229########################################################
9230
9231function Invoke-ThreadedFunction {
9232 # Helper used by any threaded host enumeration functions
9233 [CmdletBinding()]
9234 param(
9235 [Parameter(Position=0,Mandatory=$True)]
9236 [String[]]
9237 $ComputerName,
9238
9239 [Parameter(Position=1,Mandatory=$True)]
9240 [System.Management.Automation.ScriptBlock]
9241 $ScriptBlock,
9242
9243 [Parameter(Position=2)]
9244 [Hashtable]
9245 $ScriptParameters,
9246
9247 [Int]
9248 [ValidateRange(1,100)]
9249 $Threads = 20,
9250
9251 [Switch]
9252 $NoImports
9253 )
9254
9255 begin {
9256
9257 if ($PSBoundParameters['Debug']) {
9258 $DebugPreference = 'Continue'
9259 }
9260
9261 Write-Verbose "[*] Total number of hosts: $($ComputerName.count)"
9262
9263 # Adapted from:
9264 # http://powershell.org/wp/forums/topic/invpke-parallel-need-help-to-clone-the-current-runspace/
9265 $SessionState = [System.Management.Automation.Runspaces.InitialSessionState]::CreateDefault()
9266 $SessionState.ApartmentState = [System.Threading.Thread]::CurrentThread.GetApartmentState()
9267
9268 # import the current session state's variables and functions so the chained PowerView
9269 # functionality can be used by the threaded blocks
9270 if(!$NoImports) {
9271
9272 # grab all the current variables for this runspace
9273 $MyVars = Get-Variable -Scope 2
9274
9275 # these Variables are added by Runspace.Open() Method and produce Stop errors if you add them twice
9276 $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")
9277
9278 # Add Variables from Parent Scope (current runspace) into the InitialSessionState
9279 ForEach($Var in $MyVars) {
9280 if($VorbiddenVars -NotContains $Var.Name) {
9281 $SessionState.Variables.Add((New-Object -TypeName System.Management.Automation.Runspaces.SessionStateVariableEntry -ArgumentList $Var.name,$Var.Value,$Var.description,$Var.options,$Var.attributes))
9282 }
9283 }
9284
9285 # Add Functions from current runspace to the InitialSessionState
9286 ForEach($Function in (Get-ChildItem Function:)) {
9287 $SessionState.Commands.Add((New-Object -TypeName System.Management.Automation.Runspaces.SessionStateFunctionEntry -ArgumentList $Function.Name, $Function.Definition))
9288 }
9289 }
9290
9291 # threading adapted from
9292 # https://github.com/darkoperator/Posh-SecMod/blob/master/Discovery/Discovery.psm1#L407
9293 # Thanks Carlos!
9294
9295 # create a pool of maxThread runspaces
9296 $Pool = [runspacefactory]::CreateRunspacePool(1, $Threads, $SessionState, $Host)
9297 $Pool.Open()
9298
9299 $method = $null
9300 ForEach ($m in [PowerShell].GetMethods() | Where-Object { $_.Name -eq "BeginInvoke" }) {
9301 $methodParameters = $m.GetParameters()
9302 if (($methodParameters.Count -eq 2) -and $methodParameters[0].Name -eq "input" -and $methodParameters[1].Name -eq "output") {
9303 $method = $m.MakeGenericMethod([Object], [Object])
9304 break
9305 }
9306 }
9307
9308 $Jobs = @()
9309 }
9310
9311 process {
9312
9313 ForEach ($Computer in $ComputerName) {
9314
9315 # make sure we get a server name
9316 if ($Computer -ne '') {
9317 # Write-Verbose "[*] Enumerating server $Computer ($($Counter+1) of $($ComputerName.count))"
9318
9319 While ($($Pool.GetAvailableRunspaces()) -le 0) {
9320 Start-Sleep -MilliSeconds 500
9321 }
9322
9323 # create a "powershell pipeline runner"
9324 $p = [powershell]::create()
9325
9326 $p.runspacepool = $Pool
9327
9328 # add the script block + arguments
9329 $Null = $p.AddScript($ScriptBlock).AddParameter('ComputerName', $Computer)
9330 if($ScriptParameters) {
9331 ForEach ($Param in $ScriptParameters.GetEnumerator()) {
9332 $Null = $p.AddParameter($Param.Name, $Param.Value)
9333 }
9334 }
9335
9336 $o = New-Object Management.Automation.PSDataCollection[Object]
9337
9338 $Jobs += @{
9339 PS = $p
9340 Output = $o
9341 Result = $method.Invoke($p, @($null, [Management.Automation.PSDataCollection[Object]]$o))
9342 }
9343 }
9344 }
9345 }
9346
9347 end {
9348 Write-Verbose "Waiting for threads to finish..."
9349
9350 Do {
9351 ForEach ($Job in $Jobs) {
9352 $Job.Output.ReadAll()
9353 }
9354 } While (($Jobs | Where-Object { ! $_.Result.IsCompleted }).Count -gt 0)
9355
9356 ForEach ($Job in $Jobs) {
9357 $Job.PS.Dispose()
9358 }
9359
9360 $Pool.Dispose()
9361 Write-Verbose "All threads completed!"
9362 }
9363}
9364
9365
9366function Invoke-UserHunter {
9367<#
9368 .SYNOPSIS
9369
9370 Finds which machines users of a specified group are logged into.
9371
9372 Author: @harmj0y
9373 License: BSD 3-Clause
9374
9375 .DESCRIPTION
9376
9377 This function finds the local domain name for a host using Get-NetDomain,
9378 queries the domain for users of a specified group (default "domain admins")
9379 with Get-NetGroupMember or reads in a target user list, queries the domain for all
9380 active machines with Get-NetComputer or reads in a pre-populated host list,
9381 randomly shuffles the target list, then for each server it gets a list of
9382 active users with Get-NetSession/Get-NetLoggedon. The found user list is compared
9383 against the target list, and a status message is displayed for any hits.
9384 The flag -CheckAccess will check each positive host to see if the current
9385 user has local admin access to the machine.
9386
9387 .PARAMETER ComputerName
9388
9389 Host array to enumerate, passable on the pipeline.
9390
9391 .PARAMETER ComputerFile
9392
9393 File of hostnames/IPs to search.
9394
9395 .PARAMETER ComputerFilter
9396
9397 Host filter name to query AD for, wildcards accepted.
9398
9399 .PARAMETER ComputerADSpath
9400
9401 The LDAP source to search through for hosts, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
9402 Useful for OU queries.
9403
9404 .PARAMETER Unconstrained
9405
9406 Switch. Only enumerate computers that have unconstrained delegation.
9407
9408 .PARAMETER GroupName
9409
9410 Group name to query for target users.
9411
9412 .PARAMETER TargetServer
9413
9414 Hunt for users who are effective local admins on a target server.
9415
9416 .PARAMETER UserName
9417
9418 Specific username to search for.
9419
9420 .PARAMETER UserFilter
9421
9422 A customized ldap filter string to use for user enumeration, e.g. "(description=*admin*)"
9423
9424 .PARAMETER UserADSpath
9425
9426 The LDAP source to search through for users, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
9427 Useful for OU queries.
9428
9429 .PARAMETER UserFile
9430
9431 File of usernames to search for.
9432
9433 .PARAMETER AdminCount
9434
9435 Switch. Hunt for users with adminCount=1.
9436
9437 .PARAMETER AllowDelegation
9438
9439 Switch. Return user accounts that are not marked as 'sensitive and not allowed for delegation'
9440
9441 .PARAMETER StopOnSuccess
9442
9443 Switch. Stop hunting after finding after finding a target user.
9444
9445 .PARAMETER NoPing
9446
9447 Don't ping each host to ensure it's up before enumerating.
9448
9449 .PARAMETER CheckAccess
9450
9451 Switch. Check if the current user has local admin access to found machines.
9452
9453 .PARAMETER Delay
9454
9455 Delay between enumerating hosts, defaults to 0
9456
9457 .PARAMETER Jitter
9458
9459 Jitter for the host delay, defaults to +/- 0.3
9460
9461 .PARAMETER Domain
9462
9463 Domain for query for machines, defaults to the current domain.
9464
9465 .PARAMETER DomainController
9466
9467 Domain controller to reflect LDAP queries through.
9468
9469 .PARAMETER ShowAll
9470
9471 Switch. Return all user location results, i.e. Invoke-UserView functionality.
9472
9473 .PARAMETER SearchForest
9474
9475 Switch. Search all domains in the forest for target users instead of just
9476 a single domain.
9477
9478 .PARAMETER Stealth
9479
9480 Switch. Only enumerate sessions from connonly used target servers.
9481
9482 .PARAMETER StealthSource
9483
9484 The source of target servers to use, 'DFS' (distributed file servers),
9485 'DC' (domain controllers), 'File' (file servers), or 'All'
9486
9487 .PARAMETER ForeignUsers
9488
9489 Switch. Only return results that are not part of searched domain.
9490
9491 .PARAMETER Threads
9492
9493 The maximum concurrent threads to execute.
9494
9495 .PARAMETER Poll
9496
9497 Continuously poll for sessions for the given duration. Automatically
9498 sets Threads to the number of computers being polled.
9499
9500 .EXAMPLE
9501
9502 PS C:\> Invoke-UserHunter -CheckAccess
9503
9504 Finds machines on the local domain where domain admins are logged into
9505 and checks if the current user has local administrator access.
9506
9507 .EXAMPLE
9508
9509 PS C:\> Invoke-UserHunter -Domain 'testing'
9510
9511 Finds machines on the 'testing' domain where domain admins are logged into.
9512
9513 .EXAMPLE
9514
9515 PS C:\> Invoke-UserHunter -Threads 20
9516
9517 Multi-threaded user hunting, replaces Invoke-UserHunterThreaded.
9518
9519 .EXAMPLE
9520
9521 PS C:\> Invoke-UserHunter -UserFile users.txt -ComputerFile hosts.txt
9522
9523 Finds machines in hosts.txt where any members of users.txt are logged in
9524 or have sessions.
9525
9526 .EXAMPLE
9527
9528 PS C:\> Invoke-UserHunter -GroupName "Power Users" -Delay 60
9529
9530 Find machines on the domain where members of the "Power Users" groups are
9531 logged into with a 60 second (+/- *.3) randomized delay between
9532 touching each host.
9533
9534 .EXAMPLE
9535
9536 PS C:\> Invoke-UserHunter -TargetServer FILESERVER
9537
9538 Query FILESERVER for useres who are effective local administrators using
9539 Get-NetLocalGroup -Recurse, and hunt for that user set on the network.
9540
9541 .EXAMPLE
9542
9543 PS C:\> Invoke-UserHunter -SearchForest
9544
9545 Find all machines in the current forest where domain admins are logged in.
9546
9547 .EXAMPLE
9548
9549 PS C:\> Invoke-UserHunter -Stealth
9550
9551 Executes old Invoke-StealthUserHunter functionality, enumerating commonly
9552 used servers and checking just sessions for each.
9553
9554 .EXAMPLE
9555
9556 PS C:\> Invoke-UserHunter -Stealth -StealthSource DC -Poll 3600 -Delay 5 -ShowAll | ? { ! $_.UserName.EndsWith('$') }
9557
9558 Poll Domain Controllers in parallel for sessions for an hour, waiting five
9559 seconds before querying each DC again and filtering out computer accounts.
9560
9561 .LINK
9562 http://blog.harmj0y.net
9563#>
9564
9565 [CmdletBinding()]
9566 param(
9567 [Parameter(Position=0,ValueFromPipeline=$True)]
9568 [Alias('Hosts')]
9569 [String[]]
9570 $ComputerName,
9571
9572 [ValidateScript({Test-Path -Path $_ })]
9573 [Alias('HostList')]
9574 [String]
9575 $ComputerFile,
9576
9577 [String]
9578 $ComputerFilter,
9579
9580 [String]
9581 $ComputerADSpath,
9582
9583 [Switch]
9584 $Unconstrained,
9585
9586 [String]
9587 $GroupName = 'Domain Admins',
9588
9589 [String]
9590 $TargetServer,
9591
9592 [String]
9593 $UserName,
9594
9595 [String]
9596 $UserFilter,
9597
9598 [String]
9599 $UserADSpath,
9600
9601 [ValidateScript({Test-Path -Path $_ })]
9602 [String]
9603 $UserFile,
9604
9605 [Switch]
9606 $AdminCount,
9607
9608 [Switch]
9609 $AllowDelegation,
9610
9611 [Switch]
9612 $CheckAccess,
9613
9614 [Switch]
9615 $StopOnSuccess,
9616
9617 [Switch]
9618 $NoPing,
9619
9620 [UInt32]
9621 $Delay = 0,
9622
9623 [Double]
9624 $Jitter = .3,
9625
9626 [String]
9627 $Domain,
9628
9629 [String]
9630 $DomainController,
9631
9632 [Switch]
9633 $ShowAll,
9634
9635 [Switch]
9636 $SearchForest,
9637
9638 [Switch]
9639 $Stealth,
9640
9641 [String]
9642 [ValidateSet("DFS","DC","File","All")]
9643 $StealthSource ="All",
9644
9645 [Switch]
9646 $ForeignUsers,
9647
9648 [Int]
9649 [ValidateRange(1,100)]
9650 $Threads,
9651
9652 [UInt32]
9653 $Poll = 0
9654 )
9655
9656 begin {
9657
9658 if ($PSBoundParameters['Debug']) {
9659 $DebugPreference = 'Continue'
9660 }
9661
9662 Write-Verbose "[*] Running Invoke-UserHunter with delay of $Delay"
9663
9664 #####################################################
9665 #
9666 # First we build the host target set
9667 #
9668 #####################################################
9669
9670 if($ComputerFile) {
9671 # if we're using a host list, read the targets in and add them to the target list
9672 $ComputerName = Get-Content -Path $ComputerFile
9673 }
9674
9675 if(!$ComputerName) {
9676 [Array]$ComputerName = @()
9677
9678 if($Domain) {
9679 $TargetDomains = @($Domain)
9680 }
9681 elseif($SearchForest) {
9682 # get ALL the domains in the forest to search
9683 $TargetDomains = Get-NetForestDomain | ForEach-Object { $_.Name }
9684 }
9685 else {
9686 # use the local domain
9687 $TargetDomains = @( (Get-NetDomain).name )
9688 }
9689
9690 if($Stealth) {
9691 Write-Verbose "Stealth mode! Enumerating commonly used servers"
9692 Write-Verbose "Stealth source: $StealthSource"
9693
9694 ForEach ($Domain in $TargetDomains) {
9695 if (($StealthSource -eq "File") -or ($StealthSource -eq "All")) {
9696 Write-Verbose "[*] Querying domain $Domain for File Servers..."
9697 $ComputerName += Get-NetFileServer -Domain $Domain -DomainController $DomainController
9698 }
9699 if (($StealthSource -eq "DFS") -or ($StealthSource -eq "All")) {
9700 Write-Verbose "[*] Querying domain $Domain for DFS Servers..."
9701 $ComputerName += Get-DFSshare -Domain $Domain -DomainController $DomainController | ForEach-Object {$_.RemoteServerName}
9702 }
9703 if (($StealthSource -eq "DC") -or ($StealthSource -eq "All")) {
9704 Write-Verbose "[*] Querying domain $Domain for Domain Controllers..."
9705 $ComputerName += Get-NetDomainController -LDAP -Domain $Domain -DomainController $DomainController | ForEach-Object { $_.dnshostname}
9706 }
9707 }
9708 }
9709 else {
9710 ForEach ($Domain in $TargetDomains) {
9711 Write-Verbose "[*] Querying domain $Domain for hosts"
9712
9713 $Arguments = @{
9714 'Domain' = $Domain
9715 'DomainController' = $DomainController
9716 'ADSpath' = $ADSpath
9717 'Filter' = $ComputerFilter
9718 'Unconstrained' = $Unconstrained
9719 }
9720
9721 $ComputerName += Get-NetComputer @Arguments
9722 }
9723 }
9724
9725 # remove any null target hosts, uniquify the list and shuffle it
9726 $ComputerName = $ComputerName | Where-Object { $_ } | Sort-Object -Unique | Sort-Object { Get-Random }
9727 if($($ComputerName.Count) -eq 0) {
9728 throw "No hosts found!"
9729 }
9730 }
9731
9732 if ($Poll -gt 0) {
9733 Write-Verbose "[*] Polling for $Poll seconds. Automatically enabling threaded mode."
9734 if ($ComputerName.Count -gt 100) {
9735 throw "Too many hosts to poll! Try fewer than 100."
9736 }
9737 $Threads = $ComputerName.Count
9738 }
9739
9740 #####################################################
9741 #
9742 # Now we build the user target set
9743 #
9744 #####################################################
9745
9746 # users we're going to be searching for
9747 $TargetUsers = @()
9748
9749 # get the current user so we can ignore it in the results
9750 $CurrentUser = ([Environment]::UserName).toLower()
9751
9752 # if we're showing all results, skip username enumeration
9753 if($ShowAll -or $ForeignUsers) {
9754 $User = New-Object PSObject
9755 $User | Add-Member Noteproperty 'MemberDomain' $Null
9756 $User | Add-Member Noteproperty 'MemberName' '*'
9757 $TargetUsers = @($User)
9758
9759 if($ForeignUsers) {
9760 # if we're searching for user results not in the primary domain
9761 $krbtgtName = Convert-ADName -ObjectName "krbtgt@$($Domain)" -InputType Simple -OutputType NT4
9762 $DomainShortName = $krbtgtName.split("\")[0]
9763 }
9764 }
9765 # if we want to hunt for the effective domain users who can access a target server
9766 elseif($TargetServer) {
9767 Write-Verbose "Querying target server '$TargetServer' for local users"
9768 $TargetUsers = Get-NetLocalGroup $TargetServer -Recurse | Where-Object {(-not $_.IsGroup) -and $_.IsDomain } | ForEach-Object {
9769 $User = New-Object PSObject
9770 $User | Add-Member Noteproperty 'MemberDomain' ($_.AccountName).split("/")[0].toLower()
9771 $User | Add-Member Noteproperty 'MemberName' ($_.AccountName).split("/")[1].toLower()
9772 $User
9773 } | Where-Object {$_}
9774 }
9775 # if we get a specific username, only use that
9776 elseif($UserName) {
9777 Write-Verbose "[*] Using target user '$UserName'..."
9778 $User = New-Object PSObject
9779 if($TargetDomains) {
9780 $User | Add-Member Noteproperty 'MemberDomain' $TargetDomains[0]
9781 }
9782 else {
9783 $User | Add-Member Noteproperty 'MemberDomain' $Null
9784 }
9785 $User | Add-Member Noteproperty 'MemberName' $UserName.ToLower()
9786 $TargetUsers = @($User)
9787 }
9788 # read in a target user list if we have one
9789 elseif($UserFile) {
9790 $TargetUsers = Get-Content -Path $UserFile | ForEach-Object {
9791 $User = New-Object PSObject
9792 if($TargetDomains) {
9793 $User | Add-Member Noteproperty 'MemberDomain' $TargetDomains[0]
9794 }
9795 else {
9796 $User | Add-Member Noteproperty 'MemberDomain' $Null
9797 }
9798 $User | Add-Member Noteproperty 'MemberName' $_
9799 $User
9800 } | Where-Object {$_}
9801 }
9802 elseif($UserADSpath -or $UserFilter -or $AdminCount) {
9803 ForEach ($Domain in $TargetDomains) {
9804
9805 $Arguments = @{
9806 'Domain' = $Domain
9807 'DomainController' = $DomainController
9808 'ADSpath' = $UserADSpath
9809 'Filter' = $UserFilter
9810 'AdminCount' = $AdminCount
9811 'AllowDelegation' = $AllowDelegation
9812 }
9813
9814 Write-Verbose "[*] Querying domain $Domain for users"
9815 $TargetUsers += Get-NetUser @Arguments | ForEach-Object {
9816 $User = New-Object PSObject
9817 $User | Add-Member Noteproperty 'MemberDomain' $Domain
9818 $User | Add-Member Noteproperty 'MemberName' $_.samaccountname
9819 $User
9820 } | Where-Object {$_}
9821
9822 }
9823 }
9824 else {
9825 ForEach ($Domain in $TargetDomains) {
9826 Write-Verbose "[*] Querying domain $Domain for users of group '$GroupName'"
9827 $TargetUsers += Get-NetGroupMember -GroupName $GroupName -Domain $Domain -DomainController $DomainController
9828 }
9829 }
9830
9831 if (( (-not $ShowAll) -and (-not $ForeignUsers) ) -and ((!$TargetUsers) -or ($TargetUsers.Count -eq 0))) {
9832 throw "[!] No users found to search for!"
9833 }
9834
9835 # script block that enumerates a server
9836 $HostEnumBlock = {
9837 param($ComputerName, $Ping, $TargetUsers, $CurrentUser, $Stealth, $DomainShortName, $Poll, $Delay, $Jitter)
9838
9839 # optionally check if the server is up first
9840 $Up = $True
9841 if($Ping) {
9842 $Up = Test-Connection -Count 1 -Quiet -ComputerName $ComputerName
9843 }
9844 if($Up) {
9845 $Timer = [System.Diagnostics.Stopwatch]::StartNew()
9846 $RandNo = New-Object System.Random
9847
9848 Do {
9849 if(!$DomainShortName) {
9850 # if we're not searching for foreign users, check session information
9851 $Sessions = Get-NetSession -ComputerName $ComputerName
9852 ForEach ($Session in $Sessions) {
9853 $UserName = $Session.sesi10_username
9854 $CName = $Session.sesi10_cname
9855
9856 if($CName -and $CName.StartsWith("\\")) {
9857 $CName = $CName.TrimStart("\")
9858 }
9859
9860 # make sure we have a result
9861 if (($UserName) -and ($UserName.trim() -ne '') -and (!($UserName -match $CurrentUser))) {
9862
9863 $TargetUsers | Where-Object {$UserName -like $_.MemberName} | ForEach-Object {
9864
9865 $IPAddress = @(Get-IPAddress -ComputerName $ComputerName)[0].IPAddress
9866 $FoundUser = New-Object PSObject
9867 $FoundUser | Add-Member Noteproperty 'UserDomain' $_.MemberDomain
9868 $FoundUser | Add-Member Noteproperty 'UserName' $UserName
9869 $FoundUser | Add-Member Noteproperty 'ComputerName' $ComputerName
9870 $FoundUser | Add-Member Noteproperty 'IPAddress' $IPAddress
9871 $FoundUser | Add-Member Noteproperty 'SessionFrom' $CName
9872
9873 # Try to resolve the DNS hostname of $Cname
9874 try {
9875 $CNameDNSName = [System.Net.Dns]::GetHostEntry($CName) | Select-Object -ExpandProperty HostName
9876 $FoundUser | Add-Member NoteProperty 'SessionFromName' $CnameDNSName
9877 }
9878 catch {
9879 $FoundUser | Add-Member NoteProperty 'SessionFromName' $Null
9880 }
9881
9882 # see if we're checking to see if we have local admin access on this machine
9883 if ($CheckAccess) {
9884 $Admin = Invoke-CheckLocalAdminAccess -ComputerName $CName
9885 $FoundUser | Add-Member Noteproperty 'LocalAdmin' $Admin.IsAdmin
9886 }
9887 else {
9888 $FoundUser | Add-Member Noteproperty 'LocalAdmin' $Null
9889 }
9890 $FoundUser.PSObject.TypeNames.Add('PowerView.UserSession')
9891 $FoundUser
9892 }
9893 }
9894 }
9895 }
9896 if(!$Stealth) {
9897 # if we're not 'stealthy', enumerate loggedon users as well
9898 $LoggedOn = Get-NetLoggedon -ComputerName $ComputerName
9899 ForEach ($User in $LoggedOn) {
9900 $UserName = $User.wkui1_username
9901 # TODO: translate domain to authoratative name
9902 # then match domain name ?
9903 $UserDomain = $User.wkui1_logon_domain
9904
9905 # make sure wet have a result
9906 if (($UserName) -and ($UserName.trim() -ne '')) {
9907
9908 $TargetUsers | Where-Object {$UserName -like $_.MemberName} | ForEach-Object {
9909
9910 $Proceed = $True
9911 if($DomainShortName) {
9912 if ($DomainShortName.ToLower() -ne $UserDomain.ToLower()) {
9913 $Proceed = $True
9914 }
9915 else {
9916 $Proceed = $False
9917 }
9918 }
9919 if($Proceed) {
9920 $IPAddress = @(Get-IPAddress -ComputerName $ComputerName)[0].IPAddress
9921 $FoundUser = New-Object PSObject
9922 $FoundUser | Add-Member Noteproperty 'UserDomain' $UserDomain
9923 $FoundUser | Add-Member Noteproperty 'UserName' $UserName
9924 $FoundUser | Add-Member Noteproperty 'ComputerName' $ComputerName
9925 $FoundUser | Add-Member Noteproperty 'IPAddress' $IPAddress
9926 $FoundUser | Add-Member Noteproperty 'SessionFrom' $Null
9927 $FoundUser | Add-Member Noteproperty 'SessionFromName' $Null
9928
9929 # see if we're checking to see if we have local admin access on this machine
9930 if ($CheckAccess) {
9931 $Admin = Invoke-CheckLocalAdminAccess -ComputerName $ComputerName
9932 $FoundUser | Add-Member Noteproperty 'LocalAdmin' $Admin.IsAdmin
9933 }
9934 else {
9935 $FoundUser | Add-Member Noteproperty 'LocalAdmin' $Null
9936 }
9937 $FoundUser.PSObject.TypeNames.Add('PowerView.UserSession')
9938 $FoundUser
9939 }
9940 }
9941 }
9942 }
9943 }
9944
9945 if ($Poll -gt 0) {
9946 Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay)
9947 }
9948 } While ($Poll -gt 0 -and $Timer.Elapsed.TotalSeconds -lt $Poll)
9949 }
9950 }
9951 }
9952
9953 process {
9954
9955 if($Threads) {
9956 Write-Verbose "Using threading with threads = $Threads"
9957
9958 # if we're using threading, kick off the script block with Invoke-ThreadedFunction
9959 $ScriptParams = @{
9960 'Ping' = $(-not $NoPing)
9961 'TargetUsers' = $TargetUsers
9962 'CurrentUser' = $CurrentUser
9963 'Stealth' = $Stealth
9964 'DomainShortName' = $DomainShortName
9965 'Poll' = $Poll
9966 'Delay' = $Delay
9967 'Jitter' = $Jitter
9968 }
9969
9970 # kick off the threaded script block + arguments
9971 Invoke-ThreadedFunction -ComputerName $ComputerName -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams -Threads $Threads
9972 }
9973
9974 else {
9975 if(-not $NoPing -and ($ComputerName.count -ne 1)) {
9976 # ping all hosts in parallel
9977 $Ping = {param($ComputerName) if(Test-Connection -ComputerName $ComputerName -Count 1 -Quiet -ErrorAction Stop){$ComputerName}}
9978 $ComputerName = Invoke-ThreadedFunction -NoImports -ComputerName $ComputerName -ScriptBlock $Ping -Threads 100
9979 }
9980
9981 Write-Verbose "[*] Total number of active hosts: $($ComputerName.count)"
9982 $Counter = 0
9983 $RandNo = New-Object System.Random
9984
9985 ForEach ($Computer in $ComputerName) {
9986
9987 $Counter = $Counter + 1
9988
9989 # sleep for our semi-randomized interval
9990 Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay)
9991
9992 Write-Verbose "[*] Enumerating server $Computer ($Counter of $($ComputerName.count))"
9993 $Result = Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $Computer, $False, $TargetUsers, $CurrentUser, $Stealth, $DomainShortName, 0, 0, 0
9994 $Result
9995
9996 if($Result -and $StopOnSuccess) {
9997 Write-Verbose "[*] Target user found, returning early"
9998 return
9999 }
10000 }
10001 }
10002
10003 }
10004}
10005
10006
10007function Invoke-StealthUserHunter {
10008 [CmdletBinding()]
10009 param(
10010 [Parameter(Position=0,ValueFromPipeline=$True)]
10011 [Alias('Hosts')]
10012 [String[]]
10013 $ComputerName,
10014
10015 [ValidateScript({Test-Path -Path $_ })]
10016 [Alias('HostList')]
10017 [String]
10018 $ComputerFile,
10019
10020 [String]
10021 $ComputerFilter,
10022
10023 [String]
10024 $ComputerADSpath,
10025
10026 [String]
10027 $GroupName = 'Domain Admins',
10028
10029 [String]
10030 $TargetServer,
10031
10032 [String]
10033 $UserName,
10034
10035 [String]
10036 $UserFilter,
10037
10038 [String]
10039 $UserADSpath,
10040
10041 [ValidateScript({Test-Path -Path $_ })]
10042 [String]
10043 $UserFile,
10044
10045 [Switch]
10046 $CheckAccess,
10047
10048 [Switch]
10049 $StopOnSuccess,
10050
10051 [Switch]
10052 $NoPing,
10053
10054 [UInt32]
10055 $Delay = 0,
10056
10057 [Double]
10058 $Jitter = .3,
10059
10060 [String]
10061 $Domain,
10062
10063 [Switch]
10064 $ShowAll,
10065
10066 [Switch]
10067 $SearchForest,
10068
10069 [String]
10070 [ValidateSet("DFS","DC","File","All")]
10071 $StealthSource ="All"
10072 )
10073 # kick off Invoke-UserHunter with stealth options
10074 Invoke-UserHunter -Stealth @PSBoundParameters
10075}
10076
10077
10078function Invoke-ProcessHunter {
10079<#
10080 .SYNOPSIS
10081
10082 Query the process lists of remote machines, searching for
10083 processes with a specific name or owned by a specific user.
10084 Thanks to @paulbrandau for the approach idea.
10085
10086 Author: @harmj0y
10087 License: BSD 3-Clause
10088
10089 .PARAMETER ComputerName
10090
10091 Host array to enumerate, passable on the pipeline.
10092
10093 .PARAMETER ComputerFile
10094
10095 File of hostnames/IPs to search.
10096
10097 .PARAMETER ComputerFilter
10098
10099 Host filter name to query AD for, wildcards accepted.
10100
10101 .PARAMETER ComputerADSpath
10102
10103 The LDAP source to search through for hosts, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
10104 Useful for OU queries.
10105
10106 .PARAMETER ProcessName
10107
10108 The name of the process to hunt, or a comma separated list of names.
10109
10110 .PARAMETER GroupName
10111
10112 Group name to query for target users.
10113
10114 .PARAMETER TargetServer
10115
10116 Hunt for users who are effective local admins on a target server.
10117
10118 .PARAMETER UserName
10119
10120 Specific username to search for.
10121
10122 .PARAMETER UserFilter
10123
10124 A customized ldap filter string to use for user enumeration, e.g. "(description=*admin*)"
10125
10126 .PARAMETER UserADSpath
10127
10128 The LDAP source to search through for users, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
10129 Useful for OU queries.
10130
10131 .PARAMETER UserFile
10132
10133 File of usernames to search for.
10134
10135 .PARAMETER StopOnSuccess
10136
10137 Switch. Stop hunting after finding after finding a target user/process.
10138
10139 .PARAMETER NoPing
10140
10141 Switch. Don't ping each host to ensure it's up before enumerating.
10142
10143 .PARAMETER Delay
10144
10145 Delay between enumerating hosts, defaults to 0
10146
10147 .PARAMETER Jitter
10148
10149 Jitter for the host delay, defaults to +/- 0.3
10150
10151 .PARAMETER Domain
10152
10153 Domain for query for machines, defaults to the current domain.
10154
10155 .PARAMETER DomainController
10156
10157 Domain controller to reflect LDAP queries through.
10158
10159 .PARAMETER ShowAll
10160
10161 Switch. Return all user location results.
10162
10163 .PARAMETER SearchForest
10164
10165 Switch. Search all domains in the forest for target users instead of just
10166 a single domain.
10167
10168 .PARAMETER Threads
10169
10170 The maximum concurrent threads to execute.
10171
10172 .PARAMETER Credential
10173
10174 A [Management.Automation.PSCredential] object of alternate credentials
10175 for connection to the target machine/domain.
10176
10177 .EXAMPLE
10178
10179 PS C:\> Invoke-ProcessHunter -Domain 'testing'
10180
10181 Finds machines on the 'testing' domain where domain admins have a
10182 running process.
10183
10184 .EXAMPLE
10185
10186 PS C:\> Invoke-ProcessHunter -Threads 20
10187
10188 Multi-threaded process hunting, replaces Invoke-ProcessHunterThreaded.
10189
10190 .EXAMPLE
10191
10192 PS C:\> Invoke-ProcessHunter -UserFile users.txt -ComputerFile hosts.txt
10193
10194 Finds machines in hosts.txt where any members of users.txt have running
10195 processes.
10196
10197 .EXAMPLE
10198
10199 PS C:\> Invoke-ProcessHunter -GroupName "Power Users" -Delay 60
10200
10201 Find machines on the domain where members of the "Power Users" groups have
10202 running processes with a 60 second (+/- *.3) randomized delay between
10203 touching each host.
10204
10205 .LINK
10206
10207 http://blog.harmj0y.net
10208#>
10209
10210 [CmdletBinding()]
10211 param(
10212 [Parameter(Position=0,ValueFromPipeline=$True)]
10213 [Alias('Hosts')]
10214 [String[]]
10215 $ComputerName,
10216
10217 [ValidateScript({Test-Path -Path $_ })]
10218 [Alias('HostList')]
10219 [String]
10220 $ComputerFile,
10221
10222 [String]
10223 $ComputerFilter,
10224
10225 [String]
10226 $ComputerADSpath,
10227
10228 [String]
10229 $ProcessName,
10230
10231 [String]
10232 $GroupName = 'Domain Admins',
10233
10234 [String]
10235 $TargetServer,
10236
10237 [String]
10238 $UserName,
10239
10240 [String]
10241 $UserFilter,
10242
10243 [String]
10244 $UserADSpath,
10245
10246 [ValidateScript({Test-Path -Path $_ })]
10247 [String]
10248 $UserFile,
10249
10250 [Switch]
10251 $StopOnSuccess,
10252
10253 [Switch]
10254 $NoPing,
10255
10256 [UInt32]
10257 $Delay = 0,
10258
10259 [Double]
10260 $Jitter = .3,
10261
10262 [String]
10263 $Domain,
10264
10265 [String]
10266 $DomainController,
10267
10268 [Switch]
10269 $ShowAll,
10270
10271 [Switch]
10272 $SearchForest,
10273
10274 [ValidateRange(1,100)]
10275 [Int]
10276 $Threads,
10277
10278 [Management.Automation.PSCredential]
10279 $Credential
10280 )
10281
10282 begin {
10283
10284 if ($PSBoundParameters['Debug']) {
10285 $DebugPreference = 'Continue'
10286 }
10287
10288 # random object for delay
10289 $RandNo = New-Object System.Random
10290
10291 Write-Verbose "[*] Running Invoke-ProcessHunter with delay of $Delay"
10292
10293 #####################################################
10294 #
10295 # First we build the host target set
10296 #
10297 #####################################################
10298
10299 # if we're using a host list, read the targets in and add them to the target list
10300 if($ComputerFile) {
10301 $ComputerName = Get-Content -Path $ComputerFile
10302 }
10303
10304 if(!$ComputerName) {
10305 [array]$ComputerName = @()
10306
10307 if($Domain) {
10308 $TargetDomains = @($Domain)
10309 }
10310 elseif($SearchForest) {
10311 # get ALL the domains in the forest to search
10312 $TargetDomains = Get-NetForestDomain -DomainController $DomainController -Credential $Credential | ForEach-Object { $_.Name }
10313 }
10314 else {
10315 # use the local domain
10316 $TargetDomains = @( (Get-NetDomain -Domain $Domain -Credential $Credential).name )
10317 }
10318
10319 ForEach ($Domain in $TargetDomains) {
10320 Write-Verbose "[*] Querying domain $Domain for hosts"
10321 $ComputerName += Get-NetComputer -Domain $Domain -DomainController $DomainController -Credential $Credential -Filter $ComputerFilter -ADSpath $ComputerADSpath
10322 }
10323
10324 # remove any null target hosts, uniquify the list and shuffle it
10325 $ComputerName = $ComputerName | Where-Object { $_ } | Sort-Object -Unique | Sort-Object { Get-Random }
10326 if($($ComputerName.Count) -eq 0) {
10327 throw "No hosts found!"
10328 }
10329 }
10330
10331 #####################################################
10332 #
10333 # Now we build the user target set
10334 #
10335 #####################################################
10336
10337 if(!$ProcessName) {
10338 Write-Verbose "No process name specified, building a target user set"
10339
10340 # users we're going to be searching for
10341 $TargetUsers = @()
10342
10343 # if we want to hunt for the effective domain users who can access a target server
10344 if($TargetServer) {
10345 Write-Verbose "Querying target server '$TargetServer' for local users"
10346 $TargetUsers = Get-NetLocalGroup $TargetServer -Recurse | Where-Object {(-not $_.IsGroup) -and $_.IsDomain } | ForEach-Object {
10347 ($_.AccountName).split("/")[1].toLower()
10348 } | Where-Object {$_}
10349 }
10350 # if we get a specific username, only use that
10351 elseif($UserName) {
10352 Write-Verbose "[*] Using target user '$UserName'..."
10353 $TargetUsers = @( $UserName.ToLower() )
10354 }
10355 # read in a target user list if we have one
10356 elseif($UserFile) {
10357 $TargetUsers = Get-Content -Path $UserFile | Where-Object {$_}
10358 }
10359 elseif($UserADSpath -or $UserFilter) {
10360 ForEach ($Domain in $TargetDomains) {
10361 Write-Verbose "[*] Querying domain $Domain for users"
10362 $TargetUsers += Get-NetUser -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $UserADSpath -Filter $UserFilter | ForEach-Object {
10363 $_.samaccountname
10364 } | Where-Object {$_}
10365 }
10366 }
10367 else {
10368 ForEach ($Domain in $TargetDomains) {
10369 Write-Verbose "[*] Querying domain $Domain for users of group '$GroupName'"
10370 $TargetUsers += Get-NetGroupMember -GroupName $GroupName -Domain $Domain -DomainController $DomainController -Credential $Credential| ForEach-Object {
10371 $_.MemberName
10372 }
10373 }
10374 }
10375
10376 if ((-not $ShowAll) -and ((!$TargetUsers) -or ($TargetUsers.Count -eq 0))) {
10377 throw "[!] No users found to search for!"
10378 }
10379 }
10380
10381 # script block that enumerates a server
10382 $HostEnumBlock = {
10383 param($ComputerName, $Ping, $ProcessName, $TargetUsers, $Credential)
10384
10385 # optionally check if the server is up first
10386 $Up = $True
10387 if($Ping) {
10388 $Up = Test-Connection -Count 1 -Quiet -ComputerName $ComputerName
10389 }
10390 if($Up) {
10391 # try to enumerate all active processes on the remote host
10392 # and search for a specific process name
10393 $Processes = Get-NetProcess -Credential $Credential -ComputerName $ComputerName -ErrorAction SilentlyContinue
10394
10395 ForEach ($Process in $Processes) {
10396 # if we're hunting for a process name or comma-separated names
10397 if($ProcessName) {
10398 $ProcessName.split(",") | ForEach-Object {
10399 if ($Process.ProcessName -match $_) {
10400 $Process
10401 }
10402 }
10403 }
10404 # if the session user is in the target list, display some output
10405 elseif ($TargetUsers -contains $Process.User) {
10406 $Process
10407 }
10408 }
10409 }
10410 }
10411
10412 }
10413
10414 process {
10415
10416 if($Threads) {
10417 Write-Verbose "Using threading with threads = $Threads"
10418
10419 # if we're using threading, kick off the script block with Invoke-ThreadedFunction
10420 $ScriptParams = @{
10421 'Ping' = $(-not $NoPing)
10422 'ProcessName' = $ProcessName
10423 'TargetUsers' = $TargetUsers
10424 'Credential' = $Credential
10425 }
10426
10427 # kick off the threaded script block + arguments
10428 Invoke-ThreadedFunction -ComputerName $ComputerName -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams -Threads $Threads
10429 }
10430
10431 else {
10432 if(-not $NoPing -and ($ComputerName.count -ne 1)) {
10433 # ping all hosts in parallel
10434 $Ping = {param($ComputerName) if(Test-Connection -ComputerName $ComputerName -Count 1 -Quiet -ErrorAction Stop){$ComputerName}}
10435 $ComputerName = Invoke-ThreadedFunction -NoImports -ComputerName $ComputerName -ScriptBlock $Ping -Threads 100
10436 }
10437
10438 Write-Verbose "[*] Total number of active hosts: $($ComputerName.count)"
10439 $Counter = 0
10440
10441 ForEach ($Computer in $ComputerName) {
10442
10443 $Counter = $Counter + 1
10444
10445 # sleep for our semi-randomized interval
10446 Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay)
10447
10448 Write-Verbose "[*] Enumerating server $Computer ($Counter of $($ComputerName.count))"
10449 $Result = Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $Computer, $False, $ProcessName, $TargetUsers, $Credential
10450 $Result
10451
10452 if($Result -and $StopOnSuccess) {
10453 Write-Verbose "[*] Target user/process found, returning early"
10454 return
10455 }
10456 }
10457 }
10458 }
10459}
10460
10461
10462function Invoke-EventHunter {
10463<#
10464 .SYNOPSIS
10465
10466 Queries all domain controllers on the network for account
10467 logon events (ID 4624) and TGT request events (ID 4768),
10468 searching for target users.
10469
10470 Note: Domain Admin (or equiv) rights are needed to query
10471 this information from the DCs.
10472
10473 Author: @sixdub, @harmj0y
10474 License: BSD 3-Clause
10475
10476 .PARAMETER ComputerName
10477
10478 Host array to enumerate, passable on the pipeline.
10479
10480 .PARAMETER ComputerFile
10481
10482 File of hostnames/IPs to search.
10483
10484 .PARAMETER ComputerFilter
10485
10486 Host filter name to query AD for, wildcards accepted.
10487
10488 .PARAMETER ComputerADSpath
10489
10490 The LDAP source to search through for hosts, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
10491 Useful for OU queries.
10492
10493 .PARAMETER GroupName
10494
10495 Group name to query for target users.
10496
10497 .PARAMETER TargetServer
10498
10499 Hunt for users who are effective local admins on a target server.
10500
10501 .PARAMETER UserName
10502
10503 Specific username to search for.
10504
10505 .PARAMETER UserFilter
10506
10507 A customized ldap filter string to use for user enumeration, e.g. "(description=*admin*)"
10508
10509 .PARAMETER UserADSpath
10510
10511 The LDAP source to search through for users, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
10512 Useful for OU queries.
10513
10514 .PARAMETER UserFile
10515
10516 File of usernames to search for.
10517
10518 .PARAMETER NoPing
10519
10520 Don't ping each host to ensure it's up before enumerating.
10521
10522 .PARAMETER Domain
10523
10524 Domain for query for machines, defaults to the current domain.
10525
10526 .PARAMETER DomainController
10527
10528 Domain controller to reflect LDAP queries through.
10529
10530 .PARAMETER SearchDays
10531
10532 Number of days back to search logs for. Default 3.
10533
10534 .PARAMETER SearchForest
10535
10536 Switch. Search all domains in the forest for target users instead of just
10537 a single domain.
10538
10539 .PARAMETER Threads
10540
10541 The maximum concurrent threads to execute.
10542
10543 .PARAMETER Credential
10544
10545 A [Management.Automation.PSCredential] object of alternate credentials
10546 for connection to the target domain.
10547
10548 .EXAMPLE
10549
10550 PS C:\> Invoke-EventHunter
10551
10552 .LINK
10553
10554 http://blog.harmj0y.net
10555#>
10556
10557 [CmdletBinding()]
10558 param(
10559 [Parameter(Position=0,ValueFromPipeline=$True)]
10560 [Alias('Hosts')]
10561 [String[]]
10562 $ComputerName,
10563
10564 [ValidateScript({Test-Path -Path $_ })]
10565 [Alias('HostList')]
10566 [String]
10567 $ComputerFile,
10568
10569 [String]
10570 $ComputerFilter,
10571
10572 [String]
10573 $ComputerADSpath,
10574
10575 [String]
10576 $GroupName = 'Domain Admins',
10577
10578 [String]
10579 $TargetServer,
10580
10581 [String[]]
10582 $UserName,
10583
10584 [String]
10585 $UserFilter,
10586
10587 [String]
10588 $UserADSpath,
10589
10590 [ValidateScript({Test-Path -Path $_ })]
10591 [String]
10592 $UserFile,
10593
10594 [String]
10595 $Domain,
10596
10597 [String]
10598 $DomainController,
10599
10600 [Int32]
10601 $SearchDays = 3,
10602
10603 [Switch]
10604 $SearchForest,
10605
10606 [ValidateRange(1,100)]
10607 [Int]
10608 $Threads,
10609
10610 [Management.Automation.PSCredential]
10611 $Credential
10612 )
10613
10614 begin {
10615
10616 if ($PSBoundParameters['Debug']) {
10617 $DebugPreference = 'Continue'
10618 }
10619
10620 # random object for delay
10621 $RandNo = New-Object System.Random
10622
10623 Write-Verbose "[*] Running Invoke-EventHunter"
10624
10625 if($Domain) {
10626 $TargetDomains = @($Domain)
10627 }
10628 elseif($SearchForest) {
10629 # get ALL the domains in the forest to search
10630 $TargetDomains = Get-NetForestDomain | ForEach-Object { $_.Name }
10631 }
10632 else {
10633 # use the local domain
10634 $TargetDomains = @( (Get-NetDomain -Credential $Credential).name )
10635 }
10636
10637 #####################################################
10638 #
10639 # First we build the host target set
10640 #
10641 #####################################################
10642
10643 if(!$ComputerName) {
10644 # if we're using a host list, read the targets in and add them to the target list
10645 if($ComputerFile) {
10646 $ComputerName = Get-Content -Path $ComputerFile
10647 }
10648 elseif($ComputerFilter -or $ComputerADSpath) {
10649 [array]$ComputerName = @()
10650 ForEach ($Domain in $TargetDomains) {
10651 Write-Verbose "[*] Querying domain $Domain for hosts"
10652 $ComputerName += Get-NetComputer -Domain $Domain -DomainController $DomainController -Credential $Credential -Filter $ComputerFilter -ADSpath $ComputerADSpath
10653 }
10654 }
10655 else {
10656 # if a computer specifier isn't given, try to enumerate all domain controllers
10657 [array]$ComputerName = @()
10658 ForEach ($Domain in $TargetDomains) {
10659 Write-Verbose "[*] Querying domain $Domain for domain controllers"
10660 $ComputerName += Get-NetDomainController -LDAP -Domain $Domain -DomainController $DomainController -Credential $Credential | ForEach-Object { $_.dnshostname}
10661 }
10662 }
10663
10664 # remove any null target hosts, uniquify the list and shuffle it
10665 $ComputerName = $ComputerName | Where-Object { $_ } | Sort-Object -Unique | Sort-Object { Get-Random }
10666 if($($ComputerName.Count) -eq 0) {
10667 throw "No hosts found!"
10668 }
10669 }
10670
10671 #####################################################
10672 #
10673 # Now we build the user target set
10674 #
10675 #####################################################
10676
10677 # users we're going to be searching for
10678 $TargetUsers = @()
10679
10680 # if we want to hunt for the effective domain users who can access a target server
10681 if($TargetServer) {
10682 Write-Verbose "Querying target server '$TargetServer' for local users"
10683 $TargetUsers = Get-NetLocalGroup $TargetServer -Recurse | Where-Object {(-not $_.IsGroup) -and $_.IsDomain } | ForEach-Object {
10684 ($_.AccountName).split("/")[1].toLower()
10685 } | Where-Object {$_}
10686 }
10687 # if we get a specific username, only use that
10688 elseif($UserName) {
10689 # Write-Verbose "[*] Using target user '$UserName'..."
10690 $TargetUsers = $UserName | ForEach-Object {$_.ToLower()}
10691 if($TargetUsers -isnot [System.Array]) {
10692 $TargetUsers = @($TargetUsers)
10693 }
10694 }
10695 # read in a target user list if we have one
10696 elseif($UserFile) {
10697 $TargetUsers = Get-Content -Path $UserFile | Where-Object {$_}
10698 }
10699 elseif($UserADSpath -or $UserFilter) {
10700 ForEach ($Domain in $TargetDomains) {
10701 Write-Verbose "[*] Querying domain $Domain for users"
10702 $TargetUsers += Get-NetUser -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $UserADSpath -Filter $UserFilter | ForEach-Object {
10703 $_.samaccountname
10704 } | Where-Object {$_}
10705 }
10706 }
10707 else {
10708 ForEach ($Domain in $TargetDomains) {
10709 Write-Verbose "[*] Querying domain $Domain for users of group '$GroupName'"
10710 $TargetUsers += Get-NetGroupMember -GroupName $GroupName -Domain $Domain -DomainController $DomainController -Credential $Credential | ForEach-Object {
10711 $_.MemberName
10712 }
10713 }
10714 }
10715
10716 if (((!$TargetUsers) -or ($TargetUsers.Count -eq 0))) {
10717 throw "[!] No users found to search for!"
10718 }
10719
10720 # script block that enumerates a server
10721 $HostEnumBlock = {
10722 param($ComputerName, $Ping, $TargetUsers, $SearchDays, $Credential)
10723
10724 # optionally check if the server is up first
10725 $Up = $True
10726 if($Ping) {
10727 $Up = Test-Connection -Count 1 -Quiet -ComputerName $ComputerName
10728 }
10729 if($Up) {
10730 # try to enumerate
10731 if($Credential) {
10732 Get-UserEvent -ComputerName $ComputerName -Credential $Credential -EventType 'all' -DateStart ([DateTime]::Today.AddDays(-$SearchDays)) | Where-Object {
10733 # filter for the target user set
10734 $TargetUsers -contains $_.UserName
10735 }
10736 }
10737 else {
10738 Get-UserEvent -ComputerName $ComputerName -EventType 'all' -DateStart ([DateTime]::Today.AddDays(-$SearchDays)) | Where-Object {
10739 # filter for the target user set
10740 $TargetUsers -contains $_.UserName
10741 }
10742 }
10743 }
10744 }
10745
10746 }
10747
10748 process {
10749
10750 if($Threads) {
10751 Write-Verbose "Using threading with threads = $Threads"
10752
10753 # if we're using threading, kick off the script block with Invoke-ThreadedFunction
10754 $ScriptParams = @{
10755 'Ping' = $(-not $NoPing)
10756 'TargetUsers' = $TargetUsers
10757 'SearchDays' = $SearchDays
10758 'Credential' = $Credential
10759 }
10760
10761 # kick off the threaded script block + arguments
10762 Invoke-ThreadedFunction -ComputerName $ComputerName -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams -Threads $Threads
10763 }
10764
10765 else {
10766 if(-not $NoPing -and ($ComputerName.count -ne 1)) {
10767 # ping all hosts in parallel
10768 $Ping = {param($ComputerName) if(Test-Connection -ComputerName $ComputerName -Count 1 -Quiet -ErrorAction Stop){$ComputerName}}
10769 $ComputerName = Invoke-ThreadedFunction -NoImports -ComputerName $ComputerName -ScriptBlock $Ping -Threads 100
10770 }
10771
10772 Write-Verbose "[*] Total number of active hosts: $($ComputerName.count)"
10773 $Counter = 0
10774
10775 ForEach ($Computer in $ComputerName) {
10776
10777 $Counter = $Counter + 1
10778
10779 # sleep for our semi-randomized interval
10780 Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay)
10781
10782 Write-Verbose "[*] Enumerating server $Computer ($Counter of $($ComputerName.count))"
10783 Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $Computer, $(-not $NoPing), $TargetUsers, $SearchDays, $Credential
10784 }
10785 }
10786
10787 }
10788}
10789
10790
10791function Invoke-ShareFinder {
10792<#
10793 .SYNOPSIS
10794
10795 This function finds the local domain name for a host using Get-NetDomain,
10796 queries the domain for all active machines with Get-NetComputer, then for
10797 each server it lists of active shares with Get-NetShare. Non-standard shares
10798 can be filtered out with -Exclude* flags.
10799
10800 Author: @harmj0y
10801 License: BSD 3-Clause
10802
10803 .PARAMETER ComputerName
10804
10805 Host array to enumerate, passable on the pipeline.
10806
10807 .PARAMETER ComputerFile
10808
10809 File of hostnames/IPs to search.
10810
10811 .PARAMETER ComputerFilter
10812
10813 Host filter name to query AD for, wildcards accepted.
10814
10815 .PARAMETER ComputerADSpath
10816
10817 The LDAP source to search through for hosts, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
10818 Useful for OU queries.
10819
10820 .PARAMETER ExcludeStandard
10821
10822 Switch. Exclude standard shares from display (C$, IPC$, print$ etc.)
10823
10824 .PARAMETER ExcludePrint
10825
10826 Switch. Exclude the print$ share.
10827
10828 .PARAMETER ExcludeIPC
10829
10830 Switch. Exclude the IPC$ share.
10831
10832 .PARAMETER CheckShareAccess
10833
10834 Switch. Only display found shares that the local user has access to.
10835
10836 .PARAMETER CheckAdmin
10837
10838 Switch. Only display ADMIN$ shares the local user has access to.
10839
10840 .PARAMETER NoPing
10841
10842 Switch. Don't ping each host to ensure it's up before enumerating.
10843
10844 .PARAMETER Delay
10845
10846 Delay between enumerating hosts, defaults to 0.
10847
10848 .PARAMETER Jitter
10849
10850 Jitter for the host delay, defaults to +/- 0.3.
10851
10852 .PARAMETER Domain
10853
10854 Domain to query for machines, defaults to the current domain.
10855
10856 .PARAMETER DomainController
10857
10858 Domain controller to reflect LDAP queries through.
10859
10860 .PARAMETER SearchForest
10861
10862 Switch. Search all domains in the forest for target users instead of just
10863 a single domain.
10864
10865 .PARAMETER Threads
10866
10867 The maximum concurrent threads to execute.
10868
10869 .EXAMPLE
10870
10871 PS C:\> Invoke-ShareFinder -ExcludeStandard
10872
10873 Find non-standard shares on the domain.
10874
10875 .EXAMPLE
10876
10877 PS C:\> Invoke-ShareFinder -Threads 20
10878
10879 Multi-threaded share finding, replaces Invoke-ShareFinderThreaded.
10880
10881 .EXAMPLE
10882
10883 PS C:\> Invoke-ShareFinder -Delay 60
10884
10885 Find shares on the domain with a 60 second (+/- *.3)
10886 randomized delay between touching each host.
10887
10888 .EXAMPLE
10889
10890 PS C:\> Invoke-ShareFinder -ComputerFile hosts.txt
10891
10892 Find shares for machines in the specified hosts file.
10893
10894 .LINK
10895 http://blog.harmj0y.net
10896#>
10897
10898 [CmdletBinding()]
10899 param(
10900 [Parameter(Position=0,ValueFromPipeline=$True)]
10901 [Alias('Hosts')]
10902 [String[]]
10903 $ComputerName,
10904
10905 [ValidateScript({Test-Path -Path $_ })]
10906 [Alias('HostList')]
10907 [String]
10908 $ComputerFile,
10909
10910 [String]
10911 $ComputerFilter,
10912
10913 [String]
10914 $ComputerADSpath,
10915
10916 [Switch]
10917 $ExcludeStandard,
10918
10919 [Switch]
10920 $ExcludePrint,
10921
10922 [Switch]
10923 $ExcludeIPC,
10924
10925 [Switch]
10926 $NoPing,
10927
10928 [Switch]
10929 $CheckShareAccess,
10930
10931 [Switch]
10932 $CheckAdmin,
10933
10934 [UInt32]
10935 $Delay = 0,
10936
10937 [Double]
10938 $Jitter = .3,
10939
10940 [String]
10941 $Domain,
10942
10943 [String]
10944 $DomainController,
10945
10946 [Switch]
10947 $SearchForest,
10948
10949 [ValidateRange(1,100)]
10950 [Int]
10951 $Threads
10952 )
10953
10954 begin {
10955 if ($PSBoundParameters['Debug']) {
10956 $DebugPreference = 'Continue'
10957 }
10958
10959 # random object for delay
10960 $RandNo = New-Object System.Random
10961
10962 Write-Verbose "[*] Running Invoke-ShareFinder with delay of $Delay"
10963
10964 # figure out the shares we want to ignore
10965 [String[]] $ExcludedShares = @('')
10966
10967 if ($ExcludePrint) {
10968 $ExcludedShares = $ExcludedShares + "PRINT$"
10969 }
10970 if ($ExcludeIPC) {
10971 $ExcludedShares = $ExcludedShares + "IPC$"
10972 }
10973 if ($ExcludeStandard) {
10974 $ExcludedShares = @('', "ADMIN$", "IPC$", "C$", "PRINT$")
10975 }
10976
10977 # if we're using a host file list, read the targets in and add them to the target list
10978 if($ComputerFile) {
10979 $ComputerName = Get-Content -Path $ComputerFile
10980 }
10981
10982 if(!$ComputerName) {
10983 [array]$ComputerName = @()
10984
10985 if($Domain) {
10986 $TargetDomains = @($Domain)
10987 }
10988 elseif($SearchForest) {
10989 # get ALL the domains in the forest to search
10990 $TargetDomains = Get-NetForestDomain | ForEach-Object { $_.Name }
10991 }
10992 else {
10993 # use the local domain
10994 $TargetDomains = @( (Get-NetDomain).name )
10995 }
10996
10997 ForEach ($Domain in $TargetDomains) {
10998 Write-Verbose "[*] Querying domain $Domain for hosts"
10999 $ComputerName += Get-NetComputer -Domain $Domain -DomainController $DomainController -Filter $ComputerFilter -ADSpath $ComputerADSpath
11000 }
11001
11002 # remove any null target hosts, uniquify the list and shuffle it
11003 $ComputerName = $ComputerName | Where-Object { $_ } | Sort-Object -Unique | Sort-Object { Get-Random }
11004 if($($ComputerName.count) -eq 0) {
11005 throw "No hosts found!"
11006 }
11007 }
11008
11009 # script block that enumerates a server
11010 $HostEnumBlock = {
11011 param($ComputerName, $Ping, $CheckShareAccess, $ExcludedShares, $CheckAdmin)
11012
11013 # optionally check if the server is up first
11014 $Up = $True
11015 if($Ping) {
11016 $Up = Test-Connection -Count 1 -Quiet -ComputerName $ComputerName
11017 }
11018 if($Up) {
11019 # get the shares for this host and check what we find
11020 $Shares = Get-NetShare -ComputerName $ComputerName
11021 ForEach ($Share in $Shares) {
11022 Write-Verbose "[*] Server share: $Share"
11023 $NetName = $Share.shi1_netname
11024 $Remark = $Share.shi1_remark
11025 $Path = '\\'+$ComputerName+'\'+$NetName
11026
11027 # make sure we get a real share name back
11028 if (($NetName) -and ($NetName.trim() -ne '')) {
11029 # if we're just checking for access to ADMIN$
11030 if($CheckAdmin) {
11031 if($NetName.ToUpper() -eq "ADMIN$") {
11032 try {
11033 $Null = [IO.Directory]::GetFiles($Path)
11034 "\\$ComputerName\$NetName `t- $Remark"
11035 }
11036 catch {
11037 Write-Verbose "Error accessing path $Path : $_"
11038 }
11039 }
11040 }
11041 # skip this share if it's in the exclude list
11042 elseif ($ExcludedShares -NotContains $NetName.ToUpper()) {
11043 # see if we want to check access to this share
11044 if($CheckShareAccess) {
11045 # check if the user has access to this path
11046 try {
11047 $Null = [IO.Directory]::GetFiles($Path)
11048 "\\$ComputerName\$NetName `t- $Remark"
11049 }
11050 catch {
11051 Write-Verbose "Error accessing path $Path : $_"
11052 }
11053 }
11054 else {
11055 "\\$ComputerName\$NetName `t- $Remark"
11056 }
11057 }
11058 }
11059 }
11060 }
11061 }
11062
11063 }
11064
11065 process {
11066
11067 if($Threads) {
11068 Write-Verbose "Using threading with threads = $Threads"
11069
11070 # if we're using threading, kick off the script block with Invoke-ThreadedFunction
11071 $ScriptParams = @{
11072 'Ping' = $(-not $NoPing)
11073 'CheckShareAccess' = $CheckShareAccess
11074 'ExcludedShares' = $ExcludedShares
11075 'CheckAdmin' = $CheckAdmin
11076 }
11077
11078 # kick off the threaded script block + arguments
11079 Invoke-ThreadedFunction -ComputerName $ComputerName -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams -Threads $Threads
11080 }
11081
11082 else {
11083 if(-not $NoPing -and ($ComputerName.count -ne 1)) {
11084 # ping all hosts in parallel
11085 $Ping = {param($ComputerName) if(Test-Connection -ComputerName $ComputerName -Count 1 -Quiet -ErrorAction Stop){$ComputerName}}
11086 $ComputerName = Invoke-ThreadedFunction -NoImports -ComputerName $ComputerName -ScriptBlock $Ping -Threads 100
11087 }
11088
11089 Write-Verbose "[*] Total number of active hosts: $($ComputerName.count)"
11090 $Counter = 0
11091
11092 ForEach ($Computer in $ComputerName) {
11093
11094 $Counter = $Counter + 1
11095
11096 # sleep for our semi-randomized interval
11097 Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay)
11098
11099 Write-Verbose "[*] Enumerating server $Computer ($Counter of $($ComputerName.count))"
11100 Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $Computer, $False, $CheckShareAccess, $ExcludedShares, $CheckAdmin
11101 }
11102 }
11103
11104 }
11105}
11106
11107
11108function Invoke-FileFinder {
11109<#
11110 .SYNOPSIS
11111
11112 Finds sensitive files on the domain.
11113
11114 Author: @harmj0y
11115 License: BSD 3-Clause
11116
11117 .DESCRIPTION
11118
11119 This function finds the local domain name for a host using Get-NetDomain,
11120 queries the domain for all active machines with Get-NetComputer, grabs
11121 the readable shares for each server, and recursively searches every
11122 share for files with specific keywords in the name.
11123 If a share list is passed, EVERY share is enumerated regardless of
11124 other options.
11125
11126 .PARAMETER ComputerName
11127
11128 Host array to enumerate, passable on the pipeline.
11129
11130 .PARAMETER ComputerFile
11131
11132 File of hostnames/IPs to search.
11133
11134 .PARAMETER ComputerFilter
11135
11136 Host filter name to query AD for, wildcards accepted.
11137
11138 .PARAMETER ComputerADSpath
11139
11140 The LDAP source to search through for hosts, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
11141 Useful for OU queries.
11142
11143 .PARAMETER ShareList
11144
11145 List if \\HOST\shares to search through.
11146
11147 .PARAMETER Terms
11148
11149 Terms to search for.
11150
11151 .PARAMETER OfficeDocs
11152
11153 Switch. Search for office documents (*.doc*, *.xls*, *.ppt*)
11154
11155 .PARAMETER FreshEXEs
11156
11157 Switch. Find .EXEs accessed within the last week.
11158
11159 .PARAMETER LastAccessTime
11160
11161 Only return files with a LastAccessTime greater than this date value.
11162
11163 .PARAMETER LastWriteTime
11164
11165 Only return files with a LastWriteTime greater than this date value.
11166
11167 .PARAMETER CreationTime
11168
11169 Only return files with a CreationDate greater than this date value.
11170
11171 .PARAMETER IncludeC
11172
11173 Switch. Include any C$ shares in recursive searching (default ignore).
11174
11175 .PARAMETER IncludeAdmin
11176
11177 Switch. Include any ADMIN$ shares in recursive searching (default ignore).
11178
11179 .PARAMETER ExcludeFolders
11180
11181 Switch. Exclude folders from the search results.
11182
11183 .PARAMETER ExcludeHidden
11184
11185 Switch. Exclude hidden files and folders from the search results.
11186
11187 .PARAMETER CheckWriteAccess
11188
11189 Switch. Only returns files the current user has write access to.
11190
11191 .PARAMETER OutFile
11192
11193 Output results to a specified csv output file.
11194
11195 .PARAMETER NoClobber
11196
11197 Switch. Don't overwrite any existing output file.
11198
11199 .PARAMETER NoPing
11200
11201 Switch. Don't ping each host to ensure it's up before enumerating.
11202
11203 .PARAMETER Delay
11204
11205 Delay between enumerating hosts, defaults to 0
11206
11207 .PARAMETER Jitter
11208
11209 Jitter for the host delay, defaults to +/- 0.3
11210
11211 .PARAMETER Domain
11212
11213 Domain to query for machines, defaults to the current domain.
11214
11215 .PARAMETER DomainController
11216
11217 Domain controller to reflect LDAP queries through.
11218
11219 .PARAMETER SearchForest
11220
11221 Search all domains in the forest for target users instead of just
11222 a single domain.
11223
11224 .PARAMETER SearchSYSVOL
11225
11226 Switch. Search for login scripts on the SYSVOL of the primary DCs for each specified domain.
11227
11228 .PARAMETER Threads
11229
11230 The maximum concurrent threads to execute.
11231
11232 .PARAMETER UsePSDrive
11233
11234 Switch. Mount target remote path with temporary PSDrives.
11235
11236 .EXAMPLE
11237
11238 PS C:\> Invoke-FileFinder
11239
11240 Find readable files on the domain with 'pass', 'sensitive',
11241 'secret', 'admin', 'login', or 'unattend*.xml' in the name,
11242
11243 .EXAMPLE
11244
11245 PS C:\> Invoke-FileFinder -Domain testing
11246
11247 Find readable files on the 'testing' domain with 'pass', 'sensitive',
11248 'secret', 'admin', 'login', or 'unattend*.xml' in the name,
11249
11250 .EXAMPLE
11251
11252 PS C:\> Invoke-FileFinder -IncludeC
11253
11254 Find readable files on the domain with 'pass', 'sensitive',
11255 'secret', 'admin', 'login' or 'unattend*.xml' in the name,
11256 including C$ shares.
11257
11258 .EXAMPLE
11259
11260 PS C:\> Invoke-FileFinder -ShareList shares.txt -Terms accounts,ssn -OutFile out.csv
11261
11262 Enumerate a specified share list for files with 'accounts' or
11263 'ssn' in the name, and write everything to "out.csv"
11264
11265 .LINK
11266 http://www.harmj0y.net/blog/redteaming/file-server-triage-on-red-team-engagements/
11267
11268#>
11269
11270 [CmdletBinding()]
11271 param(
11272 [Parameter(Position=0,ValueFromPipeline=$True)]
11273 [Alias('Hosts')]
11274 [String[]]
11275 $ComputerName,
11276
11277 [ValidateScript({Test-Path -Path $_ })]
11278 [Alias('HostList')]
11279 [String]
11280 $ComputerFile,
11281
11282 [String]
11283 $ComputerFilter,
11284
11285 [String]
11286 $ComputerADSpath,
11287
11288 [ValidateScript({Test-Path -Path $_ })]
11289 [String]
11290 $ShareList,
11291
11292 [Switch]
11293 $OfficeDocs,
11294
11295 [Switch]
11296 $FreshEXEs,
11297
11298 [Alias('Terms')]
11299 [String[]]
11300 $SearchTerms,
11301
11302 [ValidateScript({Test-Path -Path $_ })]
11303 [String]
11304 $TermList,
11305
11306 [String]
11307 $LastAccessTime,
11308
11309 [String]
11310 $LastWriteTime,
11311
11312 [String]
11313 $CreationTime,
11314
11315 [Switch]
11316 $IncludeC,
11317
11318 [Switch]
11319 $IncludeAdmin,
11320
11321 [Switch]
11322 $ExcludeFolders,
11323
11324 [Switch]
11325 $ExcludeHidden,
11326
11327 [Switch]
11328 $CheckWriteAccess,
11329
11330 [String]
11331 $OutFile,
11332
11333 [Switch]
11334 $NoClobber,
11335
11336 [Switch]
11337 $NoPing,
11338
11339 [UInt32]
11340 $Delay = 0,
11341
11342 [Double]
11343 $Jitter = .3,
11344
11345 [String]
11346 $Domain,
11347
11348 [String]
11349 $DomainController,
11350
11351 [Switch]
11352 $SearchForest,
11353
11354 [Switch]
11355 $SearchSYSVOL,
11356
11357 [ValidateRange(1,100)]
11358 [Int]
11359 $Threads,
11360
11361 [Switch]
11362 $UsePSDrive
11363 )
11364
11365 begin {
11366 if ($PSBoundParameters['Debug']) {
11367 $DebugPreference = 'Continue'
11368 }
11369
11370 # random object for delay
11371 $RandNo = New-Object System.Random
11372
11373 Write-Verbose "[*] Running Invoke-FileFinder with delay of $Delay"
11374
11375 $Shares = @()
11376
11377 # figure out the shares we want to ignore
11378 [String[]] $ExcludedShares = @("C$", "ADMIN$")
11379
11380 # see if we're specifically including any of the normally excluded sets
11381 if ($IncludeC) {
11382 if ($IncludeAdmin) {
11383 $ExcludedShares = @()
11384 }
11385 else {
11386 $ExcludedShares = @("ADMIN$")
11387 }
11388 }
11389
11390 if ($IncludeAdmin) {
11391 if ($IncludeC) {
11392 $ExcludedShares = @()
11393 }
11394 else {
11395 $ExcludedShares = @("C$")
11396 }
11397 }
11398
11399 # delete any existing output file if it already exists
11400 if(!$NoClobber) {
11401 if ($OutFile -and (Test-Path -Path $OutFile)) { Remove-Item -Path $OutFile }
11402 }
11403
11404 # if there's a set of terms specified to search for
11405 if ($TermList) {
11406 ForEach ($Term in Get-Content -Path $TermList) {
11407 if (($Term -ne $Null) -and ($Term.trim() -ne '')) {
11408 $SearchTerms += $Term
11409 }
11410 }
11411 }
11412
11413 # if we're hard-passed a set of shares
11414 if($ShareList) {
11415 ForEach ($Item in Get-Content -Path $ShareList) {
11416 if (($Item -ne $Null) -and ($Item.trim() -ne '')) {
11417 # exclude any "[tab]- commants", i.e. the output from Invoke-ShareFinder
11418 $Share = $Item.Split("`t")[0]
11419 $Shares += $Share
11420 }
11421 }
11422 }
11423 else {
11424 # if we're using a host file list, read the targets in and add them to the target list
11425 if($ComputerFile) {
11426 $ComputerName = Get-Content -Path $ComputerFile
11427 }
11428
11429 if(!$ComputerName) {
11430
11431 if($Domain) {
11432 $TargetDomains = @($Domain)
11433 }
11434 elseif($SearchForest) {
11435 # get ALL the domains in the forest to search
11436 $TargetDomains = Get-NetForestDomain | ForEach-Object { $_.Name }
11437 }
11438 else {
11439 # use the local domain
11440 $TargetDomains = @( (Get-NetDomain).name )
11441 }
11442
11443 if($SearchSYSVOL) {
11444 ForEach ($Domain in $TargetDomains) {
11445 $DCSearchPath = "\\$Domain\SYSVOL\"
11446 Write-Verbose "[*] Adding share search path $DCSearchPath"
11447 $Shares += $DCSearchPath
11448 }
11449 if(!$SearchTerms) {
11450 # search for interesting scripts on SYSVOL
11451 $SearchTerms = @('.vbs', '.bat', '.ps1')
11452 }
11453 }
11454 else {
11455 [array]$ComputerName = @()
11456
11457 ForEach ($Domain in $TargetDomains) {
11458 Write-Verbose "[*] Querying domain $Domain for hosts"
11459 $ComputerName += Get-NetComputer -Filter $ComputerFilter -ADSpath $ComputerADSpath -Domain $Domain -DomainController $DomainController
11460 }
11461
11462 # remove any null target hosts, uniquify the list and shuffle it
11463 $ComputerName = $ComputerName | Where-Object { $_ } | Sort-Object -Unique | Sort-Object { Get-Random }
11464 if($($ComputerName.Count) -eq 0) {
11465 throw "No hosts found!"
11466 }
11467 }
11468 }
11469 }
11470
11471 # script block that enumerates shares and files on a server
11472 $HostEnumBlock = {
11473 param($ComputerName, $Ping, $ExcludedShares, $SearchTerms, $ExcludeFolders, $OfficeDocs, $ExcludeHidden, $FreshEXEs, $CheckWriteAccess, $OutFile, $UsePSDrive)
11474
11475 Write-Verbose "ComputerName: $ComputerName"
11476 Write-Verbose "ExcludedShares: $ExcludedShares"
11477 $SearchShares = @()
11478
11479 if($ComputerName.StartsWith("\\")) {
11480 # if a share is passed as the server
11481 $SearchShares += $ComputerName
11482 }
11483 else {
11484 # if we're enumerating the shares on the target server first
11485 $Up = $True
11486 if($Ping) {
11487 $Up = Test-Connection -Count 1 -Quiet -ComputerName $ComputerName
11488 }
11489 if($Up) {
11490 # get the shares for this host and display what we find
11491 $Shares = Get-NetShare -ComputerName $ComputerName
11492 ForEach ($Share in $Shares) {
11493
11494 $NetName = $Share.shi1_netname
11495 $Path = '\\'+$ComputerName+'\'+$NetName
11496
11497 # make sure we get a real share name back
11498 if (($NetName) -and ($NetName.trim() -ne '')) {
11499
11500 # skip this share if it's in the exclude list
11501 if ($ExcludedShares -NotContains $NetName.ToUpper()) {
11502 # check if the user has access to this path
11503 try {
11504 $Null = [IO.Directory]::GetFiles($Path)
11505 $SearchShares += $Path
11506 }
11507 catch {
11508 Write-Verbose "[!] No access to $Path"
11509 }
11510 }
11511 }
11512 }
11513 }
11514 }
11515
11516 ForEach($Share in $SearchShares) {
11517 $SearchArgs = @{
11518 'Path' = $Share
11519 'SearchTerms' = $SearchTerms
11520 'OfficeDocs' = $OfficeDocs
11521 'FreshEXEs' = $FreshEXEs
11522 'LastAccessTime' = $LastAccessTime
11523 'LastWriteTime' = $LastWriteTime
11524 'CreationTime' = $CreationTime
11525 'ExcludeFolders' = $ExcludeFolders
11526 'ExcludeHidden' = $ExcludeHidden
11527 'CheckWriteAccess' = $CheckWriteAccess
11528 'OutFile' = $OutFile
11529 'UsePSDrive' = $UsePSDrive
11530 }
11531
11532 Find-InterestingFile @SearchArgs
11533 }
11534 }
11535 }
11536
11537 process {
11538
11539 if($Threads) {
11540 Write-Verbose "Using threading with threads = $Threads"
11541
11542 # if we're using threading, kick off the script block with Invoke-ThreadedFunction
11543 $ScriptParams = @{
11544 'Ping' = $(-not $NoPing)
11545 'ExcludedShares' = $ExcludedShares
11546 'SearchTerms' = $SearchTerms
11547 'ExcludeFolders' = $ExcludeFolders
11548 'OfficeDocs' = $OfficeDocs
11549 'ExcludeHidden' = $ExcludeHidden
11550 'FreshEXEs' = $FreshEXEs
11551 'CheckWriteAccess' = $CheckWriteAccess
11552 'OutFile' = $OutFile
11553 'UsePSDrive' = $UsePSDrive
11554 }
11555
11556 # kick off the threaded script block + arguments
11557 if($Shares) {
11558 # pass the shares as the hosts so the threaded function code doesn't have to be hacked up
11559 Invoke-ThreadedFunction -ComputerName $Shares -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams -Threads $Threads
11560 }
11561 else {
11562 Invoke-ThreadedFunction -ComputerName $ComputerName -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams -Threads $Threads
11563 }
11564 }
11565
11566 else {
11567 if($Shares){
11568 $ComputerName = $Shares
11569 }
11570 elseif(-not $NoPing -and ($ComputerName.count -gt 1)) {
11571 # ping all hosts in parallel
11572 $Ping = {param($ComputerName) if(Test-Connection -ComputerName $ComputerName -Count 1 -Quiet -ErrorAction Stop){$ComputerName}}
11573 $ComputerName = Invoke-ThreadedFunction -NoImports -ComputerName $ComputerName -ScriptBlock $Ping -Threads 100
11574 }
11575
11576 Write-Verbose "[*] Total number of active hosts: $($ComputerName.count)"
11577 $Counter = 0
11578
11579 $ComputerName | Where-Object {$_} | ForEach-Object {
11580 Write-Verbose "Computer: $_"
11581 $Counter = $Counter + 1
11582
11583 # sleep for our semi-randomized interval
11584 Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay)
11585
11586 Write-Verbose "[*] Enumerating server $_ ($Counter of $($ComputerName.count))"
11587
11588 Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $_, $False, $ExcludedShares, $SearchTerms, $ExcludeFolders, $OfficeDocs, $ExcludeHidden, $FreshEXEs, $CheckWriteAccess, $OutFile, $UsePSDrive
11589 }
11590 }
11591 }
11592}
11593
11594
11595function Find-LocalAdminAccess {
11596<#
11597 .SYNOPSIS
11598
11599 Finds machines on the local domain where the current user has
11600 local administrator access. Uses multithreading to
11601 speed up enumeration.
11602
11603 Author: @harmj0y
11604 License: BSD 3-Clause
11605
11606 .DESCRIPTION
11607
11608 This function finds the local domain name for a host using Get-NetDomain,
11609 queries the domain for all active machines with Get-NetComputer, then for
11610 each server it checks if the current user has local administrator
11611 access using Invoke-CheckLocalAdminAccess.
11612
11613 Idea stolen from the local_admin_search_enum post module in
11614 Metasploit written by:
11615 'Brandon McCann "zeknox" <bmccann[at]accuvant.com>'
11616 'Thomas McCarthy "smilingraccoon" <smilingraccoon[at]gmail.com>'
11617 'Royce Davis "r3dy" <rdavis[at]accuvant.com>'
11618
11619 .PARAMETER ComputerName
11620
11621 Host array to enumerate, passable on the pipeline.
11622
11623 .PARAMETER ComputerFile
11624
11625 File of hostnames/IPs to search.
11626
11627 .PARAMETER ComputerFilter
11628
11629 Host filter name to query AD for, wildcards accepted.
11630
11631 .PARAMETER ComputerADSpath
11632
11633 The LDAP source to search through for hosts, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
11634 Useful for OU queries.
11635
11636 .PARAMETER NoPing
11637
11638 Switch. Don't ping each host to ensure it's up before enumerating.
11639
11640 .PARAMETER Delay
11641
11642 Delay between enumerating hosts, defaults to 0
11643
11644 .PARAMETER Jitter
11645
11646 Jitter for the host delay, defaults to +/- 0.3
11647
11648 .PARAMETER Domain
11649
11650 Domain to query for machines, defaults to the current domain.
11651
11652 .PARAMETER DomainController
11653
11654 Domain controller to reflect LDAP queries through.
11655
11656 .PARAMETER SearchForest
11657
11658 Switch. Search all domains in the forest for target users instead of just
11659 a single domain.
11660
11661 .PARAMETER Threads
11662
11663 The maximum concurrent threads to execute.
11664
11665 .EXAMPLE
11666
11667 PS C:\> Find-LocalAdminAccess
11668
11669 Find machines on the local domain where the current user has local
11670 administrator access.
11671
11672 .EXAMPLE
11673
11674 PS C:\> Find-LocalAdminAccess -Threads 10
11675
11676 Multi-threaded access hunting, replaces Find-LocalAdminAccessThreaded.
11677
11678 .EXAMPLE
11679
11680 PS C:\> Find-LocalAdminAccess -Domain testing
11681
11682 Find machines on the 'testing' domain where the current user has
11683 local administrator access.
11684
11685 .EXAMPLE
11686
11687 PS C:\> Find-LocalAdminAccess -ComputerFile hosts.txt
11688
11689 Find which machines in the host list the current user has local
11690 administrator access.
11691
11692 .LINK
11693
11694 https://github.com/rapid7/metasploit-framework/blob/master/modules/post/windows/gather/local_admin_search_enum.rb
11695 http://www.harmj0y.net/blog/penetesting/finding-local-admin-with-the-veil-framework/
11696#>
11697
11698 [CmdletBinding()]
11699 param(
11700 [Parameter(Position=0,ValueFromPipeline=$True)]
11701 [Alias('Hosts')]
11702 [String[]]
11703 $ComputerName,
11704
11705 [ValidateScript({Test-Path -Path $_ })]
11706 [Alias('HostList')]
11707 [String]
11708 $ComputerFile,
11709
11710 [String]
11711 $ComputerFilter,
11712
11713 [String]
11714 $ComputerADSpath,
11715
11716 [Switch]
11717 $NoPing,
11718
11719 [UInt32]
11720 $Delay = 0,
11721
11722 [Double]
11723 $Jitter = .3,
11724
11725 [String]
11726 $Domain,
11727
11728 [String]
11729 $DomainController,
11730
11731 [Switch]
11732 $SearchForest,
11733
11734 [ValidateRange(1,100)]
11735 [Int]
11736 $Threads
11737 )
11738
11739 begin {
11740 if ($PSBoundParameters['Debug']) {
11741 $DebugPreference = 'Continue'
11742 }
11743
11744 # random object for delay
11745 $RandNo = New-Object System.Random
11746
11747 Write-Verbose "[*] Running Find-LocalAdminAccess with delay of $Delay"
11748
11749 # if we're using a host list, read the targets in and add them to the target list
11750 if($ComputerFile) {
11751 $ComputerName = Get-Content -Path $ComputerFile
11752 }
11753
11754 if(!$ComputerName) {
11755 [array]$ComputerName = @()
11756
11757 if($Domain) {
11758 $TargetDomains = @($Domain)
11759 }
11760 elseif($SearchForest) {
11761 # get ALL the domains in the forest to search
11762 $TargetDomains = Get-NetForestDomain | ForEach-Object { $_.Name }
11763 }
11764 else {
11765 # use the local domain
11766 $TargetDomains = @( (Get-NetDomain).name )
11767 }
11768
11769 ForEach ($Domain in $TargetDomains) {
11770 Write-Verbose "[*] Querying domain $Domain for hosts"
11771 $ComputerName += Get-NetComputer -Filter $ComputerFilter -ADSpath $ComputerADSpath -Domain $Domain -DomainController $DomainController
11772 }
11773
11774 # remove any null target hosts, uniquify the list and shuffle it
11775 $ComputerName = $ComputerName | Where-Object { $_ } | Sort-Object -Unique | Sort-Object { Get-Random }
11776 if($($ComputerName.Count) -eq 0) {
11777 throw "No hosts found!"
11778 }
11779 }
11780
11781 # script block that enumerates a server
11782 $HostEnumBlock = {
11783 param($ComputerName, $Ping)
11784
11785 $Up = $True
11786 if($Ping) {
11787 $Up = Test-Connection -Count 1 -Quiet -ComputerName $ComputerName
11788 }
11789 if($Up) {
11790 # check if the current user has local admin access to this server
11791 $Access = Invoke-CheckLocalAdminAccess -ComputerName $ComputerName
11792 if ($Access.IsAdmin) {
11793 $ComputerName
11794 }
11795 }
11796 }
11797
11798 }
11799
11800 process {
11801
11802 if($Threads) {
11803 Write-Verbose "Using threading with threads = $Threads"
11804
11805 # if we're using threading, kick off the script block with Invoke-ThreadedFunction
11806 $ScriptParams = @{
11807 'Ping' = $(-not $NoPing)
11808 }
11809
11810 # kick off the threaded script block + arguments
11811 Invoke-ThreadedFunction -ComputerName $ComputerName -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams -Threads $Threads
11812 }
11813
11814 else {
11815 if(-not $NoPing -and ($ComputerName.count -ne 1)) {
11816 # ping all hosts in parallel
11817 $Ping = {param($ComputerName) if(Test-Connection -ComputerName $ComputerName -Count 1 -Quiet -ErrorAction Stop){$ComputerName}}
11818 $ComputerName = Invoke-ThreadedFunction -NoImports -ComputerName $ComputerName -ScriptBlock $Ping -Threads 100
11819 }
11820
11821 Write-Verbose "[*] Total number of active hosts: $($ComputerName.count)"
11822 $Counter = 0
11823
11824 ForEach ($Computer in $ComputerName) {
11825
11826 $Counter = $Counter + 1
11827
11828 # sleep for our semi-randomized interval
11829 Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay)
11830
11831 Write-Verbose "[*] Enumerating server $Computer ($Counter of $($ComputerName.count))"
11832 Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $Computer, $False
11833 }
11834 }
11835 }
11836}
11837
11838
11839function Get-ExploitableSystem {
11840<#
11841 .Synopsis
11842
11843 This module will query Active Directory for the hostname, OS version, and service pack level
11844 for each computer account. That information is then cross-referenced against a list of common
11845 Metasploit exploits that can be used during penetration testing.
11846
11847 .DESCRIPTION
11848
11849 This module will query Active Directory for the hostname, OS version, and service pack level
11850 for each computer account. That information is then cross-referenced against a list of common
11851 Metasploit exploits that can be used during penetration testing. The script filters out disabled
11852 domain computers and provides the computer's last logon time to help determine if it's been
11853 decommissioned. Also, since the script uses data tables to output affected systems the results
11854 can be easily piped to other commands such as test-connection or a Export-Csv.
11855
11856 .PARAMETER ComputerName
11857
11858 Return computers with a specific name, wildcards accepted.
11859
11860 .PARAMETER SPN
11861
11862 Return computers with a specific service principal name, wildcards accepted.
11863
11864 .PARAMETER OperatingSystem
11865
11866 Return computers with a specific operating system, wildcards accepted.
11867
11868 .PARAMETER ServicePack
11869
11870 Return computers with a specific service pack, wildcards accepted.
11871
11872 .PARAMETER Filter
11873
11874 A customized ldap filter string to use, e.g. "(description=*admin*)"
11875
11876 .PARAMETER Ping
11877
11878 Switch. Ping each host to ensure it's up before enumerating.
11879
11880 .PARAMETER Domain
11881
11882 The domain to query for computers, defaults to the current domain.
11883
11884 .PARAMETER DomainController
11885
11886 Domain controller to reflect LDAP queries through.
11887
11888 .PARAMETER ADSpath
11889
11890 The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
11891 Useful for OU queries.
11892
11893 .PARAMETER Unconstrained
11894
11895 Switch. Return computer objects that have unconstrained delegation.
11896
11897 .PARAMETER PageSize
11898
11899 The PageSize to set for the LDAP searcher object.
11900
11901 .PARAMETER Credential
11902
11903 A [Management.Automation.PSCredential] object of alternate credentials
11904 for connection to the target domain.
11905
11906 .EXAMPLE
11907
11908 The example below shows the standard command usage. Disabled system are excluded by default, but
11909 the "LastLgon" column can be used to determine which systems are live. Usually, if a system hasn't
11910 logged on for two or more weeks it's been decommissioned.
11911 PS C:\> Get-ExploitableSystem -DomainController 192.168.1.1 -Credential demo.com\user | Format-Table -AutoSize
11912 [*] Grabbing computer accounts from Active Directory...
11913 [*] Loading exploit list for critical missing patches...
11914 [*] Checking computers for vulnerable OS and SP levels...
11915 [+] Found 5 potentially vulnerable systems!
11916 ComputerName OperatingSystem ServicePack LastLogon MsfModule CVE
11917 ------------ --------------- ----------- --------- --------- ---
11918 ADS.demo.com Windows Server 2003 Service Pack 2 4/8/2015 5:46:52 PM exploit/windows/dcerpc/ms07_029_msdns_zonename http://www.cvedetails....
11919 ADS.demo.com Windows Server 2003 Service Pack 2 4/8/2015 5:46:52 PM exploit/windows/smb/ms08_067_netapi http://www.cvedetails....
11920 ADS.demo.com Windows Server 2003 Service Pack 2 4/8/2015 5:46:52 PM exploit/windows/smb/ms10_061_spoolss http://www.cvedetails....
11921 LVA.demo.com Windows Server 2003 Service Pack 2 4/8/2015 1:44:46 PM exploit/windows/dcerpc/ms07_029_msdns_zonename http://www.cvedetails....
11922 LVA.demo.com Windows Server 2003 Service Pack 2 4/8/2015 1:44:46 PM exploit/windows/smb/ms08_067_netapi http://www.cvedetails....
11923 LVA.demo.com Windows Server 2003 Service Pack 2 4/8/2015 1:44:46 PM exploit/windows/smb/ms10_061_spoolss http://www.cvedetails....
11924 assess-xppro.demo.com Windows XP Professional Service Pack 3 4/1/2014 11:11:54 AM exploit/windows/smb/ms08_067_netapi http://www.cvedetails....
11925 assess-xppro.demo.com Windows XP Professional Service Pack 3 4/1/2014 11:11:54 AM exploit/windows/smb/ms10_061_spoolss http://www.cvedetails....
11926 HVA.demo.com Windows Server 2003 Service Pack 2 11/5/2013 9:16:31 PM exploit/windows/dcerpc/ms07_029_msdns_zonename http://www.cvedetails....
11927 HVA.demo.com Windows Server 2003 Service Pack 2 11/5/2013 9:16:31 PM exploit/windows/smb/ms08_067_netapi http://www.cvedetails....
11928 HVA.demo.com Windows Server 2003 Service Pack 2 11/5/2013 9:16:31 PM exploit/windows/smb/ms10_061_spoolss http://www.cvedetails....
11929 DB1.demo.com Windows Server 2003 Service Pack 2 3/22/2012 5:05:34 PM exploit/windows/dcerpc/ms07_029_msdns_zonename http://www.cvedetails....
11930 DB1.demo.com Windows Server 2003 Service Pack 2 3/22/2012 5:05:34 PM exploit/windows/smb/ms08_067_netapi http://www.cvedetails....
11931 DB1.demo.com Windows Server 2003 Service Pack 2 3/22/2012 5:05:34 PM exploit/windows/smb/ms10_061_spoolss http://www.cvedetails....
11932
11933 .EXAMPLE
11934
11935 PS C:\> Get-ExploitableSystem | Export-Csv c:\temp\output.csv -NoTypeInformation
11936
11937 How to write the output to a csv file.
11938
11939 .EXAMPLE
11940
11941 PS C:\> Get-ExploitableSystem -Domain testlab.local -Ping
11942
11943 Return a set of live hosts from the testlab.local domain
11944
11945 .LINK
11946
11947 http://www.netspi.com
11948 https://github.com/nullbind/Powershellery/blob/master/Stable-ish/ADS/Get-ExploitableSystems.psm1
11949
11950 .NOTES
11951
11952 Author: Scott Sutherland - 2015, NetSPI
11953 Modifications to integrate into PowerView by @harmj0y
11954 Version: Get-ExploitableSystem.psm1 v1.1
11955 Comments: The technique used to query LDAP was based on the "Get-AuditDSComputerAccount"
11956 function found in Carols Perez's PoshSec-Mod project. The general idea is based off of
11957 Will Schroeder's "Invoke-FindVulnSystems" function from the PowerView toolkit.
11958#>
11959 [CmdletBinding()]
11960 Param(
11961 [Parameter(ValueFromPipeline=$True)]
11962 [Alias('HostName')]
11963 [String]
11964 $ComputerName = '*',
11965
11966 [String]
11967 $SPN,
11968
11969 [String]
11970 $OperatingSystem = '*',
11971
11972 [String]
11973 $ServicePack = '*',
11974
11975 [String]
11976 $Filter,
11977
11978 [Switch]
11979 $Ping,
11980
11981 [String]
11982 $Domain,
11983
11984 [String]
11985 $DomainController,
11986
11987 [String]
11988 $ADSpath,
11989
11990 [Switch]
11991 $Unconstrained,
11992
11993 [ValidateRange(1,10000)]
11994 [Int]
11995 $PageSize = 200,
11996
11997 [Management.Automation.PSCredential]
11998 $Credential
11999 )
12000
12001 Write-Verbose "[*] Grabbing computer accounts from Active Directory..."
12002
12003 # Create data table for hostnames, os, and service packs from LDAP
12004 $TableAdsComputers = New-Object System.Data.DataTable
12005 $Null = $TableAdsComputers.Columns.Add('Hostname')
12006 $Null = $TableAdsComputers.Columns.Add('OperatingSystem')
12007 $Null = $TableAdsComputers.Columns.Add('ServicePack')
12008 $Null = $TableAdsComputers.Columns.Add('LastLogon')
12009
12010 Get-NetComputer -FullData @PSBoundParameters | ForEach-Object {
12011
12012 $CurrentHost = $_.dnshostname
12013 $CurrentOs = $_.operatingsystem
12014 $CurrentSp = $_.operatingsystemservicepack
12015 $CurrentLast = $_.lastlogon
12016 $CurrentUac = $_.useraccountcontrol
12017
12018 $CurrentUacBin = [convert]::ToString($_.useraccountcontrol,2)
12019
12020 # Check the 2nd to last value to determine if its disabled
12021 $DisableOffset = $CurrentUacBin.Length - 2
12022 $CurrentDisabled = $CurrentUacBin.Substring($DisableOffset,1)
12023
12024 # Add computer to list if it's enabled
12025 if ($CurrentDisabled -eq 0) {
12026 # Add domain computer to data table
12027 $Null = $TableAdsComputers.Rows.Add($CurrentHost,$CurrentOS,$CurrentSP,$CurrentLast)
12028 }
12029 }
12030
12031 # Status user
12032 Write-Verbose "[*] Loading exploit list for critical missing patches..."
12033
12034 # ----------------------------------------------------------------
12035 # Setup data table for list of msf exploits
12036 # ----------------------------------------------------------------
12037
12038 # Create data table for list of patches levels with a MSF exploit
12039 $TableExploits = New-Object System.Data.DataTable
12040 $Null = $TableExploits.Columns.Add('OperatingSystem')
12041 $Null = $TableExploits.Columns.Add('ServicePack')
12042 $Null = $TableExploits.Columns.Add('MsfModule')
12043 $Null = $TableExploits.Columns.Add('CVE')
12044
12045 # Add exploits to data table
12046 $Null = $TableExploits.Rows.Add("Windows 7","","exploit/windows/smb/ms10_061_spoolss","http://www.cvedetails.com/cve/2010-2729")
12047 $Null = $TableExploits.Rows.Add("Windows Server 2000","Server Pack 1","exploit/windows/dcerpc/ms03_026_dcom","http://www.cvedetails.com/cve/2003-0352/")
12048 $Null = $TableExploits.Rows.Add("Windows Server 2000","Server Pack 1","exploit/windows/dcerpc/ms05_017_msmq","http://www.cvedetails.com/cve/2005-0059")
12049 $Null = $TableExploits.Rows.Add("Windows Server 2000","Server Pack 1","exploit/windows/iis/ms03_007_ntdll_webdav","http://www.cvedetails.com/cve/2003-0109")
12050 $Null = $TableExploits.Rows.Add("Windows Server 2000","Server Pack 1","exploit/windows/wins/ms04_045_wins","http://www.cvedetails.com/cve/2004-1080/")
12051 $Null = $TableExploits.Rows.Add("Windows Server 2000","Service Pack 2","exploit/windows/dcerpc/ms03_026_dcom","http://www.cvedetails.com/cve/2003-0352/")
12052 $Null = $TableExploits.Rows.Add("Windows Server 2000","Service Pack 2","exploit/windows/dcerpc/ms05_017_msmq","http://www.cvedetails.com/cve/2005-0059")
12053 $Null = $TableExploits.Rows.Add("Windows Server 2000","Service Pack 2","exploit/windows/iis/ms03_007_ntdll_webdav","http://www.cvedetails.com/cve/2003-0109")
12054 $Null = $TableExploits.Rows.Add("Windows Server 2000","Service Pack 2","exploit/windows/smb/ms04_011_lsass","http://www.cvedetails.com/cve/2003-0533/")
12055 $Null = $TableExploits.Rows.Add("Windows Server 2000","Service Pack 2","exploit/windows/wins/ms04_045_wins","http://www.cvedetails.com/cve/2004-1080/")
12056 $Null = $TableExploits.Rows.Add("Windows Server 2000","Service Pack 3","exploit/windows/dcerpc/ms03_026_dcom","http://www.cvedetails.com/cve/2003-0352/")
12057 $Null = $TableExploits.Rows.Add("Windows Server 2000","Service Pack 3","exploit/windows/dcerpc/ms05_017_msmq","http://www.cvedetails.com/cve/2005-0059")
12058 $Null = $TableExploits.Rows.Add("Windows Server 2000","Service Pack 3","exploit/windows/iis/ms03_007_ntdll_webdav","http://www.cvedetails.com/cve/2003-0109")
12059 $Null = $TableExploits.Rows.Add("Windows Server 2000","Service Pack 3","exploit/windows/wins/ms04_045_wins","http://www.cvedetails.com/cve/2004-1080/")
12060 $Null = $TableExploits.Rows.Add("Windows Server 2000","Service Pack 4","exploit/windows/dcerpc/ms03_026_dcom","http://www.cvedetails.com/cve/2003-0352/")
12061 $Null = $TableExploits.Rows.Add("Windows Server 2000","Service Pack 4","exploit/windows/dcerpc/ms05_017_msmq","http://www.cvedetails.com/cve/2005-0059")
12062 $Null = $TableExploits.Rows.Add("Windows Server 2000","Service Pack 4","exploit/windows/dcerpc/ms07_029_msdns_zonename","http://www.cvedetails.com/cve/2007-1748")
12063 $Null = $TableExploits.Rows.Add("Windows Server 2000","Service Pack 4","exploit/windows/smb/ms04_011_lsass","http://www.cvedetails.com/cve/2003-0533/")
12064 $Null = $TableExploits.Rows.Add("Windows Server 2000","Service Pack 4","exploit/windows/smb/ms06_040_netapi","http://www.cvedetails.com/cve/2006-3439")
12065 $Null = $TableExploits.Rows.Add("Windows Server 2000","Service Pack 4","exploit/windows/smb/ms06_066_nwapi","http://www.cvedetails.com/cve/2006-4688")
12066 $Null = $TableExploits.Rows.Add("Windows Server 2000","Service Pack 4","exploit/windows/smb/ms06_070_wkssvc","http://www.cvedetails.com/cve/2006-4691")
12067 $Null = $TableExploits.Rows.Add("Windows Server 2000","Service Pack 4","exploit/windows/smb/ms08_067_netapi","http://www.cvedetails.com/cve/2008-4250")
12068 $Null = $TableExploits.Rows.Add("Windows Server 2000","Service Pack 4","exploit/windows/wins/ms04_045_wins","http://www.cvedetails.com/cve/2004-1080/")
12069 $Null = $TableExploits.Rows.Add("Windows Server 2000","","exploit/windows/dcerpc/ms03_026_dcom","http://www.cvedetails.com/cve/2003-0352/")
12070 $Null = $TableExploits.Rows.Add("Windows Server 2000","","exploit/windows/dcerpc/ms05_017_msmq","http://www.cvedetails.com/cve/2005-0059")
12071 $Null = $TableExploits.Rows.Add("Windows Server 2000","","exploit/windows/iis/ms03_007_ntdll_webdav","http://www.cvedetails.com/cve/2003-0109")
12072 $Null = $TableExploits.Rows.Add("Windows Server 2000","","exploit/windows/smb/ms05_039_pnp","http://www.cvedetails.com/cve/2005-1983")
12073 $Null = $TableExploits.Rows.Add("Windows Server 2000","","exploit/windows/wins/ms04_045_wins","http://www.cvedetails.com/cve/2004-1080/")
12074 $Null = $TableExploits.Rows.Add("Windows Server 2003","Server Pack 1","exploit/windows/dcerpc/ms07_029_msdns_zonename","http://www.cvedetails.com/cve/2007-1748")
12075 $Null = $TableExploits.Rows.Add("Windows Server 2003","Server Pack 1","exploit/windows/smb/ms06_040_netapi","http://www.cvedetails.com/cve/2006-3439")
12076 $Null = $TableExploits.Rows.Add("Windows Server 2003","Server Pack 1","exploit/windows/smb/ms06_066_nwapi","http://www.cvedetails.com/cve/2006-4688")
12077 $Null = $TableExploits.Rows.Add("Windows Server 2003","Server Pack 1","exploit/windows/smb/ms08_067_netapi","http://www.cvedetails.com/cve/2008-4250")
12078 $Null = $TableExploits.Rows.Add("Windows Server 2003","Server Pack 1","exploit/windows/wins/ms04_045_wins","http://www.cvedetails.com/cve/2004-1080/")
12079 $Null = $TableExploits.Rows.Add("Windows Server 2003","Service Pack 2","exploit/windows/dcerpc/ms07_029_msdns_zonename","http://www.cvedetails.com/cve/2007-1748")
12080 $Null = $TableExploits.Rows.Add("Windows Server 2003","Service Pack 2","exploit/windows/smb/ms08_067_netapi","http://www.cvedetails.com/cve/2008-4250")
12081 $Null = $TableExploits.Rows.Add("Windows Server 2003","Service Pack 2","exploit/windows/smb/ms10_061_spoolss","http://www.cvedetails.com/cve/2010-2729")
12082 $Null = $TableExploits.Rows.Add("Windows Server 2003","","exploit/windows/dcerpc/ms03_026_dcom","http://www.cvedetails.com/cve/2003-0352/")
12083 $Null = $TableExploits.Rows.Add("Windows Server 2003","","exploit/windows/smb/ms06_040_netapi","http://www.cvedetails.com/cve/2006-3439")
12084 $Null = $TableExploits.Rows.Add("Windows Server 2003","","exploit/windows/smb/ms08_067_netapi","http://www.cvedetails.com/cve/2008-4250")
12085 $Null = $TableExploits.Rows.Add("Windows Server 2003","","exploit/windows/wins/ms04_045_wins","http://www.cvedetails.com/cve/2004-1080/")
12086 $Null = $TableExploits.Rows.Add("Windows Server 2003 R2","","exploit/windows/dcerpc/ms03_026_dcom","http://www.cvedetails.com/cve/2003-0352/")
12087 $Null = $TableExploits.Rows.Add("Windows Server 2003 R2","","exploit/windows/smb/ms04_011_lsass","http://www.cvedetails.com/cve/2003-0533/")
12088 $Null = $TableExploits.Rows.Add("Windows Server 2003 R2","","exploit/windows/smb/ms06_040_netapi","http://www.cvedetails.com/cve/2006-3439")
12089 $Null = $TableExploits.Rows.Add("Windows Server 2003 R2","","exploit/windows/wins/ms04_045_wins","http://www.cvedetails.com/cve/2004-1080/")
12090 $Null = $TableExploits.Rows.Add("Windows Server 2008","Service Pack 2","exploit/windows/smb/ms09_050_smb2_negotiate_func_index","http://www.cvedetails.com/cve/2009-3103")
12091 $Null = $TableExploits.Rows.Add("Windows Server 2008","Service Pack 2","exploit/windows/smb/ms10_061_spoolss","http://www.cvedetails.com/cve/2010-2729")
12092 $Null = $TableExploits.Rows.Add("Windows Server 2008","","exploit/windows/smb/ms09_050_smb2_negotiate_func_index","http://www.cvedetails.com/cve/2009-3103")
12093 $Null = $TableExploits.Rows.Add("Windows Server 2008","","exploit/windows/smb/ms10_061_spoolss","http://www.cvedetails.com/cve/2010-2729")
12094 $Null = $TableExploits.Rows.Add("Windows Server 2008 R2","","exploit/windows/smb/ms10_061_spoolss","http://www.cvedetails.com/cve/2010-2729")
12095 $Null = $TableExploits.Rows.Add("Windows Vista","Server Pack 1","exploit/windows/smb/ms09_050_smb2_negotiate_func_index","http://www.cvedetails.com/cve/2009-3103")
12096 $Null = $TableExploits.Rows.Add("Windows Vista","Server Pack 1","exploit/windows/smb/ms10_061_spoolss","http://www.cvedetails.com/cve/2010-2729")
12097 $Null = $TableExploits.Rows.Add("Windows Vista","Service Pack 2","exploit/windows/smb/ms09_050_smb2_negotiate_func_index","http://www.cvedetails.com/cve/2009-3103")
12098 $Null = $TableExploits.Rows.Add("Windows Vista","Service Pack 2","exploit/windows/smb/ms10_061_spoolss","http://www.cvedetails.com/cve/2010-2729")
12099 $Null = $TableExploits.Rows.Add("Windows Vista","","exploit/windows/smb/ms09_050_smb2_negotiate_func_index","http://www.cvedetails.com/cve/2009-3103")
12100 $Null = $TableExploits.Rows.Add("Windows XP","Server Pack 1","exploit/windows/dcerpc/ms03_026_dcom","http://www.cvedetails.com/cve/2003-0352/")
12101 $Null = $TableExploits.Rows.Add("Windows XP","Server Pack 1","exploit/windows/dcerpc/ms05_017_msmq","http://www.cvedetails.com/cve/2005-0059")
12102 $Null = $TableExploits.Rows.Add("Windows XP","Server Pack 1","exploit/windows/smb/ms04_011_lsass","http://www.cvedetails.com/cve/2003-0533/")
12103 $Null = $TableExploits.Rows.Add("Windows XP","Server Pack 1","exploit/windows/smb/ms05_039_pnp","http://www.cvedetails.com/cve/2005-1983")
12104 $Null = $TableExploits.Rows.Add("Windows XP","Server Pack 1","exploit/windows/smb/ms06_040_netapi","http://www.cvedetails.com/cve/2006-3439")
12105 $Null = $TableExploits.Rows.Add("Windows XP","Service Pack 2","exploit/windows/dcerpc/ms05_017_msmq","http://www.cvedetails.com/cve/2005-0059")
12106 $Null = $TableExploits.Rows.Add("Windows XP","Service Pack 2","exploit/windows/smb/ms06_040_netapi","http://www.cvedetails.com/cve/2006-3439")
12107 $Null = $TableExploits.Rows.Add("Windows XP","Service Pack 2","exploit/windows/smb/ms06_066_nwapi","http://www.cvedetails.com/cve/2006-4688")
12108 $Null = $TableExploits.Rows.Add("Windows XP","Service Pack 2","exploit/windows/smb/ms06_070_wkssvc","http://www.cvedetails.com/cve/2006-4691")
12109 $Null = $TableExploits.Rows.Add("Windows XP","Service Pack 2","exploit/windows/smb/ms08_067_netapi","http://www.cvedetails.com/cve/2008-4250")
12110 $Null = $TableExploits.Rows.Add("Windows XP","Service Pack 2","exploit/windows/smb/ms10_061_spoolss","http://www.cvedetails.com/cve/2010-2729")
12111 $Null = $TableExploits.Rows.Add("Windows XP","Service Pack 3","exploit/windows/smb/ms08_067_netapi","http://www.cvedetails.com/cve/2008-4250")
12112 $Null = $TableExploits.Rows.Add("Windows XP","Service Pack 3","exploit/windows/smb/ms10_061_spoolss","http://www.cvedetails.com/cve/2010-2729")
12113 $Null = $TableExploits.Rows.Add("Windows XP","","exploit/windows/dcerpc/ms03_026_dcom","http://www.cvedetails.com/cve/2003-0352/")
12114 $Null = $TableExploits.Rows.Add("Windows XP","","exploit/windows/dcerpc/ms05_017_msmq","http://www.cvedetails.com/cve/2005-0059")
12115 $Null = $TableExploits.Rows.Add("Windows XP","","exploit/windows/smb/ms06_040_netapi","http://www.cvedetails.com/cve/2006-3439")
12116 $Null = $TableExploits.Rows.Add("Windows XP","","exploit/windows/smb/ms08_067_netapi","http://www.cvedetails.com/cve/2008-4250")
12117
12118 # Status user
12119 Write-Verbose "[*] Checking computers for vulnerable OS and SP levels..."
12120
12121 # ----------------------------------------------------------------
12122 # Setup data table to store vulnerable systems
12123 # ----------------------------------------------------------------
12124
12125 # Create data table to house vulnerable server list
12126 $TableVulnComputers = New-Object System.Data.DataTable
12127 $Null = $TableVulnComputers.Columns.Add('ComputerName')
12128 $Null = $TableVulnComputers.Columns.Add('OperatingSystem')
12129 $Null = $TableVulnComputers.Columns.Add('ServicePack')
12130 $Null = $TableVulnComputers.Columns.Add('LastLogon')
12131 $Null = $TableVulnComputers.Columns.Add('MsfModule')
12132 $Null = $TableVulnComputers.Columns.Add('CVE')
12133
12134 # Iterate through each exploit
12135 $TableExploits | ForEach-Object {
12136
12137 $ExploitOS = $_.OperatingSystem
12138 $ExploitSP = $_.ServicePack
12139 $ExploitMsf = $_.MsfModule
12140 $ExploitCVE = $_.CVE
12141
12142 # Iterate through each ADS computer
12143 $TableAdsComputers | ForEach-Object {
12144
12145 $AdsHostname = $_.Hostname
12146 $AdsOS = $_.OperatingSystem
12147 $AdsSP = $_.ServicePack
12148 $AdsLast = $_.LastLogon
12149
12150 # Add exploitable systems to vul computers data table
12151 if ($AdsOS -like "$ExploitOS*" -and $AdsSP -like "$ExploitSP" ) {
12152 # Add domain computer to data table
12153 $Null = $TableVulnComputers.Rows.Add($AdsHostname,$AdsOS,$AdsSP,$AdsLast,$ExploitMsf,$ExploitCVE)
12154 }
12155 }
12156 }
12157
12158 # Display results
12159 $VulnComputer = $TableVulnComputers | Select-Object ComputerName -Unique | Measure-Object
12160 $VulnComputerCount = $VulnComputer.Count
12161
12162 if ($VulnComputer.Count -gt 0) {
12163 # Return vulnerable server list order with some hack date casting
12164 Write-Verbose "[+] Found $VulnComputerCount potentially vulnerable systems!"
12165 $TableVulnComputers | Sort-Object { $_.lastlogon -as [datetime]} -Descending
12166 }
12167 else {
12168 Write-Verbose "[-] No vulnerable systems were found."
12169 }
12170}
12171
12172
12173function Invoke-EnumerateLocalAdmin {
12174<#
12175 .SYNOPSIS
12176
12177 This function queries the domain for all active machines with
12178 Get-NetComputer, then for each server it queries the local
12179 Administrators with Get-NetLocalGroup.
12180
12181 Author: @harmj0y
12182 License: BSD 3-Clause
12183
12184 .PARAMETER ComputerName
12185
12186 Host array to enumerate, passable on the pipeline.
12187
12188 .PARAMETER ComputerFile
12189
12190 File of hostnames/IPs to search.
12191
12192 .PARAMETER ComputerFilter
12193
12194 Host filter name to query AD for, wildcards accepted.
12195
12196 .PARAMETER ComputerADSpath
12197
12198 The LDAP source to search through for hosts, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
12199 Useful for OU queries.
12200
12201 .PARAMETER NoPing
12202
12203 Switch. Don't ping each host to ensure it's up before enumerating.
12204
12205 .PARAMETER Delay
12206
12207 Delay between enumerating hosts, defaults to 0
12208
12209 .PARAMETER Jitter
12210
12211 Jitter for the host delay, defaults to +/- 0.3
12212
12213 .PARAMETER OutFile
12214
12215 Output results to a specified csv output file.
12216
12217 .PARAMETER NoClobber
12218
12219 Switch. Don't overwrite any existing output file.
12220
12221 .PARAMETER TrustGroups
12222
12223 Switch. Only return results that are not part of the local machine
12224 or the machine's domain. Old Invoke-EnumerateLocalTrustGroup
12225 functionality.
12226
12227 .PARAMETER DomainOnly
12228
12229 Switch. Only return domain (non-local) results
12230
12231 .PARAMETER Domain
12232
12233 Domain to query for machines, defaults to the current domain.
12234
12235 .PARAMETER DomainController
12236
12237 Domain controller to reflect LDAP queries through.
12238
12239 .PARAMETER SearchForest
12240
12241 Switch. Search all domains in the forest for target users instead of just
12242 a single domain.
12243
12244 .PARAMETER API
12245
12246 Switch. Use API calls instead of the WinNT service provider. Less information,
12247 but the results are faster.
12248
12249 .PARAMETER Threads
12250
12251 The maximum concurrent threads to execute.
12252
12253 .EXAMPLE
12254
12255 PS C:\> Invoke-EnumerateLocalAdmin
12256
12257 Enumerates the members of local administrators for all machines
12258 in the current domain.
12259
12260 .EXAMPLE
12261
12262 PS C:\> Invoke-EnumerateLocalAdmin -Threads 10
12263
12264 Threaded local admin enumeration, replaces Invoke-EnumerateLocalAdminThreaded
12265
12266 .LINK
12267
12268 http://blog.harmj0y.net/
12269#>
12270
12271 [CmdletBinding()]
12272 param(
12273 [Parameter(Position=0,ValueFromPipeline=$True)]
12274 [Alias('Hosts')]
12275 [String[]]
12276 $ComputerName,
12277
12278 [ValidateScript({Test-Path -Path $_ })]
12279 [Alias('HostList')]
12280 [String]
12281 $ComputerFile,
12282
12283 [String]
12284 $ComputerFilter,
12285
12286 [String]
12287 $ComputerADSpath,
12288
12289 [Switch]
12290 $NoPing,
12291
12292 [UInt32]
12293 $Delay = 0,
12294
12295 [Double]
12296 $Jitter = .3,
12297
12298 [String]
12299 $OutFile,
12300
12301 [Switch]
12302 $NoClobber,
12303
12304 [Switch]
12305 $TrustGroups,
12306
12307 [Switch]
12308 $DomainOnly,
12309
12310 [String]
12311 $Domain,
12312
12313 [String]
12314 $DomainController,
12315
12316 [Switch]
12317 $SearchForest,
12318
12319 [ValidateRange(1,100)]
12320 [Int]
12321 $Threads,
12322
12323 [Switch]
12324 $API
12325 )
12326
12327 begin {
12328 if ($PSBoundParameters['Debug']) {
12329 $DebugPreference = 'Continue'
12330 }
12331
12332 # random object for delay
12333 $RandNo = New-Object System.Random
12334
12335 Write-Verbose "[*] Running Invoke-EnumerateLocalAdmin with delay of $Delay"
12336
12337 # if we're using a host list, read the targets in and add them to the target list
12338 if($ComputerFile) {
12339 $ComputerName = Get-Content -Path $ComputerFile
12340 }
12341
12342 if(!$ComputerName) {
12343 [array]$ComputerName = @()
12344
12345 if($Domain) {
12346 $TargetDomains = @($Domain)
12347 }
12348 elseif($SearchForest) {
12349 # get ALL the domains in the forest to search
12350 $TargetDomains = Get-NetForestDomain | ForEach-Object { $_.Name }
12351 }
12352 else {
12353 # use the local domain
12354 $TargetDomains = @( (Get-NetDomain).name )
12355 }
12356
12357 ForEach ($Domain in $TargetDomains) {
12358 Write-Verbose "[*] Querying domain $Domain for hosts"
12359 $ComputerName += Get-NetComputer -Filter $ComputerFilter -ADSpath $ComputerADSpath -Domain $Domain -DomainController $DomainController
12360 }
12361
12362 # remove any null target hosts, uniquify the list and shuffle it
12363 $ComputerName = $ComputerName | Where-Object { $_ } | Sort-Object -Unique | Sort-Object { Get-Random }
12364 if($($ComputerName.Count) -eq 0) {
12365 throw "No hosts found!"
12366 }
12367 }
12368
12369 # delete any existing output file if it already exists
12370 if(!$NoClobber) {
12371 if ($OutFile -and (Test-Path -Path $OutFile)) { Remove-Item -Path $OutFile }
12372 }
12373
12374 if($TrustGroups) {
12375
12376 Write-Verbose "Determining domain trust groups"
12377
12378 # find all group names that have one or more users in another domain
12379 $TrustGroupNames = Find-ForeignGroup -Domain $Domain -DomainController $DomainController | ForEach-Object { $_.GroupName } | Sort-Object -Unique
12380
12381 $TrustGroupsSIDs = $TrustGroupNames | ForEach-Object {
12382 # ignore the builtin administrators group for a DC (S-1-5-32-544)
12383 # TODO: ignore all default built in sids?
12384 Get-NetGroup -Domain $Domain -DomainController $DomainController -GroupName $_ -FullData | Where-Object { $_.objectsid -notmatch "S-1-5-32-544" } | ForEach-Object { $_.objectsid }
12385 }
12386
12387 # query for the primary domain controller so we can extract the domain SID for filtering
12388 $DomainSID = Get-DomainSID -Domain $Domain -DomainController $DomainController
12389 }
12390
12391 # script block that enumerates a server
12392 $HostEnumBlock = {
12393 param($ComputerName, $Ping, $OutFile, $DomainSID, $TrustGroupsSIDs, $API, $DomainOnly)
12394
12395 # optionally check if the server is up first
12396 $Up = $True
12397 if($Ping) {
12398 $Up = Test-Connection -Count 1 -Quiet -ComputerName $ComputerName
12399 }
12400 if($Up) {
12401 # grab the users for the local admins on this server
12402 if($API) {
12403 $LocalAdmins = Get-NetLocalGroup -ComputerName $ComputerName -API
12404 }
12405 else {
12406 $LocalAdmins = Get-NetLocalGroup -ComputerName $ComputerName
12407 }
12408
12409 # if we just want to return cross-trust users
12410 if($DomainSID) {
12411 # get the local machine SID
12412 $LocalSID = ($LocalAdmins | Where-Object { $_.SID -match '.*-500$' }).SID -replace "-500$"
12413 Write-Verbose "LocalSid for $ComputerName : $LocalSID"
12414 # filter out accounts that begin with the machine SID and domain SID
12415 # but preserve any groups that have users across a trust ($TrustGroupSIDS)
12416 $LocalAdmins = $LocalAdmins | Where-Object { ($TrustGroupsSIDs -contains $_.SID) -or ((-not $_.SID.startsWith($LocalSID)) -and (-not $_.SID.startsWith($DomainSID))) }
12417 }
12418
12419 if($DomainOnly) {
12420 $LocalAdmins = $LocalAdmins | Where-Object {$_.IsDomain}
12421 }
12422
12423 if($LocalAdmins -and ($LocalAdmins.Length -ne 0)) {
12424 # output the results to a csv if specified
12425 if($OutFile) {
12426 $LocalAdmins | Export-PowerViewCSV -OutFile $OutFile
12427 }
12428 else {
12429 # otherwise return the user objects
12430 $LocalAdmins
12431 }
12432 }
12433 else {
12434 Write-Verbose "[!] No users returned from $ComputerName"
12435 }
12436 }
12437 }
12438 }
12439
12440 process {
12441
12442 if($Threads) {
12443 Write-Verbose "Using threading with threads = $Threads"
12444
12445 # if we're using threading, kick off the script block with Invoke-ThreadedFunction
12446 $ScriptParams = @{
12447 'Ping' = $(-not $NoPing)
12448 'OutFile' = $OutFile
12449 'DomainSID' = $DomainSID
12450 'TrustGroupsSIDs' = $TrustGroupsSIDs
12451 }
12452
12453 # kick off the threaded script block + arguments
12454 if($API) {
12455 $ScriptParams['API'] = $True
12456 }
12457
12458 if($DomainOnly) {
12459 $ScriptParams['DomainOnly'] = $True
12460 }
12461
12462 Invoke-ThreadedFunction -ComputerName $ComputerName -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams -Threads $Threads
12463 }
12464
12465 else {
12466 if(-not $NoPing -and ($ComputerName.count -ne 1)) {
12467 # ping all hosts in parallel
12468 $Ping = {param($ComputerName) if(Test-Connection -ComputerName $ComputerName -Count 1 -Quiet -ErrorAction Stop){$ComputerName}}
12469 $ComputerName = Invoke-ThreadedFunction -NoImports -ComputerName $ComputerName -ScriptBlock $Ping -Threads 100
12470 }
12471
12472 Write-Verbose "[*] Total number of active hosts: $($ComputerName.count)"
12473 $Counter = 0
12474
12475 ForEach ($Computer in $ComputerName) {
12476
12477 $Counter = $Counter + 1
12478
12479 # sleep for our semi-randomized interval
12480 Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay)
12481 Write-Verbose "[*] Enumerating server $Computer ($Counter of $($ComputerName.count))"
12482
12483 $ScriptArgs = @($Computer, $False, $OutFile, $DomainSID, $TrustGroupsSIDs, $API, $DomainOnly)
12484
12485 Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $ScriptArgs
12486 }
12487 }
12488 }
12489}
12490
12491
12492########################################################
12493#
12494# Domain trust functions below.
12495#
12496########################################################
12497
12498function Get-NetDomainTrust {
12499<#
12500 .SYNOPSIS
12501
12502 Return all domain trusts for the current domain or
12503 a specified domain.
12504
12505 .PARAMETER Domain
12506
12507 The domain whose trusts to enumerate, defaults to the current domain.
12508
12509 .PARAMETER DomainController
12510
12511 Domain controller to reflect LDAP queries through.
12512
12513 .PARAMETER ADSpath
12514
12515 The LDAP source to search through, e.g. "LDAP://DC=testlab,DC=local".
12516 Useful for global catalog queries ;)
12517
12518 .PARAMETER API
12519
12520 Use an API call (DsEnumerateDomainTrusts) to enumerate the trusts.
12521
12522 .PARAMETER LDAP
12523
12524 Switch. Use LDAP queries to enumerate the trusts instead of direct domain connections.
12525 More likely to get around network segmentation, but not as accurate.
12526
12527 .PARAMETER PageSize
12528
12529 The PageSize to set for the LDAP searcher object.
12530
12531 .EXAMPLE
12532
12533 PS C:\> Get-NetDomainTrust
12534
12535 Return domain trusts for the current domain using built in .NET methods.
12536
12537 .EXAMPLE
12538
12539 PS C:\> Get-NetDomainTrust -Domain "prod.testlab.local"
12540
12541 Return domain trusts for the "prod.testlab.local" domain using .NET methods
12542
12543 .EXAMPLE
12544
12545 PS C:\> Get-NetDomainTrust -LDAP -Domain "prod.testlab.local" -DomainController "PRIMARY.testlab.local"
12546
12547 Return domain trusts for the "prod.testlab.local" domain enumerated through LDAP
12548 queries, reflecting queries through the "Primary.testlab.local" domain controller,
12549 using .NET methods.
12550
12551 .EXAMPLE
12552
12553 PS C:\> Get-NetDomainTrust -API -Domain "prod.testlab.local"
12554
12555 Return domain trusts for the "prod.testlab.local" domain enumerated through API calls.
12556
12557 .EXAMPLE
12558
12559 PS C:\> Get-NetDomainTrust -API -DomainController WINDOWS2.testlab.local
12560
12561 Return domain trusts reachable from the WINDOWS2 machine through API calls.
12562#>
12563
12564 [CmdletBinding()]
12565 param(
12566 [Parameter(Position=0, ValueFromPipeline=$True)]
12567 [String]
12568 $Domain,
12569
12570 [String]
12571 $DomainController,
12572
12573 [String]
12574 $ADSpath,
12575
12576 [Switch]
12577 $API,
12578
12579 [Switch]
12580 $LDAP,
12581
12582 [ValidateRange(1,10000)]
12583 [Int]
12584 $PageSize = 200,
12585
12586 [Management.Automation.PSCredential]
12587 $Credential
12588 )
12589
12590 begin {
12591 $TrustAttributes = @{
12592 [uint32]'0x00000001' = 'non_transitive'
12593 [uint32]'0x00000002' = 'uplevel_only'
12594 [uint32]'0x00000004' = 'quarantined_domain'
12595 [uint32]'0x00000008' = 'forest_transitive'
12596 [uint32]'0x00000010' = 'cross_organization'
12597 [uint32]'0x00000020' = 'within_forest'
12598 [uint32]'0x00000040' = 'treat_as_external'
12599 [uint32]'0x00000080' = 'trust_uses_rc4_encryption'
12600 [uint32]'0x00000100' = 'trust_uses_aes_keys'
12601 [uint32]'0x00000200' = 'cross_organization_no_tgt_delegation'
12602 [uint32]'0x00000400' = 'pim_trust'
12603 }
12604 }
12605
12606 process {
12607
12608 if(-not $Domain) {
12609 # if not domain is specified grab the current domain
12610 $SourceDomain = (Get-NetDomain -Credential $Credential).Name
12611 }
12612 else {
12613 $SourceDomain = $Domain
12614 }
12615
12616 if($LDAP -or $ADSPath) {
12617
12618 $TrustSearcher = Get-DomainSearcher -Domain $SourceDomain -DomainController $DomainController -Credential $Credential -PageSize $PageSize -ADSpath $ADSpath
12619
12620 $SourceSID = Get-DomainSID -Domain $SourceDomain -DomainController $DomainController
12621
12622 if($TrustSearcher) {
12623
12624 $TrustSearcher.Filter = '(objectClass=trustedDomain)'
12625
12626 $Results = $TrustSearcher.FindAll()
12627 $Results | Where-Object {$_} | ForEach-Object {
12628 $Props = $_.Properties
12629 $DomainTrust = New-Object PSObject
12630
12631 $TrustAttrib = @()
12632 $TrustAttrib += $TrustAttributes.Keys | Where-Object { $Props.trustattributes[0] -band $_ } | ForEach-Object { $TrustAttributes[$_] }
12633
12634 $Direction = Switch ($Props.trustdirection) {
12635 0 { 'Disabled' }
12636 1 { 'Inbound' }
12637 2 { 'Outbound' }
12638 3 { 'Bidirectional' }
12639 }
12640 $ObjectGuid = New-Object Guid @(,$Props.objectguid[0])
12641 $TargetSID = (New-Object System.Security.Principal.SecurityIdentifier($Props.securityidentifier[0],0)).Value
12642 $DomainTrust | Add-Member Noteproperty 'SourceName' $SourceDomain
12643 $DomainTrust | Add-Member Noteproperty 'SourceSID' $SourceSID
12644 $DomainTrust | Add-Member Noteproperty 'TargetName' $Props.name[0]
12645 $DomainTrust | Add-Member Noteproperty 'TargetSID' $TargetSID
12646 $DomainTrust | Add-Member Noteproperty 'ObjectGuid' "{$ObjectGuid}"
12647 $DomainTrust | Add-Member Noteproperty 'TrustType' $($TrustAttrib -join ',')
12648 $DomainTrust | Add-Member Noteproperty 'TrustDirection' "$Direction"
12649 $DomainTrust.PSObject.TypeNames.Add('PowerView.DomainTrustLDAP')
12650 $DomainTrust
12651 }
12652 $Results.dispose()
12653 $TrustSearcher.dispose()
12654 }
12655 }
12656 elseif($API) {
12657 if(-not $DomainController) {
12658 $DomainController = Get-NetDomainController -Credential $Credential -Domain $SourceDomain | Select-Object -First 1 | Select-Object -ExpandProperty Name
12659 }
12660
12661 if($DomainController) {
12662 # arguments for DsEnumerateDomainTrusts
12663 $PtrInfo = [IntPtr]::Zero
12664
12665 # 63 = DS_DOMAIN_IN_FOREST + DS_DOMAIN_DIRECT_OUTBOUND + DS_DOMAIN_TREE_ROOT + DS_DOMAIN_PRIMARY + DS_DOMAIN_NATIVE_MODE + DS_DOMAIN_DIRECT_INBOUND
12666 $Flags = 63
12667 $DomainCount = 0
12668
12669 # get the trust information from the target server
12670 $Result = $Netapi32::DsEnumerateDomainTrusts($DomainController, $Flags, [ref]$PtrInfo, [ref]$DomainCount)
12671
12672 # Locate the offset of the initial intPtr
12673 $Offset = $PtrInfo.ToInt64()
12674
12675 # 0 = success
12676 if (($Result -eq 0) -and ($Offset -gt 0)) {
12677
12678 # Work out how much to increment the pointer by finding out the size of the structure
12679 $Increment = $DS_DOMAIN_TRUSTS::GetSize()
12680
12681 # parse all the result structures
12682 for ($i = 0; ($i -lt $DomainCount); $i++) {
12683 # create a new int ptr at the given offset and cast the pointer as our result structure
12684 $NewIntPtr = New-Object System.Intptr -ArgumentList $Offset
12685 $Info = $NewIntPtr -as $DS_DOMAIN_TRUSTS
12686
12687 $Offset = $NewIntPtr.ToInt64()
12688 $Offset += $Increment
12689
12690 $SidString = ""
12691 $Result = $Advapi32::ConvertSidToStringSid($Info.DomainSid, [ref]$SidString);$LastError = [Runtime.InteropServices.Marshal]::GetLastWin32Error()
12692
12693 if($Result -eq 0) {
12694 Write-Verbose "Error: $(([ComponentModel.Win32Exception] $LastError).Message)"
12695 }
12696 else {
12697 $DomainTrust = New-Object PSObject
12698 $DomainTrust | Add-Member Noteproperty 'SourceDomain' $SourceDomain
12699 $DomainTrust | Add-Member Noteproperty 'SourceDomainController' $DomainController
12700 $DomainTrust | Add-Member Noteproperty 'NetbiosDomainName' $Info.NetbiosDomainName
12701 $DomainTrust | Add-Member Noteproperty 'DnsDomainName' $Info.DnsDomainName
12702 $DomainTrust | Add-Member Noteproperty 'Flags' $Info.Flags
12703 $DomainTrust | Add-Member Noteproperty 'ParentIndex' $Info.ParentIndex
12704 $DomainTrust | Add-Member Noteproperty 'TrustType' $Info.TrustType
12705 $DomainTrust | Add-Member Noteproperty 'TrustAttributes' $Info.TrustAttributes
12706 $DomainTrust | Add-Member Noteproperty 'DomainSid' $SidString
12707 $DomainTrust | Add-Member Noteproperty 'DomainGuid' $Info.DomainGuid
12708 $DomainTrust.PSObject.TypeNames.Add('PowerView.APIDomainTrust')
12709 $DomainTrust
12710 }
12711 }
12712 # free up the result buffer
12713 $Null = $Netapi32::NetApiBufferFree($PtrInfo)
12714 }
12715 else {
12716 Write-Verbose "Error: $(([ComponentModel.Win32Exception] $Result).Message)"
12717 }
12718 }
12719 else {
12720 Write-Verbose "Could not retrieve domain controller for $Domain"
12721 }
12722 }
12723 else {
12724 # if we're using direct domain connections through .NET
12725 $FoundDomain = Get-NetDomain -Domain $Domain -Credential $Credential
12726 if($FoundDomain) {
12727 $FoundDomain.GetAllTrustRelationships() | ForEach-Object {
12728 $_.PSObject.TypeNames.Add('PowerView.DomainTrust')
12729 $_
12730 }
12731 }
12732 }
12733 }
12734}
12735
12736
12737function Get-NetForestTrust {
12738<#
12739 .SYNOPSIS
12740
12741 Return all trusts for the current forest.
12742
12743 .PARAMETER Forest
12744
12745 Return trusts for the specified forest.
12746
12747 .PARAMETER Credential
12748
12749 A [Management.Automation.PSCredential] object of alternate credentials
12750 for connection to the target domain.
12751
12752 .EXAMPLE
12753
12754 PS C:\> Get-NetForestTrust
12755
12756 Return current forest trusts.
12757
12758 .EXAMPLE
12759
12760 PS C:\> Get-NetForestTrust -Forest "test"
12761
12762 Return trusts for the "test" forest.
12763#>
12764
12765 [CmdletBinding()]
12766 param(
12767 [Parameter(Position=0,ValueFromPipeline=$True)]
12768 [String]
12769 $Forest,
12770
12771 [Management.Automation.PSCredential]
12772 $Credential
12773 )
12774
12775 process {
12776 $FoundForest = Get-NetForest -Forest $Forest -Credential $Credential
12777
12778 if($FoundForest) {
12779 $FoundForest.GetAllTrustRelationships() | ForEach-Object {
12780 $_.PSObject.TypeNames.Add('PowerView.ForestTrust')
12781 $_
12782 }
12783 }
12784 }
12785}
12786
12787
12788function Find-ForeignUser {
12789<#
12790 .SYNOPSIS
12791
12792 Enumerates users who are in groups outside of their
12793 principal domain. The -Recurse option will try to map all
12794 transitive domain trust relationships and enumerate all
12795 users who are in groups outside of their principal domain.
12796
12797 .PARAMETER UserName
12798
12799 Username to filter results for, wildcards accepted.
12800
12801 .PARAMETER Domain
12802
12803 Domain to query for users, defaults to the current domain.
12804
12805 .PARAMETER DomainController
12806
12807 Domain controller to reflect LDAP queries through.
12808
12809 .PARAMETER LDAP
12810
12811 Switch. Use LDAP queries to enumerate the trusts instead of direct domain connections.
12812 More likely to get around network segmentation, but not as accurate.
12813
12814 .PARAMETER Recurse
12815
12816 Switch. Enumerate all user trust groups from all reachable domains recursively.
12817
12818 .PARAMETER PageSize
12819
12820 The PageSize to set for the LDAP searcher object.
12821
12822 .LINK
12823
12824 http://blog.harmj0y.net/
12825#>
12826
12827 [CmdletBinding()]
12828 param(
12829 [String]
12830 $UserName,
12831
12832 [String]
12833 $Domain,
12834
12835 [String]
12836 $DomainController,
12837
12838 [Switch]
12839 $LDAP,
12840
12841 [Switch]
12842 $Recurse,
12843
12844 [ValidateRange(1,10000)]
12845 [Int]
12846 $PageSize = 200
12847 )
12848
12849 function Get-ForeignUser {
12850 # helper used to enumerate users who are in groups outside of their principal domain
12851 param(
12852 [String]
12853 $UserName,
12854
12855 [String]
12856 $Domain,
12857
12858 [String]
12859 $DomainController,
12860
12861 [ValidateRange(1,10000)]
12862 [Int]
12863 $PageSize = 200
12864 )
12865
12866 if ($Domain) {
12867 # get the domain name into distinguished form
12868 $DistinguishedDomainName = "DC=" + $Domain -replace '\.',',DC='
12869 }
12870 else {
12871 $DistinguishedDomainName = [String] ([adsi]'').distinguishedname
12872 $Domain = $DistinguishedDomainName -replace 'DC=','' -replace ',','.'
12873 }
12874
12875 Get-NetUser -Domain $Domain -DomainController $DomainController -UserName $UserName -PageSize $PageSize -Filter '(memberof=*)' | ForEach-Object {
12876 ForEach ($Membership in $_.memberof) {
12877 $Index = $Membership.IndexOf("DC=")
12878 if($Index) {
12879
12880 $GroupDomain = $($Membership.substring($Index)) -replace 'DC=','' -replace ',','.'
12881
12882 if ($GroupDomain.CompareTo($Domain)) {
12883 # if the group domain doesn't match the user domain, output
12884 $GroupName = $Membership.split(",")[0].split("=")[1]
12885 $ForeignUser = New-Object PSObject
12886 $ForeignUser | Add-Member Noteproperty 'UserDomain' $Domain
12887 $ForeignUser | Add-Member Noteproperty 'UserName' $_.samaccountname
12888 $ForeignUser | Add-Member Noteproperty 'GroupDomain' $GroupDomain
12889 $ForeignUser | Add-Member Noteproperty 'GroupName' $GroupName
12890 $ForeignUser | Add-Member Noteproperty 'GroupDN' $Membership
12891 $ForeignUser
12892 }
12893 }
12894 }
12895 }
12896 }
12897
12898 if ($Recurse) {
12899 # get all rechable domains in the trust mesh and uniquify them
12900 if($LDAP -or $DomainController) {
12901 $DomainTrusts = Invoke-MapDomainTrust -LDAP -DomainController $DomainController -PageSize $PageSize | ForEach-Object { $_.SourceDomain } | Sort-Object -Unique
12902 }
12903 else {
12904 $DomainTrusts = Invoke-MapDomainTrust -PageSize $PageSize | ForEach-Object { $_.SourceDomain } | Sort-Object -Unique
12905 }
12906
12907 ForEach($DomainTrust in $DomainTrusts) {
12908 # get the trust groups for each domain in the trust mesh
12909 Write-Verbose "Enumerating trust groups in domain $DomainTrust"
12910 Get-ForeignUser -Domain $DomainTrust -UserName $UserName -PageSize $PageSize
12911 }
12912 }
12913 else {
12914 Get-ForeignUser -Domain $Domain -DomainController $DomainController -UserName $UserName -PageSize $PageSize
12915 }
12916}
12917
12918
12919function Find-ForeignGroup {
12920<#
12921 .SYNOPSIS
12922
12923 Enumerates all the members of a given domain's groups
12924 and finds users that are not in the queried domain.
12925 The -Recurse flag will perform this enumeration for all
12926 eachable domain trusts.
12927
12928 .PARAMETER GroupName
12929
12930 Groupname to filter results for, wildcards accepted.
12931
12932 .PARAMETER Domain
12933
12934 Domain to query for groups, defaults to the current domain.
12935
12936 .PARAMETER DomainController
12937
12938 Domain controller to reflect LDAP queries through.
12939
12940 .PARAMETER LDAP
12941
12942 Switch. Use LDAP queries to enumerate the trusts instead of direct domain connections.
12943 More likely to get around network segmentation, but not as accurate.
12944
12945 .PARAMETER Recurse
12946
12947 Switch. Enumerate all group trust users from all reachable domains recursively.
12948
12949 .PARAMETER PageSize
12950
12951 The PageSize to set for the LDAP searcher object.
12952
12953 .LINK
12954
12955 http://blog.harmj0y.net/
12956#>
12957
12958 [CmdletBinding()]
12959 param(
12960 [String]
12961 $GroupName = '*',
12962
12963 [String]
12964 $Domain,
12965
12966 [String]
12967 $DomainController,
12968
12969 [Switch]
12970 $LDAP,
12971
12972 [Switch]
12973 $Recurse,
12974
12975 [ValidateRange(1,10000)]
12976 [Int]
12977 $PageSize = 200
12978 )
12979
12980 function Get-ForeignGroup {
12981 param(
12982 [String]
12983 $GroupName = '*',
12984
12985 [String]
12986 $Domain,
12987
12988 [String]
12989 $DomainController,
12990
12991 [ValidateRange(1,10000)]
12992 [Int]
12993 $PageSize = 200
12994 )
12995
12996 if(-not $Domain) {
12997 $Domain = (Get-NetDomain).Name
12998 }
12999
13000 $DomainDN = "DC=$($Domain.Replace('.', ',DC='))"
13001 Write-Verbose "DomainDN: $DomainDN"
13002
13003 # standard group names to ignore
13004 $ExcludeGroups = @("Users", "Domain Users", "Guests")
13005
13006 # get all the groupnames for the given domain
13007 Get-NetGroup -GroupName $GroupName -Filter '(member=*)' -Domain $Domain -DomainController $DomainController -FullData -PageSize $PageSize | Where-Object {
13008 # exclude common large groups
13009 -not ($ExcludeGroups -contains $_.samaccountname) } | ForEach-Object {
13010
13011 $GroupName = $_.samAccountName
13012
13013 $_.member | ForEach-Object {
13014 # filter for foreign SIDs in the cn field for users in another domain,
13015 # or if the DN doesn't end with the proper DN for the queried domain
13016 if (($_ -match 'CN=S-1-5-21.*-.*') -or ($DomainDN -ne ($_.substring($_.IndexOf("DC="))))) {
13017
13018 $UserDomain = $_.subString($_.IndexOf("DC=")) -replace 'DC=','' -replace ',','.'
13019 $UserName = $_.split(",")[0].split("=")[1]
13020
13021 $ForeignGroupUser = New-Object PSObject
13022 $ForeignGroupUser | Add-Member Noteproperty 'GroupDomain' $Domain
13023 $ForeignGroupUser | Add-Member Noteproperty 'GroupName' $GroupName
13024 $ForeignGroupUser | Add-Member Noteproperty 'UserDomain' $UserDomain
13025 $ForeignGroupUser | Add-Member Noteproperty 'UserName' $UserName
13026 $ForeignGroupUser | Add-Member Noteproperty 'UserDN' $_
13027 $ForeignGroupUser
13028 }
13029 }
13030 }
13031 }
13032
13033 if ($Recurse) {
13034 # get all rechable domains in the trust mesh and uniquify them
13035 if($LDAP -or $DomainController) {
13036 $DomainTrusts = Invoke-MapDomainTrust -LDAP -DomainController $DomainController -PageSize $PageSize | ForEach-Object { $_.SourceDomain } | Sort-Object -Unique
13037 }
13038 else {
13039 $DomainTrusts = Invoke-MapDomainTrust -PageSize $PageSize | ForEach-Object { $_.SourceDomain } | Sort-Object -Unique
13040 }
13041
13042 ForEach($DomainTrust in $DomainTrusts) {
13043 # get the trust groups for each domain in the trust mesh
13044 Write-Verbose "Enumerating trust groups in domain $DomainTrust"
13045 Get-ForeignGroup -GroupName $GroupName -Domain $Domain -DomainController $DomainController -PageSize $PageSize
13046 }
13047 }
13048 else {
13049 Get-ForeignGroup -GroupName $GroupName -Domain $Domain -DomainController $DomainController -PageSize $PageSize
13050 }
13051}
13052
13053
13054function Find-ManagedSecurityGroups {
13055<#
13056 .SYNOPSIS
13057
13058 This function retrieves all security groups in the domain and identifies ones that
13059 have a manager set. It also determines whether the manager has the ability to add
13060 or remove members from the group.
13061
13062 Author: Stuart Morgan (@ukstufus) <stuart.morgan@mwrinfosecurity.com>
13063 License: BSD 3-Clause
13064
13065 .EXAMPLE
13066
13067 PS C:\> Find-ManagedSecurityGroups | Export-PowerViewCSV -NoTypeInformation group-managers.csv
13068
13069 Store a list of all security groups with managers in group-managers.csv
13070
13071 .DESCRIPTION
13072
13073 Authority to manipulate the group membership of AD security groups and distribution groups
13074 can be delegated to non-administrators by setting the 'managedBy' attribute. This is typically
13075 used to delegate management authority to distribution groups, but Windows supports security groups
13076 being managed in the same way.
13077
13078 This function searches for AD groups which have a group manager set, and determines whether that
13079 user can manipulate group membership. This could be a useful method of horizontal privilege
13080 escalation, especially if the manager can manipulate the membership of a privileged group.
13081
13082 .LINK
13083
13084 https://github.com/PowerShellEmpire/Empire/pull/119
13085
13086#>
13087
13088 # Go through the list of security groups on the domain and identify those who have a manager
13089 Get-NetGroup -FullData -Filter '(managedBy=*)' | Select-Object -Unique distinguishedName,managedBy,cn | ForEach-Object {
13090
13091 # Retrieve the object that the managedBy DN refers to
13092 $group_manager = Get-ADObject -ADSPath $_.managedBy | Select-Object cn,distinguishedname,name,samaccounttype,samaccountname
13093
13094 # Create a results object to store our findings
13095 $results_object = New-Object -TypeName PSObject -Property @{
13096 'GroupCN' = $_.cn
13097 'GroupDN' = $_.distinguishedname
13098 'ManagerCN' = $group_manager.cn
13099 'ManagerDN' = $group_manager.distinguishedName
13100 'ManagerSAN' = $group_manager.samaccountname
13101 'ManagerType' = ''
13102 'CanManagerWrite' = $FALSE
13103 }
13104
13105 # Determine whether the manager is a user or a group
13106 if ($group_manager.samaccounttype -eq 0x10000000) {
13107 $results_object.ManagerType = 'Group'
13108 } elseif ($group_manager.samaccounttype -eq 0x30000000) {
13109 $results_object.ManagerType = 'User'
13110 }
13111
13112 # Find the ACLs that relate to the ability to write to the group
13113 $xacl = Get-ObjectAcl -ADSPath $_.distinguishedname -Rights WriteMembers
13114
13115 # Double-check that the manager
13116 if ($xacl.ObjectType -eq 'bf9679c0-0de6-11d0-a285-00aa003049e2' -and $xacl.AccessControlType -eq 'Allow' -and $xacl.IdentityReference.Value.Contains($group_manager.samaccountname)) {
13117 $results_object.CanManagerWrite = $TRUE
13118 }
13119 $results_object
13120 }
13121}
13122
13123
13124function Invoke-MapDomainTrust {
13125<#
13126 .SYNOPSIS
13127
13128 This function gets all trusts for the current domain,
13129 and tries to get all trusts for each domain it finds.
13130
13131 .PARAMETER LDAP
13132
13133 Switch. Use LDAP queries to enumerate the trusts instead of direct domain connections.
13134 More likely to get around network segmentation, but not as accurate.
13135
13136 .PARAMETER DomainController
13137
13138 Domain controller to reflect LDAP queries through.
13139
13140 .PARAMETER PageSize
13141
13142 The PageSize to set for the LDAP searcher object.
13143
13144 .PARAMETER Credential
13145
13146 A [Management.Automation.PSCredential] object of alternate credentials
13147 for connection to the target domain.
13148
13149 .EXAMPLE
13150
13151 PS C:\> Invoke-MapDomainTrust | Export-CSV -NoTypeInformation trusts.csv
13152
13153 Map all reachable domain trusts and output everything to a .csv file.
13154
13155 .LINK
13156
13157 http://blog.harmj0y.net/
13158#>
13159 [CmdletBinding()]
13160 param(
13161 [Switch]
13162 $LDAP,
13163
13164 [String]
13165 $DomainController,
13166
13167 [ValidateRange(1,10000)]
13168 [Int]
13169 $PageSize = 200,
13170
13171 [Management.Automation.PSCredential]
13172 $Credential
13173 )
13174
13175 # keep track of domains seen so we don't hit infinite recursion
13176 $SeenDomains = @{}
13177
13178 # our domain status tracker
13179 $Domains = New-Object System.Collections.Stack
13180
13181 # get the current domain and push it onto the stack
13182 $CurrentDomain = (Get-NetDomain -Credential $Credential).Name
13183 $Domains.push($CurrentDomain)
13184
13185 while($Domains.Count -ne 0) {
13186
13187 $Domain = $Domains.Pop()
13188
13189 # if we haven't seen this domain before
13190 if ($Domain -and ($Domain.Trim() -ne "") -and (-not $SeenDomains.ContainsKey($Domain))) {
13191
13192 Write-Verbose "Enumerating trusts for domain '$Domain'"
13193
13194 # mark it as seen in our list
13195 $Null = $SeenDomains.add($Domain, "")
13196
13197 try {
13198 # get all the trusts for this domain
13199 if($LDAP -or $DomainController) {
13200 $Trusts = Get-NetDomainTrust -Domain $Domain -LDAP -DomainController $DomainController -PageSize $PageSize -Credential $Credential
13201 }
13202 else {
13203 $Trusts = Get-NetDomainTrust -Domain $Domain -PageSize $PageSize -Credential $Credential
13204 }
13205
13206 if($Trusts -isnot [System.Array]) {
13207 $Trusts = @($Trusts)
13208 }
13209
13210 # get any forest trusts, if they exist
13211 if(-not ($LDAP -or $DomainController) ) {
13212 $Trusts += Get-NetForestTrust -Forest $Domain -Credential $Credential
13213 }
13214
13215 if ($Trusts) {
13216 if($Trusts -isnot [System.Array]) {
13217 $Trusts = @($Trusts)
13218 }
13219
13220 # enumerate each trust found
13221 ForEach ($Trust in $Trusts) {
13222 if($Trust.SourceName -and $Trust.TargetName) {
13223 $SourceDomain = $Trust.SourceName
13224 $TargetDomain = $Trust.TargetName
13225 $TrustType = $Trust.TrustType
13226 $TrustDirection = $Trust.TrustDirection
13227 $ObjectType = $Trust.PSObject.TypeNames | Where-Object {$_ -match 'PowerView'} | Select-Object -First 1
13228
13229 # make sure we process the target
13230 $Null = $Domains.Push($TargetDomain)
13231
13232 # build the nicely-parsable custom output object
13233 $DomainTrust = New-Object PSObject
13234 $DomainTrust | Add-Member Noteproperty 'SourceDomain' "$SourceDomain"
13235 $DomainTrust | Add-Member Noteproperty 'SourceSID' $Trust.SourceSID
13236 $DomainTrust | Add-Member Noteproperty 'TargetDomain' "$TargetDomain"
13237 $DomainTrust | Add-Member Noteproperty 'TargetSID' $Trust.TargetSID
13238 $DomainTrust | Add-Member Noteproperty 'TrustType' "$TrustType"
13239 $DomainTrust | Add-Member Noteproperty 'TrustDirection' "$TrustDirection"
13240 $DomainTrust.PSObject.TypeNames.Add($ObjectType)
13241 $DomainTrust
13242 }
13243 }
13244 }
13245 }
13246 catch {
13247 Write-Verbose "[!] Error: $_"
13248 }
13249 }
13250 }
13251}
13252
13253
13254########################################################
13255#
13256# Expose the Win32API functions and datastructures below
13257# using PSReflect.
13258# Warning: Once these are executed, they are baked in
13259# and can't be changed while the script is running!
13260#
13261########################################################
13262
13263$Mod = New-InMemoryModule -ModuleName Win32
13264
13265# all of the Win32 API functions we need
13266$FunctionDefinitions = @(
13267 (func netapi32 NetShareEnum ([Int]) @([String], [Int], [IntPtr].MakeByRefType(), [Int], [Int32].MakeByRefType(), [Int32].MakeByRefType(), [Int32].MakeByRefType())),
13268 (func netapi32 NetWkstaUserEnum ([Int]) @([String], [Int], [IntPtr].MakeByRefType(), [Int], [Int32].MakeByRefType(), [Int32].MakeByRefType(), [Int32].MakeByRefType())),
13269 (func netapi32 NetSessionEnum ([Int]) @([String], [String], [String], [Int], [IntPtr].MakeByRefType(), [Int], [Int32].MakeByRefType(), [Int32].MakeByRefType(), [Int32].MakeByRefType())),
13270 (func netapi32 NetLocalGroupGetMembers ([Int]) @([String], [String], [Int], [IntPtr].MakeByRefType(), [Int], [Int32].MakeByRefType(), [Int32].MakeByRefType(), [Int32].MakeByRefType())),
13271 (func netapi32 DsGetSiteName ([Int]) @([String], [IntPtr].MakeByRefType())),
13272 (func netapi32 DsEnumerateDomainTrusts ([Int]) @([String], [UInt32], [IntPtr].MakeByRefType(), [IntPtr].MakeByRefType())),
13273 (func netapi32 NetApiBufferFree ([Int]) @([IntPtr])),
13274 (func advapi32 ConvertSidToStringSid ([Int]) @([IntPtr], [String].MakeByRefType()) -SetLastError),
13275 (func advapi32 OpenSCManagerW ([IntPtr]) @([String], [String], [Int]) -SetLastError),
13276 (func advapi32 CloseServiceHandle ([Int]) @([IntPtr])),
13277 (func wtsapi32 WTSOpenServerEx ([IntPtr]) @([String])),
13278 (func wtsapi32 WTSEnumerateSessionsEx ([Int]) @([IntPtr], [Int32].MakeByRefType(), [Int], [IntPtr].MakeByRefType(), [Int32].MakeByRefType()) -SetLastError),
13279 (func wtsapi32 WTSQuerySessionInformation ([Int]) @([IntPtr], [Int], [Int], [IntPtr].MakeByRefType(), [Int32].MakeByRefType()) -SetLastError),
13280 (func wtsapi32 WTSFreeMemoryEx ([Int]) @([Int32], [IntPtr], [Int32])),
13281 (func wtsapi32 WTSFreeMemory ([Int]) @([IntPtr])),
13282 (func wtsapi32 WTSCloseServer ([Int]) @([IntPtr]))
13283)
13284
13285# enum used by $WTS_SESSION_INFO_1 below
13286$WTSConnectState = psenum $Mod WTS_CONNECTSTATE_CLASS UInt16 @{
13287 Active = 0
13288 Connected = 1
13289 ConnectQuery = 2
13290 Shadow = 3
13291 Disconnected = 4
13292 Idle = 5
13293 Listen = 6
13294 Reset = 7
13295 Down = 8
13296 Init = 9
13297}
13298
13299# the WTSEnumerateSessionsEx result structure
13300$WTS_SESSION_INFO_1 = struct $Mod WTS_SESSION_INFO_1 @{
13301 ExecEnvId = field 0 UInt32
13302 State = field 1 $WTSConnectState
13303 SessionId = field 2 UInt32
13304 pSessionName = field 3 String -MarshalAs @('LPWStr')
13305 pHostName = field 4 String -MarshalAs @('LPWStr')
13306 pUserName = field 5 String -MarshalAs @('LPWStr')
13307 pDomainName = field 6 String -MarshalAs @('LPWStr')
13308 pFarmName = field 7 String -MarshalAs @('LPWStr')
13309}
13310
13311# the particular WTSQuerySessionInformation result structure
13312$WTS_CLIENT_ADDRESS = struct $mod WTS_CLIENT_ADDRESS @{
13313 AddressFamily = field 0 UInt32
13314 Address = field 1 Byte[] -MarshalAs @('ByValArray', 20)
13315}
13316
13317# the NetShareEnum result structure
13318$SHARE_INFO_1 = struct $Mod SHARE_INFO_1 @{
13319 shi1_netname = field 0 String -MarshalAs @('LPWStr')
13320 shi1_type = field 1 UInt32
13321 shi1_remark = field 2 String -MarshalAs @('LPWStr')
13322}
13323
13324# the NetWkstaUserEnum result structure
13325$WKSTA_USER_INFO_1 = struct $Mod WKSTA_USER_INFO_1 @{
13326 wkui1_username = field 0 String -MarshalAs @('LPWStr')
13327 wkui1_logon_domain = field 1 String -MarshalAs @('LPWStr')
13328 wkui1_oth_domains = field 2 String -MarshalAs @('LPWStr')
13329 wkui1_logon_server = field 3 String -MarshalAs @('LPWStr')
13330}
13331
13332# the NetSessionEnum result structure
13333$SESSION_INFO_10 = struct $Mod SESSION_INFO_10 @{
13334 sesi10_cname = field 0 String -MarshalAs @('LPWStr')
13335 sesi10_username = field 1 String -MarshalAs @('LPWStr')
13336 sesi10_time = field 2 UInt32
13337 sesi10_idle_time = field 3 UInt32
13338}
13339
13340# enum used by $LOCALGROUP_MEMBERS_INFO_2 below
13341$SID_NAME_USE = psenum $Mod SID_NAME_USE UInt16 @{
13342 SidTypeUser = 1
13343 SidTypeGroup = 2
13344 SidTypeDomain = 3
13345 SidTypeAlias = 4
13346 SidTypeWellKnownGroup = 5
13347 SidTypeDeletedAccount = 6
13348 SidTypeInvalid = 7
13349 SidTypeUnknown = 8
13350 SidTypeComputer = 9
13351}
13352
13353# the NetLocalGroupGetMembers result structure
13354$LOCALGROUP_MEMBERS_INFO_2 = struct $Mod LOCALGROUP_MEMBERS_INFO_2 @{
13355 lgrmi2_sid = field 0 IntPtr
13356 lgrmi2_sidusage = field 1 $SID_NAME_USE
13357 lgrmi2_domainandname = field 2 String -MarshalAs @('LPWStr')
13358}
13359
13360# enums used in DS_DOMAIN_TRUSTS
13361$DsDomainFlag = psenum $Mod DsDomain.Flags UInt32 @{
13362 IN_FOREST = 1
13363 DIRECT_OUTBOUND = 2
13364 TREE_ROOT = 4
13365 PRIMARY = 8
13366 NATIVE_MODE = 16
13367 DIRECT_INBOUND = 32
13368} -Bitfield
13369$DsDomainTrustType = psenum $Mod DsDomain.TrustType UInt32 @{
13370 DOWNLEVEL = 1
13371 UPLEVEL = 2
13372 MIT = 3
13373 DCE = 4
13374}
13375$DsDomainTrustAttributes = psenum $Mod DsDomain.TrustAttributes UInt32 @{
13376 NON_TRANSITIVE = 1
13377 UPLEVEL_ONLY = 2
13378 FILTER_SIDS = 4
13379 FOREST_TRANSITIVE = 8
13380 CROSS_ORGANIZATION = 16
13381 WITHIN_FOREST = 32
13382 TREAT_AS_EXTERNAL = 64
13383}
13384
13385# the DsEnumerateDomainTrusts result structure
13386$DS_DOMAIN_TRUSTS = struct $Mod DS_DOMAIN_TRUSTS @{
13387 NetbiosDomainName = field 0 String -MarshalAs @('LPWStr')
13388 DnsDomainName = field 1 String -MarshalAs @('LPWStr')
13389 Flags = field 2 $DsDomainFlag
13390 ParentIndex = field 3 UInt32
13391 TrustType = field 4 $DsDomainTrustType
13392 TrustAttributes = field 5 $DsDomainTrustAttributes
13393 DomainSid = field 6 IntPtr
13394 DomainGuid = field 7 Guid
13395}
13396
13397$Types = $FunctionDefinitions | Add-Win32Type -Module $Mod -Namespace 'Win32'
13398$Netapi32 = $Types['netapi32']
13399$Advapi32 = $Types['advapi32']
13400$Wtsapi32 = $Types['wtsapi32']