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