Last active
January 25, 2024 13:28
-
-
Save nullbind/f5e26b6e6024e5c21256e8bfb7babf2d to your computer and use it in GitHub Desktop.
Obfuscated-PowerView-Example.psm1
This file contains hidden or 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
function New-InMemoryModule | |
{ | |
Param | |
( | |
[Parameter(Position = 0)] | |
[ValidateNotNullOrEmpty()] | |
[String] | |
$ModuleName = [Guid]::NewGuid().ToString() | |
) | |
$LoadedAssemblies = [AppDomain]::CurrentDomain.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]::CurrentDomain | |
$AssemblyBuilder = $Domain.DefineDynamicAssembly($DynAssembly, 'Run') | |
$ModuleBuilder = $AssemblyBuilder.DefineDynamicModule($ModuleName, $False) | |
return $ModuleBuilder | |
} | |
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, | |
[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 } | |
New-Object PSObject -Property $Properties | |
} | |
function Add-Win32Type | |
{ | |
[OutputType([Hashtable])] | |
Param( | |
[Parameter(Mandatory = $True, ValueFromPipelineByPropertyName = $True)] | |
[String] | |
$DllName, | |
[Parameter(Mandatory = $True, ValueFromPipelineByPropertyName = $True)] | |
[String] | |
$FunctionName, | |
[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') | |
if ($SetLastError) { $SLEValue = $True } else { $SLEValue = $False } | |
# 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), | |
[Object[]] @($SLEValue, ([Runtime.InteropServices.CallingConvention] $NativeCallingConvention), ([Runtime.InteropServices.CharSet] $Charset))) | |
$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 | |
{ | |
[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() | |
} | |
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 | |
{ | |
[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() | |
} | |
filter Get-IniContent { | |
[CmdletBinding()] | |
Param( | |
[Parameter(Mandatory=$True, ValueFromPipeline=$True, ValueFromPipelineByPropertyName=$True)] | |
[Alias('FullName')] | |
[ValidateScript({ Test-Path -Path $_ })] | |
[String[]] | |
$Path | |
) | |
ForEach($TargetPath in $Path) { | |
$IniObject = @{} | |
Switch -Regex -File $TargetPath { | |
"^\[(.+)\]" # Section | |
{ | |
$Section = $matches[1].Trim() | |
$IniObject[$Section] = @{} | |
$CommentCount = 0 | |
} | |
"^(;.*)$" # Comment | |
{ | |
$Value = $matches[1].Trim() | |
$CommentCount = $CommentCount + 1 | |
$Name = 'Comment' + $CommentCount | |
$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)} | |
$IniObject[$Section][$Name] = $Values | |
} | |
} | |
$IniObject | |
} | |
} | |
filter Export-PowerViewCSV { | |
Param( | |
[Parameter(Mandatory=$True, ValueFromPipeline=$True, ValueFromPipelineByPropertyName=$True)] | |
[System.Management.Automation.PSObject[]] | |
$InputObject, | |
[Parameter(Mandatory=$True, Position=0)] | |
[String] | |
[ValidateNotNullOrEmpty()] | |
$OutFile | |
) | |
$ObjectCSV = $InputObject | ConvertTo-Csv -NoTypeInformation | |
# mutex so threaded code doesn't stomp on the output file | |
$Mutex = New-Object System.Threading.Mutex $False,'CSVMutex'; | |
$Null = $Mutex.WaitOne() | |
if (Test-Path -Path $OutFile) { | |
# hack to skip the first line of output if the file already exists | |
$ObjectCSV | ForEach-Object { $Start=$True }{ if ($Start) {$Start=$False} else {$_} } | Out-File -Encoding 'ASCII' -Append -FilePath $OutFile | |
} | |
else { | |
$ObjectCSV | Out-File -Encoding 'ASCII' -Append -FilePath $OutFile | |
} | |
$Mutex.ReleaseMutex() | |
} | |
filter Get-IPAddress { | |
[CmdletBinding()] | |
param( | |
[Parameter(Position=0, ValueFromPipeline=$True)] | |
[Alias('HostName')] | |
[String] | |
$ComputerName = $Env:ComputerName | |
) | |
try { | |
# extract the computer name from whatever object was passed on the pipeline | |
$Computer = $ComputerName | Get-NameField | |
# get the IP resolution of this specified hostname | |
@(([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 -Message 'Could not resolve host to an IP Address.' | |
} | |
} | |
filter Convert-NameToSid { | |
[CmdletBinding()] | |
param( | |
[Parameter(Mandatory=$True, ValueFromPipeline=$True)] | |
[String] | |
[Alias('Name')] | |
$ObjectName, | |
[String] | |
$Domain | |
) | |
$ObjectName = $ObjectName -Replace "/","\" | |
if($ObjectName.Contains("\")) { | |
# if we get a DOMAIN\user format, auto convert it | |
$Domain = $ObjectName.Split("\")[0] | |
$ObjectName = $ObjectName.Split("\")[1] | |
} | |
elseif(-not $Domain) { | |
$Domain = (Get-ThisThingDomain).Name | |
} | |
try { | |
$Obj = (New-Object System.Security.Principal.NTAccount($Domain, $ObjectName)) | |
$SID = $Obj.Translate([System.Security.Principal.SecurityIdentifier]).Value | |
$Out = New-Object PSObject | |
$Out | Add-Member Noteproperty 'ObjectName' $ObjectName | |
$Out | Add-Member Noteproperty 'SID' $SID | |
$Out | |
} | |
catch { | |
Write-Verbose "Invalid object/name: $Domain\$ObjectName" | |
$Null | |
} | |
} | |
filter Convert-SidToName { | |
[CmdletBinding()] | |
param( | |
[Parameter(Mandatory=$True, ValueFromPipeline=$True)] | |
[String] | |
[ValidatePattern('^S-1-.*')] | |
$SID | |
) | |
try { | |
$SID2 = $SID.trim('*') | |
# try to resolve any built-in SIDs first | |
# from https://support.microsoft.com/en-us/kb/243330 | |
Switch ($SID2) { | |
'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 { | |
$Obj = (New-Object System.Security.Principal.SecurityIdentifier($SID2)) | |
$Obj.Translate( [System.Security.Principal.NTAccount]).Value | |
} | |
} | |
} | |
catch { | |
Write-Verbose "Invalid SID: $SID" | |
$SID | |
} | |
} | |
filter Convert-ADName { | |
[CmdletBinding()] | |
param( | |
[Parameter(Mandatory=$True, ValueFromPipeline=$True)] | |
[String] | |
$ObjectName, | |
[String] | |
[ValidateSet("NT4","Simple","Canonical")] | |
$InputType, | |
[String] | |
[ValidateSet("NT4","Simple","Canonical")] | |
$OutputType | |
) | |
$NameTypes = @{ | |
'Canonical' = 2 | |
'NT4' = 3 | |
'Simple' = 5 | |
} | |
if(-not $PSBoundParameters['InputType']) { | |
if( ($ObjectName.split('/')).Count -eq 2 ) { | |
$ObjectName = $ObjectName.replace('/', '\') | |
} | |
if($ObjectName -match "^[A-Za-z]+\\[A-Za-z ]+") { | |
$InputType = 'NT4' | |
} | |
elseif($ObjectName -match "^[A-Za-z ]+@[A-Za-z\.]+") { | |
$InputType = 'Simple' | |
} | |
elseif($ObjectName -match "^[A-Za-z\.]+/[A-Za-z]+/[A-Za-z/ ]+") { | |
$InputType = 'Canonical' | |
} | |
else { | |
Write-Warning "Can not identify InType for $ObjectName" | |
return $ObjectName | |
} | |
} | |
elseif($InputType -eq 'NT4') { | |
$ObjectName = $ObjectName.replace('/', '\') | |
} | |
if(-not $PSBoundParameters['OutputType']) { | |
$OutputType = Switch($InputType) { | |
'NT4' {'Canonical'} | |
'Simple' {'NT4'} | |
'Canonical' {'NT4'} | |
} | |
} | |
# try to extract the domain from the given format | |
$Domain = Switch($InputType) { | |
'NT4' { $ObjectName.split("\")[0] } | |
'Simple' { $ObjectName.split("@")[1] } | |
'Canonical' { $ObjectName.split("/")[0] } | |
} | |
# Accessor functions to simplify calls to NameTranslate | |
function Invoke-Method([__ComObject] $Object, [String] $Method, $Parameters) { | |
$Output = $Object.GetType().InvokeMember($Method, "InvokeMethod", $Null, $Object, $Parameters) | |
if ( $Output ) { $Output } | |
} | |
function Set-Property([__ComObject] $Object, [String] $Property, $Parameters) { | |
[Void] $Object.GetType().InvokeMember($Property, "SetProperty", $Null, $Object, $Parameters) | |
} | |
$Translate = New-Object -ComObject NameTranslate | |
try { | |
Invoke-Method $Translate "Init" (1, $Domain) | |
} | |
catch [System.Management.Automation.MethodInvocationException] { | |
Write-Verbose "Error with translate init in Convert-ADName: $_" | |
} | |
Set-Property $Translate "ChaseReferral" (0x60) | |
try { | |
Invoke-Method $Translate "Set" ($NameTypes[$InputType], $ObjectName) | |
(Invoke-Method $Translate "Get" ($NameTypes[$OutputType])) | |
} | |
catch [System.Management.Automation.MethodInvocationException] { | |
Write-Verbose "Error with translate Set/Get in Convert-ADName: $_" | |
} | |
} | |
function ConvertFrom-UACValue { | |
[CmdletBinding()] | |
param( | |
[Parameter(Mandatory=$True, ValueFromPipeline=$True)] | |
$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($Value -is [Int]) { | |
$IntValue = $Value | |
} | |
elseif ($Value -is [PSCustomObject]) { | |
if($Value.useraccountcontrol) { | |
$IntValue = $Value.useraccountcontrol | |
} | |
} | |
else { | |
Write-Warning "Invalid object input for -Value : $Value" | |
return $Null | |
} | |
if($ShowAll) { | |
foreach ($UACValue in $UACValues.GetEnumerator()) { | |
if( ($IntValue -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( ($IntValue -band $UACValue.Value) -eq $UACValue.Value) { | |
$ResultUACValues.Add($UACValue.Name, "$($UACValue.Value)") | |
} | |
} | |
} | |
$ResultUACValues | |
} | |
} | |
filter Get-Proxy { | |
param( | |
[Parameter(ValueFromPipeline=$True)] | |
[ValidateNotNullOrEmpty()] | |
[String] | |
$ComputerName = $ENV:COMPUTERNAME | |
) | |
try { | |
$Reg = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('CurrentUser', $ComputerName) | |
$RegKey = $Reg.OpenSubkey("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Internet Settings") | |
$ProxyServer = $RegKey.GetValue('ProxyServer') | |
$AutoConfigURL = $RegKey.GetValue('AutoConfigURL') | |
$Wpad = "" | |
if($AutoConfigURL -and ($AutoConfigURL -ne "")) { | |
try { | |
$Wpad = (New-Object Net.Webclient).DownloadString($AutoConfigURL) | |
} | |
catch { | |
Write-Warning "Error connecting to AutoConfigURL : $AutoConfigURL" | |
} | |
} | |
if($ProxyServer -or $AutoConfigUrl) { | |
$Properties = @{ | |
'ProxyServer' = $ProxyServer | |
'AutoConfigURL' = $AutoConfigURL | |
'Wpad' = $Wpad | |
} | |
New-Object -TypeName PSObject -Property $Properties | |
} | |
else { | |
Write-Warning "No proxy settings found for $ComputerName" | |
} | |
} | |
catch { | |
Write-Warning "Error enumerating proxy settings for $ComputerName : $_" | |
} | |
} | |
function Request-SPNTicket { | |
[CmdletBinding()] | |
Param ( | |
[Parameter(Mandatory=$True, ValueFromPipelineByPropertyName = $True)] | |
[Alias('ServicePrincipalName')] | |
[String[]] | |
$SPN, | |
[Alias('EncryptedPart')] | |
[Switch] | |
$EncPart | |
) | |
begin { | |
Add-Type -AssemblyName System.IdentityModel | |
} | |
process { | |
ForEach($UserSPN in $SPN) { | |
Write-Verbose "Requesting ticket for: $UserSPN" | |
if (!$EncPart) { | |
New-Object System.IdentityModel.Tokens.KerberosRequestorSecurityToken -ArgumentList $UserSPN | |
} | |
else { | |
$Ticket = New-Object System.IdentityModel.Tokens.KerberosRequestorSecurityToken -ArgumentList $UserSPN | |
$TicketByteStream = $Ticket.GetRequest() | |
if ($TicketByteStream) | |
{ | |
$TicketHexStream = [System.BitConverter]::ToString($TicketByteStream) -replace "-" | |
[System.Collections.ArrayList]$Parts = ($TicketHexStream -replace '^(.*?)04820...(.*)','$2') -Split "A48201" | |
$Parts.RemoveAt($Parts.Count - 1) | |
$Parts -join "A48201" | |
break | |
} | |
} | |
} | |
} | |
} | |
function Get-PathAcl { | |
[CmdletBinding()] | |
param( | |
[Parameter(Mandatory=$True, ValueFromPipeline=$True)] | |
[String] | |
$Path, | |
[Switch] | |
$Recurse | |
) | |
begin { | |
function Convert-FileRight { | |
# From 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 | % { | |
if (($FSR -band $_) -eq $_) { | |
$SimplePermissions[$_] | |
$FSR = $FSR -band (-not $_) | |
} | |
} | |
# get remaining extended permissions | |
$Permissions += $AccessMask.Keys | | |
? { $FSR -band $_ } | | |
% { $AccessMask[$_] } | |
($Permissions | ?{$_}) -join "," | |
} | |
} | |
process { | |
try { | |
$ACL = Get-Acl -Path $Path | |
$ACL.GetAccessRules($true,$true,[System.Security.Principal.SecurityIdentifier]) | ForEach-Object { | |
$Names = @() | |
if ($_.IdentityReference -match '^S-1-5-21-[0-9]+-[0-9]+-[0-9]+-[0-9]+') { | |
$Object = Get-ADObject -SID $_.IdentityReference | |
$Names = @() | |
$SIDs = @($Object.objectsid) | |
if ($Recurse -and (@('268435456','268435457','536870912','536870913') -contains $Object.samAccountType)) { | |
$SIDs += Get-ThisThingGroupMember -SID $Object.objectsid | Select-Object -ExpandProperty MemberSid | |
} | |
$SIDs | ForEach-Object { | |
$Names += ,@($_, (Convert-SidToName $_)) | |
} | |
} | |
else { | |
$Names += ,@($_.IdentityReference.Value, (Convert-SidToName $_.IdentityReference.Value)) | |
} | |
ForEach($Name in $Names) { | |
$Out = New-Object PSObject | |
$Out | Add-Member Noteproperty 'Path' $Path | |
$Out | Add-Member Noteproperty 'FileSystemRights' (Convert-FileRight -FSR $_.FileSystemRights.value__) | |
$Out | Add-Member Noteproperty 'IdentityReference' $Name[1] | |
$Out | Add-Member Noteproperty 'IdentitySID' $Name[0] | |
$Out | Add-Member Noteproperty 'AccessControlType' $_.AccessControlType | |
$Out | |
} | |
} | |
} | |
catch { | |
Write-Warning $_ | |
} | |
} | |
} | |
filter Get-NameField { | |
[CmdletBinding()] | |
param( | |
[Parameter(ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)] | |
[Object] | |
$Object, | |
[Parameter(ValueFromPipelineByPropertyName = $True)] | |
[String] | |
$DnsHostName, | |
[Parameter(ValueFromPipelineByPropertyName = $True)] | |
[String] | |
$Name | |
) | |
if($PSBoundParameters['DnsHostName']) { | |
$DnsHostName | |
} | |
elseif($PSBoundParameters['Name']) { | |
$Name | |
} | |
elseif($Object) { | |
if ( [bool]($Object.PSobject.Properties.name -match "dnshostname") ) { | |
# objects from Get-ThisThingComputer | |
$Object.dnshostname | |
} | |
elseif ( [bool]($Object.PSobject.Properties.name -match "name") ) { | |
# objects from Get-ThisThingDomainController | |
$Object.name | |
} | |
else { | |
# strings and catch alls | |
$Object | |
} | |
} | |
else { | |
return $Null | |
} | |
} | |
function Convert-LDAPProperty { | |
param( | |
[Parameter(Mandatory=$True, ValueFromPipeline=$True)] | |
[ValidateNotNullOrEmpty()] | |
$Properties | |
) | |
$ObjectProperties = @{} | |
$Properties.PropertyNames | ForEach-Object { | |
if (($_ -eq "objectsid") -or ($_ -eq "sidhistory")) { | |
# convert the SID to a string | |
$ObjectProperties[$_] = (New-Object System.Security.Principal.SecurityIdentifier($Properties[$_][0],0)).Value | |
} | |
elseif($_ -eq "objectguid") { | |
# convert the GUID to a string | |
$ObjectProperties[$_] = (New-Object Guid (,$Properties[$_][0])).Guid | |
} | |
elseif( ($_ -eq "lastlogon") -or ($_ -eq "lastlogontimestamp") -or ($_ -eq "pwdlastset") -or ($_ -eq "lastlogoff") -or ($_ -eq "badPasswordTime") ) { | |
# 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 { | |
$ObjectProperties[$_] = ([datetime]::FromFileTime(($Properties[$_][0]))) | |
} | |
} | |
elseif($Properties[$_][0] -is [System.MarshalByRefObject]) { | |
# try to convert misc com objects | |
$Prop = $Properties[$_] | |
try { | |
$Temp = $Prop[$_][0] | |
Write-Verbose $_ | |
[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 { | |
$ObjectProperties[$_] = $Prop[$_] | |
} | |
} | |
elseif($Properties[$_].count -eq 1) { | |
$ObjectProperties[$_] = $Properties[$_][0] | |
} | |
else { | |
$ObjectProperties[$_] = $Properties[$_] | |
} | |
} | |
New-Object -TypeName PSObject -Property $ObjectProperties | |
} | |
filter Get-DomainSearcher { | |
param( | |
[Parameter(ValueFromPipeline=$True)] | |
[String] | |
$Domain, | |
[String] | |
$DomainController, | |
[String] | |
$ADSpath, | |
[String] | |
$ADSprefix, | |
[ValidateRange(1,10000)] | |
[Int] | |
$PageSize = 200, | |
[Management.Automation.PSCredential] | |
$Credential | |
) | |
if(-not $Credential) { | |
if(-not $Domain) { | |
$Domain = (Get-ThisThingDomain).name | |
} | |
elseif(-not $DomainController) { | |
try { | |
# if there's no -DomainController specified, try to pull the primary DC to reflect queries through | |
$DomainController = ((Get-ThisThingDomain).PdcRoleOwner).Name | |
} | |
catch { | |
throw "Get-DomainSearcher: Error in retrieving PDC for current domain" | |
} | |
} | |
} | |
elseif (-not $DomainController) { | |
# if a DC isn't specified | |
try { | |
$DomainController = ((Get-ThisThingDomain -Credential $Credential).PdcRoleOwner).Name | |
} | |
catch { | |
throw "Get-DomainSearcher: Error in retrieving PDC for current domain" | |
} | |
if(!$DomainController) { | |
throw "Get-DomainSearcher: Error in retrieving PDC for current domain" | |
} | |
} | |
$SearchString = "LDAP://" | |
if($DomainController) { | |
$SearchString += $DomainController | |
if($Domain){ | |
$SearchString += '/' | |
} | |
} | |
if($ADSprefix) { | |
$SearchString += $ADSprefix + ',' | |
} | |
if($ADSpath) { | |
if($ADSpath -Match '^GC://') { | |
# if we're searching the global catalog | |
$DN = $AdsPath.ToUpper().Trim('/') | |
$SearchString = '' | |
} | |
else { | |
if($ADSpath -match '^LDAP://') { | |
if($ADSpath -match "LDAP://.+/.+") { | |
$SearchString = '' | |
} | |
else { | |
$ADSpath = $ADSpath.Substring(7) | |
} | |
} | |
$DN = $ADSpath | |
} | |
} | |
else { | |
if($Domain -and ($Domain.Trim() -ne "")) { | |
$DN = "DC=$($Domain.Replace('.', ',DC='))" | |
} | |
} | |
$SearchString += $DN | |
Write-Verbose "Get-DomainSearcher search string: $SearchString" | |
if($Credential) { | |
Write-Verbose "Using alternate credentials for LDAP connection" | |
$DomainObject = New-Object DirectoryServices.DirectoryEntry($SearchString, $Credential.UserName, $Credential.GetNetworkCredential().Password) | |
$Searcher = New-Object System.DirectoryServices.DirectorySearcher($DomainObject) | |
} | |
else { | |
$Searcher = New-Object System.DirectoryServices.DirectorySearcher([ADSI]$SearchString) | |
} | |
$Searcher.PageSize = $PageSize | |
$Searcher.CacheResults = $False | |
$Searcher | |
} | |
filter Convert-DNSRecord { | |
param( | |
[Parameter(Position=0, ValueFromPipelineByPropertyName=$True, Mandatory=$True)] | |
[Byte[]] | |
$DNSRecord | |
) | |
function Get-Name { | |
# modified decodeName from https://raw.githubusercontent.com/mmessano/PowerShell/master/dns-dump.ps1 | |
[CmdletBinding()] | |
param( | |
[Byte[]] | |
$Raw | |
) | |
[Int]$Length = $Raw[0] | |
[Int]$Segments = $Raw[1] | |
[Int]$Index = 2 | |
[String]$Name = "" | |
while ($Segments-- -gt 0) | |
{ | |
[Int]$SegmentLength = $Raw[$Index++] | |
while ($SegmentLength-- -gt 0) { | |
$Name += [Char]$Raw[$Index++] | |
} | |
$Name += "." | |
} | |
$Name | |
} | |
$RDataLen = [BitConverter]::ToUInt16($DNSRecord, 0) | |
$RDataType = [BitConverter]::ToUInt16($DNSRecord, 2) | |
$UpdatedAtSerial = [BitConverter]::ToUInt32($DNSRecord, 8) | |
$TTLRaw = $DNSRecord[12..15] | |
# reverse for big endian | |
$Null = [array]::Reverse($TTLRaw) | |
$TTL = [BitConverter]::ToUInt32($TTLRaw, 0) | |
$Age = [BitConverter]::ToUInt32($DNSRecord, 20) | |
if($Age -ne 0) { | |
$TimeStamp = ((Get-Date -Year 1601 -Month 1 -Day 1 -Hour 0 -Minute 0 -Second 0).AddHours($age)).ToString() | |
} | |
else { | |
$TimeStamp = "[static]" | |
} | |
$DNSRecordObject = New-Object PSObject | |
if($RDataType -eq 1) { | |
$IP = "{0}.{1}.{2}.{3}" -f $DNSRecord[24], $DNSRecord[25], $DNSRecord[26], $DNSRecord[27] | |
$Data = $IP | |
$DNSRecordObject | Add-Member Noteproperty 'RecordType' 'A' | |
} | |
elseif($RDataType -eq 2) { | |
$NSName = Get-Name $DNSRecord[24..$DNSRecord.length] | |
$Data = $NSName | |
$DNSRecordObject | Add-Member Noteproperty 'RecordType' 'NS' | |
} | |
elseif($RDataType -eq 5) { | |
$Alias = Get-Name $DNSRecord[24..$DNSRecord.length] | |
$Data = $Alias | |
$DNSRecordObject | Add-Member Noteproperty 'RecordType' 'CNAME' | |
} | |
elseif($RDataType -eq 6) { | |
# TODO: how to implement properly? nested object? | |
$Data = $([System.Convert]::ToBase64String($DNSRecord[24..$DNSRecord.length])) | |
$DNSRecordObject | Add-Member Noteproperty 'RecordType' 'SOA' | |
} | |
elseif($RDataType -eq 12) { | |
$Ptr = Get-Name $DNSRecord[24..$DNSRecord.length] | |
$Data = $Ptr | |
$DNSRecordObject | Add-Member Noteproperty 'RecordType' 'PTR' | |
} | |
elseif($RDataType -eq 13) { | |
# TODO: how to implement properly? nested object? | |
$Data = $([System.Convert]::ToBase64String($DNSRecord[24..$DNSRecord.length])) | |
$DNSRecordObject | Add-Member Noteproperty 'RecordType' 'HINFO' | |
} | |
elseif($RDataType -eq 15) { | |
# TODO: how to implement properly? nested object? | |
$Data = $([System.Convert]::ToBase64String($DNSRecord[24..$DNSRecord.length])) | |
$DNSRecordObject | Add-Member Noteproperty 'RecordType' 'MX' | |
} | |
elseif($RDataType -eq 16) { | |
[string]$TXT = "" | |
[int]$SegmentLength = $DNSRecord[24] | |
$Index = 25 | |
while ($SegmentLength-- -gt 0) { | |
$TXT += [char]$DNSRecord[$index++] | |
} | |
$Data = $TXT | |
$DNSRecordObject | Add-Member Noteproperty 'RecordType' 'TXT' | |
} | |
elseif($RDataType -eq 28) { | |
# TODO: how to implement properly? nested object? | |
$Data = $([System.Convert]::ToBase64String($DNSRecord[24..$DNSRecord.length])) | |
$DNSRecordObject | Add-Member Noteproperty 'RecordType' 'AAAA' | |
} | |
elseif($RDataType -eq 33) { | |
# TODO: how to implement properly? nested object? | |
$Data = $([System.Convert]::ToBase64String($DNSRecord[24..$DNSRecord.length])) | |
$DNSRecordObject | Add-Member Noteproperty 'RecordType' 'SRV' | |
} | |
else { | |
$Data = $([System.Convert]::ToBase64String($DNSRecord[24..$DNSRecord.length])) | |
$DNSRecordObject | Add-Member Noteproperty 'RecordType' 'UNKNOWN' | |
} | |
$DNSRecordObject | Add-Member Noteproperty 'UpdatedAtSerial' $UpdatedAtSerial | |
$DNSRecordObject | Add-Member Noteproperty 'TTL' $TTL | |
$DNSRecordObject | Add-Member Noteproperty 'Age' $Age | |
$DNSRecordObject | Add-Member Noteproperty 'TimeStamp' $TimeStamp | |
$DNSRecordObject | Add-Member Noteproperty 'Data' $Data | |
$DNSRecordObject | |
} | |
filter Get-DNSZone { | |
param( | |
[Parameter(Position=0, ValueFromPipeline=$True)] | |
[String] | |
$Domain, | |
[String] | |
$DomainController, | |
[ValidateRange(1,10000)] | |
[Int] | |
$PageSize = 200, | |
[Management.Automation.PSCredential] | |
$Credential, | |
[Switch] | |
$FullData | |
) | |
$DNSSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -PageSize $PageSize -Credential $Credential | |
$DNSSearcher.filter="(objectClass=dnsZone)" | |
if($DNSSearcher) { | |
$Results = $DNSSearcher.FindAll() | |
$Results | Where-Object {$_} | ForEach-Object { | |
# convert/process the LDAP fields for each result | |
$Properties = Convert-LDAPProperty -Properties $_.Properties | |
$Properties | Add-Member NoteProperty 'ZoneName' $Properties.name | |
if ($FullData) { | |
$Properties | |
} | |
else { | |
$Properties | Select-Object ZoneName,distinguishedname,whencreated,whenchanged | |
} | |
} | |
$Results.dispose() | |
$DNSSearcher.dispose() | |
} | |
$DNSSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -PageSize $PageSize -Credential $Credential -ADSprefix "CN=MicrosoftDNS,DC=DomainDnsZones" | |
$DNSSearcher.filter="(objectClass=dnsZone)" | |
if($DNSSearcher) { | |
$Results = $DNSSearcher.FindAll() | |
$Results | Where-Object {$_} | ForEach-Object { | |
# convert/process the LDAP fields for each result | |
$Properties = Convert-LDAPProperty -Properties $_.Properties | |
$Properties | Add-Member NoteProperty 'ZoneName' $Properties.name | |
if ($FullData) { | |
$Properties | |
} | |
else { | |
$Properties | Select-Object ZoneName,distinguishedname,whencreated,whenchanged | |
} | |
} | |
$Results.dispose() | |
$DNSSearcher.dispose() | |
} | |
} | |
filter Get-DNSRecord { | |
param( | |
[Parameter(Position=0, ValueFromPipelineByPropertyName=$True, Mandatory=$True)] | |
[String] | |
$ZoneName, | |
[String] | |
$Domain, | |
[String] | |
$DomainController, | |
[ValidateRange(1,10000)] | |
[Int] | |
$PageSize = 200, | |
[Management.Automation.PSCredential] | |
$Credential | |
) | |
$DNSSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -PageSize $PageSize -Credential $Credential -ADSprefix "DC=$($ZoneName),CN=MicrosoftDNS,DC=DomainDnsZones" | |
$DNSSearcher.filter="(objectClass=dnsNode)" | |
if($DNSSearcher) { | |
$Results = $DNSSearcher.FindAll() | |
$Results | Where-Object {$_} | ForEach-Object { | |
try { | |
# convert/process the LDAP fields for each result | |
$Properties = Convert-LDAPProperty -Properties $_.Properties | Select-Object name,distinguishedname,dnsrecord,whencreated,whenchanged | |
$Properties | Add-Member NoteProperty 'ZoneName' $ZoneName | |
# convert the record and extract the properties | |
if ($Properties.dnsrecord -is [System.DirectoryServices.ResultPropertyValueCollection]) { | |
# TODO: handle multiple nested records properly? | |
$Record = Convert-DNSRecord -DNSRecord $Properties.dnsrecord[0] | |
} | |
else { | |
$Record = Convert-DNSRecord -DNSRecord $Properties.dnsrecord | |
} | |
if($Record) { | |
$Record.psobject.properties | ForEach-Object { | |
$Properties | Add-Member NoteProperty $_.Name $_.Value | |
} | |
} | |
$Properties | |
} | |
catch { | |
Write-Warning "ERROR: $_" | |
$Properties | |
} | |
} | |
$Results.dispose() | |
$DNSSearcher.dispose() | |
} | |
} | |
filter Get-ThisThingDomain { | |
param( | |
[Parameter(ValueFromPipeline=$True)] | |
[String] | |
$Domain, | |
[Management.Automation.PSCredential] | |
$Credential | |
) | |
if($Credential) { | |
Write-Verbose "Using alternate credentials for Get-ThisThingDomain" | |
if(!$Domain) { | |
# if no domain is supplied, extract the logon domain from the PSCredential passed | |
$Domain = $Credential.GetNetworkCredential().Domain | |
Write-Verbose "Extracted domain '$Domain' from -Credential" | |
} | |
$DomainContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext('Domain', $Domain, $Credential.UserName, $Credential.GetNetworkCredential().Password) | |
try { | |
[System.DirectoryServices.ActiveDirectory.Domain]::GetDomain($DomainContext) | |
} | |
catch { | |
Write-Verbose "The specified domain does '$Domain' not exist, could not be contacted, there isn't an existing trust, or the specified credentials are invalid." | |
$Null | |
} | |
} | |
elseif($Domain) { | |
$DomainContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext('Domain', $Domain) | |
try { | |
[System.DirectoryServices.ActiveDirectory.Domain]::GetDomain($DomainContext) | |
} | |
catch { | |
Write-Verbose "The specified domain '$Domain' does not exist, could not be contacted, or there isn't an existing trust." | |
$Null | |
} | |
} | |
else { | |
[System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain() | |
} | |
} | |
filter Get-ThisThingForest { | |
param( | |
[Parameter(ValueFromPipeline=$True)] | |
[String] | |
$Forest, | |
[Management.Automation.PSCredential] | |
$Credential | |
) | |
if($Credential) { | |
Write-Verbose "Using alternate credentials for Get-ThisThingForest" | |
if(!$Forest) { | |
# if no domain is supplied, extract the logon domain from the PSCredential passed | |
$Forest = $Credential.GetNetworkCredential().Domain | |
Write-Verbose "Extracted domain '$Forest' from -Credential" | |
} | |
$ForestContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext('Forest', $Forest, $Credential.UserName, $Credential.GetNetworkCredential().Password) | |
try { | |
$ForestObject = [System.DirectoryServices.ActiveDirectory.Forest]::GetForest($ForestContext) | |
} | |
catch { | |
Write-Verbose "The specified forest '$Forest' does not exist, could not be contacted, there isn't an existing trust, or the specified credentials are invalid." | |
$Null | |
} | |
} | |
elseif($Forest) { | |
$ForestContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext('Forest', $Forest) | |
try { | |
$ForestObject = [System.DirectoryServices.ActiveDirectory.Forest]::GetForest($ForestContext) | |
} | |
catch { | |
Write-Verbose "The specified forest '$Forest' does not exist, could not be contacted, or there isn't an existing trust." | |
return $Null | |
} | |
} | |
else { | |
# otherwise use the current forest | |
$ForestObject = [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest() | |
} | |
if($ForestObject) { | |
# get the SID of the forest root | |
$ForestSid = (New-Object System.Security.Principal.NTAccount($ForestObject.RootDomain,"krbtgt")).Translate([System.Security.Principal.SecurityIdentifier]).Value | |
$Parts = $ForestSid -Split "-" | |
$ForestSid = $Parts[0..$($Parts.length-2)] -join "-" | |
$ForestObject | Add-Member NoteProperty 'RootDomainSid' $ForestSid | |
$ForestObject | |
} | |
} | |
filter Get-ThisThingForestDomain { | |
param( | |
[Parameter(ValueFromPipeline=$True)] | |
[String] | |
$Forest, | |
[Management.Automation.PSCredential] | |
$Credential | |
) | |
$ForestObject = Get-ThisThingForest -Forest $Forest -Credential $Credential | |
if($ForestObject) { | |
$ForestObject.Domains | |
} | |
} | |
filter Get-ThisThingForestCatalog { | |
param( | |
[Parameter(ValueFromPipeline=$True)] | |
[String] | |
$Forest, | |
[Management.Automation.PSCredential] | |
$Credential | |
) | |
$ForestObject = Get-ThisThingForest -Forest $Forest -Credential $Credential | |
if($ForestObject) { | |
$ForestObject.FindAllGlobalCatalogs() | |
} | |
} | |
filter Get-ThisThingDomainController { | |
[CmdletBinding()] | |
param( | |
[Parameter(ValueFromPipeline=$True)] | |
[String] | |
$Domain, | |
[String] | |
$DomainController, | |
[Switch] | |
$LDAP, | |
[Management.Automation.PSCredential] | |
$Credential | |
) | |
if($LDAP -or $DomainController) { | |
# filter string to return all domain controllers | |
Get-ThisThingComputer -Domain $Domain -DomainController $DomainController -Credential $Credential -FullData -Filter '(userAccountControl:1.2.840.113556.1.4.803:=8192)' | |
} | |
else { | |
$FoundDomain = Get-ThisThingDomain -Domain $Domain -Credential $Credential | |
if($FoundDomain) { | |
$Founddomain.DomainControllers | |
} | |
} | |
} | |
function Get-ThisThingUser { | |
param( | |
[Parameter(Position=0, ValueFromPipeline=$True)] | |
[String] | |
$UserName, | |
[String] | |
$Domain, | |
[String] | |
$DomainController, | |
[String] | |
$ADSpath, | |
[String] | |
$Filter, | |
[Switch] | |
$SPN, | |
[Switch] | |
$AdminCount, | |
[Switch] | |
$Unconstrained, | |
[Switch] | |
$AllowDelegation, | |
[ValidateRange(1,10000)] | |
[Int] | |
$PageSize = 200, | |
[Management.Automation.PSCredential] | |
$Credential | |
) | |
begin { | |
# so this isn't repeated if users are passed on the pipeline | |
$UserSearcher = Get-DomainSearcher -Domain $Domain -ADSpath $ADSpath -DomainController $DomainController -PageSize $PageSize -Credential $Credential | |
} | |
process { | |
if($UserSearcher) { | |
# if we're checking for unconstrained delegation | |
if($Unconstrained) { | |
Write-Verbose "Checking for unconstrained delegation" | |
$Filter += "(userAccountControl:1.2.840.113556.1.4.803:=524288)" | |
} | |
if($AllowDelegation) { | |
Write-Verbose "Checking for users who can be delegated" | |
# negation of "Accounts that are sensitive and not trusted for delegation" | |
$Filter += "(!(userAccountControl:1.2.840.113556.1.4.803:=1048574))" | |
} | |
if($AdminCount) { | |
Write-Verbose "Checking for adminCount=1" | |
$Filter += "(admincount=1)" | |
} | |
# check if we're using a username filter or not | |
if($UserName) { | |
# samAccountType=805306368 indicates user objects | |
$UserSearcher.filter="(&(samAccountType=805306368)(samAccountName=$UserName)$Filter)" | |
} | |
elseif($SPN) { | |
$UserSearcher.filter="(&(samAccountType=805306368)(servicePrincipalName=*)$Filter)" | |
} | |
else { | |
# filter is something like "(samAccountName=*blah*)" if specified | |
$UserSearcher.filter="(&(samAccountType=805306368)$Filter)" | |
} | |
$Results = $UserSearcher.FindAll() | |
$Results | Where-Object {$_} | ForEach-Object { | |
# convert/process the LDAP fields for each result | |
$User = Convert-LDAPProperty -Properties $_.Properties | |
$User.PSObject.TypeNames.Add('PowerView.User') | |
$User | |
} | |
$Results.dispose() | |
$UserSearcher.dispose() | |
} | |
} | |
} | |
function Add-NetUser { | |
[CmdletBinding()] | |
Param ( | |
[ValidateNotNullOrEmpty()] | |
[String] | |
$UserName = 'backdoor', | |
[ValidateNotNullOrEmpty()] | |
[String] | |
$Password = 'Password123!', | |
[ValidateNotNullOrEmpty()] | |
[String] | |
$GroupName, | |
[ValidateNotNullOrEmpty()] | |
[Alias('HostName')] | |
[String] | |
$ComputerName = 'localhost', | |
[ValidateNotNullOrEmpty()] | |
[String] | |
$Domain | |
) | |
if ($Domain) { | |
$DomainObject = Get-ThisThingDomain -Domain $Domain | |
if(-not $DomainObject) { | |
Write-Warning "Error in grabbing $Domain object" | |
return $Null | |
} | |
# add the assembly we need | |
Add-Type -AssemblyName System.DirectoryServices.AccountManagement | |
# http://richardspowershellblog.wordpress.com/2008/05/25/system-directoryservices-accountmanagement/ | |
# get the domain context | |
$Context = New-Object -TypeName System.DirectoryServices.AccountManagement.PrincipalContext -ArgumentList ([System.DirectoryServices.AccountManagement.ContextType]::Domain), $DomainObject | |
# create the user object | |
$User = New-Object -TypeName System.DirectoryServices.AccountManagement.UserPrincipal -ArgumentList $Context | |
# set user properties | |
$User.Name = $UserName | |
$User.SamAccountName = $UserName | |
$User.PasswordNotRequired = $False | |
$User.SetPassword($Password) | |
$User.Enabled = $True | |
Write-Verbose "Creating user $UserName to with password '$Password' in domain $Domain" | |
try { | |
# commit the user | |
$User.Save() | |
"[*] User $UserName successfully created in domain $Domain" | |
} | |
catch { | |
Write-Warning '[!] User already exists!' | |
return | |
} | |
} | |
else { | |
Write-Verbose "Creating user $UserName to with password '$Password' on $ComputerName" | |
# if it's not a domain add, it's a local machine add | |
$ObjOu = [ADSI]"WinNT://$ComputerName" | |
$ObjUser = $ObjOu.Create('User', $UserName) | |
$ObjUser.SetPassword($Password) | |
# commit the changes to the local machine | |
try { | |
$Null = $ObjUser.SetInfo() | |
"[*] User $UserName successfully created on host $ComputerName" | |
} | |
catch { | |
Write-Warning '[!] Account already exists!' | |
return | |
} | |
} | |
# if a group is specified, invoke Add-NetGroupUser and return its value | |
if ($GroupName) { | |
# if we're adding the user to a domain | |
if ($Domain) { | |
Add-NetGroupUser -UserName $UserName -GroupName $GroupName -Domain $Domain | |
"[*] User $UserName successfully added to group $GroupName in domain $Domain" | |
} | |
# otherwise, we're adding to a local group | |
else { | |
Add-NetGroupUser -UserName $UserName -GroupName $GroupName -ComputerName $ComputerName | |
"[*] User $UserName successfully added to group $GroupName on host $ComputerName" | |
} | |
} | |
} | |
function Add-NetGroupUser { | |
[CmdletBinding()] | |
param( | |
[Parameter(Mandatory = $True)] | |
[ValidateNotNullOrEmpty()] | |
[String] | |
$UserName, | |
[Parameter(Mandatory = $True)] | |
[ValidateNotNullOrEmpty()] | |
[String] | |
$GroupName, | |
[ValidateNotNullOrEmpty()] | |
[Alias('HostName')] | |
[String] | |
$ComputerName, | |
[String] | |
$Domain | |
) | |
# add the assembly if we need it | |
Add-Type -AssemblyName System.DirectoryServices.AccountManagement | |
# if we're adding to a remote host's local group, use the WinNT provider | |
if($ComputerName -and ($ComputerName -ne "localhost")) { | |
try { | |
Write-Verbose "Adding user $UserName to $GroupName on host $ComputerName" | |
([ADSI]"WinNT://$ComputerName/$GroupName,group").add("WinNT://$ComputerName/$UserName,user") | |
"[*] User $UserName successfully added to group $GroupName on $ComputerName" | |
} | |
catch { | |
Write-Warning "[!] Error adding user $UserName to group $GroupName on $ComputerName" | |
return | |
} | |
} | |
# otherwise it's a local machine or domain add | |
else { | |
try { | |
if ($Domain) { | |
Write-Verbose "Adding user $UserName to $GroupName on domain $Domain" | |
$CT = [System.DirectoryServices.AccountManagement.ContextType]::Domain | |
$DomainObject = Get-ThisThingDomain -Domain $Domain | |
if(-not $DomainObject) { | |
return $Null | |
} | |
# get the full principal context | |
$Context = New-Object -TypeName System.DirectoryServices.AccountManagement.PrincipalContext -ArgumentList $CT, $DomainObject | |
} | |
else { | |
# otherwise, get the local machine context | |
Write-Verbose "Adding user $UserName to $GroupName on localhost" | |
$Context = New-Object System.DirectoryServices.AccountManagement.PrincipalContext([System.DirectoryServices.AccountManagement.ContextType]::Machine, $Env:ComputerName) | |
} | |
# find the particular group | |
$Group = [System.DirectoryServices.AccountManagement.GroupPrincipal]::FindByIdentity($Context,$GroupName) | |
# add the particular user to the group | |
$Group.Members.add($Context, [System.DirectoryServices.AccountManagement.IdentityType]::SamAccountName, $UserName) | |
# commit the changes | |
$Group.Save() | |
} | |
catch { | |
Write-Warning "Error adding $UserName to $GroupName : $_" | |
} | |
} | |
} | |
function Get-UserProperty { | |
[CmdletBinding()] | |
param( | |
[String[]] | |
$Properties, | |
[String] | |
$Domain, | |
[String] | |
$DomainController, | |
[ValidateRange(1,10000)] | |
[Int] | |
$PageSize = 200, | |
[Management.Automation.PSCredential] | |
$Credential | |
) | |
if($Properties) { | |
# extract out the set of all properties for each object | |
$Properties = ,"name" + $Properties | |
Get-ThisThingUser -Domain $Domain -DomainController $DomainController -PageSize $PageSize -Credential $Credential | Select-Object -Property $Properties | |
} | |
else { | |
# extract out just the property names | |
Get-ThisThingUser -Domain $Domain -DomainController $DomainController -PageSize $PageSize -Credential $Credential | Select-Object -First 1 | Get-Member -MemberType *Property | Select-Object -Property 'Name' | |
} | |
} | |
filter Find-UserField { | |
[CmdletBinding()] | |
param( | |
[Parameter(Position=0,ValueFromPipeline=$True)] | |
[String] | |
$SearchTerm = 'pass', | |
[String] | |
$SearchField = 'description', | |
[String] | |
$ADSpath, | |
[String] | |
$Domain, | |
[String] | |
$DomainController, | |
[ValidateRange(1,10000)] | |
[Int] | |
$PageSize = 200, | |
[Management.Automation.PSCredential] | |
$Credential | |
) | |
Get-ThisThingUser -ADSpath $ADSpath -Domain $Domain -DomainController $DomainController -Credential $Credential -Filter "($SearchField=*$SearchTerm*)" -PageSize $PageSize | Select-Object samaccountname,$SearchField | |
} | |
filter Get-UserEvent { | |
Param( | |
[Parameter(ValueFromPipeline=$True)] | |
[String] | |
$ComputerName = $Env:ComputerName, | |
[String] | |
[ValidateSet("logon","tgt","all")] | |
$EventType = "logon", | |
[DateTime] | |
$DateStart = [DateTime]::Today.AddDays(-5), | |
[Management.Automation.PSCredential] | |
$Credential | |
) | |
if($EventType.ToLower() -like "logon") { | |
[Int32[]]$ID = @(4624) | |
} | |
elseif($EventType.ToLower() -like "tgt") { | |
[Int32[]]$ID = @(4768) | |
} | |
else { | |
[Int32[]]$ID = @(4624, 4768) | |
} | |
if($Credential) { | |
Write-Verbose "Using alternative credentials" | |
$Arguments = @{ | |
'ComputerName' = $ComputerName; | |
'Credential' = $Credential; | |
'FilterHashTable' = @{ LogName = 'Security'; ID=$ID; StartTime=$DateStart}; | |
'ErrorAction' = 'SilentlyContinue'; | |
} | |
} | |
else { | |
$Arguments = @{ | |
'ComputerName' = $ComputerName; | |
'FilterHashTable' = @{ LogName = 'Security'; ID=$ID; StartTime=$DateStart}; | |
'ErrorAction' = 'SilentlyContinue'; | |
} | |
} | |
# grab all events matching our filter for the specified host | |
Get-WinEvent @Arguments | ForEach-Object { | |
if($ID -contains 4624) { | |
# first parse and check the logon event type. This could be later adapted and tested for RDP logons (type 10) | |
if($_.message -match '(?s)(?<=Logon Type:).*?(?=(Impersonation Level:|New Logon:))') { | |
if($Matches) { | |
$LogonType = $Matches[0].trim() | |
$Matches = $Null | |
} | |
} | |
else { | |
$LogonType = "" | |
} | |
# interactive logons or domain logons | |
if (($LogonType -eq 2) -or ($LogonType -eq 3)) { | |
try { | |
# parse and store the account used and the address they came from | |
if($_.message -match '(?s)(?<=New Logon:).*?(?=Process Information:)') { | |
if($Matches) { | |
$UserName = $Matches[0].split("`n")[2].split(":")[1].trim() | |
$Domain = $Matches[0].split("`n")[3].split(":")[1].trim() | |
$Matches = $Null | |
} | |
} | |
if($_.message -match '(?s)(?<=Network Information:).*?(?=Source Port:)') { | |
if($Matches) { | |
$Address = $Matches[0].split("`n")[2].split(":")[1].trim() | |
$Matches = $Null | |
} | |
} | |
# only add if there was account information not for a machine or anonymous logon | |
if ($UserName -and (-not $UserName.endsWith('$')) -and ($UserName -ne 'ANONYMOUS LOGON')) { | |
$LogonEventProperties = @{ | |
'Domain' = $Domain | |
'ComputerName' = $ComputerName | |
'Username' = $UserName | |
'Address' = $Address | |
'ID' = '4624' | |
'LogonType' = $LogonType | |
'Time' = $_.TimeCreated | |
} | |
New-Object -TypeName PSObject -Property $LogonEventProperties | |
} | |
} | |
catch { | |
Write-Verbose "Error parsing event logs: $_" | |
} | |
} | |
} | |
if($ID -contains 4768) { | |
# the TGT event type | |
try { | |
if($_.message -match '(?s)(?<=Account Information:).*?(?=Service Information:)') { | |
if($Matches) { | |
$Username = $Matches[0].split("`n")[1].split(":")[1].trim() | |
$Domain = $Matches[0].split("`n")[2].split(":")[1].trim() | |
$Matches = $Null | |
} | |
} | |
if($_.message -match '(?s)(?<=Network Information:).*?(?=Additional Information:)') { | |
if($Matches) { | |
$Address = $Matches[0].split("`n")[1].split(":")[-1].trim() | |
$Matches = $Null | |
} | |
} | |
$LogonEventProperties = @{ | |
'Domain' = $Domain | |
'ComputerName' = $ComputerName | |
'Username' = $UserName | |
'Address' = $Address | |
'ID' = '4768' | |
'LogonType' = '' | |
'Time' = $_.TimeCreated | |
} | |
New-Object -TypeName PSObject -Property $LogonEventProperties | |
} | |
catch { | |
Write-Verbose "Error parsing event logs: $_" | |
} | |
} | |
} | |
} | |
function Get-ObjectAcl { | |
[CmdletBinding()] | |
Param ( | |
[Parameter(ValueFromPipelineByPropertyName=$True)] | |
[String] | |
$SamAccountName, | |
[Parameter(ValueFromPipelineByPropertyName=$True)] | |
[String] | |
$Name = "*", | |
[Parameter(ValueFromPipelineByPropertyName=$True)] | |
[String] | |
$DistinguishedName = "*", | |
[Switch] | |
$ResolveGUIDs, | |
[String] | |
$Filter, | |
[String] | |
$ADSpath, | |
[String] | |
$ADSprefix, | |
[String] | |
[ValidateSet("All","ResetPassword","WriteMembers")] | |
$RightsFilter, | |
[String] | |
$Domain, | |
[String] | |
$DomainController, | |
[ValidateRange(1,10000)] | |
[Int] | |
$PageSize = 200 | |
) | |
begin { | |
$Searcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -ADSpath $ADSpath -ADSprefix $ADSprefix -PageSize $PageSize | |
# get a GUID -> name mapping | |
if($ResolveGUIDs) { | |
$GUIDs = Get-GUIDMap -Domain $Domain -DomainController $DomainController -PageSize $PageSize | |
} | |
} | |
process { | |
if ($Searcher) { | |
if($SamAccountName) { | |
$Searcher.filter="(&(samaccountname=$SamAccountName)(name=$Name)(distinguishedname=$DistinguishedName)$Filter)" | |
} | |
else { | |
$Searcher.filter="(&(name=$Name)(distinguishedname=$DistinguishedName)$Filter)" | |
} | |
try { | |
$Results = $Searcher.FindAll() | |
$Results | Where-Object {$_} | ForEach-Object { | |
$Object = [adsi]($_.path) | |
if($Object.distinguishedname) { | |
$Access = $Object.PsBase.ObjectSecurity.access | |
$Access | ForEach-Object { | |
$_ | Add-Member NoteProperty 'ObjectDN' $Object.distinguishedname[0] | |
if($Object.objectsid[0]){ | |
$S = (New-Object System.Security.Principal.SecurityIdentifier($Object.objectsid[0],0)).Value | |
} | |
else { | |
$S = $Null | |
} | |
$_ | Add-Member NoteProperty 'ObjectSID' $S | |
$_ | |
} | |
} | |
} | ForEach-Object { | |
if($RightsFilter) { | |
$GuidFilter = Switch ($RightsFilter) { | |
"ResetPassword" { "00299570-246d-11d0-a768-00aa006e0529" } | |
"WriteMembers" { "bf9679c0-0de6-11d0-a285-00aa003049e2" } | |
Default { "00000000-0000-0000-0000-000000000000"} | |
} | |
if($_.ObjectType -eq $GuidFilter) { $_ } | |
} | |
else { | |
$_ | |
} | |
} | ForEach-Object { | |
if($GUIDs) { | |
# if we're resolving GUIDs, map them them to the resolved hash table | |
$AclProperties = @{} | |
$_.psobject.properties | ForEach-Object { | |
if( ($_.Name -eq 'ObjectType') -or ($_.Name -eq 'InheritedObjectType') ) { | |
try { | |
$AclProperties[$_.Name] = $GUIDS[$_.Value.toString()] | |
} | |
catch { | |
$AclProperties[$_.Name] = $_.Value | |
} | |
} | |
else { | |
$AclProperties[$_.Name] = $_.Value | |
} | |
} | |
New-Object -TypeName PSObject -Property $AclProperties | |
} | |
else { $_ } | |
} | |
$Results.dispose() | |
$Searcher.dispose() | |
} | |
catch { | |
Write-Warning $_ | |
} | |
} | |
} | |
} | |
function Add-ObjectAcl { | |
[CmdletBinding()] | |
Param ( | |
[String] | |
$TargetSamAccountName, | |
[String] | |
$TargetName = "*", | |
[Alias('DN')] | |
[String] | |
$TargetDistinguishedName = "*", | |
[String] | |
$TargetFilter, | |
[String] | |
$TargetADSpath, | |
[String] | |
$TargetADSprefix, | |
[String] | |
[ValidatePattern('^S-1-5-21-[0-9]+-[0-9]+-[0-9]+-[0-9]+')] | |
$PrincipalSID, | |
[String] | |
$PrincipalName, | |
[String] | |
$PrincipalSamAccountName, | |
[String] | |
[ValidateSet("All","ResetPassword","WriteMembers","DCSync")] | |
$Rights = "All", | |
[String] | |
$RightsGUID, | |
[String] | |
$Domain, | |
[String] | |
$DomainController, | |
[ValidateRange(1,10000)] | |
[Int] | |
$PageSize = 200 | |
) | |
begin { | |
$Searcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -ADSpath $TargetADSpath -ADSprefix $TargetADSprefix -PageSize $PageSize | |
if($PrincipalSID) { | |
$ResolvedPrincipalSID = $PrincipalSID | |
} | |
else { | |
$Principal = Get-ADObject -Domain $Domain -DomainController $DomainController -Name $PrincipalName -SamAccountName $PrincipalSamAccountName -PageSize $PageSize | |
if(!$Principal) { | |
throw "Error resolving principal" | |
} | |
$ResolvedPrincipalSID = $Principal.objectsid | |
} | |
if(!$ResolvedPrincipalSID) { | |
throw "Error resolving principal" | |
} | |
} | |
process { | |
if ($Searcher) { | |
if($TargetSamAccountName) { | |
$Searcher.filter="(&(samaccountname=$TargetSamAccountName)(name=$TargetName)(distinguishedname=$TargetDistinguishedName)$TargetFilter)" | |
} | |
else { | |
$Searcher.filter="(&(name=$TargetName)(distinguishedname=$TargetDistinguishedName)$TargetFilter)" | |
} | |
try { | |
$Results = $Searcher.FindAll() | |
$Results | Where-Object {$_} | ForEach-Object { | |
# adapted from https://social.technet.microsoft.com/Forums/windowsserver/en-US/df3bfd33-c070-4a9c-be98-c4da6e591a0a/forum-faq-using-powershell-to-assign-permissions-on-active-directory-objects | |
$TargetDN = $_.Properties.distinguishedname | |
$Identity = [System.Security.Principal.IdentityReference] ([System.Security.Principal.SecurityIdentifier]$ResolvedPrincipalSID) | |
$InheritanceType = [System.DirectoryServices.ActiveDirectorySecurityInheritance] "None" | |
$ControlType = [System.Security.AccessControl.AccessControlType] "Allow" | |
$ACEs = @() | |
if($RightsGUID) { | |
$GUIDs = @($RightsGUID) | |
} | |
else { | |
$GUIDs = Switch ($Rights) { | |
# ResetPassword doesn't need to know the user's current password | |
"ResetPassword" { "00299570-246d-11d0-a768-00aa006e0529" } | |
# allows for the modification of group membership | |
"WriteMembers" { "bf9679c0-0de6-11d0-a285-00aa003049e2" } | |
# 'DS-Replication-Get-Changes' = 1131f6aa-9c07-11d1-f79f-00c04fc2dcd2 | |
# 'DS-Replication-Get-Changes-All' = 1131f6ad-9c07-11d1-f79f-00c04fc2dcd2 | |
# 'DS-Replication-Get-Changes-In-Filtered-Set' = 89e95b76-444d-4c62-991a-0facbeda640c | |
# when applied to a domain's ACL, allows for the use of DCSync | |
"DCSync" { "1131f6aa-9c07-11d1-f79f-00c04fc2dcd2", "1131f6ad-9c07-11d1-f79f-00c04fc2dcd2", "89e95b76-444d-4c62-991a-0facbeda640c"} | |
} | |
} | |
if($GUIDs) { | |
foreach($GUID in $GUIDs) { | |
$NewGUID = New-Object Guid $GUID | |
$ADRights = [System.DirectoryServices.ActiveDirectoryRights] "ExtendedRight" | |
$ACEs += New-Object System.DirectoryServices.ActiveDirectoryAccessRule $Identity,$ADRights,$ControlType,$NewGUID,$InheritanceType | |
} | |
} | |
else { | |
# deault to GenericAll rights | |
$ADRights = [System.DirectoryServices.ActiveDirectoryRights] "GenericAll" | |
$ACEs += New-Object System.DirectoryServices.ActiveDirectoryAccessRule $Identity,$ADRights,$ControlType,$InheritanceType | |
} | |
Write-Verbose "Granting principal $ResolvedPrincipalSID '$Rights' on $($_.Properties.distinguishedname)" | |
try { | |
# add all the new ACEs to the specified object | |
ForEach ($ACE in $ACEs) { | |
Write-Verbose "Granting principal $ResolvedPrincipalSID '$($ACE.ObjectType)' rights on $($_.Properties.distinguishedname)" | |
$Object = [adsi]($_.path) | |
$Object.PsBase.ObjectSecurity.AddAccessRule($ACE) | |
$Object.PsBase.commitchanges() | |
} | |
} | |
catch { | |
Write-Warning "Error granting principal $ResolvedPrincipalSID '$Rights' on $TargetDN : $_" | |
} | |
} | |
$Results.dispose() | |
$Searcher.dispose() | |
} | |
catch { | |
Write-Warning "Error: $_" | |
} | |
} | |
} | |
} | |
function Invoke-ACLScanner { | |
[CmdletBinding()] | |
Param ( | |
[Parameter(ValueFromPipeline=$True)] | |
[String] | |
$SamAccountName, | |
[String] | |
$Name = "*", | |
[Alias('DN')] | |
[String] | |
$DistinguishedName = "*", | |
[String] | |
$Filter, | |
[String] | |
$ADSpath, | |
[String] | |
$ADSprefix, | |
[String] | |
$Domain, | |
[String] | |
$DomainController, | |
[Switch] | |
$ResolveGUIDs, | |
[ValidateRange(1,10000)] | |
[Int] | |
$PageSize = 200 | |
) | |
# Get all domain ACLs with the appropriate parameters | |
Get-ObjectACL @PSBoundParameters | ForEach-Object { | |
# add in the translated SID for the object identity | |
$_ | Add-Member Noteproperty 'IdentitySID' ($_.IdentityReference.Translate([System.Security.Principal.SecurityIdentifier]).Value) | |
$_ | |
} | Where-Object { | |
# check for any ACLs with SIDs > -1000 | |
try { | |
# TODO: change this to a regex for speedup? | |
[int]($_.IdentitySid.split("-")[-1]) -ge 1000 | |
} | |
catch {} | |
} | Where-Object { | |
# filter for modifiable rights | |
($_.ActiveDirectoryRights -eq "GenericAll") -or ($_.ActiveDirectoryRights -match "Write") -or ($_.ActiveDirectoryRights -match "Create") -or ($_.ActiveDirectoryRights -match "Delete") -or (($_.ActiveDirectoryRights -match "ExtendedRight") -and ($_.AccessControlType -eq "Allow")) | |
} | |
} | |
filter Get-GUIDMap { | |
[CmdletBinding()] | |
Param ( | |
[Parameter(ValueFromPipeline=$True)] | |
[String] | |
$Domain, | |
[String] | |
$DomainController, | |
[ValidateRange(1,10000)] | |
[Int] | |
$PageSize = 200 | |
) | |
$GUIDs = @{'00000000-0000-0000-0000-000000000000' = 'All'} | |
$SchemaPath = (Get-ThisThingForest).schema.name | |
$SchemaSearcher = Get-DomainSearcher -ADSpath $SchemaPath -DomainController $DomainController -PageSize $PageSize | |
if($SchemaSearcher) { | |
$SchemaSearcher.filter = "(schemaIDGUID=*)" | |
try { | |
$Results = $SchemaSearcher.FindAll() | |
$Results | Where-Object {$_} | ForEach-Object { | |
# convert the GUID | |
$GUIDs[(New-Object Guid (,$_.properties.schemaidguid[0])).Guid] = $_.properties.name[0] | |
} | |
$Results.dispose() | |
$SchemaSearcher.dispose() | |
} | |
catch { | |
Write-Verbose "Error in building GUID map: $_" | |
} | |
} | |
$RightsSearcher = Get-DomainSearcher -ADSpath $SchemaPath.replace("Schema","Extended-Rights") -DomainController $DomainController -PageSize $PageSize -Credential $Credential | |
if ($RightsSearcher) { | |
$RightsSearcher.filter = "(objectClass=controlAccessRight)" | |
try { | |
$Results = $RightsSearcher.FindAll() | |
$Results | Where-Object {$_} | ForEach-Object { | |
# convert the GUID | |
$GUIDs[$_.properties.rightsguid[0].toString()] = $_.properties.name[0] | |
} | |
$Results.dispose() | |
$RightsSearcher.dispose() | |
} | |
catch { | |
Write-Verbose "Error in building GUID map: $_" | |
} | |
} | |
$GUIDs | |
} | |
function Get-ThisThingComputer { | |
[CmdletBinding()] | |
Param ( | |
[Parameter(ValueFromPipeline=$True)] | |
[Alias('HostName')] | |
[String] | |
$ComputerName = '*', | |
[String] | |
$SPN, | |
[String] | |
$OperatingSystem, | |
[String] | |
$ServicePack, | |
[String] | |
$Filter, | |
[Switch] | |
$Printers, | |
[Switch] | |
$Ping, | |
[Switch] | |
$FullData, | |
[String] | |
$Domain, | |
[String] | |
$DomainController, | |
[String] | |
$ADSpath, | |
[String] | |
$SiteName, | |
[Switch] | |
$Unconstrained, | |
[ValidateRange(1,10000)] | |
[Int] | |
$PageSize = 200, | |
[Management.Automation.PSCredential] | |
$Credential | |
) | |
begin { | |
# so this isn't repeated if multiple computer names are passed on the pipeline | |
$CompSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -ADSpath $ADSpath -PageSize $PageSize -Credential $Credential | |
} | |
process { | |
if ($CompSearcher) { | |
# if we're checking for unconstrained delegation | |
if($Unconstrained) { | |
Write-Verbose "Searching for computers with for unconstrained delegation" | |
$Filter += "(userAccountControl:1.2.840.113556.1.4.803:=524288)" | |
} | |
# set the filters for the seracher if it exists | |
if($Printers) { | |
Write-Verbose "Searching for printers" | |
# $CompSearcher.filter="(&(objectCategory=printQueue)$Filter)" | |
$Filter += "(objectCategory=printQueue)" | |
} | |
if($SPN) { | |
Write-Verbose "Searching for computers with SPN: $SPN" | |
$Filter += "(servicePrincipalName=$SPN)" | |
} | |
if($OperatingSystem) { | |
$Filter += "(operatingsystem=$OperatingSystem)" | |
} | |
if($ServicePack) { | |
$Filter += "(operatingsystemservicepack=$ServicePack)" | |
} | |
if($SiteName) { | |
$Filter += "(serverreferencebl=$SiteName)" | |
} | |
$CompFilter = "(&(sAMAccountType=805306369)(dnshostname=$ComputerName)$Filter)" | |
Write-Verbose "Get-ThisThingComputer filter : '$CompFilter'" | |
$CompSearcher.filter = $CompFilter | |
try { | |
$Results = $CompSearcher.FindAll() | |
$Results | Where-Object {$_} | ForEach-Object { | |
$Up = $True | |
if($Ping) { | |
# TODO: how can these results be piped to ping for a speedup? | |
$Up = Test-Connection -Count 1 -Quiet -ComputerName $_.properties.dnshostname | |
} | |
if($Up) { | |
# return full data objects | |
if ($FullData) { | |
# convert/process the LDAP fields for each result | |
$Computer = Convert-LDAPProperty -Properties $_.Properties | |
$Computer.PSObject.TypeNames.Add('PowerView.Computer') | |
$Computer | |
} | |
else { | |
# otherwise we're just returning the DNS host name | |
$_.properties.dnshostname | |
} | |
} | |
} | |
$Results.dispose() | |
$CompSearcher.dispose() | |
} | |
catch { | |
Write-Warning "Error: $_" | |
} | |
} | |
} | |
} | |
function Get-ADObject { | |
[CmdletBinding()] | |
Param ( | |
[Parameter(ValueFromPipeline=$True)] | |
[String] | |
$SID, | |
[String] | |
$Name, | |
[String] | |
$SamAccountName, | |
[String] | |
$Domain, | |
[String] | |
$DomainController, | |
[String] | |
$ADSpath, | |
[String] | |
$Filter, | |
[Switch] | |
$ReturnRaw, | |
[ValidateRange(1,10000)] | |
[Int] | |
$PageSize = 200, | |
[Management.Automation.PSCredential] | |
$Credential | |
) | |
process { | |
if($SID) { | |
# if a SID is passed, try to resolve it to a reachable domain name for the searcher | |
try { | |
$Name = Convert-SidToName $SID | |
if($Name) { | |
$Canonical = Convert-ADName -ObjectName $Name -InputType NT4 -OutputType Canonical | |
if($Canonical) { | |
$Domain = $Canonical.split("/")[0] | |
} | |
else { | |
Write-Warning "Error resolving SID '$SID'" | |
return $Null | |
} | |
} | |
} | |
catch { | |
Write-Warning "Error resolving SID '$SID' : $_" | |
return $Null | |
} | |
} | |
$ObjectSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $ADSpath -PageSize $PageSize | |
if($ObjectSearcher) { | |
if($SID) { | |
$ObjectSearcher.filter = "(&(objectsid=$SID)$Filter)" | |
} | |
elseif($Name) { | |
$ObjectSearcher.filter = "(&(name=$Name)$Filter)" | |
} | |
elseif($SamAccountName) { | |
$ObjectSearcher.filter = "(&(samAccountName=$SamAccountName)$Filter)" | |
} | |
$Results = $ObjectSearcher.FindAll() | |
$Results | Where-Object {$_} | ForEach-Object { | |
if($ReturnRaw) { | |
$_ | |
} | |
else { | |
# convert/process the LDAP fields for each result | |
Convert-LDAPProperty -Properties $_.Properties | |
} | |
} | |
$Results.dispose() | |
$ObjectSearcher.dispose() | |
} | |
} | |
} | |
function Set-ADObject { | |
[CmdletBinding()] | |
Param ( | |
[String] | |
$SID, | |
[String] | |
$Name, | |
[String] | |
$SamAccountName, | |
[String] | |
$Domain, | |
[String] | |
$DomainController, | |
[String] | |
$Filter, | |
[Parameter(Mandatory = $True)] | |
[String] | |
$PropertyName, | |
$PropertyValue, | |
[Int] | |
$PropertyXorValue, | |
[Switch] | |
$ClearValue, | |
[ValidateRange(1,10000)] | |
[Int] | |
$PageSize = 200, | |
[Management.Automation.PSCredential] | |
$Credential | |
) | |
$Arguments = @{ | |
'SID' = $SID | |
'Name' = $Name | |
'SamAccountName' = $SamAccountName | |
'Domain' = $Domain | |
'DomainController' = $DomainController | |
'Filter' = $Filter | |
'PageSize' = $PageSize | |
'Credential' = $Credential | |
} | |
# splat the appropriate arguments to Get-ADObject | |
$RawObject = Get-ADObject -ReturnRaw @Arguments | |
try { | |
# get the modifiable object for this search result | |
$Entry = $RawObject.GetDirectoryEntry() | |
if($ClearValue) { | |
Write-Verbose "Clearing value" | |
$Entry.$PropertyName.clear() | |
$Entry.commitchanges() | |
} | |
elseif($PropertyXorValue) { | |
$TypeName = $Entry.$PropertyName[0].GetType().name | |
# UAC value references- https://support.microsoft.com/en-us/kb/305144 | |
$PropertyValue = $($Entry.$PropertyName) -bxor $PropertyXorValue | |
$Entry.$PropertyName = $PropertyValue -as $TypeName | |
$Entry.commitchanges() | |
} | |
else { | |
$Entry.put($PropertyName, $PropertyValue) | |
$Entry.setinfo() | |
} | |
} | |
catch { | |
Write-Warning "Error setting property $PropertyName to value '$PropertyValue' for object $($RawObject.Properties.samaccountname) : $_" | |
} | |
} | |
function Invoke-DowngradeAccount { | |
[CmdletBinding()] | |
Param ( | |
[Parameter(ParameterSetName = 'SamAccountName', Position=0, ValueFromPipeline=$True)] | |
[String] | |
$SamAccountName, | |
[Parameter(ParameterSetName = 'Name')] | |
[String] | |
$Name, | |
[String] | |
$Domain, | |
[String] | |
$DomainController, | |
[String] | |
$Filter, | |
[Switch] | |
$Repair, | |
[Management.Automation.PSCredential] | |
$Credential | |
) | |
process { | |
$Arguments = @{ | |
'SamAccountName' = $SamAccountName | |
'Name' = $Name | |
'Domain' = $Domain | |
'DomainController' = $DomainController | |
'Filter' = $Filter | |
'Credential' = $Credential | |
} | |
# splat the appropriate arguments to Get-ADObject | |
$UACValues = Get-ADObject @Arguments | select useraccountcontrol | ConvertFrom-UACValue | |
if($Repair) { | |
if($UACValues.Keys -contains "ENCRYPTED_TEXT_PWD_ALLOWED") { | |
# if reversible encryption is set, unset it | |
Set-ADObject @Arguments -PropertyName useraccountcontrol -PropertyXorValue 128 | |
} | |
# unset the forced password change | |
Set-ADObject @Arguments -PropertyName pwdlastset -PropertyValue -1 | |
} | |
else { | |
if($UACValues.Keys -contains "DONT_EXPIRE_PASSWORD") { | |
# if the password is set to never expire, unset | |
Set-ADObject @Arguments -PropertyName useraccountcontrol -PropertyXorValue 65536 | |
} | |
if($UACValues.Keys -notcontains "ENCRYPTED_TEXT_PWD_ALLOWED") { | |
# if reversible encryption is not set, set it | |
Set-ADObject @Arguments -PropertyName useraccountcontrol -PropertyXorValue 128 | |
} | |
# force the password to be changed on next login | |
Set-ADObject @Arguments -PropertyName pwdlastset -PropertyValue 0 | |
} | |
} | |
} | |
function Get-ComputerProperty { | |
[CmdletBinding()] | |
param( | |
[String[]] | |
$Properties, | |
[String] | |
$Domain, | |
[String] | |
$DomainController, | |
[ValidateRange(1,10000)] | |
[Int] | |
$PageSize = 200, | |
[Management.Automation.PSCredential] | |
$Credential | |
) | |
if($Properties) { | |
# extract out the set of all properties for each object | |
$Properties = ,"name" + $Properties | Sort-Object -Unique | |
Get-ThisThingComputer -Domain $Domain -DomainController $DomainController -Credential $Credential -FullData -PageSize $PageSize | Select-Object -Property $Properties | |
} | |
else { | |
# extract out just the property names | |
Get-ThisThingComputer -Domain $Domain -DomainController $DomainController -Credential $Credential -FullData -PageSize $PageSize | Select-Object -first 1 | Get-Member -MemberType *Property | Select-Object -Property "Name" | |
} | |
} | |
function Find-ComputerField { | |
[CmdletBinding()] | |
param( | |
[Parameter(Position=0,ValueFromPipeline=$True)] | |
[Alias('Term')] | |
[String] | |
$SearchTerm = 'pass', | |
[Alias('Field')] | |
[String] | |
$SearchField = 'description', | |
[String] | |
$ADSpath, | |
[String] | |
$Domain, | |
[String] | |
$DomainController, | |
[ValidateRange(1,10000)] | |
[Int] | |
$PageSize = 200, | |
[Management.Automation.PSCredential] | |
$Credential | |
) | |
process { | |
Get-ThisThingComputer -ADSpath $ADSpath -Domain $Domain -DomainController $DomainController -Credential $Credential -FullData -Filter "($SearchField=*$SearchTerm*)" -PageSize $PageSize | Select-Object samaccountname,$SearchField | |
} | |
} | |
function Get-ThisThingOU { | |
[CmdletBinding()] | |
Param ( | |
[Parameter(ValueFromPipeline=$True)] | |
[String] | |
$OUName = '*', | |
[String] | |
$GUID, | |
[String] | |
$Domain, | |
[String] | |
$DomainController, | |
[String] | |
$ADSpath, | |
[Switch] | |
$FullData, | |
[ValidateRange(1,10000)] | |
[Int] | |
$PageSize = 200, | |
[Management.Automation.PSCredential] | |
$Credential | |
) | |
begin { | |
$OUSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $ADSpath -PageSize $PageSize | |
} | |
process { | |
if ($OUSearcher) { | |
if ($GUID) { | |
# if we're filtering for a GUID in .gplink | |
$OUSearcher.filter="(&(objectCategory=organizationalUnit)(name=$OUName)(gplink=*$GUID*))" | |
} | |
else { | |
$OUSearcher.filter="(&(objectCategory=organizationalUnit)(name=$OUName))" | |
} | |
try { | |
$Results = $OUSearcher.FindAll() | |
$Results | Where-Object {$_} | ForEach-Object { | |
if ($FullData) { | |
# convert/process the LDAP fields for each result | |
$OU = Convert-LDAPProperty -Properties $_.Properties | |
$OU.PSObject.TypeNames.Add('PowerView.OU') | |
$OU | |
} | |
else { | |
# otherwise just returning the ADS paths of the OUs | |
$_.properties.adspath | |
} | |
} | |
$Results.dispose() | |
$OUSearcher.dispose() | |
} | |
catch { | |
Write-Warning $_ | |
} | |
} | |
} | |
} | |
function Get-ThisThingSite { | |
[CmdletBinding()] | |
Param ( | |
[Parameter(ValueFromPipeline=$True)] | |
[String] | |
$SiteName = "*", | |
[String] | |
$Domain, | |
[String] | |
$DomainController, | |
[String] | |
$ADSpath, | |
[String] | |
$GUID, | |
[Switch] | |
$FullData, | |
[ValidateRange(1,10000)] | |
[Int] | |
$PageSize = 200, | |
[Management.Automation.PSCredential] | |
$Credential | |
) | |
begin { | |
$SiteSearcher = Get-DomainSearcher -ADSpath $ADSpath -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSprefix "CN=Sites,CN=Configuration" -PageSize $PageSize | |
} | |
process { | |
if($SiteSearcher) { | |
if ($GUID) { | |
# if we're filtering for a GUID in .gplink | |
$SiteSearcher.filter="(&(objectCategory=site)(name=$SiteName)(gplink=*$GUID*))" | |
} | |
else { | |
$SiteSearcher.filter="(&(objectCategory=site)(name=$SiteName))" | |
} | |
try { | |
$Results = $SiteSearcher.FindAll() | |
$Results | Where-Object {$_} | ForEach-Object { | |
if ($FullData) { | |
# convert/process the LDAP fields for each result | |
$Site = Convert-LDAPProperty -Properties $_.Properties | |
$Site.PSObject.TypeNames.Add('PowerView.Site') | |
$Site | |
} | |
else { | |
# otherwise just return the site name | |
$_.properties.name | |
} | |
} | |
$Results.dispose() | |
$SiteSearcher.dispose() | |
} | |
catch { | |
Write-Verbose $_ | |
} | |
} | |
} | |
} | |
function Get-ThisThingSubnet { | |
[CmdletBinding()] | |
Param ( | |
[Parameter(ValueFromPipeline=$True)] | |
[String] | |
$SiteName = "*", | |
[String] | |
$Domain, | |
[String] | |
$ADSpath, | |
[String] | |
$DomainController, | |
[Switch] | |
$FullData, | |
[ValidateRange(1,10000)] | |
[Int] | |
$PageSize = 200, | |
[Management.Automation.PSCredential] | |
$Credential | |
) | |
begin { | |
$SubnetSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $ADSpath -ADSprefix "CN=Subnets,CN=Sites,CN=Configuration" -PageSize $PageSize | |
} | |
process { | |
if($SubnetSearcher) { | |
$SubnetSearcher.filter="(&(objectCategory=subnet))" | |
try { | |
$Results = $SubnetSearcher.FindAll() | |
$Results | Where-Object {$_} | ForEach-Object { | |
if ($FullData) { | |
# convert/process the LDAP fields for each result | |
Convert-LDAPProperty -Properties $_.Properties | Where-Object { $_.siteobject -match "CN=$SiteName" } | |
} | |
else { | |
# otherwise just return the subnet name and site name | |
if ( ($SiteName -and ($_.properties.siteobject -match "CN=$SiteName,")) -or ($SiteName -eq '*')) { | |
$SubnetProperties = @{ | |
'Subnet' = $_.properties.name[0] | |
} | |
try { | |
$SubnetProperties['Site'] = ($_.properties.siteobject[0]).split(",")[0] | |
} | |
catch { | |
$SubnetProperties['Site'] = 'Error' | |
} | |
New-Object -TypeName PSObject -Property $SubnetProperties | |
} | |
} | |
} | |
$Results.dispose() | |
$SubnetSearcher.dispose() | |
} | |
catch { | |
Write-Warning $_ | |
} | |
} | |
} | |
} | |
function Get-DomainSID { | |
param( | |
[String] | |
$Domain, | |
[String] | |
$DomainController | |
) | |
$DCSID = Get-ThisThingComputer -Domain $Domain -DomainController $DomainController -FullData -Filter '(userAccountControl:1.2.840.113556.1.4.803:=8192)' | Select-Object -First 1 -ExpandProperty objectsid | |
if($DCSID) { | |
$DCSID.Substring(0, $DCSID.LastIndexOf('-')) | |
} | |
else { | |
Write-Verbose "Error extracting domain SID for $Domain" | |
} | |
} | |
function Get-ThisThingGroup { | |
[CmdletBinding()] | |
param( | |
[Parameter(ValueFromPipeline=$True)] | |
[String] | |
$GroupName = '*', | |
[String] | |
$SID, | |
[String] | |
$UserName, | |
[String] | |
$Filter, | |
[String] | |
$Domain, | |
[String] | |
$DomainController, | |
[String] | |
$ADSpath, | |
[Switch] | |
$AdminCount, | |
[Switch] | |
$FullData, | |
[Switch] | |
$RawSids, | |
[Switch] | |
$AllTypes, | |
[ValidateRange(1,10000)] | |
[Int] | |
$PageSize = 200, | |
[Management.Automation.PSCredential] | |
$Credential | |
) | |
begin { | |
$GroupSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $ADSpath -PageSize $PageSize | |
if (!$AllTypes) | |
{ | |
$Filter += "(groupType:1.2.840.113556.1.4.803:=2147483648)" | |
} | |
} | |
process { | |
if($GroupSearcher) { | |
if($AdminCount) { | |
Write-Verbose "Checking for adminCount=1" | |
$Filter += "(admincount=1)" | |
} | |
if ($UserName) { | |
# get the raw user object | |
$User = Get-ADObject -SamAccountName $UserName -Domain $Domain -DomainController $DomainController -Credential $Credential -ReturnRaw -PageSize $PageSize | Select-Object -First 1 | |
if($User) { | |
# convert the user to a directory entry | |
$UserDirectoryEntry = $User.GetDirectoryEntry() | |
# cause the cache to calculate the token groups for the user | |
$UserDirectoryEntry.RefreshCache("tokenGroups") | |
$UserDirectoryEntry.TokenGroups | ForEach-Object { | |
# convert the token group sid | |
$GroupSid = (New-Object System.Security.Principal.SecurityIdentifier($_,0)).Value | |
# ignore the built in groups | |
if($GroupSid -notmatch '^S-1-5-32-.*') { | |
if($FullData) { | |
$Group = Get-ADObject -SID $GroupSid -PageSize $PageSize -Domain $Domain -DomainController $DomainController -Credential $Credential | |
$Group.PSObject.TypeNames.Add('PowerView.Group') | |
$Group | |
} | |
else { | |
if($RawSids) { | |
$GroupSid | |
} | |
else { | |
Convert-SidToName -SID $GroupSid | |
} | |
} | |
} | |
} | |
} | |
else { | |
Write-Warning "UserName '$UserName' failed to resolve." | |
} | |
} | |
else { | |
if ($SID) { | |
$GroupSearcher.filter = "(&(objectCategory=group)(objectSID=$SID)$Filter)" | |
} | |
else { | |
$GroupSearcher.filter = "(&(objectCategory=group)(samaccountname=$GroupName)$Filter)" | |
} | |
$Results = $GroupSearcher.FindAll() | |
$Results | Where-Object {$_} | ForEach-Object { | |
# if we're returning full data objects | |
if ($FullData) { | |
# convert/process the LDAP fields for each result | |
$Group = Convert-LDAPProperty -Properties $_.Properties | |
$Group.PSObject.TypeNames.Add('PowerView.Group') | |
$Group | |
} | |
else { | |
# otherwise we're just returning the group name | |
$_.properties.samaccountname | |
} | |
} | |
$Results.dispose() | |
$GroupSearcher.dispose() | |
} | |
} | |
} | |
} | |
function Get-ThisThingGroupMember { | |
[CmdletBinding()] | |
param( | |
[Parameter(ValueFromPipeline=$True)] | |
[String] | |
$GroupName, | |
[String] | |
$SID, | |
[String] | |
$Domain, | |
[String] | |
$DomainController, | |
[String] | |
$ADSpath, | |
[Switch] | |
$FullData, | |
[Switch] | |
$Recurse, | |
[Switch] | |
$UseMatchingRule, | |
[ValidateRange(1,10000)] | |
[Int] | |
$PageSize = 200, | |
[Management.Automation.PSCredential] | |
$Credential | |
) | |
begin { | |
if($DomainController) { | |
$TargetDomainController = $DomainController | |
} | |
else { | |
$TargetDomainController = ((Get-ThisThingDomain -Credential $Credential).PdcRoleOwner).Name | |
} | |
if($Domain) { | |
$TargetDomain = $Domain | |
} | |
else { | |
$TargetDomain = Get-ThisThingDomain -Credential $Credential | Select-Object -ExpandProperty name | |
} | |
# so this isn't repeated if users are passed on the pipeline | |
$GroupSearcher = Get-DomainSearcher -Domain $TargetDomain -DomainController $TargetDomainController -Credential $Credential -ADSpath $ADSpath -PageSize $PageSize | |
} | |
process { | |
if ($GroupSearcher) { | |
if ($Recurse -and $UseMatchingRule) { | |
# resolve the group to a distinguishedname | |
if ($GroupName) { | |
$Group = Get-ThisThingGroup -AllTypes -GroupName $GroupName -Domain $TargetDomain -DomainController $TargetDomainController -Credential $Credential -FullData -PageSize $PageSize | |
} | |
elseif ($SID) { | |
$Group = Get-ThisThingGroup -AllTypes -SID $SID -Domain $TargetDomain -DomainController $TargetDomainController -Credential $Credential -FullData -PageSize $PageSize | |
} | |
else { | |
# default to domain admins | |
$SID = (Get-DomainSID -Domain $TargetDomain -DomainController $TargetDomainController) + "-512" | |
$Group = Get-ThisThingGroup -AllTypes -SID $SID -Domain $TargetDomain -DomainController $TargetDomainController -Credential $Credential -FullData -PageSize $PageSize | |
} | |
$GroupDN = $Group.distinguishedname | |
$GroupFoundName = $Group.samaccountname | |
if ($GroupDN) { | |
$GroupSearcher.filter = "(&(samAccountType=805306368)(memberof:1.2.840.113556.1.4.1941:=$GroupDN)$Filter)" | |
$GroupSearcher.PropertiesToLoad.AddRange(('distinguishedName','samaccounttype','lastlogon','lastlogontimestamp','dscorepropagationdata','objectsid','whencreated','badpasswordtime','accountexpires','iscriticalsystemobject','name','usnchanged','objectcategory','description','codepage','instancetype','countrycode','distinguishedname','cn','admincount','logonhours','objectclass','logoncount','usncreated','useraccountcontrol','objectguid','primarygroupid','lastlogoff','samaccountname','badpwdcount','whenchanged','memberof','pwdlastset','adspath')) | |
$Members = $GroupSearcher.FindAll() | |
$GroupFoundName = $GroupName | |
} | |
else { | |
Write-Error "Unable to find Group" | |
} | |
} | |
else { | |
if ($GroupName) { | |
$GroupSearcher.filter = "(&(objectCategory=group)(samaccountname=$GroupName)$Filter)" | |
} | |
elseif ($SID) { | |
$GroupSearcher.filter = "(&(objectCategory=group)(objectSID=$SID)$Filter)" | |
} | |
else { | |
# default to domain admins | |
$SID = (Get-DomainSID -Domain $TargetDomain -DomainController $TargetDomainController) + "-512" | |
$GroupSearcher.filter = "(&(objectCategory=group)(objectSID=$SID)$Filter)" | |
} | |
try { | |
$Result = $GroupSearcher.FindOne() | |
} | |
catch { | |
$Members = @() | |
} | |
$GroupFoundName = '' | |
if ($Result) { | |
$Members = $Result.properties.item("member") | |
if($Members.count -eq 0) { | |
$Finished = $False | |
$Bottom = 0 | |
$Top = 0 | |
while(!$Finished) { | |
$Top = $Bottom + 1499 | |
$MemberRange="member;range=$Bottom-$Top" | |
$Bottom += 1500 | |
$GroupSearcher.PropertiesToLoad.Clear() | |
[void]$GroupSearcher.PropertiesToLoad.Add("$MemberRange") | |
[void]$GroupSearcher.PropertiesToLoad.Add("samaccountname") | |
try { | |
$Result = $GroupSearcher.FindOne() | |
$RangedProperty = $Result.Properties.PropertyNames -like "member;range=*" | |
$Members += $Result.Properties.item($RangedProperty) | |
$GroupFoundName = $Result.properties.item("samaccountname")[0] | |
if ($Members.count -eq 0) { | |
$Finished = $True | |
} | |
} | |
catch [System.Management.Automation.MethodInvocationException] { | |
$Finished = $True | |
} | |
} | |
} | |
else { | |
$GroupFoundName = $Result.properties.item("samaccountname")[0] | |
$Members += $Result.Properties.item($RangedProperty) | |
} | |
} | |
$GroupSearcher.dispose() | |
} | |
$Members | Where-Object {$_} | ForEach-Object { | |
# if we're doing the LDAP_MATCHING_RULE_IN_CHAIN recursion | |
if ($Recurse -and $UseMatchingRule) { | |
$Properties = $_.Properties | |
} | |
else { | |
if($TargetDomainController) { | |
$Result = [adsi]"LDAP://$TargetDomainController/$_" | |
} | |
else { | |
$Result = [adsi]"LDAP://$_" | |
} | |
if($Result){ | |
$Properties = $Result.Properties | |
} | |
} | |
if($Properties) { | |
$IsGroup = @('268435456','268435457','536870912','536870913') -contains $Properties.samaccounttype | |
if ($FullData) { | |
$GroupMember = Convert-LDAPProperty -Properties $Properties | |
} | |
else { | |
$GroupMember = New-Object PSObject | |
} | |
$GroupMember | Add-Member Noteproperty 'GroupDomain' $TargetDomain | |
$GroupMember | Add-Member Noteproperty 'GroupName' $GroupFoundName | |
if($Properties.objectSid) { | |
$MemberSID = ((New-Object System.Security.Principal.SecurityIdentifier $Properties.objectSid[0],0).Value) | |
} | |
else { | |
$MemberSID = $Null | |
} | |
try { | |
$MemberDN = $Properties.distinguishedname[0] | |
if (($MemberDN -match 'ForeignSecurityPrincipals') -and ($MemberDN -match 'S-1-5-21')) { | |
try { | |
if(-not $MemberSID) { | |
$MemberSID = $Properties.cn[0] | |
} | |
$MemberSimpleName = Convert-SidToName -SID $MemberSID | Convert-ADName -InputType 'NT4' -OutputType 'Simple' | |
if($MemberSimpleName) { | |
$MemberDomain = $MemberSimpleName.Split('@')[1] | |
} | |
else { | |
Write-Warning "Error converting $MemberDN" | |
$MemberDomain = $Null | |
} | |
} | |
catch { | |
Write-Warning "Error converting $MemberDN" | |
$MemberDomain = $Null | |
} | |
} | |
else { | |
# extract the FQDN from the Distinguished Name | |
$MemberDomain = $MemberDN.subString($MemberDN.IndexOf("DC=")) -replace 'DC=','' -replace ',','.' | |
} | |
} | |
catch { | |
$MemberDN = $Null | |
$MemberDomain = $Null | |
} | |
if ($Properties.samaccountname) { | |
# forest users have the samAccountName set | |
$MemberName = $Properties.samaccountname[0] | |
} | |
else { | |
# external trust users have a SID, so convert it | |
try { | |
$MemberName = Convert-SidToName $Properties.cn[0] | |
} | |
catch { | |
# if there's a problem contacting the domain to resolve the SID | |
$MemberName = $Properties.cn | |
} | |
} | |
$GroupMember | Add-Member Noteproperty 'MemberDomain' $MemberDomain | |
$GroupMember | Add-Member Noteproperty 'MemberName' $MemberName | |
$GroupMember | Add-Member Noteproperty 'MemberSID' $MemberSID | |
$GroupMember | Add-Member Noteproperty 'IsGroup' $IsGroup | |
$GroupMember | Add-Member Noteproperty 'MemberDN' $MemberDN | |
$GroupMember.PSObject.TypeNames.Add('PowerView.GroupMember') | |
$GroupMember | |
# if we're doing manual recursion | |
if ($Recurse -and !$UseMatchingRule -and $IsGroup -and $MemberName) { | |
if($FullData) { | |
Get-ThisThingGroupMember -FullData -Domain $MemberDomain -DomainController $TargetDomainController -Credential $Credential -GroupName $MemberName -Recurse -PageSize $PageSize | |
} | |
else { | |
Get-ThisThingGroupMember -Domain $MemberDomain -DomainController $TargetDomainController -Credential $Credential -GroupName $MemberName -Recurse -PageSize $PageSize | |
} | |
} | |
} | |
} | |
} | |
} | |
} | |
function Get-ThisThingFileServer { | |
[CmdletBinding()] | |
param( | |
[String] | |
$Domain, | |
[String] | |
$DomainController, | |
[String[]] | |
$TargetUsers, | |
[ValidateRange(1,10000)] | |
[Int] | |
$PageSize = 200, | |
[Management.Automation.PSCredential] | |
$Credential | |
) | |
function SplitPath { | |
# short internal helper to split UNC server paths | |
param([String]$Path) | |
if ($Path -and ($Path.split("\\").Count -ge 3)) { | |
$Temp = $Path.split("\\")[2] | |
if($Temp -and ($Temp -ne '')) { | |
$Temp | |
} | |
} | |
} | |
$filter = "(!(userAccountControl:1.2.840.113556.1.4.803:=2))(|(scriptpath=*)(homedirectory=*)(profilepath=*))" | |
Get-ThisThingUser -Domain $Domain -DomainController $DomainController -Credential $Credential -PageSize $PageSize -Filter $filter | Where-Object {$_} | Where-Object { | |
# filter for any target users | |
if($TargetUsers) { | |
$TargetUsers -Match $_.samAccountName | |
} | |
else { $True } | |
} | ForEach-Object { | |
# split out every potential file server path | |
if($_.homedirectory) { | |
SplitPath($_.homedirectory) | |
} | |
if($_.scriptpath) { | |
SplitPath($_.scriptpath) | |
} | |
if($_.profilepath) { | |
SplitPath($_.profilepath) | |
} | |
} | Where-Object {$_} | Sort-Object -Unique | |
} | |
function Get-DFSshare { | |
[CmdletBinding()] | |
param( | |
[String] | |
[ValidateSet("All","V1","1","V2","2")] | |
$Version = "All", | |
[String] | |
$Domain, | |
[String] | |
$DomainController, | |
[String] | |
$ADSpath, | |
[ValidateRange(1,10000)] | |
[Int] | |
$PageSize = 200, | |
[Management.Automation.PSCredential] | |
$Credential | |
) | |
function Parse-Pkt { | |
[CmdletBinding()] | |
param( | |
[byte[]] | |
$Pkt | |
) | |
$bin = $Pkt | |
$blob_version = [bitconverter]::ToUInt32($bin[0..3],0) | |
$blob_element_count = [bitconverter]::ToUInt32($bin[4..7],0) | |
$offset = 8 | |
#https://msdn.microsoft.com/en-us/library/cc227147.aspx | |
$object_list = @() | |
for($i=1; $i -le $blob_element_count; $i++){ | |
$blob_name_size_start = $offset | |
$blob_name_size_end = $offset + 1 | |
$blob_name_size = [bitconverter]::ToUInt16($bin[$blob_name_size_start..$blob_name_size_end],0) | |
$blob_name_start = $blob_name_size_end + 1 | |
$blob_name_end = $blob_name_start + $blob_name_size - 1 | |
$blob_name = [System.Text.Encoding]::Unicode.GetString($bin[$blob_name_start..$blob_name_end]) | |
$blob_data_size_start = $blob_name_end + 1 | |
$blob_data_size_end = $blob_data_size_start + 3 | |
$blob_data_size = [bitconverter]::ToUInt32($bin[$blob_data_size_start..$blob_data_size_end],0) | |
$blob_data_start = $blob_data_size_end + 1 | |
$blob_data_end = $blob_data_start + $blob_data_size - 1 | |
$blob_data = $bin[$blob_data_start..$blob_data_end] | |
switch -wildcard ($blob_name) { | |
"\siteroot" { } | |
"\domainroot*" { | |
# Parse DFSNamespaceRootOrLinkBlob object. Starts with variable length DFSRootOrLinkIDBlob which we parse first... | |
# DFSRootOrLinkIDBlob | |
$root_or_link_guid_start = 0 | |
$root_or_link_guid_end = 15 | |
$root_or_link_guid = [byte[]]$blob_data[$root_or_link_guid_start..$root_or_link_guid_end] | |
$guid = New-Object Guid(,$root_or_link_guid) # should match $guid_str | |
$prefix_size_start = $root_or_link_guid_end + 1 | |
$prefix_size_end = $prefix_size_start + 1 | |
$prefix_size = [bitconverter]::ToUInt16($blob_data[$prefix_size_start..$prefix_size_end],0) | |
$prefix_start = $prefix_size_end + 1 | |
$prefix_end = $prefix_start + $prefix_size - 1 | |
$prefix = [System.Text.Encoding]::Unicode.GetString($blob_data[$prefix_start..$prefix_end]) | |
$short_prefix_size_start = $prefix_end + 1 | |
$short_prefix_size_end = $short_prefix_size_start + 1 | |
$short_prefix_size = [bitconverter]::ToUInt16($blob_data[$short_prefix_size_start..$short_prefix_size_end],0) | |
$short_prefix_start = $short_prefix_size_end + 1 | |
$short_prefix_end = $short_prefix_start + $short_prefix_size - 1 | |
$short_prefix = [System.Text.Encoding]::Unicode.GetString($blob_data[$short_prefix_start..$short_prefix_end]) | |
$type_start = $short_prefix_end + 1 | |
$type_end = $type_start + 3 | |
$type = [bitconverter]::ToUInt32($blob_data[$type_start..$type_end],0) | |
$state_start = $type_end + 1 | |
$state_end = $state_start + 3 | |
$state = [bitconverter]::ToUInt32($blob_data[$state_start..$state_end],0) | |
$comment_size_start = $state_end + 1 | |
$comment_size_end = $comment_size_start + 1 | |
$comment_size = [bitconverter]::ToUInt16($blob_data[$comment_size_start..$comment_size_end],0) | |
$comment_start = $comment_size_end + 1 | |
$comment_end = $comment_start + $comment_size - 1 | |
if ($comment_size -gt 0) { | |
$comment = [System.Text.Encoding]::Unicode.GetString($blob_data[$comment_start..$comment_end]) | |
} | |
$prefix_timestamp_start = $comment_end + 1 | |
$prefix_timestamp_end = $prefix_timestamp_start + 7 | |
# https://msdn.microsoft.com/en-us/library/cc230324.aspx FILETIME | |
$prefix_timestamp = $blob_data[$prefix_timestamp_start..$prefix_timestamp_end] #dword lowDateTime #dword highdatetime | |
$state_timestamp_start = $prefix_timestamp_end + 1 | |
$state_timestamp_end = $state_timestamp_start + 7 | |
$state_timestamp = $blob_data[$state_timestamp_start..$state_timestamp_end] | |
$comment_timestamp_start = $state_timestamp_end + 1 | |
$comment_timestamp_end = $comment_timestamp_start + 7 | |
$comment_timestamp = $blob_data[$comment_timestamp_start..$comment_timestamp_end] | |
$version_start = $comment_timestamp_end + 1 | |
$version_end = $version_start + 3 | |
$version = [bitconverter]::ToUInt32($blob_data[$version_start..$version_end],0) | |
# Parse rest of DFSNamespaceRootOrLinkBlob here | |
$dfs_targetlist_blob_size_start = $version_end + 1 | |
$dfs_targetlist_blob_size_end = $dfs_targetlist_blob_size_start + 3 | |
$dfs_targetlist_blob_size = [bitconverter]::ToUInt32($blob_data[$dfs_targetlist_blob_size_start..$dfs_targetlist_blob_size_end],0) | |
$dfs_targetlist_blob_start = $dfs_targetlist_blob_size_end + 1 | |
$dfs_targetlist_blob_end = $dfs_targetlist_blob_start + $dfs_targetlist_blob_size - 1 | |
$dfs_targetlist_blob = $blob_data[$dfs_targetlist_blob_start..$dfs_targetlist_blob_end] | |
$reserved_blob_size_start = $dfs_targetlist_blob_end + 1 | |
$reserved_blob_size_end = $reserved_blob_size_start + 3 | |
$reserved_blob_size = [bitconverter]::ToUInt32($blob_data[$reserved_blob_size_start..$reserved_blob_size_end],0) | |
$reserved_blob_start = $reserved_blob_size_end + 1 | |
$reserved_blob_end = $reserved_blob_start + $reserved_blob_size - 1 | |
$reserved_blob = $blob_data[$reserved_blob_start..$reserved_blob_end] | |
$referral_ttl_start = $reserved_blob_end + 1 | |
$referral_ttl_end = $referral_ttl_start + 3 | |
$referral_ttl = [bitconverter]::ToUInt32($blob_data[$referral_ttl_start..$referral_ttl_end],0) | |
#Parse DFSTargetListBlob | |
$target_count_start = 0 | |
$target_count_end = $target_count_start + 3 | |
$target_count = [bitconverter]::ToUInt32($dfs_targetlist_blob[$target_count_start..$target_count_end],0) | |
$t_offset = $target_count_end + 1 | |
for($j=1; $j -le $target_count; $j++){ | |
$target_entry_size_start = $t_offset | |
$target_entry_size_end = $target_entry_size_start + 3 | |
$target_entry_size = [bitconverter]::ToUInt32($dfs_targetlist_blob[$target_entry_size_start..$target_entry_size_end],0) | |
$target_time_stamp_start = $target_entry_size_end + 1 | |
$target_time_stamp_end = $target_time_stamp_start + 7 | |
# FILETIME again or special if priority rank and priority class 0 | |
$target_time_stamp = $dfs_targetlist_blob[$target_time_stamp_start..$target_time_stamp_end] | |
$target_state_start = $target_time_stamp_end + 1 | |
$target_state_end = $target_state_start + 3 | |
$target_state = [bitconverter]::ToUInt32($dfs_targetlist_blob[$target_state_start..$target_state_end],0) | |
$target_type_start = $target_state_end + 1 | |
$target_type_end = $target_type_start + 3 | |
$target_type = [bitconverter]::ToUInt32($dfs_targetlist_blob[$target_type_start..$target_type_end],0) | |
$server_name_size_start = $target_type_end + 1 | |
$server_name_size_end = $server_name_size_start + 1 | |
$server_name_size = [bitconverter]::ToUInt16($dfs_targetlist_blob[$server_name_size_start..$server_name_size_end],0) | |
$server_name_start = $server_name_size_end + 1 | |
$server_name_end = $server_name_start + $server_name_size - 1 | |
$server_name = [System.Text.Encoding]::Unicode.GetString($dfs_targetlist_blob[$server_name_start..$server_name_end]) | |
$share_name_size_start = $server_name_end + 1 | |
$share_name_size_end = $share_name_size_start + 1 | |
$share_name_size = [bitconverter]::ToUInt16($dfs_targetlist_blob[$share_name_size_start..$share_name_size_end],0) | |
$share_name_start = $share_name_size_end + 1 | |
$share_name_end = $share_name_start + $share_name_size - 1 | |
$share_name = [System.Text.Encoding]::Unicode.GetString($dfs_targetlist_blob[$share_name_start..$share_name_end]) | |
$target_list += "\\$server_name\$share_name" | |
$t_offset = $share_name_end + 1 | |
} | |
} | |
} | |
$offset = $blob_data_end + 1 | |
$dfs_pkt_properties = @{ | |
'Name' = $blob_name | |
'Prefix' = $prefix | |
'TargetList' = $target_list | |
} | |
$object_list += New-Object -TypeName PSObject -Property $dfs_pkt_properties | |
$prefix = $null | |
$blob_name = $null | |
$target_list = $null | |
} | |
$servers = @() | |
$object_list | ForEach-Object { | |
if ($_.TargetList) { | |
$_.TargetList | ForEach-Object { | |
$servers += $_.split("\")[2] | |
} | |
} | |
} | |
$servers | |
} | |
function Get-DFSshareV1 { | |
[CmdletBinding()] | |
param( | |
[String] | |
$Domain, | |
[String] | |
$DomainController, | |
[String] | |
$ADSpath, | |
[ValidateRange(1,10000)] | |
[Int] | |
$PageSize = 200, | |
[Management.Automation.PSCredential] | |
$Credential | |
) | |
$DFSsearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $ADSpath -PageSize $PageSize | |
if($DFSsearcher) { | |
$DFSshares = @() | |
$DFSsearcher.filter = "(&(objectClass=fTDfs))" | |
try { | |
$Results = $DFSSearcher.FindAll() | |
$Results | Where-Object {$_} | ForEach-Object { | |
$Properties = $_.Properties | |
$RemoteNames = $Properties.remoteservername | |
$Pkt = $Properties.pkt | |
$DFSshares += $RemoteNames | ForEach-Object { | |
try { | |
if ( $_.Contains('\') ) { | |
New-Object -TypeName PSObject -Property @{'Name'=$Properties.name[0];'RemoteServerName'=$_.split("\")[2]} | |
} | |
} | |
catch { | |
Write-Verbose "Error in parsing DFS share : $_" | |
} | |
} | |
} | |
$Results.dispose() | |
$DFSSearcher.dispose() | |
if($pkt -and $pkt[0]) { | |
Parse-Pkt $pkt[0] | ForEach-Object { | |
# If a folder doesn't have a redirection it will | |
# have a target like | |
# \\null\TestNameSpace\folder\.DFSFolderLink so we | |
# do actually want to match on "null" rather than | |
# $null | |
if ($_ -ne "null") { | |
New-Object -TypeName PSObject -Property @{'Name'=$Properties.name[0];'RemoteServerName'=$_} | |
} | |
} | |
} | |
} | |
catch { | |
Write-Warning "Get-DFSshareV1 error : $_" | |
} | |
$DFSshares | Sort-Object -Property "RemoteServerName" | |
} | |
} | |
function Get-DFSshareV2 { | |
[CmdletBinding()] | |
param( | |
[String] | |
$Domain, | |
[String] | |
$DomainController, | |
[String] | |
$ADSpath, | |
[ValidateRange(1,10000)] | |
[Int] | |
$PageSize = 200, | |
[Management.Automation.PSCredential] | |
$Credential | |
) | |
$DFSsearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $ADSpath -PageSize $PageSize | |
if($DFSsearcher) { | |
$DFSshares = @() | |
$DFSsearcher.filter = "(&(objectClass=msDFS-Linkv2))" | |
$DFSSearcher.PropertiesToLoad.AddRange(('msdfs-linkpathv2','msDFS-TargetListv2')) | |
try { | |
$Results = $DFSSearcher.FindAll() | |
$Results | Where-Object {$_} | ForEach-Object { | |
$Properties = $_.Properties | |
$target_list = $Properties.'msdfs-targetlistv2'[0] | |
$xml = [xml][System.Text.Encoding]::Unicode.GetString($target_list[2..($target_list.Length-1)]) | |
$DFSshares += $xml.targets.ChildNodes | ForEach-Object { | |
try { | |
$Target = $_.InnerText | |
if ( $Target.Contains('\') ) { | |
$DFSroot = $Target.split("\")[3] | |
$ShareName = $Properties.'msdfs-linkpathv2'[0] | |
New-Object -TypeName PSObject -Property @{'Name'="$DFSroot$ShareName";'RemoteServerName'=$Target.split("\")[2]} | |
} | |
} | |
catch { | |
Write-Verbose "Error in parsing target : $_" | |
} | |
} | |
} | |
$Results.dispose() | |
$DFSSearcher.dispose() | |
} | |
catch { | |
Write-Warning "Get-DFSshareV2 error : $_" | |
} | |
$DFSshares | Sort-Object -Unique -Property "RemoteServerName" | |
} | |
} | |
$DFSshares = @() | |
if ( ($Version -eq "all") -or ($Version.endsWith("1")) ) { | |
$DFSshares += Get-DFSshareV1 -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $ADSpath -PageSize $PageSize | |
} | |
if ( ($Version -eq "all") -or ($Version.endsWith("2")) ) { | |
$DFSshares += Get-DFSshareV2 -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $ADSpath -PageSize $PageSize | |
} | |
$DFSshares | Sort-Object -Property ("RemoteServerName","Name") -Unique | |
} | |
filter Get-GptTmpl { | |
[CmdletBinding()] | |
Param ( | |
[Parameter(Mandatory=$True, ValueFromPipeline=$True)] | |
[String] | |
$GptTmplPath, | |
[Switch] | |
$UsePSDrive | |
) | |
if($UsePSDrive) { | |
# if we're PSDrives, create a temporary mount point | |
$Parts = $GptTmplPath.split('\') | |
$FolderPath = $Parts[0..($Parts.length-2)] -join '\' | |
$FilePath = $Parts[-1] | |
$RandDrive = ("abcdefghijklmnopqrstuvwxyz".ToCharArray() | Get-Random -Count 7) -join '' | |
Write-Verbose "Mounting path $GptTmplPath using a temp PSDrive at $RandDrive" | |
try { | |
$Null = New-PSDrive -Name $RandDrive -PSProvider FileSystem -Root $FolderPath -ErrorAction Stop | |
} | |
catch { | |
Write-Verbose "Error mounting path $GptTmplPath : $_" | |
return $Null | |
} | |
# so we can cd/dir the new drive | |
$TargetGptTmplPath = $RandDrive + ":\" + $FilePath | |
} | |
else { | |
$TargetGptTmplPath = $GptTmplPath | |
} | |
Write-Verbose "GptTmplPath: $GptTmplPath" | |
try { | |
Write-Verbose "Parsing $TargetGptTmplPath" | |
$TargetGptTmplPath | Get-IniContent -ErrorAction SilentlyContinue | |
} | |
catch { | |
Write-Verbose "Error parsing $TargetGptTmplPath : $_" | |
} | |
if($UsePSDrive -and $RandDrive) { | |
Write-Verbose "Removing temp PSDrive $RandDrive" | |
Get-PSDrive -Name $RandDrive -ErrorAction SilentlyContinue | Remove-PSDrive -Force | |
} | |
} | |
filter Get-GroupsXML { | |
[CmdletBinding()] | |
Param ( | |
[Parameter(Mandatory=$True, ValueFromPipeline=$True)] | |
[String] | |
$GroupsXMLPath, | |
[Switch] | |
$UsePSDrive | |
) | |
if($UsePSDrive) { | |
# if we're PSDrives, create a temporary mount point | |
$Parts = $GroupsXMLPath.split('\') | |
$FolderPath = $Parts[0..($Parts.length-2)] -join '\' | |
$FilePath = $Parts[-1] | |
$RandDrive = ("abcdefghijklmnopqrstuvwxyz".ToCharArray() | Get-Random -Count 7) -join '' | |
Write-Verbose "Mounting path $GroupsXMLPath using a temp PSDrive at $RandDrive" | |
try { | |
$Null = New-PSDrive -Name $RandDrive -PSProvider FileSystem -Root $FolderPath -ErrorAction Stop | |
} | |
catch { | |
Write-Verbose "Error mounting path $GroupsXMLPath : $_" | |
return $Null | |
} | |
# so we can cd/dir the new drive | |
$TargetGroupsXMLPath = $RandDrive + ":\" + $FilePath | |
} | |
else { | |
$TargetGroupsXMLPath = $GroupsXMLPath | |
} | |
try { | |
[XML]$GroupsXMLcontent = Get-Content $TargetGroupsXMLPath -ErrorAction Stop | |
# process all group properties in the XML | |
$GroupsXMLcontent | Select-Xml "/Groups/Group" | Select-Object -ExpandProperty node | ForEach-Object { | |
$Groupname = $_.Properties.groupName | |
# extract the localgroup sid for memberof | |
$GroupSID = $_.Properties.groupSid | |
if(-not $GroupSID) { | |
if($Groupname -match 'Administrators') { | |
$GroupSID = 'S-1-5-32-544' | |
} | |
elseif($Groupname -match 'Remote Desktop') { | |
$GroupSID = 'S-1-5-32-555' | |
} | |
elseif($Groupname -match 'Guests') { | |
$GroupSID = 'S-1-5-32-546' | |
} | |
else { | |
$GroupSID = Convert-NameToSid -ObjectName $Groupname | Select-Object -ExpandProperty SID | |
} | |
} | |
# extract out members added to this group | |
$Members = $_.Properties.members | Select-Object -ExpandProperty Member | Where-Object { $_.action -match 'ADD' } | ForEach-Object { | |
if($_.sid) { $_.sid } | |
else { $_.name } | |
} | |
if ($Members) { | |
# extract out any/all filters...I hate you GPP | |
if($_.filters) { | |
$Filters = $_.filters.GetEnumerator() | ForEach-Object { | |
New-Object -TypeName PSObject -Property @{'Type' = $_.LocalName;'Value' = $_.name} | |
} | |
} | |
else { | |
$Filters = $Null | |
} | |
if($Members -isnot [System.Array]) { $Members = @($Members) } | |
$GPOGroup = New-Object PSObject | |
$GPOGroup | Add-Member Noteproperty 'GPOPath' $TargetGroupsXMLPath | |
$GPOGroup | Add-Member Noteproperty 'Filters' $Filters | |
$GPOGroup | Add-Member Noteproperty 'GroupName' $GroupName | |
$GPOGroup | Add-Member Noteproperty 'GroupSID' $GroupSID | |
$GPOGroup | Add-Member Noteproperty 'GroupMemberOf' $Null | |
$GPOGroup | Add-Member Noteproperty 'GroupMembers' $Members | |
$GPOGroup | |
} | |
} | |
} | |
catch { | |
Write-Verbose "Error parsing $TargetGroupsXMLPath : $_" | |
} | |
if($UsePSDrive -and $RandDrive) { | |
Write-Verbose "Removing temp PSDrive $RandDrive" | |
Get-PSDrive -Name $RandDrive -ErrorAction SilentlyContinue | Remove-PSDrive -Force | |
} | |
} | |
function Get-ThisThingGPO { | |
[CmdletBinding()] | |
Param ( | |
[Parameter(ValueFromPipeline=$True)] | |
[String] | |
$GPOname = '*', | |
[String] | |
$DisplayName, | |
[String] | |
$ComputerName, | |
[String] | |
$Domain, | |
[String] | |
$DomainController, | |
[String] | |
$ADSpath, | |
[ValidateRange(1,10000)] | |
[Int] | |
$PageSize = 200, | |
[Management.Automation.PSCredential] | |
$Credential | |
) | |
begin { | |
$GPOSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $ADSpath -PageSize $PageSize | |
} | |
process { | |
if ($GPOSearcher) { | |
if($ComputerName) { | |
$GPONames = @() | |
$Computers = Get-ThisThingComputer -ComputerName $ComputerName -Domain $Domain -DomainController $DomainController -FullData -PageSize $PageSize | |
if(!$Computers) { | |
throw "Computer $ComputerName in domain '$Domain' not found! Try a fully qualified host name" | |
} | |
# get the given computer's OU | |
$ComputerOUs = @() | |
ForEach($Computer in $Computers) { | |
# extract all OUs a computer is a part of | |
$DN = $Computer.distinguishedname | |
$ComputerOUs += $DN.split(",") | ForEach-Object { | |
if($_.startswith("OU=")) { | |
$DN.substring($DN.indexof($_)) | |
} | |
} | |
} | |
Write-Verbose "ComputerOUs: $ComputerOUs" | |
# find all the GPOs linked to the computer's OU | |
ForEach($ComputerOU in $ComputerOUs) { | |
$GPONames += Get-ThisThingOU -Domain $Domain -DomainController $DomainController -ADSpath $ComputerOU -FullData -PageSize $PageSize | ForEach-Object { | |
# get any GPO links | |
write-verbose "blah: $($_.name)" | |
$_.gplink.split("][") | ForEach-Object { | |
if ($_.startswith("LDAP")) { | |
$_.split(";")[0] | |
} | |
} | |
} | |
} | |
Write-Verbose "GPONames: $GPONames" | |
# find any GPOs linked to the site for the given computer | |
$ComputerSite = (Get-SiteName -ComputerName $ComputerName).SiteName | |
if($ComputerSite -and ($ComputerSite -notlike 'Error*')) { | |
$GPONames += Get-ThisThingSite -SiteName $ComputerSite -FullData | ForEach-Object { | |
if($_.gplink) { | |
$_.gplink.split("][") | ForEach-Object { | |
if ($_.startswith("LDAP")) { | |
$_.split(";")[0] | |
} | |
} | |
} | |
} | |
} | |
$GPONames | Where-Object{$_ -and ($_ -ne '')} | ForEach-Object { | |
# use the gplink as an ADS path to enumerate all GPOs for the computer | |
$GPOSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $_ -PageSize $PageSize | |
$GPOSearcher.filter="(&(objectCategory=groupPolicyContainer)(name=$GPOname))" | |
try { | |
$Results = $GPOSearcher.FindAll() | |
$Results | Where-Object {$_} | ForEach-Object { | |
$Out = Convert-LDAPProperty -Properties $_.Properties | |
$Out | Add-Member Noteproperty 'ComputerName' $ComputerName | |
$Out | |
} | |
$Results.dispose() | |
$GPOSearcher.dispose() | |
} | |
catch { | |
Write-Warning $_ | |
} | |
} | |
} | |
else { | |
if($DisplayName) { | |
$GPOSearcher.filter="(&(objectCategory=groupPolicyContainer)(displayname=$DisplayName))" | |
} | |
else { | |
$GPOSearcher.filter="(&(objectCategory=groupPolicyContainer)(name=$GPOname))" | |
} | |
try { | |
$Results = $GPOSearcher.FindAll() | |
$Results | Where-Object {$_} | ForEach-Object { | |
if($ADSPath -and ($ADSpath -Match '^GC://')) { | |
$Properties = Convert-LDAPProperty -Properties $_.Properties | |
try { | |
$GPODN = $Properties.distinguishedname | |
$GPODomain = $GPODN.subString($GPODN.IndexOf("DC=")) -replace 'DC=','' -replace ',','.' | |
$gpcfilesyspath = "\\$GPODomain\SysVol\$GPODomain\Policies\$($Properties.cn)" | |
$Properties | Add-Member Noteproperty 'gpcfilesyspath' $gpcfilesyspath | |
$Properties | |
} | |
catch { | |
$Properties | |
} | |
} | |
else { | |
# convert/process the LDAP fields for each result | |
Convert-LDAPProperty -Properties $_.Properties | |
} | |
} | |
$Results.dispose() | |
$GPOSearcher.dispose() | |
} | |
catch { | |
Write-Warning $_ | |
} | |
} | |
} | |
} | |
} | |
function New-GPOImmediateTask { | |
[CmdletBinding(DefaultParameterSetName = 'Create')] | |
Param ( | |
[Parameter(ParameterSetName = 'Create', Mandatory = $True)] | |
[String] | |
[ValidateNotNullOrEmpty()] | |
$TaskName, | |
[Parameter(ParameterSetName = 'Create')] | |
[String] | |
[ValidateNotNullOrEmpty()] | |
$Command = 'powershell', | |
[Parameter(ParameterSetName = 'Create')] | |
[String] | |
[ValidateNotNullOrEmpty()] | |
$CommandArguments, | |
[Parameter(ParameterSetName = 'Create')] | |
[String] | |
[ValidateNotNullOrEmpty()] | |
$TaskDescription = '', | |
[Parameter(ParameterSetName = 'Create')] | |
[String] | |
[ValidateNotNullOrEmpty()] | |
$TaskAuthor = 'NT AUTHORITY\System', | |
[Parameter(ParameterSetName = 'Create')] | |
[String] | |
[ValidateNotNullOrEmpty()] | |
$TaskModifiedDate = (Get-Date (Get-Date).AddDays(-30) -Format u).trim("Z"), | |
[Parameter(ParameterSetName = 'Create')] | |
[Parameter(ParameterSetName = 'Remove')] | |
[String] | |
$GPOname, | |
[Parameter(ParameterSetName = 'Create')] | |
[Parameter(ParameterSetName = 'Remove')] | |
[String] | |
$GPODisplayName, | |
[Parameter(ParameterSetName = 'Create')] | |
[Parameter(ParameterSetName = 'Remove')] | |
[String] | |
$Domain, | |
[Parameter(ParameterSetName = 'Create')] | |
[Parameter(ParameterSetName = 'Remove')] | |
[String] | |
$DomainController, | |
[Parameter(ParameterSetName = 'Create')] | |
[Parameter(ParameterSetName = 'Remove')] | |
[String] | |
$ADSpath, | |
[Parameter(ParameterSetName = 'Create')] | |
[Parameter(ParameterSetName = 'Remove')] | |
[Switch] | |
$Force, | |
[Parameter(ParameterSetName = 'Remove')] | |
[Switch] | |
$Remove, | |
[Parameter(ParameterSetName = 'Create')] | |
[Parameter(ParameterSetName = 'Remove')] | |
[Management.Automation.PSCredential] | |
$Credential | |
) | |
# build the XML spec for our 'immediate' scheduled task | |
$TaskXML = '<?xml version="1.0" encoding="utf-8"?><ScheduledTasks clsid="{CC63F200-7309-4ba0-B154-A71CD118DBCC}"><ImmediateTaskV2 clsid="{9756B581-76EC-4169-9AFC-0CA8D43ADB5F}" name="'+$TaskName+'" image="0" changed="'+$TaskModifiedDate+'" uid="{'+$([guid]::NewGuid())+'}" userContext="0" removePolicy="0"><Properties action="C" name="'+$TaskName+'" runAs="NT AUTHORITY\System" logonType="S4U"><Task version="1.3"><RegistrationInfo><Author>'+$TaskAuthor+'</Author><Description>'+$TaskDescription+'</Description></RegistrationInfo><Principals><Principal id="Author"><UserId>NT AUTHORITY\System</UserId><RunLevel>HighestAvailable</RunLevel><LogonType>S4U</LogonType></Principal></Principals><Settings><IdleSettings><Duration>PT10M</Duration><WaitTimeout>PT1H</WaitTimeout><StopOnIdleEnd>true</StopOnIdleEnd><RestartOnIdle>false</RestartOnIdle></IdleSettings><MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy><DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries><StopIfGoingOnBatteries>true</StopIfGoingOnBatteries><AllowHardTerminate>false</AllowHardTerminate><StartWhenAvailable>true</StartWhenAvailable><AllowStartOnDemand>false</AllowStartOnDemand><Enabled>true</Enabled><Hidden>true</Hidden><ExecutionTimeLimit>PT0S</ExecutionTimeLimit><Priority>7</Priority><DeleteExpiredTaskAfter>PT0S</DeleteExpiredTaskAfter><RestartOnFailure><Interval>PT15M</Interval><Count>3</Count></RestartOnFailure></Settings><Actions Context="Author"><Exec><Command>'+$Command+'</Command><Arguments>'+$CommandArguments+'</Arguments></Exec></Actions><Triggers><TimeTrigger><StartBoundary>%LocalTimeXmlEx%</StartBoundary><EndBoundary>%LocalTimeXmlEx%</EndBoundary><Enabled>true</Enabled></TimeTrigger></Triggers></Task></Properties></ImmediateTaskV2></ScheduledTasks>' | |
if (!$PSBoundParameters['GPOname'] -and !$PSBoundParameters['GPODisplayName']) { | |
Write-Warning 'Either -GPOName or -GPODisplayName must be specified' | |
return | |
} | |
# eunmerate the specified GPO(s) | |
$GPOs = Get-ThisThingGPO -GPOname $GPOname -DisplayName $GPODisplayName -Domain $Domain -DomainController $DomainController -ADSpath $ADSpath -Credential $Credential | |
if(!$GPOs) { | |
Write-Warning 'No GPO found.' | |
return | |
} | |
$GPOs | ForEach-Object { | |
$ProcessedGPOName = $_.Name | |
try { | |
Write-Verbose "Trying to weaponize GPO: $ProcessedGPOName" | |
# map a network drive as New-PSDrive/New-Item/etc. don't accept -Credential properly :( | |
if($Credential) { | |
Write-Verbose "Mapping '$($_.gpcfilesyspath)' to network drive N:\" | |
$Path = $_.gpcfilesyspath.TrimEnd('\') | |
$Net = New-Object -ComObject WScript.Network | |
$Net.MapNetworkDrive("N:", $Path, $False, $Credential.UserName, $Credential.GetNetworkCredential().Password) | |
$TaskPath = "N:\Machine\Preferences\ScheduledTasks\" | |
} | |
else { | |
$TaskPath = $_.gpcfilesyspath + "\Machine\Preferences\ScheduledTasks\" | |
} | |
if($Remove) { | |
if(!(Test-Path "$TaskPath\ScheduledTasks.xml")) { | |
Throw "Scheduled task doesn't exist at $TaskPath\ScheduledTasks.xml" | |
} | |
if (!$Force -and !$psCmdlet.ShouldContinue('Do you want to continue?',"Removing schtask at $TaskPath\ScheduledTasks.xml")) { | |
return | |
} | |
Remove-Item -Path "$TaskPath\ScheduledTasks.xml" -Force | |
} | |
else { | |
if (!$Force -and !$psCmdlet.ShouldContinue('Do you want to continue?',"Creating schtask at $TaskPath\ScheduledTasks.xml")) { | |
return | |
} | |
# create the folder if it doesn't exist | |
$Null = New-Item -ItemType Directory -Force -Path $TaskPath | |
if(Test-Path "$TaskPath\ScheduledTasks.xml") { | |
Throw "Scheduled task already exists at $TaskPath\ScheduledTasks.xml !" | |
} | |
$TaskXML | Set-Content -Encoding ASCII -Path "$TaskPath\ScheduledTasks.xml" | |
} | |
if($Credential) { | |
Write-Verbose "Removing mounted drive at N:\" | |
$Net = New-Object -ComObject WScript.Network | |
$Net.RemoveNetworkDrive("N:") | |
} | |
} | |
catch { | |
Write-Warning "Error for GPO $ProcessedGPOName : $_" | |
if($Credential) { | |
Write-Verbose "Removing mounted drive at N:\" | |
$Net = New-Object -ComObject WScript.Network | |
$Net.RemoveNetworkDrive("N:") | |
} | |
} | |
} | |
} | |
function Get-ThisThingGPOGroup { | |
[CmdletBinding()] | |
Param ( | |
[String] | |
$GPOname = '*', | |
[String] | |
$DisplayName, | |
[String] | |
$Domain, | |
[String] | |
$DomainController, | |
[String] | |
$ADSpath, | |
[Switch] | |
$ResolveMemberSIDs, | |
[Switch] | |
$UsePSDrive, | |
[ValidateRange(1,10000)] | |
[Int] | |
$PageSize = 200 | |
) | |
$Option = [System.StringSplitOptions]::RemoveEmptyEntries | |
# get every GPO from the specified domain with restricted groups set | |
Get-ThisThingGPO -GPOName $GPOname -DisplayName $DisplayName -Domain $Domain -DomainController $DomainController -ADSpath $ADSpath -PageSize $PageSize | ForEach-Object { | |
$GPOdisplayName = $_.displayname | |
$GPOname = $_.name | |
$GPOPath = $_.gpcfilesyspath | |
$ParseArgs = @{ | |
'GptTmplPath' = "$GPOPath\MACHINE\Microsoft\Windows NT\SecEdit\GptTmpl.inf" | |
'UsePSDrive' = $UsePSDrive | |
} | |
# parse the GptTmpl.inf 'Restricted Groups' file if it exists | |
$Inf = Get-GptTmpl @ParseArgs | |
if($Inf -and ($Inf.psbase.Keys -contains 'Group Membership')) { | |
$Memberships = @{} | |
# group the members/memberof fields for each entry | |
ForEach ($Membership in $Inf.'Group Membership'.GetEnumerator()) { | |
$Group, $Relation = $Membership.Key.Split('__', $Option) | ForEach-Object {$_.Trim()} | |
# extract out ALL members | |
$MembershipValue = $Membership.Value | Where-Object {$_} | ForEach-Object { $_.Trim('*') } | Where-Object {$_} | |
if($ResolveMemberSIDs) { | |
# if the resulting member is username and not a SID, attempt to resolve it | |
$GroupMembers = @() | |
ForEach($Member in $MembershipValue) { | |
if($Member -and ($Member.Trim() -ne '')) { | |
if($Member -notmatch '^S-1-.*') { | |
$MemberSID = Convert-NameToSid -Domain $Domain -ObjectName $Member | Select-Object -ExpandProperty SID | |
if($MemberSID) { | |
$GroupMembers += $MemberSID | |
} | |
else { | |
$GroupMembers += $Member | |
} | |
} | |
else { | |
$GroupMembers += $Member | |
} | |
} | |
} | |
$MembershipValue = $GroupMembers | |
} | |
if(-not $Memberships[$Group]) { | |
$Memberships[$Group] = @{} | |
} | |
if($MembershipValue -isnot [System.Array]) {$MembershipValue = @($MembershipValue)} | |
$Memberships[$Group].Add($Relation, $MembershipValue) | |
} | |
ForEach ($Membership in $Memberships.GetEnumerator()) { | |
if($Membership -and $Membership.Key -and ($Membership.Key -match '^\*')) { | |
# if the SID is already resolved (i.e. begins with *) try to resolve SID to a name | |
$GroupSID = $Membership.Key.Trim('*') | |
if($GroupSID -and ($GroupSID.Trim() -ne '')) { | |
$GroupName = Convert-SidToName -SID $GroupSID | |
} | |
else { | |
$GroupName = $False | |
} | |
} | |
else { | |
$GroupName = $Membership.Key | |
if($GroupName -and ($GroupName.Trim() -ne '')) { | |
if($Groupname -match 'Administrators') { | |
$GroupSID = 'S-1-5-32-544' | |
} | |
elseif($Groupname -match 'Remote Desktop') { | |
$GroupSID = 'S-1-5-32-555' | |
} | |
elseif($Groupname -match 'Guests') { | |
$GroupSID = 'S-1-5-32-546' | |
} | |
elseif($GroupName.Trim() -ne '') { | |
$GroupSID = Convert-NameToSid -Domain $Domain -ObjectName $Groupname | Select-Object -ExpandProperty SID | |
} | |
else { | |
$GroupSID = $Null | |
} | |
} | |
} | |
$GPOGroup = New-Object PSObject | |
$GPOGroup | Add-Member Noteproperty 'GPODisplayName' $GPODisplayName | |
$GPOGroup | Add-Member Noteproperty 'GPOName' $GPOName | |
$GPOGroup | Add-Member Noteproperty 'GPOPath' $GPOPath | |
$GPOGroup | Add-Member Noteproperty 'GPOType' 'RestrictedGroups' | |
$GPOGroup | Add-Member Noteproperty 'Filters' $Null | |
$GPOGroup | Add-Member Noteproperty 'GroupName' $GroupName | |
$GPOGroup | Add-Member Noteproperty 'GroupSID' $GroupSID | |
$GPOGroup | Add-Member Noteproperty 'GroupMemberOf' $Membership.Value.Memberof | |
$GPOGroup | Add-Member Noteproperty 'GroupMembers' $Membership.Value.Members | |
$GPOGroup | |
} | |
} | |
$ParseArgs = @{ | |
'GroupsXMLpath' = "$GPOPath\MACHINE\Preferences\Groups\Groups.xml" | |
'UsePSDrive' = $UsePSDrive | |
} | |
Get-GroupsXML @ParseArgs | ForEach-Object { | |
if($ResolveMemberSIDs) { | |
$GroupMembers = @() | |
ForEach($Member in $_.GroupMembers) { | |
if($Member -and ($Member.Trim() -ne '')) { | |
if($Member -notmatch '^S-1-.*') { | |
# if the resulting member is username and not a SID, attempt to resolve it | |
$MemberSID = Convert-NameToSid -Domain $Domain -ObjectName $Member | Select-Object -ExpandProperty SID | |
if($MemberSID) { | |
$GroupMembers += $MemberSID | |
} | |
else { | |
$GroupMembers += $Member | |
} | |
} | |
else { | |
$GroupMembers += $Member | |
} | |
} | |
} | |
$_.GroupMembers = $GroupMembers | |
} | |
$_ | Add-Member Noteproperty 'GPODisplayName' $GPODisplayName | |
$_ | Add-Member Noteproperty 'GPOName' $GPOName | |
$_ | Add-Member Noteproperty 'GPOType' 'GroupPolicyPreferences' | |
$_ | |
} | |
} | |
} | |
function Find-GPOLocation { | |
[CmdletBinding()] | |
Param ( | |
[String] | |
$UserName, | |
[String] | |
$GroupName, | |
[String] | |
$Domain, | |
[String] | |
$DomainController, | |
[String] | |
$LocalGroup = 'Administrators', | |
[Switch] | |
$UsePSDrive, | |
[ValidateRange(1,10000)] | |
[Int] | |
$PageSize = 200 | |
) | |
if($UserName) { | |
# if a group name is specified, get that user object so we can extract the target SID | |
$User = Get-ThisThingUser -UserName $UserName -Domain $Domain -DomainController $DomainController -PageSize $PageSize | Select-Object -First 1 | |
$UserSid = $User.objectsid | |
if(-not $UserSid) { | |
Throw "User '$UserName' not found!" | |
} | |
$TargetSIDs = @($UserSid) | |
$ObjectSamAccountName = $User.samaccountname | |
$TargetObject = $UserSid | |
} | |
elseif($GroupName) { | |
# if a group name is specified, get that group object so we can extract the target SID | |
$Group = Get-ThisThingGroup -GroupName $GroupName -Domain $Domain -DomainController $DomainController -FullData -PageSize $PageSize | Select-Object -First 1 | |
$GroupSid = $Group.objectsid | |
if(-not $GroupSid) { | |
Throw "Group '$GroupName' not found!" | |
} | |
$TargetSIDs = @($GroupSid) | |
$ObjectSamAccountName = $Group.samaccountname | |
$TargetObject = $GroupSid | |
} | |
else { | |
$TargetSIDs = @('*') | |
} | |
# figure out what the SID is of the target local group we're checking for membership in | |
if($LocalGroup -like "*Admin*") { | |
$TargetLocalSID = 'S-1-5-32-544' | |
} | |
elseif ( ($LocalGroup -like "*RDP*") -or ($LocalGroup -like "*Remote*") ) { | |
$TargetLocalSID = 'S-1-5-32-555' | |
} | |
elseif ($LocalGroup -like "S-1-5-*") { | |
$TargetLocalSID = $LocalGroup | |
} | |
else { | |
throw "LocalGroup must be 'Administrators', 'RDP', or a 'S-1-5-X' SID format." | |
} | |
# if we're not listing all relationships, use the tokenGroups approach from Get-ThisThingGroup to | |
# get all effective security SIDs this object is a part of | |
if($TargetSIDs[0] -and ($TargetSIDs[0] -ne '*')) { | |
$TargetSIDs += Get-ThisThingGroup -Domain $Domain -DomainController $DomainController -PageSize $PageSize -UserName $ObjectSamAccountName -RawSids | |
} | |
if(-not $TargetSIDs) { | |
throw "No effective target SIDs!" | |
} | |
Write-Verbose "TargetLocalSID: $TargetLocalSID" | |
Write-Verbose "Effective target SIDs: $TargetSIDs" | |
$GPOGroupArgs = @{ | |
'Domain' = $Domain | |
'DomainController' = $DomainController | |
'UsePSDrive' = $UsePSDrive | |
'ResolveMemberSIDs' = $True | |
'PageSize' = $PageSize | |
} | |
# enumerate all GPO group mappings for the target domain that involve our target SID set | |
$GPOgroups = Get-ThisThingGPOGroup @GPOGroupArgs | ForEach-Object { | |
$GPOgroup = $_ | |
# if the locally set group is what we're looking for, check the GroupMembers ('members') | |
# for our target SID | |
if($GPOgroup.GroupSID -match $TargetLocalSID) { | |
$GPOgroup.GroupMembers | Where-Object {$_} | ForEach-Object { | |
if ( ($TargetSIDs[0] -eq '*') -or ($TargetSIDs -Contains $_) ) { | |
$GPOgroup | |
} | |
} | |
} | |
# if the group is a 'memberof' the group we're looking for, check GroupSID against the targt SIDs | |
if( ($GPOgroup.GroupMemberOf -contains $TargetLocalSID) ) { | |
if( ($TargetSIDs[0] -eq '*') -or ($TargetSIDs -Contains $GPOgroup.GroupSID) ) { | |
$GPOgroup | |
} | |
} | |
} | Sort-Object -Property GPOName -Unique | |
$GPOgroups | ForEach-Object { | |
$GPOname = $_.GPODisplayName | |
$GPOguid = $_.GPOName | |
$GPOPath = $_.GPOPath | |
$GPOType = $_.GPOType | |
if($_.GroupMembers) { | |
$GPOMembers = $_.GroupMembers | |
} | |
else { | |
$GPOMembers = $_.GroupSID | |
} | |
$Filters = $_.Filters | |
if(-not $TargetObject) { | |
# if the * wildcard was used, set the ObjectDistName as the GPO member SID set | |
# so all relationship mappings are output | |
$TargetObjectSIDs = $GPOMembers | |
} | |
else { | |
$TargetObjectSIDs = $TargetObject | |
} | |
# find any OUs that have this GUID applied and then retrieve any computers from the OU | |
Get-ThisThingOU -Domain $Domain -DomainController $DomainController -GUID $GPOguid -FullData -PageSize $PageSize | ForEach-Object { | |
if($Filters) { | |
# filter for computer name/org unit if a filter is specified | |
# TODO: handle other filters (i.e. OU filters?) again, I hate you GPP... | |
$OUComputers = Get-ThisThingComputer -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $_.ADSpath -FullData -PageSize $PageSize | Where-Object { | |
$_.adspath -match ($Filters.Value) | |
} | ForEach-Object { $_.dnshostname } | |
} | |
else { | |
$OUComputers = Get-ThisThingComputer -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $_.ADSpath -PageSize $PageSize | |
} | |
if($OUComputers) { | |
if($OUComputers -isnot [System.Array]) {$OUComputers = @($OUComputers)} | |
ForEach ($TargetSid in $TargetObjectSIDs) { | |
$Object = Get-ADObject -SID $TargetSid -Domain $Domain -DomainController $DomainController -Credential $Credential -PageSize $PageSize | |
$IsGroup = @('268435456','268435457','536870912','536870913') -contains $Object.samaccounttype | |
$GPOLocation = New-Object PSObject | |
$GPOLocation | Add-Member Noteproperty 'ObjectName' $Object.samaccountname | |
$GPOLocation | Add-Member Noteproperty 'ObjectDN' $Object.distinguishedname | |
$GPOLocation | Add-Member Noteproperty 'ObjectSID' $Object.objectsid | |
$GPOLocation | Add-Member Noteproperty 'Domain' $Domain | |
$GPOLocation | Add-Member Noteproperty 'IsGroup' $IsGroup | |
$GPOLocation | Add-Member Noteproperty 'GPODisplayName' $GPOname | |
$GPOLocation | Add-Member Noteproperty 'GPOGuid' $GPOGuid | |
$GPOLocation | Add-Member Noteproperty 'GPOPath' $GPOPath | |
$GPOLocation | Add-Member Noteproperty 'GPOType' $GPOType | |
$GPOLocation | Add-Member Noteproperty 'ContainerName' $_.distinguishedname | |
$GPOLocation | Add-Member Noteproperty 'ComputerName' $OUComputers | |
$GPOLocation.PSObject.TypeNames.Add('PowerView.GPOLocalGroup') | |
$GPOLocation | |
} | |
} | |
} | |
# find any sites that have this GUID applied | |
Get-ThisThingSite -Domain $Domain -DomainController $DomainController -GUID $GPOguid -PageSize $PageSize -FullData | ForEach-Object { | |
ForEach ($TargetSid in $TargetObjectSIDs) { | |
$Object = Get-ADObject -SID $TargetSid -Domain $Domain -DomainController $DomainController -Credential $Credential -PageSize $PageSize | |
$IsGroup = @('268435456','268435457','536870912','536870913') -contains $Object.samaccounttype | |
$AppliedSite = New-Object PSObject | |
$AppliedSite | Add-Member Noteproperty 'ObjectName' $Object.samaccountname | |
$AppliedSite | Add-Member Noteproperty 'ObjectDN' $Object.distinguishedname | |
$AppliedSite | Add-Member Noteproperty 'ObjectSID' $Object.objectsid | |
$AppliedSite | Add-Member Noteproperty 'IsGroup' $IsGroup | |
$AppliedSite | Add-Member Noteproperty 'Domain' $Domain | |
$AppliedSite | Add-Member Noteproperty 'GPODisplayName' $GPOname | |
$AppliedSite | Add-Member Noteproperty 'GPOGuid' $GPOGuid | |
$AppliedSite | Add-Member Noteproperty 'GPOPath' $GPOPath | |
$AppliedSite | Add-Member Noteproperty 'GPOType' $GPOType | |
$AppliedSite | Add-Member Noteproperty 'ContainerName' $_.distinguishedname | |
$AppliedSite | Add-Member Noteproperty 'ComputerName' $_.siteobjectbl | |
$AppliedSite.PSObject.TypeNames.Add('PowerView.GPOLocalGroup') | |
$AppliedSite | |
} | |
} | |
} | |
} | |
function Find-GPOComputerAdmin { | |
[CmdletBinding()] | |
Param ( | |
[Parameter(ValueFromPipeline=$True)] | |
[String] | |
$ComputerName, | |
[String] | |
$OUName, | |
[String] | |
$Domain, | |
[String] | |
$DomainController, | |
[Switch] | |
$Recurse, | |
[String] | |
$LocalGroup = 'Administrators', | |
[Switch] | |
$UsePSDrive, | |
[ValidateRange(1,10000)] | |
[Int] | |
$PageSize = 200 | |
) | |
process { | |
if(!$ComputerName -and !$OUName) { | |
Throw "-ComputerName or -OUName must be provided" | |
} | |
$GPOGroups = @() | |
if($ComputerName) { | |
$Computers = Get-ThisThingComputer -ComputerName $ComputerName -Domain $Domain -DomainController $DomainController -FullData -PageSize $PageSize | |
if(!$Computers) { | |
throw "Computer $ComputerName in domain '$Domain' not found! Try a fully qualified host name" | |
} | |
$TargetOUs = @() | |
ForEach($Computer in $Computers) { | |
# extract all OUs a computer is a part of | |
$DN = $Computer.distinguishedname | |
$TargetOUs += $DN.split(",") | ForEach-Object { | |
if($_.startswith("OU=")) { | |
$DN.substring($DN.indexof($_)) | |
} | |
} | |
} | |
# enumerate any linked GPOs for the computer's site | |
$ComputerSite = (Get-SiteName -ComputerName $ComputerName).SiteName | |
if($ComputerSite -and ($ComputerSite -notlike 'Error*')) { | |
$GPOGroups += Get-ThisThingSite -SiteName $ComputerSite -FullData | ForEach-Object { | |
if($_.gplink) { | |
$_.gplink.split("][") | ForEach-Object { | |
if ($_.startswith("LDAP")) { | |
$_.split(";")[0] | |
} | |
} | |
} | |
} | ForEach-Object { | |
$GPOGroupArgs = @{ | |
'Domain' = $Domain | |
'DomainController' = $DomainController | |
'ResolveMemberSIDs' = $True | |
'UsePSDrive' = $UsePSDrive | |
'PageSize' = $PageSize | |
} | |
# for each GPO link, get any locally set user/group SIDs | |
Get-ThisThingGPOGroup @GPOGroupArgs | |
} | |
} | |
} | |
else { | |
$TargetOUs = @($OUName) | |
} | |
Write-Verbose "Target OUs: $TargetOUs" | |
$TargetOUs | Where-Object {$_} | ForEach-Object { | |
$GPOLinks = Get-ThisThingOU -Domain $Domain -DomainController $DomainController -ADSpath $_ -FullData -PageSize $PageSize | ForEach-Object { | |
# and then get any GPO links | |
if($_.gplink) { | |
$_.gplink.split("][") | ForEach-Object { | |
if ($_.startswith("LDAP")) { | |
$_.split(";")[0] | |
} | |
} | |
} | |
} | |
$GPOGroupArgs = @{ | |
'Domain' = $Domain | |
'DomainController' = $DomainController | |
'UsePSDrive' = $UsePSDrive | |
'ResolveMemberSIDs' = $True | |
'PageSize' = $PageSize | |
} | |
# extract GPO groups that are set through any gPlink for this OU | |
$GPOGroups += Get-ThisThingGPOGroup @GPOGroupArgs | ForEach-Object { | |
ForEach($GPOLink in $GPOLinks) { | |
$Name = $_.GPOName | |
if($GPOLink -like "*$Name*") { | |
$_ | |
} | |
} | |
} | |
} | |
# for each found GPO group, resolve the SIDs of the members | |
$GPOgroups | Sort-Object -Property GPOName -Unique | ForEach-Object { | |
$GPOGroup = $_ | |
if($GPOGroup.GroupMembers) { | |
$GPOMembers = $GPOGroup.GroupMembers | |
} | |
else { | |
$GPOMembers = $GPOGroup.GroupSID | |
} | |
$GPOMembers | ForEach-Object { | |
# resolve this SID to a domain object | |
$Object = Get-ADObject -Domain $Domain -DomainController $DomainController -PageSize $PageSize -SID $_ | |
$IsGroup = @('268435456','268435457','536870912','536870913') -contains $Object.samaccounttype | |
$GPOComputerAdmin = New-Object PSObject | |
$GPOComputerAdmin | Add-Member Noteproperty 'ComputerName' $ComputerName | |
$GPOComputerAdmin | Add-Member Noteproperty 'ObjectName' $Object.samaccountname | |
$GPOComputerAdmin | Add-Member Noteproperty 'ObjectDN' $Object.distinguishedname | |
$GPOComputerAdmin | Add-Member Noteproperty 'ObjectSID' $_ | |
$GPOComputerAdmin | Add-Member Noteproperty 'IsGroup' $IsGroup | |
$GPOComputerAdmin | Add-Member Noteproperty 'GPODisplayName' $GPOGroup.GPODisplayName | |
$GPOComputerAdmin | Add-Member Noteproperty 'GPOGuid' $GPOGroup.GPOName | |
$GPOComputerAdmin | Add-Member Noteproperty 'GPOPath' $GPOGroup.GPOPath | |
$GPOComputerAdmin | Add-Member Noteproperty 'GPOType' $GPOGroup.GPOType | |
$GPOComputerAdmin | |
# if we're recursing and the current result object is a group | |
if($Recurse -and $GPOComputerAdmin.isGroup) { | |
Get-ThisThingGroupMember -Domain $Domain -DomainController $DomainController -SID $_ -FullData -Recurse -PageSize $PageSize | ForEach-Object { | |
$MemberDN = $_.distinguishedName | |
# extract the FQDN from the Distinguished Name | |
$MemberDomain = $MemberDN.subString($MemberDN.IndexOf("DC=")) -replace 'DC=','' -replace ',','.' | |
$MemberIsGroup = @('268435456','268435457','536870912','536870913') -contains $_.samaccounttype | |
if ($_.samAccountName) { | |
# forest users have the samAccountName set | |
$MemberName = $_.samAccountName | |
} | |
else { | |
# external trust users have a SID, so convert it | |
try { | |
$MemberName = Convert-SidToName $_.cn | |
} | |
catch { | |
# if there's a problem contacting the domain to resolve the SID | |
$MemberName = $_.cn | |
} | |
} | |
$GPOComputerAdmin = New-Object PSObject | |
$GPOComputerAdmin | Add-Member Noteproperty 'ComputerName' $ComputerName | |
$GPOComputerAdmin | Add-Member Noteproperty 'ObjectName' $MemberName | |
$GPOComputerAdmin | Add-Member Noteproperty 'ObjectDN' $MemberDN | |
$GPOComputerAdmin | Add-Member Noteproperty 'ObjectSID' $_.objectsid | |
$GPOComputerAdmin | Add-Member Noteproperty 'IsGroup' $MemberIsGrou | |
$GPOComputerAdmin | Add-Member Noteproperty 'GPODisplayName' $GPOGroup.GPODisplayName | |
$GPOComputerAdmin | Add-Member Noteproperty 'GPOGuid' $GPOGroup.GPOName | |
$GPOComputerAdmin | Add-Member Noteproperty 'GPOPath' $GPOGroup.GPOPath | |
$GPOComputerAdmin | Add-Member Noteproperty 'GPOType' $GPOTypep | |
$GPOComputerAdmin | |
} | |
} | |
} | |
} | |
} | |
} | |
function Get-DomainPolicy { | |
[CmdletBinding()] | |
Param ( | |
[String] | |
[ValidateSet("Domain","DC")] | |
$Source ="Domain", | |
[String] | |
$Domain, | |
[String] | |
$DomainController, | |
[Switch] | |
$ResolveSids, | |
[Switch] | |
$UsePSDrive | |
) | |
if($Source -eq "Domain") { | |
# query the given domain for the default domain policy object | |
$GPO = Get-ThisThingGPO -Domain $Domain -DomainController $DomainController -GPOname "{31B2F340-016D-11D2-945F-00C04FB984F9}" | |
if($GPO) { | |
# grab the GptTmpl.inf file and parse it | |
$GptTmplPath = $GPO.gpcfilesyspath + "\MACHINE\Microsoft\Windows NT\SecEdit\GptTmpl.inf" | |
$ParseArgs = @{ | |
'GptTmplPath' = $GptTmplPath | |
'UsePSDrive' = $UsePSDrive | |
} | |
# parse the GptTmpl.inf | |
Get-GptTmpl @ParseArgs | |
} | |
} | |
elseif($Source -eq "DC") { | |
# query the given domain/dc for the default domain controller policy object | |
$GPO = Get-ThisThingGPO -Domain $Domain -DomainController $DomainController -GPOname "{6AC1786C-016F-11D2-945F-00C04FB984F9}" | |
if($GPO) { | |
# grab the GptTmpl.inf file and parse it | |
$GptTmplPath = $GPO.gpcfilesyspath + "\MACHINE\Microsoft\Windows NT\SecEdit\GptTmpl.inf" | |
$ParseArgs = @{ | |
'GptTmplPath' = $GptTmplPath | |
'UsePSDrive' = $UsePSDrive | |
} | |
# parse the GptTmpl.inf | |
Get-GptTmpl @ParseArgs | ForEach-Object { | |
if($ResolveSids) { | |
# if we're resolving sids in PrivilegeRights to names | |
$Policy = New-Object PSObject | |
$_.psobject.properties | ForEach-Object { | |
if( $_.Name -eq 'PrivilegeRights') { | |
$PrivilegeRights = New-Object PSObject | |
# for every nested SID member of PrivilegeRights, try to unpack everything and resolve the SIDs as appropriate | |
$_.Value.psobject.properties | ForEach-Object { | |
$Sids = $_.Value | ForEach-Object { | |
try { | |
if($_ -isnot [System.Array]) { | |
Convert-SidToName $_ | |
} | |
else { | |
$_ | ForEach-Object { Convert-SidToName $_ } | |
} | |
} | |
catch { | |
Write-Verbose "Error resolving SID : $_" | |
} | |
} | |
$PrivilegeRights | Add-Member Noteproperty $_.Name $Sids | |
} | |
$Policy | Add-Member Noteproperty 'PrivilegeRights' $PrivilegeRights | |
} | |
else { | |
$Policy | Add-Member Noteproperty $_.Name $_.Value | |
} | |
} | |
$Policy | |
} | |
else { $_ } | |
} | |
} | |
} | |
} | |
function Get-ThisThingLocalGroup { | |
[CmdletBinding(DefaultParameterSetName = 'WinNT')] | |
param( | |
[Parameter(ParameterSetName = 'API', Position=0, ValueFromPipeline=$True)] | |
[Parameter(ParameterSetName = 'WinNT', Position=0, ValueFromPipeline=$True)] | |
[Alias('HostName')] | |
[String[]] | |
$ComputerName = $Env:ComputerName, | |
[Parameter(ParameterSetName = 'WinNT')] | |
[Parameter(ParameterSetName = 'API')] | |
[ValidateScript({Test-Path -Path $_ })] | |
[Alias('HostList')] | |
[String] | |
$ComputerFile, | |
[Parameter(ParameterSetName = 'WinNT')] | |
[Parameter(ParameterSetName = 'API')] | |
[String] | |
$GroupName = 'Administrators', | |
[Parameter(ParameterSetName = 'WinNT')] | |
[Switch] | |
$ListGroups, | |
[Parameter(ParameterSetName = 'WinNT')] | |
[Switch] | |
$Recurse, | |
[Parameter(ParameterSetName = 'API')] | |
[Switch] | |
$API | |
) | |
process { | |
$Servers = @() | |
# if we have a host list passed, grab it | |
if($ComputerFile) { | |
$Servers = Get-Content -Path $ComputerFile | |
} | |
else { | |
# otherwise assume a single host name | |
$Servers += $ComputerName | Get-NameField | |
} | |
# query the specified group using the WINNT provider, and | |
# extract fields as appropriate from the results | |
ForEach($Server in $Servers) { | |
if($API) { | |
# if we're using the Netapi32 NetLocalGroupGetMembers API call to get the local group information | |
# arguments for NetLocalGroupGetMembers | |
$QueryLevel = 2 | |
$PtrInfo = [IntPtr]::Zero | |
$EntriesRead = 0 | |
$TotalRead = 0 | |
$ResumeHandle = 0 | |
# get the local user information | |
$Result = $Netapi32::NetLocalGroupGetMembers($Server, $GroupName, $QueryLevel, [ref]$PtrInfo, -1, [ref]$EntriesRead, [ref]$TotalRead, [ref]$ResumeHandle) | |
# Locate the offset of the initial intPtr | |
$Offset = $PtrInfo.ToInt64() | |
$LocalUsers = @() | |
# 0 = success | |
if (($Result -eq 0) -and ($Offset -gt 0)) { | |
# Work out how much to increment the pointer by finding out the size of the structure | |
$Increment = $LOCALGROUP_MEMBERS_INFO_2::GetSize() | |
# parse all the result structures | |
for ($i = 0; ($i -lt $EntriesRead); $i++) { | |
# create a new int ptr at the given offset and cast the pointer as our result structure | |
$NewIntPtr = New-Object System.Intptr -ArgumentList $Offset | |
$Info = $NewIntPtr -as $LOCALGROUP_MEMBERS_INFO_2 | |
$Offset = $NewIntPtr.ToInt64() | |
$Offset += $Increment | |
$SidString = "" | |
$Result2 = $Advapi32::ConvertSidToStringSid($Info.lgrmi2_sid, [ref]$SidString);$LastError = [Runtime.InteropServices.Marshal]::GetLastWin32Error() | |
if($Result2 -eq 0) { | |
Write-Verbose "Error: $(([ComponentModel.Win32Exception] $LastError).Message)" | |
} | |
else { | |
$LocalUser = New-Object PSObject | |
$LocalUser | Add-Member Noteproperty 'ComputerName' $Server | |
$LocalUser | Add-Member Noteproperty 'AccountName' $Info.lgrmi2_domainandname | |
$LocalUser | Add-Member Noteproperty 'SID' $SidString | |
$IsGroup = $($Info.lgrmi2_sidusage -eq 'SidTypeGroup') | |
$LocalUser | Add-Member Noteproperty 'IsGroup' $IsGroup | |
$LocalUser.PSObject.TypeNames.Add('PowerView.LocalUserAPI') | |
$LocalUsers += $LocalUser | |
} | |
} | |
# free up the result buffer | |
$Null = $Netapi32::NetApiBufferFree($PtrInfo) | |
# try to extract out the machine SID by using the -500 account as a reference | |
$MachineSid = $LocalUsers | Where-Object {$_.SID -like '*-500'} | |
$Parts = $MachineSid.SID.Split('-') | |
$MachineSid = $Parts[0..($Parts.Length -2)] -join '-' | |
$LocalUsers | ForEach-Object { | |
if($_.SID -match $MachineSid) { | |
$_ | Add-Member Noteproperty 'IsDomain' $False | |
} | |
else { | |
$_ | Add-Member Noteproperty 'IsDomain' $True | |
} | |
} | |
$LocalUsers | |
} | |
else { | |
Write-Verbose "Error: $(([ComponentModel.Win32Exception] $Result).Message)" | |
} | |
} | |
else { | |
# otherwise we're using the WinNT service provider | |
try { | |
if($ListGroups) { | |
# if we're listing the group names on a remote server | |
$Computer = [ADSI]"WinNT://$Server,computer" | |
$Computer.psbase.children | Where-Object { $_.psbase.schemaClassName -eq 'group' } | ForEach-Object { | |
$Group = New-Object PSObject | |
$Group | Add-Member Noteproperty 'Server' $Server | |
$Group | Add-Member Noteproperty 'Group' ($_.name[0]) | |
$Group | Add-Member Noteproperty 'SID' ((New-Object System.Security.Principal.SecurityIdentifier $_.objectsid[0],0).Value) | |
$Group | Add-Member Noteproperty 'Description' ($_.Description[0]) | |
$Group.PSObject.TypeNames.Add('PowerView.LocalGroup') | |
$Group | |
} | |
} | |
else { | |
# otherwise we're listing the group members | |
$Members = @($([ADSI]"WinNT://$Server/$GroupName,group").psbase.Invoke('Members')) | |
$Members | ForEach-Object { | |
$Member = New-Object PSObject | |
$Member | Add-Member Noteproperty 'ComputerName' $Server | |
$AdsPath = ($_.GetType().InvokeMember('Adspath', 'GetProperty', $Null, $_, $Null)).Replace('WinNT://', '') | |
$Class = $_.GetType().InvokeMember('Class', 'GetProperty', $Null, $_, $Null) | |
# try to translate the NT4 domain to a FQDN if possible | |
$Name = Convert-ADName -ObjectName $AdsPath -InputType 'NT4' -OutputType 'Canonical' | |
$IsGroup = $Class -eq "Group" | |
if($Name) { | |
$FQDN = $Name.split("/")[0] | |
$ObjName = $AdsPath.split("/")[-1] | |
$Name = "$FQDN/$ObjName" | |
$IsDomain = $True | |
} | |
else { | |
$ObjName = $AdsPath.split("/")[-1] | |
$Name = $AdsPath | |
$IsDomain = $False | |
} | |
$Member | Add-Member Noteproperty 'AccountName' $Name | |
$Member | Add-Member Noteproperty 'IsDomain' $IsDomain | |
$Member | Add-Member Noteproperty 'IsGroup' $IsGroup | |
if($IsDomain) { | |
# translate the binary sid to a string | |
$Member | Add-Member Noteproperty 'SID' ((New-Object System.Security.Principal.SecurityIdentifier($_.GetType().InvokeMember('ObjectSID', 'GetProperty', $Null, $_, $Null),0)).Value) | |
$Member | Add-Member Noteproperty 'Description' "" | |
$Member | Add-Member Noteproperty 'Disabled' "" | |
if($IsGroup) { | |
$Member | Add-Member Noteproperty 'LastLogin' "" | |
} | |
else { | |
try { | |
$Member | Add-Member Noteproperty 'LastLogin' ( $_.GetType().InvokeMember('LastLogin', 'GetProperty', $Null, $_, $Null)) | |
} | |
catch { | |
$Member | Add-Member Noteproperty 'LastLogin' "" | |
} | |
} | |
$Member | Add-Member Noteproperty 'PwdLastSet' "" | |
$Member | Add-Member Noteproperty 'PwdExpired' "" | |
$Member | Add-Member Noteproperty 'UserFlags' "" | |
} | |
else { | |
# repull this user object so we can ensure correct information | |
$LocalUser = $([ADSI] "WinNT://$AdsPath") | |
# translate the binary sid to a string | |
$Member | Add-Member Noteproperty 'SID' ((New-Object System.Security.Principal.SecurityIdentifier($LocalUser.objectSid.value,0)).Value) | |
$Member | Add-Member Noteproperty 'Description' ($LocalUser.Description[0]) | |
if($IsGroup) { | |
$Member | Add-Member Noteproperty 'PwdLastSet' "" | |
$Member | Add-Member Noteproperty 'PwdExpired' "" | |
$Member | Add-Member Noteproperty 'UserFlags' "" | |
$Member | Add-Member Noteproperty 'Disabled' "" | |
$Member | Add-Member Noteproperty 'LastLogin' "" | |
} | |
else { | |
$Member | Add-Member Noteproperty 'PwdLastSet' ( (Get-Date).AddSeconds(-$LocalUser.PasswordAge[0])) | |
$Member | Add-Member Noteproperty 'PwdExpired' ( $LocalUser.PasswordExpired[0] -eq '1') | |
$Member | Add-Member Noteproperty 'UserFlags' ( $LocalUser.UserFlags[0] ) | |
# UAC flags of 0x2 mean the account is disabled | |
$Member | Add-Member Noteproperty 'Disabled' $(($LocalUser.userFlags.value -band 2) -eq 2) | |
try { | |
$Member | Add-Member Noteproperty 'LastLogin' ( $LocalUser.LastLogin[0]) | |
} | |
catch { | |
$Member | Add-Member Noteproperty 'LastLogin' "" | |
} | |
} | |
} | |
$Member.PSObject.TypeNames.Add('PowerView.LocalUser') | |
$Member | |
# if the result is a group domain object and we're recursing, | |
# try to resolve all the group member results | |
if($Recurse -and $IsGroup) { | |
if($IsDomain) { | |
$FQDN = $Name.split("/")[0] | |
$GroupName = $Name.split("/")[1].trim() | |
Get-ThisThingGroupMember -GroupName $GroupName -Domain $FQDN -FullData -Recurse | ForEach-Object { | |
$Member = New-Object PSObject | |
$Member | Add-Member Noteproperty 'ComputerName' "$FQDN/$($_.GroupName)" | |
$MemberDN = $_.distinguishedName | |
# extract the FQDN from the Distinguished Name | |
$MemberDomain = $MemberDN.subString($MemberDN.IndexOf("DC=")) -replace 'DC=','' -replace ',','.' | |
$MemberIsGroup = @('268435456','268435457','536870912','536870913') -contains $_.samaccounttype | |
if ($_.samAccountName) { | |
# forest users have the samAccountName set | |
$MemberName = $_.samAccountName | |
} | |
else { | |
try { | |
# external trust users have a SID, so convert it | |
try { | |
$MemberName = Convert-SidToName $_.cn | |
} | |
catch { | |
# if there's a problem contacting the domain to resolve the SID | |
$MemberName = $_.cn | |
} | |
} | |
catch { | |
Write-Debug "Error resolving SID : $_" | |
} | |
} | |
$Member | Add-Member Noteproperty 'AccountName' "$MemberDomain/$MemberName" | |
$Member | Add-Member Noteproperty 'SID' $_.objectsid | |
$Member | Add-Member Noteproperty 'Description' $_.description | |
$Member | Add-Member Noteproperty 'Disabled' $False | |
$Member | Add-Member Noteproperty 'IsGroup' $MemberIsGroup | |
$Member | Add-Member Noteproperty 'IsDomain' $True | |
$Member | Add-Member Noteproperty 'LastLogin' '' | |
$Member | Add-Member Noteproperty 'PwdLastSet' $_.pwdLastSet | |
$Member | Add-Member Noteproperty 'PwdExpired' '' | |
$Member | Add-Member Noteproperty 'UserFlags' $_.userAccountControl | |
$Member.PSObject.TypeNames.Add('PowerView.LocalUser') | |
$Member | |
} | |
} else { | |
Get-ThisThingLocalGroup -ComputerName $Server -GroupName $ObjName -Recurse | |
} | |
} | |
} | |
} | |
} | |
catch { | |
Write-Warning "[!] Error: $_" | |
} | |
} | |
} | |
} | |
} | |
filter Get-MySMBShare { | |
[CmdletBinding()] | |
param( | |
[Parameter(ValueFromPipeline=$True)] | |
[Alias('HostName')] | |
[Object[]] | |
[ValidateNotNullOrEmpty()] | |
$ComputerName = 'localhost' | |
) | |
# extract the computer name from whatever object was passed on the pipeline | |
$Computer = $ComputerName | Get-NameField | |
# arguments for NetShareEnum | |
$QueryLevel = 1 | |
$PtrInfo = [IntPtr]::Zero | |
$EntriesRead = 0 | |
$TotalRead = 0 | |
$ResumeHandle = 0 | |
# get the share information | |
$Result = $Netapi32::NetShareEnum($Computer, $QueryLevel, [ref]$PtrInfo, -1, [ref]$EntriesRead, [ref]$TotalRead, [ref]$ResumeHandle) | |
# Locate the offset of the initial intPtr | |
$Offset = $PtrInfo.ToInt64() | |
# 0 = success | |
if (($Result -eq 0) -and ($Offset -gt 0)) { | |
# Work out how much to increment the pointer by finding out the size of the structure | |
$Increment = $SHARE_INFO_1::GetSize() | |
# parse all the result structures | |
for ($i = 0; ($i -lt $EntriesRead); $i++) { | |
# create a new int ptr at the given offset and cast the pointer as our result structure | |
$NewIntPtr = New-Object System.Intptr -ArgumentList $Offset | |
$Info = $NewIntPtr -as $SHARE_INFO_1 | |
# return all the sections of the structure | |
$Shares = $Info | Select-Object * | |
$Shares | Add-Member Noteproperty 'ComputerName' $Computer | |
$Offset = $NewIntPtr.ToInt64() | |
$Offset += $Increment | |
$ShareObject = New-Object -TypeName PSObject | |
$ShareObject | Add-Member NoteProperty "ComputerName" $Shares.ComputerName | |
$ShareObject | Add-Member NoteProperty "ShareName" $Shares.shi1_netname | |
$ShareObject | Add-Member NoteProperty "ShareDesc" $Shares.shi1_remark | |
$ShareObject | Add-Member NoteProperty "Sharetype" $Shares.shi1_type | |
$ComputerName = $Shares.ComputerName | |
$ShareName = $Shares.shi1_netname | |
$ShareType = $Shares.shi1_type | |
$ShareDesc = $Shares.shi1_remark | |
# Check access | |
try{ | |
$TargetPath = "\\$ComputerName\$ShareName" | |
$Null = [IO.Directory]::GetFiles($TargetPath) | |
Write-Verbose "$Computer : ACCESSIBLE! - Share: \\$computerName\$ShareName Desc: $ShareDesc Type:$ShareType" | |
$ShareObject | Add-Member NoteProperty "ShareAccess" "Yes" | |
$ShareObject | |
}catch{ | |
Write-Verbose "$Computer : NOT ACCESSIBLE - Share: \\$computerName\$ShareName Desc: $ShareDesc Type:$ShareType" | |
$ShareObject | Add-Member NoteProperty "ShareAccess" "No" | |
$ShareObject | |
} | |
} | |
# free up the result buffer | |
$Null = $Netapi32::NetApiBufferFree($PtrInfo) | |
} | |
else { | |
Write-Verbose "Error: $(([ComponentModel.Win32Exception] $Result).Message)" | |
} | |
} | |
function Find-InterestingFile { | |
<# | |
.SYNOPSIS | |
This function recursively searches a given UNC path for files with | |
specific keywords in the name (default of pass, sensitive, secret, admin, | |
login and unattend*.xml). The output can be piped out to a csv with the | |
-OutFile flag. By default, hidden files/folders are included in search results. | |
.PARAMETER Path | |
UNC/local path to recursively search. | |
.PARAMETER Terms | |
Terms to search for. | |
.PARAMETER OfficeDocs | |
Switch. Search for office documents (*.doc*, *.xls*, *.ppt*) | |
.PARAMETER FreshEXEs | |
Switch. Find .EXEs accessed within the last week. | |
.PARAMETER LastAccessTime | |
Only return files with a LastAccessTime greater than this date value. | |
.PARAMETER LastWriteTime | |
Only return files with a LastWriteTime greater than this date value. | |
.PARAMETER CreationTime | |
Only return files with a CreationTime greater than this date value. | |
.PARAMETER ExcludeFolders | |
Switch. Exclude folders from the search results. | |
.PARAMETER ExcludeHidden | |
Switch. Exclude hidden files and folders from the search results. | |
.PARAMETER CheckWriteAccess | |
Switch. Only returns files the current user has write access to. | |
.PARAMETER OutFile | |
Output results to a specified csv output file. | |
.PARAMETER UsePSDrive | |
Switch. Mount target remote path with temporary PSDrives. | |
.OUTPUTS | |
The full path, owner, lastaccess time, lastwrite time, and size for each found file. | |
.EXAMPLE | |
PS C:\> Find-InterestingFile -Path C:\Backup\ | |
Returns any files on the local path C:\Backup\ that have the default | |
search term set in the title. | |
.EXAMPLE | |
PS C:\> Find-InterestingFile -Path \\WINDOWS7\Users\ -Terms salaries,email -OutFile out.csv | |
Returns any files on the remote path \\WINDOWS7\Users\ that have 'salaries' | |
or 'email' in the title, and writes the results out to a csv file | |
named 'out.csv' | |
.EXAMPLE | |
PS C:\> Find-InterestingFile -Path \\WINDOWS7\Users\ -LastAccessTime (Get-Date).AddDays(-7) | |
Returns any files on the remote path \\WINDOWS7\Users\ that have the default | |
search term set in the title and were accessed within the last week. | |
.LINK | |
http://www.harmj0y.net/blog/redteaming/file-server-triage-on-red-team-engagements/ | |
#> | |
param( | |
[Parameter(ValueFromPipeline=$True)] | |
[String] | |
$Path = '.\', | |
[Alias('Terms')] | |
[String[]] | |
$SearchTerms = @('pass', 'sensitive', 'admin', 'login', 'secret', 'unattend*.xml', '.vmdk', 'creds', 'credential', '.config'), | |
[Switch] | |
$OfficeDocs, | |
[Switch] | |
$FreshEXEs, | |
[String] | |
$LastAccessTime, | |
[String] | |
$LastWriteTime, | |
[String] | |
$CreationTime, | |
[Switch] | |
$ExcludeFolders, | |
[Switch] | |
$ExcludeHidden, | |
[Switch] | |
$CheckWriteAccess, | |
[String] | |
$OutFile, | |
[Switch] | |
$UsePSDrive | |
) | |
begin { | |
$Path += if(!$Path.EndsWith('\')) {"\"} | |
if ($Credential) { | |
$UsePSDrive = $True | |
} | |
# append wildcards to the front and back of all search terms | |
$SearchTerms = $SearchTerms | ForEach-Object { if($_ -notmatch '^\*.*\*$') {"*$($_)*"} else{$_} } | |
# search just for office documents if specified | |
if ($OfficeDocs) { | |
$SearchTerms = @('*.doc', '*.docx', '*.xls', '*.xlsx', '*.ppt', '*.pptx') | |
} | |
# find .exe's accessed within the last 7 days | |
if($FreshEXEs) { | |
# get an access time limit of 7 days ago | |
$LastAccessTime = (Get-Date).AddDays(-7).ToString('MM/dd/yyyy') | |
$SearchTerms = '*.exe' | |
} | |
if($UsePSDrive) { | |
# if we're PSDrives, create a temporary mount point | |
$Parts = $Path.split('\') | |
$FolderPath = $Parts[0..($Parts.length-2)] -join '\' | |
$FilePath = $Parts[-1] | |
$RandDrive = ("abcdefghijklmnopqrstuvwxyz".ToCharArray() | Get-Random -Count 7) -join '' | |
Write-Verbose "Mounting path '$Path' using a temp PSDrive at $RandDrive" | |
try { | |
$Null = New-PSDrive -Name $RandDrive -PSProvider FileSystem -Root $FolderPath -ErrorAction Stop | |
} | |
catch { | |
Write-Verbose "Error mounting path '$Path' : $_" | |
return $Null | |
} | |
# so we can cd/dir the new drive | |
$Path = "${RandDrive}:\${FilePath}" | |
} | |
} | |
process { | |
Write-Verbose "[*] Search path $Path" | |
function Invoke-CheckWrite { | |
# short helper to check is the current user can write to a file | |
[CmdletBinding()]param([String]$Path) | |
try { | |
$Filetest = [IO.FILE]::OpenWrite($Path) | |
$Filetest.Close() | |
$True | |
} | |
catch { | |
Write-Verbose -Message $Error[0] | |
$False | |
} | |
} | |
$SearchArgs = @{ | |
'Path' = $Path | |
'Recurse' = $True | |
'Force' = $(-not $ExcludeHidden) | |
'Include' = $SearchTerms | |
'ErrorAction' = 'SilentlyContinue' | |
} | |
Get-ChildItem @SearchArgs | ForEach-Object { | |
Write-Verbose $_ | |
# check if we're excluding folders | |
if(!$ExcludeFolders -or !$_.PSIsContainer) {$_} | |
} | ForEach-Object { | |
if($LastAccessTime -or $LastWriteTime -or $CreationTime) { | |
if($LastAccessTime -and ($_.LastAccessTime -gt $LastAccessTime)) {$_} | |
elseif($LastWriteTime -and ($_.LastWriteTime -gt $LastWriteTime)) {$_} | |
elseif($CreationTime -and ($_.CreationTime -gt $CreationTime)) {$_} | |
} | |
else {$_} | |
} | ForEach-Object { | |
# filter for write access (if applicable) | |
if((-not $CheckWriteAccess) -or (Invoke-CheckWrite -Path $_.FullName)) {$_} | |
} | Select-Object FullName,@{Name='Owner';Expression={(Get-Acl $_.FullName).Owner}},LastAccessTime,LastWriteTime,CreationTime,Length | ForEach-Object { | |
# check if we're outputting to the pipeline or an output file | |
if($OutFile) {Export-PowerViewCSV -InputObject $_ -OutFile $OutFile} | |
else {$_} | |
} | |
} | |
end { | |
if($UsePSDrive -and $RandDrive) { | |
Write-Verbose "Removing temp PSDrive $RandDrive" | |
Get-PSDrive -Name $RandDrive -ErrorAction SilentlyContinue | Remove-PSDrive -Force | |
} | |
} | |
} | |
function Invoke-ThreadedFunction { | |
# Helper used by any threaded host enumeration functions | |
[CmdletBinding()] | |
param( | |
[Parameter(Position=0,Mandatory=$True)] | |
[String[]] | |
$ComputerName, | |
[Parameter(Position=1,Mandatory=$True)] | |
[System.Management.Automation.ScriptBlock] | |
$ScriptBlock, | |
[Parameter(Position=2)] | |
[Hashtable] | |
$ScriptParameters, | |
[Int] | |
[ValidateRange(1,100)] | |
$Threads = 20, | |
[Switch] | |
$NoImports | |
) | |
begin { | |
if ($PSBoundParameters['Debug']) { | |
$DebugPreference = 'Continue' | |
} | |
Write-Verbose "[*] Total number of hosts: $($ComputerName.count)" | |
# Adapted from: | |
# http://powershell.org/wp/forums/topic/invpke-parallel-need-help-to-clone-the-current-runspace/ | |
$SessionState = [System.Management.Automation.Runspaces.InitialSessionState]::CreateDefault() | |
$SessionState.ApartmentState = [System.Threading.Thread]::CurrentThread.GetApartmentState() | |
# import the current session state's variables and functions so the chained PowerView | |
# functionality can be used by the threaded blocks | |
if(!$NoImports) { | |
# grab all the current variables for this runspace | |
$MyVars = Get-Variable -Scope 2 | |
# these Variables are added by Runspace.Open() Method and produce Stop errors if you add them twice | |
$VorbiddenVars = @("?","args","ConsoleFileName","Error","ExecutionContext","false","HOME","Host","input","InputObject","MaximumAliasCount","MaximumDriveCount","MaximumErrorCount","MaximumFunctionCount","MaximumHistoryCount","MaximumVariableCount","MyInvocation","null","PID","PSBoundParameters","PSCommandPath","PSCulture","PSDefaultParameterValues","PSHOME","PSScriptRoot","PSUICulture","PSVersionTable","PWD","ShellId","SynchronizedHash","true") | |
# Add Variables from Parent Scope (current runspace) into the InitialSessionState | |
ForEach($Var in $MyVars) { | |
if($VorbiddenVars -NotContains $Var.Name) { | |
$SessionState.Variables.Add((New-Object -TypeName System.Management.Automation.Runspaces.SessionStateVariableEntry -ArgumentList $Var.name,$Var.Value,$Var.description,$Var.options,$Var.attributes)) | |
} | |
} | |
# Add Functions from current runspace to the InitialSessionState | |
ForEach($Function in (Get-ChildItem Function:)) { | |
$SessionState.Commands.Add((New-Object -TypeName System.Management.Automation.Runspaces.SessionStateFunctionEntry -ArgumentList $Function.Name, $Function.Definition)) | |
} | |
} | |
# threading adapted from | |
# https://github.com/darkoperator/Posh-SecMod/blob/master/Discovery/Discovery.psm1#L407 | |
# Thanks Carlos! | |
# create a pool of maxThread runspaces | |
$Pool = [runspacefactory]::CreateRunspacePool(1, $Threads, $SessionState, $Host) | |
$Pool.Open() | |
$method = $null | |
ForEach ($m in [PowerShell].GetMethods() | Where-Object { $_.Name -eq "BeginInvoke" }) { | |
$methodParameters = $m.GetParameters() | |
if (($methodParameters.Count -eq 2) -and $methodParameters[0].Name -eq "input" -and $methodParameters[1].Name -eq "output") { | |
$method = $m.MakeGenericMethod([Object], [Object]) | |
break | |
} | |
} | |
$Jobs = @() | |
} | |
process { | |
ForEach ($Computer in $ComputerName) { | |
# make sure we get a server name | |
if ($Computer -ne '') { | |
# Write-Verbose "[*] Enumerating server $Computer ($($Counter+1) of $($ComputerName.count))" | |
While ($($Pool.GetAvailableRunspaces()) -le 0) { | |
Start-Sleep -MilliSeconds 500 | |
} | |
# create a "powershell pipeline runner" | |
$p = [powershell]::create() | |
$p.runspacepool = $Pool | |
# add the script block + arguments | |
$Null = $p.AddScript($ScriptBlock).AddParameter('ComputerName', $Computer) | |
if($ScriptParameters) { | |
ForEach ($Param in $ScriptParameters.GetEnumerator()) { | |
$Null = $p.AddParameter($Param.Name, $Param.Value) | |
} | |
} | |
$o = New-Object Management.Automation.PSDataCollection[Object] | |
$Jobs += @{ | |
PS = $p | |
Output = $o | |
Result = $method.Invoke($p, @($null, [Management.Automation.PSDataCollection[Object]]$o)) | |
} | |
} | |
} | |
} | |
end { | |
Write-Verbose "Waiting for threads to finish..." | |
Do { | |
ForEach ($Job in $Jobs) { | |
$Job.Output.ReadAll() | |
} | |
} While (($Jobs | Where-Object { ! $_.Result.IsCompleted }).Count -gt 0) | |
ForEach ($Job in $Jobs) { | |
$Job.PS.Dispose() | |
} | |
$Pool.Dispose() | |
Write-Verbose "All threads completed!" | |
} | |
} | |
function Invoke-ShareFinder { | |
[CmdletBinding()] | |
param( | |
[Parameter(Position=0,ValueFromPipeline=$True)] | |
[Alias('Hosts')] | |
[String[]] | |
$ComputerName, | |
[ValidateScript({Test-Path -Path $_ })] | |
[Alias('HostList')] | |
[String] | |
$ComputerFile, | |
[String] | |
$ComputerFilter, | |
[String] | |
$ComputerADSpath, | |
[Switch] | |
$ExcludeStandard, | |
[Switch] | |
$ExcludePrint, | |
[Switch] | |
$ExcludeIPC, | |
[Switch] | |
$NoPing, | |
[Switch] | |
$CheckShareAccess, | |
[Switch] | |
$CheckAdmin, | |
[UInt32] | |
$Delay = 0, | |
[Double] | |
$Jitter = .3, | |
[String] | |
$Domain, | |
[String] | |
$DomainController, | |
[Switch] | |
$SearchForest, | |
[ValidateRange(1,100)] | |
[Int] | |
$Threads | |
) | |
begin { | |
if ($PSBoundParameters['Debug']) { | |
$DebugPreference = 'Continue' | |
} | |
# random object for delay | |
$RandNo = New-Object System.Random | |
Write-Verbose "[*] Running Invoke-ShareFinder with delay of $Delay" | |
# figure out the shares we want to ignore | |
[String[]] $ExcludedShares = @('') | |
if ($ExcludePrint) { | |
$ExcludedShares = $ExcludedShares + "PRINT$" | |
} | |
if ($ExcludeIPC) { | |
$ExcludedShares = $ExcludedShares + "IPC$" | |
} | |
if ($ExcludeStandard) { | |
$ExcludedShares = @('', "ADMIN$", "IPC$", "C$", "PRINT$") | |
} | |
# if we're using a host file list, read the targets in and add them to the target list | |
if($ComputerFile) { | |
$ComputerName = Get-Content -Path $ComputerFile | |
} | |
if(!$ComputerName) { | |
[array]$ComputerName = @() | |
if($Domain) { | |
$TargetDomains = @($Domain) | |
} | |
elseif($SearchForest) { | |
# get ALL the domains in the forest to search | |
$TargetDomains = Get-ThisThingForestDomain | ForEach-Object { $_.Name } | |
} | |
else { | |
# use the local domain | |
$TargetDomains = @( (Get-ThisThingDomain).name ) | |
} | |
ForEach ($Domain in $TargetDomains) { | |
Write-Verbose "[*] Querying domain $Domain for hosts" | |
$ComputerName += Get-ThisThingComputer -Domain $Domain -DomainController $DomainController -Filter $ComputerFilter -ADSpath $ComputerADSpath | |
} | |
# remove any null target hosts, uniquify the list and shuffle it | |
$ComputerName = $ComputerName | Where-Object { $_ } | Sort-Object -Unique | Sort-Object { Get-Random } | |
if($($ComputerName.count) -eq 0) { | |
throw "No hosts found!" | |
} | |
} | |
# script block that enumerates a server | |
$HostEnumBlock = { | |
param($ComputerName, $Ping, $CheckShareAccess, $ExcludedShares, $CheckAdmin) | |
# optionally check if the server is up first | |
$Up = $True | |
if($Ping) { | |
$Up = Test-Connection -Count 1 -Quiet -ComputerName $ComputerName | |
} | |
if($Up) { | |
# get the shares for this host and check what we find | |
$Shares = Get-MySMBShare -ComputerName $ComputerName | |
ForEach ($Share in $Shares) { | |
Write-Verbose "[*] Server share: $Share" | |
$NetName = $Share.shi1_netname | |
$Remark = $Share.shi1_remark | |
$Path = '\\'+$ComputerName+'\'+$NetName | |
# make sure we get a real share name back | |
if (($NetName) -and ($NetName.trim() -ne '')) { | |
# if we're just checking for access to ADMIN$ | |
if($CheckAdmin) { | |
if($NetName.ToUpper() -eq "ADMIN$") { | |
try { | |
$Null = [IO.Directory]::GetFiles($Path) | |
"\\$ComputerName\$NetName `t- $Remark" | |
} | |
catch { | |
Write-Verbose "Error accessing path $Path : $_" | |
} | |
} | |
} | |
# skip this share if it's in the exclude list | |
elseif ($ExcludedShares -NotContains $NetName.ToUpper()) { | |
# see if we want to check access to this share | |
if($CheckShareAccess) { | |
# check if the user has access to this path | |
try { | |
$Null = [IO.Directory]::GetFiles($Path) | |
"\\$ComputerName\$NetName `t- $Remark" | |
} | |
catch { | |
Write-Verbose "Error accessing path $Path : $_" | |
} | |
} | |
else { | |
"\\$ComputerName\$NetName `t- $Remark" | |
} | |
} | |
} | |
} | |
} | |
} | |
} | |
process { | |
if($Threads) { | |
Write-Verbose "Using threading with threads = $Threads" | |
# if we're using threading, kick off the script block with Invoke-ThreadedFunction | |
$ScriptParams = @{ | |
'Ping' = $(-not $NoPing) | |
'CheckShareAccess' = $CheckShareAccess | |
'ExcludedShares' = $ExcludedShares | |
'CheckAdmin' = $CheckAdmin | |
} | |
# kick off the threaded script block + arguments | |
Invoke-ThreadedFunction -ComputerName $ComputerName -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams -Threads $Threads | |
} | |
else { | |
if(-not $NoPing -and ($ComputerName.count -ne 1)) { | |
# ping all hosts in parallel | |
$Ping = {param($ComputerName) if(Test-Connection -ComputerName $ComputerName -Count 1 -Quiet -ErrorAction Stop){$ComputerName}} | |
$ComputerName = Invoke-ThreadedFunction -NoImports -ComputerName $ComputerName -ScriptBlock $Ping -Threads 100 | |
} | |
Write-Verbose "[*] Total number of active hosts: $($ComputerName.count)" | |
$Counter = 0 | |
ForEach ($Computer in $ComputerName) { | |
$Counter = $Counter + 1 | |
# sleep for our semi-randomized interval | |
Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay) | |
Write-Verbose "[*] Enumerating server $Computer ($Counter of $($ComputerName.count))" | |
Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $Computer, $False, $CheckShareAccess, $ExcludedShares, $CheckAdmin | |
} | |
} | |
} | |
} | |
function Invoke-FileFinder { | |
[CmdletBinding()] | |
param( | |
[Parameter(Position=0,ValueFromPipeline=$True)] | |
[Alias('Hosts')] | |
[String[]] | |
$ComputerName, | |
[ValidateScript({Test-Path -Path $_ })] | |
[Alias('HostList')] | |
[String] | |
$ComputerFile, | |
[String] | |
$ComputerFilter, | |
[String] | |
$ComputerADSpath, | |
[ValidateScript({Test-Path -Path $_ })] | |
[String] | |
$ShareList, | |
[Switch] | |
$OfficeDocs, | |
[Switch] | |
$FreshEXEs, | |
[Alias('Terms')] | |
[String[]] | |
$SearchTerms, | |
[ValidateScript({Test-Path -Path $_ })] | |
[String] | |
$TermList, | |
[String] | |
$LastAccessTime, | |
[String] | |
$LastWriteTime, | |
[String] | |
$CreationTime, | |
[Switch] | |
$IncludeC, | |
[Switch] | |
$IncludeAdmin, | |
[Switch] | |
$ExcludeFolders, | |
[Switch] | |
$ExcludeHidden, | |
[Switch] | |
$CheckWriteAccess, | |
[String] | |
$OutFile, | |
[Switch] | |
$NoClobber, | |
[Switch] | |
$NoPing, | |
[UInt32] | |
$Delay = 0, | |
[Double] | |
$Jitter = .3, | |
[String] | |
$Domain, | |
[String] | |
$DomainController, | |
[Switch] | |
$SearchForest, | |
[Switch] | |
$SearchSYSVOL, | |
[ValidateRange(1,100)] | |
[Int] | |
$Threads, | |
[Switch] | |
$UsePSDrive | |
) | |
begin { | |
if ($PSBoundParameters['Debug']) { | |
$DebugPreference = 'Continue' | |
} | |
# random object for delay | |
$RandNo = New-Object System.Random | |
Write-Verbose "[*] Running Invoke-FileFinder with delay of $Delay" | |
$Shares = @() | |
# figure out the shares we want to ignore | |
[String[]] $ExcludedShares = @("C$", "ADMIN$") | |
# see if we're specifically including any of the normally excluded sets | |
if ($IncludeC) { | |
if ($IncludeAdmin) { | |
$ExcludedShares = @() | |
} | |
else { | |
$ExcludedShares = @("ADMIN$") | |
} | |
} | |
if ($IncludeAdmin) { | |
if ($IncludeC) { | |
$ExcludedShares = @() | |
} | |
else { | |
$ExcludedShares = @("C$") | |
} | |
} | |
# delete any existing output file if it already exists | |
if(!$NoClobber) { | |
if ($OutFile -and (Test-Path -Path $OutFile)) { Remove-Item -Path $OutFile } | |
} | |
# if there's a set of terms specified to search for | |
if ($TermList) { | |
ForEach ($Term in Get-Content -Path $TermList) { | |
if (($Term -ne $Null) -and ($Term.trim() -ne '')) { | |
$SearchTerms += $Term | |
} | |
} | |
} | |
# if we're hard-passed a set of shares | |
if($ShareList) { | |
ForEach ($Item in Get-Content -Path $ShareList) { | |
if (($Item -ne $Null) -and ($Item.trim() -ne '')) { | |
# exclude any "[tab]- commants", i.e. the output from Invoke-ShareFinder | |
$Share = $Item.Split("`t")[0] | |
$Shares += $Share | |
} | |
} | |
} | |
else { | |
# if we're using a host file list, read the targets in and add them to the target list | |
if($ComputerFile) { | |
$ComputerName = Get-Content -Path $ComputerFile | |
} | |
if(!$ComputerName) { | |
if($Domain) { | |
$TargetDomains = @($Domain) | |
} | |
elseif($SearchForest) { | |
# get ALL the domains in the forest to search | |
$TargetDomains = Get-ThisThingForestDomain | ForEach-Object { $_.Name } | |
} | |
else { | |
# use the local domain | |
$TargetDomains = @( (Get-ThisThingDomain).name ) | |
} | |
if($SearchSYSVOL) { | |
ForEach ($Domain in $TargetDomains) { | |
$DCSearchPath = "\\$Domain\SYSVOL\" | |
Write-Verbose "[*] Adding share search path $DCSearchPath" | |
$Shares += $DCSearchPath | |
} | |
if(!$SearchTerms) { | |
# search for interesting scripts on SYSVOL | |
$SearchTerms = @('.vbs', '.bat', '.ps1') | |
} | |
} | |
else { | |
[array]$ComputerName = @() | |
ForEach ($Domain in $TargetDomains) { | |
Write-Verbose "[*] Querying domain $Domain for hosts" | |
$ComputerName += Get-ThisThingComputer -Filter $ComputerFilter -ADSpath $ComputerADSpath -Domain $Domain -DomainController $DomainController | |
} | |
# remove any null target hosts, uniquify the list and shuffle it | |
$ComputerName = $ComputerName | Where-Object { $_ } | Sort-Object -Unique | Sort-Object { Get-Random } | |
if($($ComputerName.Count) -eq 0) { | |
throw "No hosts found!" | |
} | |
} | |
} | |
} | |
# script block that enumerates shares and files on a server | |
$HostEnumBlock = { | |
param($ComputerName, $Ping, $ExcludedShares, $SearchTerms, $ExcludeFolders, $OfficeDocs, $ExcludeHidden, $FreshEXEs, $CheckWriteAccess, $OutFile, $UsePSDrive) | |
Write-Verbose "ComputerName: $ComputerName" | |
Write-Verbose "ExcludedShares: $ExcludedShares" | |
$SearchShares = @() | |
if($ComputerName.StartsWith("\\")) { | |
# if a share is passed as the server | |
$SearchShares += $ComputerName | |
} | |
else { | |
# if we're enumerating the shares on the target server first | |
$Up = $True | |
if($Ping) { | |
$Up = Test-Connection -Count 1 -Quiet -ComputerName $ComputerName | |
} | |
if($Up) { | |
# get the shares for this host and display what we find | |
$Shares = Get-MySMBShare -ComputerName $ComputerName | |
ForEach ($Share in $Shares) { | |
$NetName = $Share.shi1_netname | |
$Path = '\\'+$ComputerName+'\'+$NetName | |
# make sure we get a real share name back | |
if (($NetName) -and ($NetName.trim() -ne '')) { | |
# skip this share if it's in the exclude list | |
if ($ExcludedShares -NotContains $NetName.ToUpper()) { | |
# check if the user has access to this path | |
try { | |
$Null = [IO.Directory]::GetFiles($Path) | |
$SearchShares += $Path | |
} | |
catch { | |
Write-Verbose "[!] No access to $Path" | |
} | |
} | |
} | |
} | |
} | |
} | |
ForEach($Share in $SearchShares) { | |
$SearchArgs = @{ | |
'Path' = $Share | |
'SearchTerms' = $SearchTerms | |
'OfficeDocs' = $OfficeDocs | |
'FreshEXEs' = $FreshEXEs | |
'LastAccessTime' = $LastAccessTime | |
'LastWriteTime' = $LastWriteTime | |
'CreationTime' = $CreationTime | |
'ExcludeFolders' = $ExcludeFolders | |
'ExcludeHidden' = $ExcludeHidden | |
'CheckWriteAccess' = $CheckWriteAccess | |
'OutFile' = $OutFile | |
'UsePSDrive' = $UsePSDrive | |
} | |
Find-InterestingFile @SearchArgs | |
} | |
} | |
} | |
process { | |
if($Threads) { | |
Write-Verbose "Using threading with threads = $Threads" | |
# if we're using threading, kick off the script block with Invoke-ThreadedFunction | |
$ScriptParams = @{ | |
'Ping' = $(-not $NoPing) | |
'ExcludedShares' = $ExcludedShares | |
'SearchTerms' = $SearchTerms | |
'ExcludeFolders' = $ExcludeFolders | |
'OfficeDocs' = $OfficeDocs | |
'ExcludeHidden' = $ExcludeHidden | |
'FreshEXEs' = $FreshEXEs | |
'CheckWriteAccess' = $CheckWriteAccess | |
'OutFile' = $OutFile | |
'UsePSDrive' = $UsePSDrive | |
} | |
# kick off the threaded script block + arguments | |
if($Shares) { | |
# pass the shares as the hosts so the threaded function code doesn't have to be hacked up | |
Invoke-ThreadedFunction -ComputerName $Shares -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams -Threads $Threads | |
} | |
else { | |
Invoke-ThreadedFunction -ComputerName $ComputerName -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams -Threads $Threads | |
} | |
} | |
else { | |
if($Shares){ | |
$ComputerName = $Shares | |
} | |
elseif(-not $NoPing -and ($ComputerName.count -gt 1)) { | |
# ping all hosts in parallel | |
$Ping = {param($ComputerName) if(Test-Connection -ComputerName $ComputerName -Count 1 -Quiet -ErrorAction Stop){$ComputerName}} | |
$ComputerName = Invoke-ThreadedFunction -NoImports -ComputerName $ComputerName -ScriptBlock $Ping -Threads 100 | |
} | |
Write-Verbose "[*] Total number of active hosts: $($ComputerName.count)" | |
$Counter = 0 | |
$ComputerName | Where-Object {$_} | ForEach-Object { | |
Write-Verbose "Computer: $_" | |
$Counter = $Counter + 1 | |
# sleep for our semi-randomized interval | |
Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay) | |
Write-Verbose "[*] Enumerating server $_ ($Counter of $($ComputerName.count))" | |
Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $_, $False, $ExcludedShares, $SearchTerms, $ExcludeFolders, $OfficeDocs, $ExcludeHidden, $FreshEXEs, $CheckWriteAccess, $OutFile, $UsePSDrive | |
} | |
} | |
} | |
} | |
function Get-ThisThingDomainTrust { | |
[CmdletBinding()] | |
param( | |
[Parameter(Position=0, ValueFromPipeline=$True)] | |
[String] | |
$Domain, | |
[String] | |
$DomainController, | |
[String] | |
$ADSpath, | |
[Switch] | |
$API, | |
[Switch] | |
$LDAP, | |
[ValidateRange(1,10000)] | |
[Int] | |
$PageSize = 200, | |
[Management.Automation.PSCredential] | |
$Credential | |
) | |
begin { | |
$TrustAttributes = @{ | |
[uint32]'0x00000001' = 'non_transitive' | |
[uint32]'0x00000002' = 'uplevel_only' | |
[uint32]'0x00000004' = 'quarantined_domain' | |
[uint32]'0x00000008' = 'forest_transitive' | |
[uint32]'0x00000010' = 'cross_organization' | |
[uint32]'0x00000020' = 'within_forest' | |
[uint32]'0x00000040' = 'treat_as_external' | |
[uint32]'0x00000080' = 'trust_uses_rc4_encryption' | |
[uint32]'0x00000100' = 'trust_uses_aes_keys' | |
[uint32]'0x00000200' = 'cross_organization_no_tgt_delegation' | |
[uint32]'0x00000400' = 'pim_trust' | |
} | |
} | |
process { | |
if(-not $Domain) { | |
# if not domain is specified grab the current domain | |
$SourceDomain = (Get-ThisThingDomain -Credential $Credential).Name | |
} | |
else { | |
$SourceDomain = $Domain | |
} | |
if($LDAP -or $ADSPath) { | |
$TrustSearcher = Get-DomainSearcher -Domain $SourceDomain -DomainController $DomainController -Credential $Credential -PageSize $PageSize -ADSpath $ADSpath | |
$SourceSID = Get-DomainSID -Domain $SourceDomain -DomainController $DomainController | |
if($TrustSearcher) { | |
$TrustSearcher.Filter = '(objectClass=trustedDomain)' | |
$Results = $TrustSearcher.FindAll() | |
$Results | Where-Object {$_} | ForEach-Object { | |
$Props = $_.Properties | |
$DomainTrust = New-Object PSObject | |
$TrustAttrib = @() | |
$TrustAttrib += $TrustAttributes.Keys | Where-Object { $Props.trustattributes[0] -band $_ } | ForEach-Object { $TrustAttributes[$_] } | |
$Direction = Switch ($Props.trustdirection) { | |
0 { 'Disabled' } | |
1 { 'Inbound' } | |
2 { 'Outbound' } | |
3 { 'Bidirectional' } | |
} | |
$ObjectGuid = New-Object Guid @(,$Props.objectguid[0]) | |
$TargetSID = (New-Object System.Security.Principal.SecurityIdentifier($Props.securityidentifier[0],0)).Value | |
$DomainTrust | Add-Member Noteproperty 'SourceName' $SourceDomain | |
$DomainTrust | Add-Member Noteproperty 'SourceSID' $SourceSID | |
$DomainTrust | Add-Member Noteproperty 'TargetName' $Props.name[0] | |
$DomainTrust | Add-Member Noteproperty 'TargetSID' $TargetSID | |
$DomainTrust | Add-Member Noteproperty 'ObjectGuid' "{$ObjectGuid}" | |
$DomainTrust | Add-Member Noteproperty 'TrustType' $($TrustAttrib -join ',') | |
$DomainTrust | Add-Member Noteproperty 'TrustDirection' "$Direction" | |
$DomainTrust.PSObject.TypeNames.Add('PowerView.DomainTrustLDAP') | |
$DomainTrust | |
} | |
$Results.dispose() | |
$TrustSearcher.dispose() | |
} | |
} | |
elseif($API) { | |
if(-not $DomainController) { | |
$DomainController = Get-ThisThingDomainController -Credential $Credential -Domain $SourceDomain | Select-Object -First 1 | Select-Object -ExpandProperty Name | |
} | |
if($DomainController) { | |
# arguments for DsEnumerateDomainTrusts | |
$PtrInfo = [IntPtr]::Zero | |
# 63 = DS_DOMAIN_IN_FOREST + DS_DOMAIN_DIRECT_OUTBOUND + DS_DOMAIN_TREE_ROOT + DS_DOMAIN_PRIMARY + DS_DOMAIN_NATIVE_MODE + DS_DOMAIN_DIRECT_INBOUND | |
$Flags = 63 | |
$DomainCount = 0 | |
# get the trust information from the target server | |
$Result = $Netapi32::DsEnumerateDomainTrusts($DomainController, $Flags, [ref]$PtrInfo, [ref]$DomainCount) | |
# Locate the offset of the initial intPtr | |
$Offset = $PtrInfo.ToInt64() | |
# 0 = success | |
if (($Result -eq 0) -and ($Offset -gt 0)) { | |
# Work out how much to increment the pointer by finding out the size of the structure | |
$Increment = $DS_DOMAIN_TRUSTS::GetSize() | |
# parse all the result structures | |
for ($i = 0; ($i -lt $DomainCount); $i++) { | |
# create a new int ptr at the given offset and cast the pointer as our result structure | |
$NewIntPtr = New-Object System.Intptr -ArgumentList $Offset | |
$Info = $NewIntPtr -as $DS_DOMAIN_TRUSTS | |
$Offset = $NewIntPtr.ToInt64() | |
$Offset += $Increment | |
$SidString = "" | |
$Result = $Advapi32::ConvertSidToStringSid($Info.DomainSid, [ref]$SidString);$LastError = [Runtime.InteropServices.Marshal]::GetLastWin32Error() | |
if($Result -eq 0) { | |
Write-Verbose "Error: $(([ComponentModel.Win32Exception] $LastError).Message)" | |
} | |
else { | |
$DomainTrust = New-Object PSObject | |
$DomainTrust | Add-Member Noteproperty 'SourceDomain' $SourceDomain | |
$DomainTrust | Add-Member Noteproperty 'SourceDomainController' $DomainController | |
$DomainTrust | Add-Member Noteproperty 'NetbiosDomainName' $Info.NetbiosDomainName | |
$DomainTrust | Add-Member Noteproperty 'DnsDomainName' $Info.DnsDomainName | |
$DomainTrust | Add-Member Noteproperty 'Flags' $Info.Flags | |
$DomainTrust | Add-Member Noteproperty 'ParentIndex' $Info.ParentIndex | |
$DomainTrust | Add-Member Noteproperty 'TrustType' $Info.TrustType | |
$DomainTrust | Add-Member Noteproperty 'TrustAttributes' $Info.TrustAttributes | |
$DomainTrust | Add-Member Noteproperty 'DomainSid' $SidString | |
$DomainTrust | Add-Member Noteproperty 'DomainGuid' $Info.DomainGuid | |
$DomainTrust.PSObject.TypeNames.Add('PowerView.APIDomainTrust') | |
$DomainTrust | |
} | |
} | |
# free up the result buffer | |
$Null = $Netapi32::NetApiBufferFree($PtrInfo) | |
} | |
else { | |
Write-Verbose "Error: $(([ComponentModel.Win32Exception] $Result).Message)" | |
} | |
} | |
else { | |
Write-Verbose "Could not retrieve domain controller for $Domain" | |
} | |
} | |
else { | |
# if we're using direct domain connections through .NET | |
$FoundDomain = Get-ThisThingDomain -Domain $Domain -Credential $Credential | |
if($FoundDomain) { | |
$FoundDomain.GetAllTrustRelationships() | ForEach-Object { | |
$_.PSObject.TypeNames.Add('PowerView.DomainTrust') | |
$_ | |
} | |
} | |
} | |
} | |
} | |
function Get-ThisThingForestTrust { | |
[CmdletBinding()] | |
param( | |
[Parameter(Position=0,ValueFromPipeline=$True)] | |
[String] | |
$Forest, | |
[Management.Automation.PSCredential] | |
$Credential | |
) | |
process { | |
$FoundForest = Get-ThisThingForest -Forest $Forest -Credential $Credential | |
if($FoundForest) { | |
$FoundForest.GetAllTrustRelationships() | ForEach-Object { | |
$_.PSObject.TypeNames.Add('PowerView.ForestTrust') | |
$_ | |
} | |
} | |
} | |
} | |
function Find-ForeignUser { | |
[CmdletBinding()] | |
param( | |
[String] | |
$UserName, | |
[String] | |
$Domain, | |
[String] | |
$DomainController, | |
[Switch] | |
$LDAP, | |
[Switch] | |
$Recurse, | |
[ValidateRange(1,10000)] | |
[Int] | |
$PageSize = 200 | |
) | |
function Get-ForeignUser { | |
# helper used to enumerate users who are in groups outside of their principal domain | |
param( | |
[String] | |
$UserName, | |
[String] | |
$Domain, | |
[String] | |
$DomainController, | |
[ValidateRange(1,10000)] | |
[Int] | |
$PageSize = 200 | |
) | |
if ($Domain) { | |
# get the domain name into distinguished form | |
$DistinguishedDomainName = "DC=" + $Domain -replace '\.',',DC=' | |
} | |
else { | |
$DistinguishedDomainName = [String] ([adsi]'').distinguishedname | |
$Domain = $DistinguishedDomainName -replace 'DC=','' -replace ',','.' | |
} | |
Get-ThisThingUser -Domain $Domain -DomainController $DomainController -UserName $UserName -PageSize $PageSize -Filter '(memberof=*)' | ForEach-Object { | |
ForEach ($Membership in $_.memberof) { | |
$Index = $Membership.IndexOf("DC=") | |
if($Index) { | |
$GroupDomain = $($Membership.substring($Index)) -replace 'DC=','' -replace ',','.' | |
if ($GroupDomain.CompareTo($Domain)) { | |
# if the group domain doesn't match the user domain, output | |
$GroupName = $Membership.split(",")[0].split("=")[1] | |
$ForeignUser = New-Object PSObject | |
$ForeignUser | Add-Member Noteproperty 'UserDomain' $Domain | |
$ForeignUser | Add-Member Noteproperty 'UserName' $_.samaccountname | |
$ForeignUser | Add-Member Noteproperty 'GroupDomain' $GroupDomain | |
$ForeignUser | Add-Member Noteproperty 'GroupName' $GroupName | |
$ForeignUser | Add-Member Noteproperty 'GroupDN' $Membership | |
$ForeignUser | |
} | |
} | |
} | |
} | |
} | |
if ($Recurse) { | |
# get all rechable domains in the trust mesh and uniquify them | |
if($LDAP -or $DomainController) { | |
$DomainTrusts = Invoke-MapDomainTrust -LDAP -DomainController $DomainController -PageSize $PageSize | ForEach-Object { $_.SourceDomain } | Sort-Object -Unique | |
} | |
else { | |
$DomainTrusts = Invoke-MapDomainTrust -PageSize $PageSize | ForEach-Object { $_.SourceDomain } | Sort-Object -Unique | |
} | |
ForEach($DomainTrust in $DomainTrusts) { | |
# get the trust groups for each domain in the trust mesh | |
Write-Verbose "Enumerating trust groups in domain $DomainTrust" | |
Get-ForeignUser -Domain $DomainTrust -UserName $UserName -PageSize $PageSize | |
} | |
} | |
else { | |
Get-ForeignUser -Domain $Domain -DomainController $DomainController -UserName $UserName -PageSize $PageSize | |
} | |
} | |
function Find-ForeignGroup { | |
[CmdletBinding()] | |
param( | |
[String] | |
$GroupName = '*', | |
[String] | |
$Domain, | |
[String] | |
$DomainController, | |
[Switch] | |
$LDAP, | |
[Switch] | |
$Recurse, | |
[ValidateRange(1,10000)] | |
[Int] | |
$PageSize = 200 | |
) | |
function Get-ForeignGroup { | |
param( | |
[String] | |
$GroupName = '*', | |
[String] | |
$Domain, | |
[String] | |
$DomainController, | |
[ValidateRange(1,10000)] | |
[Int] | |
$PageSize = 200 | |
) | |
if(-not $Domain) { | |
$Domain = (Get-ThisThingDomain).Name | |
} | |
$DomainDN = "DC=$($Domain.Replace('.', ',DC='))" | |
Write-Verbose "DomainDN: $DomainDN" | |
# standard group names to ignore | |
$ExcludeGroups = @("Users", "Domain Users", "Guests") | |
# get all the groupnames for the given domain | |
Get-ThisThingGroup -GroupName $GroupName -Filter '(member=*)' -Domain $Domain -DomainController $DomainController -FullData -PageSize $PageSize | Where-Object { | |
# exclude common large groups | |
-not ($ExcludeGroups -contains $_.samaccountname) } | ForEach-Object { | |
$GroupName = $_.samAccountName | |
$_.member | ForEach-Object { | |
# filter for foreign SIDs in the cn field for users in another domain, | |
# or if the DN doesn't end with the proper DN for the queried domain | |
if (($_ -match 'CN=S-1-5-21.*-.*') -or ($DomainDN -ne ($_.substring($_.IndexOf("DC="))))) { | |
$UserDomain = $_.subString($_.IndexOf("DC=")) -replace 'DC=','' -replace ',','.' | |
$UserName = $_.split(",")[0].split("=")[1] | |
$ForeignGroupUser = New-Object PSObject | |
$ForeignGroupUser | Add-Member Noteproperty 'GroupDomain' $Domain | |
$ForeignGroupUser | Add-Member Noteproperty 'GroupName' $GroupName | |
$ForeignGroupUser | Add-Member Noteproperty 'UserDomain' $UserDomain | |
$ForeignGroupUser | Add-Member Noteproperty 'UserName' $UserName | |
$ForeignGroupUser | Add-Member Noteproperty 'UserDN' $_ | |
$ForeignGroupUser | |
} | |
} | |
} | |
} | |
if ($Recurse) { | |
# get all rechable domains in the trust mesh and uniquify them | |
if($LDAP -or $DomainController) { | |
$DomainTrusts = Invoke-MapDomainTrust -LDAP -DomainController $DomainController -PageSize $PageSize | ForEach-Object { $_.SourceDomain } | Sort-Object -Unique | |
} | |
else { | |
$DomainTrusts = Invoke-MapDomainTrust -PageSize $PageSize | ForEach-Object { $_.SourceDomain } | Sort-Object -Unique | |
} | |
ForEach($DomainTrust in $DomainTrusts) { | |
# get the trust groups for each domain in the trust mesh | |
Write-Verbose "Enumerating trust groups in domain $DomainTrust" | |
Get-ForeignGroup -GroupName $GroupName -Domain $Domain -DomainController $DomainController -PageSize $PageSize | |
} | |
} | |
else { | |
Get-ForeignGroup -GroupName $GroupName -Domain $Domain -DomainController $DomainController -PageSize $PageSize | |
} | |
} | |
function Find-ManagedSecurityGroups { | |
# Go through the list of security groups on the domain and identify those who have a manager | |
Get-ThisThingGroup -FullData -Filter '(managedBy=*)' | Select-Object -Unique distinguishedName,managedBy,cn | ForEach-Object { | |
# Retrieve the object that the managedBy DN refers to | |
$group_manager = Get-ADObject -ADSPath $_.managedBy | Select-Object cn,distinguishedname,name,samaccounttype,samaccountname | |
# Create a results object to store our findings | |
$results_object = New-Object -TypeName PSObject -Property @{ | |
'GroupCN' = $_.cn | |
'GroupDN' = $_.distinguishedname | |
'ManagerCN' = $group_manager.cn | |
'ManagerDN' = $group_manager.distinguishedName | |
'ManagerSAN' = $group_manager.samaccountname | |
'ManagerType' = '' | |
'CanManagerWrite' = $FALSE | |
} | |
# Determine whether the manager is a user or a group | |
if ($group_manager.samaccounttype -eq 0x10000000) { | |
$results_object.ManagerType = 'Group' | |
} elseif ($group_manager.samaccounttype -eq 0x30000000) { | |
$results_object.ManagerType = 'User' | |
} | |
# Find the ACLs that relate to the ability to write to the group | |
$xacl = Get-ObjectAcl -ADSPath $_.distinguishedname -Rights WriteMembers | |
# Double-check that the manager | |
if ($xacl.ObjectType -eq 'bf9679c0-0de6-11d0-a285-00aa003049e2' -and $xacl.AccessControlType -eq 'Allow' -and $xacl.IdentityReference.Value.Contains($group_manager.samaccountname)) { | |
$results_object.CanManagerWrite = $TRUE | |
} | |
$results_object | |
} | |
} | |
function Invoke-MapDomainTrust { | |
[CmdletBinding()] | |
param( | |
[Switch] | |
$LDAP, | |
[String] | |
$DomainController, | |
[ValidateRange(1,10000)] | |
[Int] | |
$PageSize = 200, | |
[Management.Automation.PSCredential] | |
$Credential | |
) | |
# keep track of domains seen so we don't hit infinite recursion | |
$SeenDomains = @{} | |
# our domain status tracker | |
$Domains = New-Object System.Collections.Stack | |
# get the current domain and push it onto the stack | |
$CurrentDomain = (Get-ThisThingDomain -Credential $Credential).Name | |
$Domains.push($CurrentDomain) | |
while($Domains.Count -ne 0) { | |
$Domain = $Domains.Pop() | |
# if we haven't seen this domain before | |
if ($Domain -and ($Domain.Trim() -ne "") -and (-not $SeenDomains.ContainsKey($Domain))) { | |
Write-Verbose "Enumerating trusts for domain '$Domain'" | |
# mark it as seen in our list | |
$Null = $SeenDomains.add($Domain, "") | |
try { | |
# get all the trusts for this domain | |
if($LDAP -or $DomainController) { | |
$Trusts = Get-ThisThingDomainTrust -Domain $Domain -LDAP -DomainController $DomainController -PageSize $PageSize -Credential $Credential | |
} | |
else { | |
$Trusts = Get-ThisThingDomainTrust -Domain $Domain -PageSize $PageSize -Credential $Credential | |
} | |
if($Trusts -isnot [System.Array]) { | |
$Trusts = @($Trusts) | |
} | |
# get any forest trusts, if they exist | |
if(-not ($LDAP -or $DomainController) ) { | |
$Trusts += Get-ThisThingForestTrust -Forest $Domain -Credential $Credential | |
} | |
if ($Trusts) { | |
if($Trusts -isnot [System.Array]) { | |
$Trusts = @($Trusts) | |
} | |
# enumerate each trust found | |
ForEach ($Trust in $Trusts) { | |
if($Trust.SourceName -and $Trust.TargetName) { | |
$SourceDomain = $Trust.SourceName | |
$TargetDomain = $Trust.TargetName | |
$TrustType = $Trust.TrustType | |
$TrustDirection = $Trust.TrustDirection | |
$ObjectType = $Trust.PSObject.TypeNames | Where-Object {$_ -match 'PowerView'} | Select-Object -First 1 | |
# make sure we process the target | |
$Null = $Domains.Push($TargetDomain) | |
# build the nicely-parsable custom output object | |
$DomainTrust = New-Object PSObject | |
$DomainTrust | Add-Member Noteproperty 'SourceDomain' "$SourceDomain" | |
$DomainTrust | Add-Member Noteproperty 'SourceSID' $Trust.SourceSID | |
$DomainTrust | Add-Member Noteproperty 'TargetDomain' "$TargetDomain" | |
$DomainTrust | Add-Member Noteproperty 'TargetSID' $Trust.TargetSID | |
$DomainTrust | Add-Member Noteproperty 'TrustType' "$TrustType" | |
$DomainTrust | Add-Member Noteproperty 'TrustDirection' "$TrustDirection" | |
$DomainTrust.PSObject.TypeNames.Add($ObjectType) | |
$DomainTrust | |
} | |
} | |
} | |
} | |
catch { | |
Write-Verbose "[!] Error: $_" | |
} | |
} | |
} | |
} | |
$Mod = New-InMemoryModule -ModuleName Win32 | |
$FunctionDefinitions = @( | |
(func netapi32 NetShareEnum ([Int]) @([String], [Int], [IntPtr].MakeByRefType(), [Int], [Int32].MakeByRefType(), [Int32].MakeByRefType(), [Int32].MakeByRefType())), | |
(func netapi32 NetWkstaUserEnum ([Int]) @([String], [Int], [IntPtr].MakeByRefType(), [Int], [Int32].MakeByRefType(), [Int32].MakeByRefType(), [Int32].MakeByRefType())), | |
(func netapi32 NetSessionEnum ([Int]) @([String], [String], [String], [Int], [IntPtr].MakeByRefType(), [Int], [Int32].MakeByRefType(), [Int32].MakeByRefType(), [Int32].MakeByRefType())), | |
(func netapi32 NetLocalGroupGetMembers ([Int]) @([String], [String], [Int], [IntPtr].MakeByRefType(), [Int], [Int32].MakeByRefType(), [Int32].MakeByRefType(), [Int32].MakeByRefType())), | |
(func netapi32 DsGetSiteName ([Int]) @([String], [IntPtr].MakeByRefType())), | |
(func netapi32 DsEnumerateDomainTrusts ([Int]) @([String], [UInt32], [IntPtr].MakeByRefType(), [IntPtr].MakeByRefType())), | |
(func netapi32 NetApiBufferFree ([Int]) @([IntPtr])), | |
(func advapi32 ConvertSidToStringSid ([Int]) @([IntPtr], [String].MakeByRefType()) -SetLastError), | |
(func advapi32 OpenSCManagerW ([IntPtr]) @([String], [String], [Int]) -SetLastError), | |
(func advapi32 CloseServiceHandle ([Int]) @([IntPtr])), | |
(func wtsapi32 WTSOpenServerEx ([IntPtr]) @([String])), | |
(func wtsapi32 WTSEnumerateSessionsEx ([Int]) @([IntPtr], [Int32].MakeByRefType(), [Int], [IntPtr].MakeByRefType(), [Int32].MakeByRefType()) -SetLastError), | |
(func wtsapi32 WTSQuerySessionInformation ([Int]) @([IntPtr], [Int], [Int], [IntPtr].MakeByRefType(), [Int32].MakeByRefType()) -SetLastError), | |
(func wtsapi32 WTSFreeMemoryEx ([Int]) @([Int32], [IntPtr], [Int32])), | |
(func wtsapi32 WTSFreeMemory ([Int]) @([IntPtr])), | |
(func wtsapi32 WTSCloseServer ([Int]) @([IntPtr])) | |
) | |
# enum used by $WTS_SESSION_INFO_1 below | |
$WTSConnectState = psenum $Mod WTS_CONNECTSTATE_CLASS UInt16 @{ | |
Active = 0 | |
Connected = 1 | |
ConnectQuery = 2 | |
Shadow = 3 | |
Disconnected = 4 | |
Idle = 5 | |
Listen = 6 | |
Reset = 7 | |
Down = 8 | |
Init = 9 | |
} | |
# the WTSEnumerateSessionsEx result structure | |
$WTS_SESSION_INFO_1 = struct $Mod WTS_SESSION_INFO_1 @{ | |
ExecEnvId = field 0 UInt32 | |
State = field 1 $WTSConnectState | |
SessionId = field 2 UInt32 | |
pSessionName = field 3 String -MarshalAs @('LPWStr') | |
pHostName = field 4 String -MarshalAs @('LPWStr') | |
pUserName = field 5 String -MarshalAs @('LPWStr') | |
pDomainName = field 6 String -MarshalAs @('LPWStr') | |
pFarmName = field 7 String -MarshalAs @('LPWStr') | |
} | |
# the particular WTSQuerySessionInformation result structure | |
$WTS_CLIENT_ADDRESS = struct $mod WTS_CLIENT_ADDRESS @{ | |
AddressFamily = field 0 UInt32 | |
Address = field 1 Byte[] -MarshalAs @('ByValArray', 20) | |
} | |
# the NetShareEnum result structure | |
$SHARE_INFO_1 = struct $Mod SHARE_INFO_1 @{ | |
shi1_netname = field 0 String -MarshalAs @('LPWStr') | |
shi1_type = field 1 UInt32 | |
shi1_remark = field 2 String -MarshalAs @('LPWStr') | |
} | |
# the NetWkstaUserEnum result structure | |
$WKSTA_USER_INFO_1 = struct $Mod WKSTA_USER_INFO_1 @{ | |
wkui1_username = field 0 String -MarshalAs @('LPWStr') | |
wkui1_logon_domain = field 1 String -MarshalAs @('LPWStr') | |
wkui1_oth_domains = field 2 String -MarshalAs @('LPWStr') | |
wkui1_logon_server = field 3 String -MarshalAs @('LPWStr') | |
} | |
# the NetSessionEnum result structure | |
$SESSION_INFO_10 = struct $Mod SESSION_INFO_10 @{ | |
sesi10_cname = field 0 String -MarshalAs @('LPWStr') | |
sesi10_username = field 1 String -MarshalAs @('LPWStr') | |
sesi10_time = field 2 UInt32 | |
sesi10_idle_time = field 3 UInt32 | |
} | |
# enum used by $LOCALGROUP_MEMBERS_INFO_2 below | |
$SID_NAME_USE = psenum $Mod SID_NAME_USE UInt16 @{ | |
SidTypeUser = 1 | |
SidTypeGroup = 2 | |
SidTypeDomain = 3 | |
SidTypeAlias = 4 | |
SidTypeWellKnownGroup = 5 | |
SidTypeDeletedAccount = 6 | |
SidTypeInvalid = 7 | |
SidTypeUnknown = 8 | |
SidTypeComputer = 9 | |
} | |
# the NetLocalGroupGetMembers result structure | |
$LOCALGROUP_MEMBERS_INFO_2 = struct $Mod LOCALGROUP_MEMBERS_INFO_2 @{ | |
lgrmi2_sid = field 0 IntPtr | |
lgrmi2_sidusage = field 1 $SID_NAME_USE | |
lgrmi2_domainandname = field 2 String -MarshalAs @('LPWStr') | |
} | |
# enums used in DS_DOMAIN_TRUSTS | |
$DsDomainFlag = psenum $Mod DsDomain.Flags UInt32 @{ | |
IN_FOREST = 1 | |
DIRECT_OUTBOUND = 2 | |
TREE_ROOT = 4 | |
PRIMARY = 8 | |
NATIVE_MODE = 16 | |
DIRECT_INBOUND = 32 | |
} -Bitfield | |
$DsDomainTrustType = psenum $Mod DsDomain.TrustType UInt32 @{ | |
DOWNLEVEL = 1 | |
UPLEVEL = 2 | |
MIT = 3 | |
DCE = 4 | |
} | |
$DsDomainTrustAttributes = psenum $Mod DsDomain.TrustAttributes UInt32 @{ | |
NON_TRANSITIVE = 1 | |
UPLEVEL_ONLY = 2 | |
FILTER_SIDS = 4 | |
FOREST_TRANSITIVE = 8 | |
CROSS_ORGANIZATION = 16 | |
WITHIN_FOREST = 32 | |
TREAT_AS_EXTERNAL = 64 | |
} | |
# the DsEnumerateDomainTrusts result structure | |
$DS_DOMAIN_TRUSTS = struct $Mod DS_DOMAIN_TRUSTS @{ | |
NetbiosDomainName = field 0 String -MarshalAs @('LPWStr') | |
DnsDomainName = field 1 String -MarshalAs @('LPWStr') | |
Flags = field 2 $DsDomainFlag | |
ParentIndex = field 3 UInt32 | |
TrustType = field 4 $DsDomainTrustType | |
TrustAttributes = field 5 $DsDomainTrustAttributes | |
DomainSid = field 6 IntPtr | |
DomainGuid = field 7 Guid | |
} | |
$Types = $FunctionDefinitions | Add-Win32Type -Module $Mod -Namespace 'Win32' | |
$Netapi32 = $Types['netapi32'] | |
$Advapi32 = $Types['advapi32'] | |
$Wtsapi32 = $Types['wtsapi32'] | |
function Invoke-Parallel | |
{ | |
[cmdletbinding(DefaultParameterSetName = 'ScriptBlock')] | |
Param ( | |
[Parameter(Mandatory = $false,position = 0,ParameterSetName = 'ScriptBlock')] | |
[System.Management.Automation.ScriptBlock]$ScriptBlock, | |
[Parameter(Mandatory = $false,ParameterSetName = 'ScriptFile')] | |
[ValidateScript({ | |
Test-Path $_ -PathType leaf | |
})] | |
$ScriptFile, | |
[Parameter(Mandatory = $true,ValueFromPipeline = $true)] | |
[Alias('CN','__Server','IPAddress','Server','ComputerName')] | |
[PSObject]$InputObject, | |
[PSObject]$Parameter, | |
[switch]$ImportSessionFunctions, | |
[switch]$ImportVariables, | |
[switch]$ImportModules, | |
[int]$Throttle = 20, | |
[int]$SleepTimer = 200, | |
[int]$RunspaceTimeout = 0, | |
[switch]$NoCloseOnTimeout = $false, | |
[int]$MaxQueue, | |
[validatescript({ | |
Test-Path (Split-Path -Path $_ -Parent) | |
})] | |
[string]$LogFile = 'C:\temp\log.log', | |
[switch] $Quiet = $false | |
) | |
Begin { | |
#No max queue specified? Estimate one. | |
#We use the script scope to resolve an odd PowerShell 2 issue where MaxQueue isn't seen later in the function | |
if( -not $PSBoundParameters.ContainsKey('MaxQueue') ) | |
{ | |
if($RunspaceTimeout -ne 0) | |
{ | |
$script:MaxQueue = $Throttle | |
} | |
else | |
{ | |
$script:MaxQueue = $Throttle * 3 | |
} | |
} | |
else | |
{ | |
$script:MaxQueue = $MaxQueue | |
} | |
#Write-Verbose "Throttle: '$throttle' SleepTimer '$sleepTimer' runSpaceTimeout '$runspaceTimeout' maxQueue '$maxQueue' logFile '$logFile'" | |
#If they want to import variables or modules, create a clean runspace, get loaded items, use those to exclude items | |
if ($ImportVariables -or $ImportModules) | |
{ | |
$StandardUserEnv = [powershell]::Create().addscript({ | |
#Get modules and snapins in this clean runspace | |
$Modules = Get-Module | Select-Object -ExpandProperty Name | |
$Snapins = Get-PSSnapin | Select-Object -ExpandProperty Name | |
#Get variables in this clean runspace | |
#Called last to get vars like $? into session | |
$Variables = Get-Variable | Select-Object -ExpandProperty Name | |
#Return a hashtable where we can access each. | |
@{ | |
Variables = $Variables | |
Modules = $Modules | |
Snapins = $Snapins | |
} | |
}).invoke()[0] | |
if ($ImportVariables) | |
{ | |
#Exclude common parameters, bound parameters, and automatic variables | |
Function _temp | |
{ | |
[cmdletbinding()] param() | |
} | |
$VariablesToExclude = @( (Get-Command _temp | Select-Object -ExpandProperty parameters).Keys + $PSBoundParameters.Keys + $StandardUserEnv.Variables ) | |
#Write-Verbose "Excluding variables $( ($VariablesToExclude | sort ) -join ", ")" | |
# we don't use 'Get-Variable -Exclude', because it uses regexps. | |
# One of the veriables that we pass is '$?'. | |
# There could be other variables with such problems. | |
# Scope 2 required if we move to a real module | |
$UserVariables = @( Get-Variable | Where-Object -FilterScript { | |
-not ($VariablesToExclude -contains $_.Name) | |
} ) | |
#Write-Verbose "Found variables to import: $( ($UserVariables | Select -expandproperty Name | Sort ) -join ", " | Out-String).`n" | |
} | |
if ($ImportModules) | |
{ | |
$UserModules = @( Get-Module | | |
Where-Object -FilterScript { | |
$StandardUserEnv.Modules -notcontains $_.Name -and (Test-Path -Path $_.Path -ErrorAction SilentlyContinue) | |
} | | |
Select-Object -ExpandProperty Path ) | |
$UserSnapins = @( Get-PSSnapin | | |
Select-Object -ExpandProperty Name | | |
Where-Object -FilterScript { | |
$StandardUserEnv.Snapins -notcontains $_ | |
} ) | |
} | |
} | |
#region functions | |
Function Get-RunspaceData | |
{ | |
[cmdletbinding()] | |
param( [switch]$Wait ) | |
#loop through runspaces | |
#if $wait is specified, keep looping until all complete | |
Do | |
{ | |
#set more to false for tracking completion | |
$more = $false | |
#Progress bar if we have inputobject count (bound parameter) | |
if (-not $Quiet) | |
{ | |
Write-Progress -Activity 'Running Query' -Status 'Starting threads'` | |
-CurrentOperation "$startedCount threads defined - $totalCount input objects - $script:completedCount input objects processed"` | |
-PercentComplete $( Try | |
{ | |
$script:completedCount / $totalCount * 100 | |
} | |
Catch | |
{ | |
0 | |
} | |
) | |
} | |
#run through each runspace. | |
Foreach($runspace in $runspaces) | |
{ | |
#get the duration - inaccurate | |
$currentdate = Get-Date | |
$runtime = $currentdate - $runspace.startTime | |
$runMin = [math]::Round( $runtime.totalminutes ,2 ) | |
#set up log object | |
$log = '' | Select-Object -Property Date, Action, Runtime, Status, Details | |
$log.Action = "Removing:'$($runspace.object)'" | |
$log.Date = $currentdate | |
$log.Runtime = "$runMin minutes" | |
#If runspace completed, end invoke, dispose, recycle, counter++ | |
If ($runspace.Runspace.isCompleted) | |
{ | |
$script:completedCount++ | |
#check if there were errors | |
if($runspace.powershell.Streams.Error.Count -gt 0) | |
{ | |
#set the logging info and move the file to completed | |
$log.status = 'CompletedWithErrors' | |
#Write-Verbose ($log | ConvertTo-Csv -Delimiter ";" -NoTypeInformation)[1] | |
foreach($ErrorRecord in $runspace.powershell.Streams.Error) | |
{ | |
Write-Error -ErrorRecord $ErrorRecord | |
} | |
} | |
else | |
{ | |
#add logging details and cleanup | |
$log.status = 'Completed' | |
#Write-Verbose ($log | ConvertTo-Csv -Delimiter ";" -NoTypeInformation)[1] | |
} | |
#everything is logged, clean up the runspace | |
$runspace.powershell.EndInvoke($runspace.Runspace) | |
$runspace.powershell.dispose() | |
$runspace.Runspace = $null | |
$runspace.powershell = $null | |
} | |
#If runtime exceeds max, dispose the runspace | |
ElseIf ( $RunspaceTimeout -ne 0 -and $runtime.totalseconds -gt $RunspaceTimeout) | |
{ | |
$script:completedCount++ | |
$timedOutTasks = $true | |
#add logging details and cleanup | |
$log.status = 'TimedOut' | |
#Write-Verbose ($log | ConvertTo-Csv -Delimiter ";" -NoTypeInformation)[1] | |
Write-Error -Message "Runspace timed out at $($runtime.totalseconds) seconds for the object:`n$($runspace.object | Out-String)" | |
#Depending on how it hangs, we could still get stuck here as dispose calls a synchronous method on the powershell instance | |
if (!$NoCloseOnTimeout) | |
{ | |
$runspace.powershell.dispose() | |
} | |
$runspace.Runspace = $null | |
$runspace.powershell = $null | |
$completedCount++ | |
} | |
#If runspace isn't null set more to true | |
ElseIf ($runspace.Runspace -ne $null ) | |
{ | |
$log = $null | |
$more = $true | |
} | |
#log the results if a log file was indicated | |
<# | |
if($logFile -and $log){ | |
($log | ConvertTo-Csv -Delimiter ";" -NoTypeInformation)[1] | out-file $LogFile -append | |
} | |
#> | |
} | |
#Clean out unused runspace jobs | |
$temphash = $runspaces.clone() | |
$temphash | | |
Where-Object -FilterScript { | |
$_.runspace -eq $null | |
} | | |
ForEach-Object -Process { | |
$runspaces.remove($_) | |
} | |
#sleep for a bit if we will loop again | |
if($PSBoundParameters['Wait']) | |
{ | |
Start-Sleep -Milliseconds $SleepTimer | |
} | |
#Loop again only if -wait parameter and there are more runspaces to process | |
} | |
while ($more -and $PSBoundParameters['Wait']) | |
#End of runspace function | |
} | |
#endregion functions | |
#region Init | |
if($PSCmdlet.ParameterSetName -eq 'ScriptFile') | |
{ | |
$ScriptBlock = [scriptblock]::Create( $(Get-Content $ScriptFile | Out-String) ) | |
} | |
elseif($PSCmdlet.ParameterSetName -eq 'ScriptBlock') | |
{ | |
#Start building parameter names for the param block | |
[string[]]$ParamsToAdd = '$_' | |
if( $PSBoundParameters.ContainsKey('Parameter') ) | |
{ | |
$ParamsToAdd += '$Parameter' | |
} | |
$UsingVariableData = $null | |
# This code enables $Using support through the AST. | |
# This is entirely from Boe Prox, and his https://github.com/proxb/PoshRSJob module; all credit to Boe! | |
if($PSVersionTable.PSVersion.Major -gt 2) | |
{ | |
#Extract using references | |
$UsingVariables = $ScriptBlock.ast.FindAll({ | |
$args[0] -is [System.Management.Automation.Language.UsingExpressionAst] | |
},$true) | |
If ($UsingVariables) | |
{ | |
$List = New-Object -TypeName 'System.Collections.Generic.List`1[System.Management.Automation.Language.VariableExpressionAst]' | |
ForEach ($Ast in $UsingVariables) | |
{ | |
[void]$List.Add($Ast.SubExpression) | |
} | |
$UsingVar = $UsingVariables | | |
Group-Object -Property SubExpression | | |
ForEach-Object -Process { | |
$_.Group | | |
Select-Object -First 1 | |
} | |
#Extract the name, value, and create replacements for each | |
$UsingVariableData = ForEach ($Var in $UsingVar) | |
{ | |
Try | |
{ | |
$Value = Get-Variable -Name $Var.SubExpression.VariablePath.UserPath -ErrorAction Stop | |
[pscustomobject]@{ | |
Name = $Var.SubExpression.Extent.Text | |
Value = $Value.Value | |
NewName = ('$__using_{0}' -f $Var.SubExpression.VariablePath.UserPath) | |
NewVarName = ('__using_{0}' -f $Var.SubExpression.VariablePath.UserPath) | |
} | |
} | |
Catch | |
{ | |
Write-Error -Message "$($Var.SubExpression.Extent.Text) is not a valid Using: variable!" | |
} | |
} | |
$ParamsToAdd += $UsingVariableData | Select-Object -ExpandProperty NewName -Unique | |
$NewParams = $UsingVariableData.NewName -join ', ' | |
$Tuple = [Tuple]::Create($List, $NewParams) | |
$bindingFlags = [Reflection.BindingFlags]'Default,NonPublic,Instance' | |
$GetWithInputHandlingForInvokeCommandImpl = ($ScriptBlock.ast.gettype().GetMethod('GetWithInputHandlingForInvokeCommandImpl',$bindingFlags)) | |
$StringScriptBlock = $GetWithInputHandlingForInvokeCommandImpl.Invoke($ScriptBlock.ast,@($Tuple)) | |
$ScriptBlock = [scriptblock]::Create($StringScriptBlock) | |
#Write-Verbose $StringScriptBlock | |
} | |
} | |
$ScriptBlock = $ExecutionContext.InvokeCommand.NewScriptBlock("param($($ParamsToAdd -Join ', '))`r`n" + $ScriptBlock.ToString()) | |
} | |
else | |
{ | |
Throw 'Must provide ScriptBlock or ScriptFile' | |
Break | |
} | |
Write-Debug -Message "`$ScriptBlock: $($ScriptBlock | Out-String)" | |
If (-not($SuppressVerbose)){ | |
Write-Verbose -Message 'Creating runspace pool and session states' | |
} | |
#If specified, add variables and modules/snapins to session state | |
$sessionstate = [System.Management.Automation.Runspaces.InitialSessionState]::CreateDefault() | |
if ($ImportVariables) | |
{ | |
if($UserVariables.count -gt 0) | |
{ | |
foreach($Variable in $UserVariables) | |
{ | |
$sessionstate.Variables.Add( (New-Object -TypeName System.Management.Automation.Runspaces.SessionStateVariableEntry -ArgumentList $Variable.Name, $Variable.Value, $null) ) | |
} | |
} | |
} | |
if ($ImportModules) | |
{ | |
if($UserModules.count -gt 0) | |
{ | |
foreach($ModulePath in $UserModules) | |
{ | |
$sessionstate.ImportPSModule($ModulePath) | |
} | |
} | |
if($UserSnapins.count -gt 0) | |
{ | |
foreach($PSSnapin in $UserSnapins) | |
{ | |
[void]$sessionstate.ImportPSSnapIn($PSSnapin, [ref]$null) | |
} | |
} | |
} | |
# -------------------------------------------------- | |
#region - Import Session Functions | |
# -------------------------------------------------- | |
# Import functions from the current session into the RunspacePool sessionstate | |
if($ImportSessionFunctions) | |
{ | |
# Import all session functions into the runspace session state from the current one | |
Get-ChildItem -Path Function:\ | | |
Where-Object -FilterScript { | |
$_.name -notlike '*:*' | |
} | | |
Select-Object -Property name -ExpandProperty name | | |
ForEach-Object -Process { | |
# Get the function code | |
$Definition = Get-Content -Path "function:\$_" -ErrorAction Stop | |
# Create a sessionstate function with the same name and code | |
$SessionStateFunction = New-Object -TypeName System.Management.Automation.Runspaces.SessionStateFunctionEntry -ArgumentList "$_", $Definition | |
# Add the function to the session state | |
$sessionstate.Commands.Add($SessionStateFunction) | |
} | |
} | |
#endregion | |
#Create runspace pool | |
$runspacepool = [runspacefactory]::CreateRunspacePool(1, $Throttle, $sessionstate, $Host) | |
$runspacepool.Open() | |
#Write-Verbose "Creating empty collection to hold runspace jobs" | |
$Script:runspaces = New-Object -TypeName System.Collections.ArrayList | |
#If inputObject is bound get a total count and set bound to true | |
$bound = $PSBoundParameters.keys -contains 'InputObject' | |
if(-not $bound) | |
{ | |
[System.Collections.ArrayList]$allObjects = @() | |
} | |
<# | |
#Set up log file if specified | |
if( $LogFile ){ | |
New-Item -ItemType file -path $logFile -force | Out-Null | |
("" | Select Date, Action, Runtime, Status, Details | ConvertTo-Csv -NoTypeInformation -Delimiter ";")[0] | Out-File $LogFile | |
} | |
#write initial log entry | |
$log = "" | Select Date, Action, Runtime, Status, Details | |
$log.Date = Get-Date | |
$log.Action = "Batch processing started" | |
$log.Runtime = $null | |
$log.Status = "Started" | |
$log.Details = $null | |
if($logFile) { | |
($log | convertto-csv -Delimiter ";" -NoTypeInformation)[1] | Out-File $LogFile -Append | |
} | |
#> | |
$timedOutTasks = $false | |
#endregion INIT | |
} | |
Process { | |
#add piped objects to all objects or set all objects to bound input object parameter | |
if($bound) | |
{ | |
$allObjects = $InputObject | |
} | |
Else | |
{ | |
[void]$allObjects.add( $InputObject ) | |
} | |
} | |
End { | |
#Use Try/Finally to catch Ctrl+C and clean up. | |
Try | |
{ | |
#counts for progress | |
$totalCount = $allObjects.count | |
$script:completedCount = 0 | |
$startedCount = 0 | |
foreach($object in $allObjects) | |
{ | |
#region add scripts to runspace pool | |
#Create the powershell instance, set verbose if needed, supply the scriptblock and parameters | |
$powershell = [powershell]::Create() | |
if ($VerbosePreference -eq 'Continue') | |
{ | |
[void]$powershell.AddScript({ | |
$VerbosePreference = 'Continue' | |
}) | |
} | |
[void]$powershell.AddScript($ScriptBlock).AddArgument($object) | |
if ($Parameter) | |
{ | |
[void]$powershell.AddArgument($Parameter) | |
} | |
# $Using support from Boe Prox | |
if ($UsingVariableData) | |
{ | |
Foreach($UsingVariable in $UsingVariableData) | |
{ | |
#Write-Verbose "Adding $($UsingVariable.Name) with value: $($UsingVariable.Value)" | |
[void]$powershell.AddArgument($UsingVariable.Value) | |
} | |
} | |
#Add the runspace into the powershell instance | |
$powershell.RunspacePool = $runspacepool | |
#Create a temporary collection for each runspace | |
$temp = '' | Select-Object -Property PowerShell, StartTime, object, Runspace | |
$temp.PowerShell = $powershell | |
$temp.StartTime = Get-Date | |
$temp.object = $object | |
#Save the handle output when calling BeginInvoke() that will be used later to end the runspace | |
$temp.Runspace = $powershell.BeginInvoke() | |
$startedCount++ | |
#Add the temp tracking info to $runspaces collection | |
#Write-Verbose ( "Adding {0} to collection at {1}" -f $temp.object, $temp.starttime.tostring() ) | |
$null = $runspaces.Add($temp) | |
#loop through existing runspaces one time | |
Get-RunspaceData | |
#If we have more running than max queue (used to control timeout accuracy) | |
#Script scope resolves odd PowerShell 2 issue | |
$firstRun = $true | |
while ($runspaces.count -ge $script:MaxQueue) | |
{ | |
#give verbose output | |
if($firstRun) | |
{ | |
#Write-Verbose "$($runspaces.count) items running - exceeded $Script:MaxQueue limit." | |
} | |
$firstRun = $false | |
#run get-runspace data and sleep for a short while | |
Get-RunspaceData | |
Start-Sleep -Milliseconds $SleepTimer | |
} | |
#endregion add scripts to runspace pool | |
} | |
#Write-Verbose ( "Finish processing the remaining runspace jobs: {0}" -f ( @($runspaces | Where {$_.Runspace -ne $Null}).Count) ) | |
Get-RunspaceData -wait | |
if (-not $Quiet) | |
{ | |
Write-Progress -Activity 'Running Query' -Status 'Starting threads' -Completed | |
} | |
} | |
Finally | |
{ | |
#Close the runspace pool, unless we specified no close on timeout and something timed out | |
if ( ($timedOutTasks -eq $false) -or ( ($timedOutTasks -eq $true) -and ($NoCloseOnTimeout -eq $false) ) ) | |
{ | |
If (-not($SuppressVerbose)){ | |
Write-Verbose -Message 'Closing the runspace pool' | |
} | |
$runspacepool.close() | |
} | |
#collect garbage | |
[gc]::Collect() | |
} | |
} | |
} | |
<# | |
$targets = import-csv .\DomainComputersTarget.txt | |
$MyScriptBlock = { | |
Get-MySMBShare -ComputerName $_.ComputerName -verbose | |
} | |
$Results = $targets | Invoke-Parallel -ScriptBlock $MyScriptBlock -ImportSessionFunctions -ImportVariables -Throttle 20 -Quiet -RunspaceTimeout 2 -ErrorAction SilentlyContinue | |
$Results | export-csv -NoTypeInformation NetworkShareInventory_good_output.csv | |
#> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment