Last active
June 4, 2021 08:15
-
-
Save daniel0x00/92469fe63fccf2bb055933a9e0705392 to your computer and use it in GitHub Desktop.
PowerView 3 'light' version, just few commands to avoid AV detection.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Author: Will Schroeder (@harmj0y) | |
# License: BSD 3-Clause | |
# Web: http://www.harmj0y.net/blog/ | |
# | |
# Original project: https://github.com/PowerShellMafia/PowerSploit/tree/master/Recon | |
function Get-NetLocalGroupMember { | |
<# | |
.SYNOPSIS | |
Enumerates members of a specific local group on the local (or remote) machine. | |
Author: Will Schroeder (@harmj0y) | |
License: BSD 3-Clause | |
Required Dependencies: PSReflect, Convert-ADName | |
.DESCRIPTION | |
This function will enumerate the members of a specified local group on the | |
current, or remote, machine. By default, the Win32 API call NetLocalGroupGetMembers | |
will be used (for speed). Specifying "-Method WinNT" causes the WinNT service provider | |
to be used instead, which returns a larger amount of information. | |
.PARAMETER ComputerName | |
Specifies the hostname to query for sessions (also accepts IP addresses). | |
Defaults to the localhost. | |
.PARAMETER GroupName | |
The local group name to query for users. If not given, it defaults to "Administrators". | |
.PARAMETER Method | |
The collection method to use, defaults to 'API', also accepts 'WinNT'. | |
.PARAMETER Credential | |
A [Management.Automation.PSCredential] object of alternate credentials | |
for connection to a remote machine. Only applicable with "-Method WinNT". | |
.EXAMPLE | |
Get-NetLocalGroupMember | ft | |
ComputerName GroupName MemberName SID IsGroup IsDomain | |
------------ --------- ---------- --- ------- -------- | |
WINDOWS1 Administrators WINDOWS1\Ad... S-1-5-21-25... False False | |
WINDOWS1 Administrators WINDOWS1\lo... S-1-5-21-25... False False | |
WINDOWS1 Administrators TESTLAB\Dom... S-1-5-21-89... True True | |
WINDOWS1 Administrators TESTLAB\har... S-1-5-21-89... False True | |
.EXAMPLE | |
Get-NetLocalGroupMember -Method winnt | ft | |
ComputerName GroupName MemberName SID IsGroup IsDomain | |
------------ --------- ---------- --- ------- -------- | |
WINDOWS1 Administrators WINDOWS1\Ad... S-1-5-21-25... False False | |
WINDOWS1 Administrators WINDOWS1\lo... S-1-5-21-25... False False | |
WINDOWS1 Administrators TESTLAB\Dom... S-1-5-21-89... True True | |
WINDOWS1 Administrators TESTLAB\har... S-1-5-21-89... False True | |
.EXAMPLE | |
Get-NetLocalGroup | Get-NetLocalGroupMember | ft | |
ComputerName GroupName MemberName SID IsGroup IsDomain | |
------------ --------- ---------- --- ------- -------- | |
WINDOWS1 Administrators WINDOWS1\Ad... S-1-5-21-25... False False | |
WINDOWS1 Administrators WINDOWS1\lo... S-1-5-21-25... False False | |
WINDOWS1 Administrators TESTLAB\Dom... S-1-5-21-89... True True | |
WINDOWS1 Administrators TESTLAB\har... S-1-5-21-89... False True | |
WINDOWS1 Guests WINDOWS1\Guest S-1-5-21-25... False False | |
WINDOWS1 IIS_IUSRS NT AUTHORIT... S-1-5-17 False False | |
WINDOWS1 Users NT AUTHORIT... S-1-5-4 False False | |
WINDOWS1 Users NT AUTHORIT... S-1-5-11 False False | |
WINDOWS1 Users WINDOWS1\lo... S-1-5-21-25... False UNKNOWN | |
WINDOWS1 Users TESTLAB\Dom... S-1-5-21-89... True UNKNOWN | |
.EXAMPLE | |
Get-NetLocalGroupMember -ComputerName primary.testlab.local | ft | |
ComputerName GroupName MemberName SID IsGroup IsDomain | |
------------ --------- ---------- --- ------- -------- | |
primary.tes... Administrators TESTLAB\Adm... S-1-5-21-89... False False | |
primary.tes... Administrators TESTLAB\loc... S-1-5-21-89... False False | |
primary.tes... Administrators TESTLAB\Ent... S-1-5-21-89... True False | |
primary.tes... Administrators TESTLAB\Dom... S-1-5-21-89... True False | |
.OUTPUTS | |
PowerView.LocalGroupMember.API | |
Custom PSObject with translated group property fields from API results. | |
PowerView.LocalGroupMember.WinNT | |
Custom PSObject with translated group property fields from WinNT results. | |
.LINK | |
http://stackoverflow.com/questions/21288220/get-all-local-members-and-groups-displayed-together | |
http://msdn.microsoft.com/en-us/library/aa772211(VS.85).aspx | |
https://msdn.microsoft.com/en-us/library/windows/desktop/aa370601(v=vs.85).aspx | |
#> | |
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')] | |
[OutputType('PowerView.LocalGroupMember.API')] | |
[OutputType('PowerView.LocalGroupMember.WinNT')] | |
Param( | |
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)] | |
[Alias('HostName', 'dnshostname', 'name')] | |
[ValidateNotNullOrEmpty()] | |
[String[]] | |
$ComputerName = $Env:COMPUTERNAME, | |
[Parameter(ValueFromPipelineByPropertyName = $True)] | |
[ValidateNotNullOrEmpty()] | |
[String] | |
$GroupName = 'Administrators', | |
[ValidateSet('API', 'WinNT')] | |
[Alias('CollectionMethod')] | |
[String] | |
$Method = 'API', | |
[Management.Automation.PSCredential] | |
[Management.Automation.CredentialAttribute()] | |
$Credential = [Management.Automation.PSCredential]::Empty | |
) | |
BEGIN { | |
if ($PSBoundParameters['Credential']) { | |
$LogonToken = Invoke-UserImpersonation -Credential $Credential | |
} | |
} | |
PROCESS { | |
ForEach ($Computer in $ComputerName) { | |
if ($Method -eq '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($Computer, $GroupName, $QueryLevel, [ref]$PtrInfo, -1, [ref]$EntriesRead, [ref]$TotalRead, [ref]$ResumeHandle) | |
# locate the offset of the initial intPtr | |
$Offset = $PtrInfo.ToInt64() | |
$Members = @() | |
# 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 "[Get-NetLocalGroupMember] Error: $(([ComponentModel.Win32Exception] $LastError).Message)" | |
} | |
else { | |
$Member = New-Object PSObject | |
$Member | Add-Member Noteproperty 'ComputerName' $Computer | |
$Member | Add-Member Noteproperty 'GroupName' $GroupName | |
$Member | Add-Member Noteproperty 'MemberName' $Info.lgrmi2_domainandname | |
$Member | Add-Member Noteproperty 'SID' $SidString | |
$IsGroup = $($Info.lgrmi2_sidusage -eq 'SidTypeGroup') | |
$Member | Add-Member Noteproperty 'IsGroup' $IsGroup | |
$Member.PSObject.TypeNames.Insert(0, 'PowerView.LocalGroupMember.API') | |
$Members += $Member | |
} | |
} | |
# 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 = $Members | Where-Object {$_.SID -match '.*-500' -or ($_.SID -match '.*-501')} | Select-Object -Expand SID | |
if ($MachineSid) { | |
$MachineSid = $MachineSid.Substring(0, $MachineSid.LastIndexOf('-')) | |
$Members | ForEach-Object { | |
if ($_.SID -match $MachineSid) { | |
$_ | Add-Member Noteproperty 'IsDomain' $False | |
} | |
else { | |
$_ | Add-Member Noteproperty 'IsDomain' $True | |
} | |
} | |
} | |
else { | |
$Members | ForEach-Object { | |
if ($_.SID -notmatch 'S-1-5-21') { | |
$_ | Add-Member Noteproperty 'IsDomain' $False | |
} | |
else { | |
$_ | Add-Member Noteproperty 'IsDomain' 'UNKNOWN' | |
} | |
} | |
} | |
$Members | |
} | |
else { | |
Write-Verbose "[Get-NetLocalGroupMember] Error: $(([ComponentModel.Win32Exception] $Result).Message)" | |
} | |
} | |
else { | |
# otherwise we're using the WinNT service provider | |
try { | |
$GroupProvider = [ADSI]"WinNT://$Computer/$GroupName,group" | |
$GroupProvider.psbase.Invoke('Members') | ForEach-Object { | |
$Member = New-Object PSObject | |
$Member | Add-Member Noteproperty 'ComputerName' $Computer | |
$Member | Add-Member Noteproperty 'GroupName' $GroupName | |
$LocalUser = ([ADSI]$_) | |
$AdsPath = $LocalUser.InvokeGet('AdsPath').Replace('WinNT://', '') | |
$IsGroup = ($LocalUser.SchemaClassName -like 'group') | |
if(([regex]::Matches($AdsPath, '/')).count -eq 1) { | |
# DOMAIN\user | |
$MemberIsDomain = $True | |
$Name = $AdsPath.Replace('/', '\') | |
} | |
else { | |
# DOMAIN\machine\user | |
$MemberIsDomain = $False | |
$Name = $AdsPath.Substring($AdsPath.IndexOf('/')+1).Replace('/', '\') | |
} | |
$Member | Add-Member Noteproperty 'AccountName' $Name | |
$Member | Add-Member Noteproperty 'SID' ((New-Object System.Security.Principal.SecurityIdentifier($LocalUser.InvokeGet('ObjectSID'),0)).Value) | |
$Member | Add-Member Noteproperty 'IsGroup' $IsGroup | |
$Member | Add-Member Noteproperty 'IsDomain' $MemberIsDomain | |
# if ($MemberIsDomain) { | |
# # translate the binary sid to a string | |
# $Member | Add-Member Noteproperty 'SID' ((New-Object System.Security.Principal.SecurityIdentifier($LocalUser.InvokeGet('ObjectSID'),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' $LocalUser.InvokeGet('LastLogin') | |
# } | |
# catch { | |
# $Member | Add-Member Noteproperty 'LastLogin' '' | |
# } | |
# } | |
# $Member | Add-Member Noteproperty 'PwdLastSet' '' | |
# $Member | Add-Member Noteproperty 'PwdExpired' '' | |
# $Member | Add-Member Noteproperty 'UserFlags' '' | |
# } | |
# else { | |
# # translate the binary sid to a string | |
# $Member | Add-Member Noteproperty 'SID' ((New-Object System.Security.Principal.SecurityIdentifier($LocalUser.InvokeGet('ObjectSID'),0)).Value) | |
# $Member | Add-Member Noteproperty 'Description' ($LocalUser.Description) | |
# 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 | |
} | |
} | |
catch { | |
Write-Verbose "[Get-NetLocalGroupMember] Error for $Computer : $_" | |
} | |
} | |
} | |
} | |
END { | |
if ($LogonToken) { | |
Invoke-RevertToSelf -TokenHandle $LogonToken | |
} | |
} | |
} | |
function Get-DomainComputer { | |
<# | |
.SYNOPSIS | |
Return all computers or specific computer objects in AD. | |
Author: Will Schroeder (@harmj0y) | |
License: BSD 3-Clause | |
Required Dependencies: Get-DomainSearcher, Convert-LDAPProperty | |
.DESCRIPTION | |
Builds a directory searcher object using Get-DomainSearcher, builds a custom | |
LDAP filter based on targeting/filter parameters, and searches for all objects | |
matching the criteria. To only return specific properties, use | |
"-Properties samaccountname,usnchanged,...". By default, all computer objects for | |
the current domain are returned. | |
.PARAMETER Identity | |
A SamAccountName (e.g. WINDOWS10$), DistinguishedName (e.g. CN=WINDOWS10,CN=Computers,DC=testlab,DC=local), | |
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1124), GUID (e.g. 4f16b6bc-7010-4cbf-b628-f3cfe20f6994), | |
or a dns host name (e.g. windows10.testlab.local). Wildcards accepted. | |
.PARAMETER UACFilter | |
Dynamic parameter that accepts one or more values from $UACEnum, including | |
"NOT_X" negation forms. To see all possible values, run '0|ConvertFrom-UACValue -ShowAll'. | |
.PARAMETER Unconstrained | |
Switch. Return computer objects that have unconstrained delegation. | |
.PARAMETER TrustedToAuth | |
Switch. Return computer objects that are trusted to authenticate for other principals. | |
.PARAMETER Printers | |
Switch. Return only printers. | |
.PARAMETER SPN | |
Return computers with a specific service principal name, wildcards accepted. | |
.PARAMETER OperatingSystem | |
Return computers with a specific operating system, wildcards accepted. | |
.PARAMETER ServicePack | |
Return computers with a specific service pack, wildcards accepted. | |
.PARAMETER SiteName | |
Return computers in the specific AD Site name, wildcards accepted. | |
.PARAMETER Ping | |
Switch. Ping each host to ensure it's up before enumerating. | |
.PARAMETER Domain | |
Specifies the domain to use for the query, defaults to the current domain. | |
.PARAMETER LDAPFilter | |
Specifies an LDAP query string that is used to filter Active Directory objects. | |
.PARAMETER Properties | |
Specifies the properties of the output object to retrieve from the server. | |
.PARAMETER SearchBase | |
The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local" | |
Useful for OU queries. | |
.PARAMETER Server | |
Specifies an Active Directory server (domain controller) to bind to. | |
.PARAMETER SearchScope | |
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree). | |
.PARAMETER ResultPageSize | |
Specifies the PageSize to set for the LDAP searcher object. | |
.PARAMETER ServerTimeLimit | |
Specifies the maximum amount of time the server spends searching. Default of 120 seconds. | |
.PARAMETER SecurityMasks | |
Specifies an option for examining security information of a directory object. | |
One of 'Dacl', 'Group', 'None', 'Owner', 'Sacl'. | |
.PARAMETER Tombstone | |
Switch. Specifies that the searcher should also return deleted/tombstoned objects. | |
.PARAMETER FindOne | |
Only return one result object. | |
.PARAMETER Credential | |
A [Management.Automation.PSCredential] object of alternate credentials | |
for connection to the target domain. | |
.PARAMETER Raw | |
Switch. Return raw results instead of translating the fields into a custom PSObject. | |
.EXAMPLE | |
Get-DomainComputer | |
Returns the current computers in current domain. | |
.EXAMPLE | |
Get-DomainComputer -SPN mssql* -Domain testlab.local | |
Returns all MS SQL servers in the testlab.local domain. | |
.EXAMPLE | |
Get-DomainComputer -UACFilter TRUSTED_FOR_DELEGATION,SERVER_TRUST_ACCOUNT -Properties dnshostname | |
Return the dns hostnames of servers trusted for delegation. | |
.EXAMPLE | |
Get-DomainComputer -SearchBase "LDAP://OU=secret,DC=testlab,DC=local" -Unconstrained | |
Search the specified OU for computeres that allow unconstrained delegation. | |
.EXAMPLE | |
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force | |
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword) | |
Get-DomainComputer -Credential $Cred | |
.OUTPUTS | |
PowerView.Computer | |
Custom PSObject with translated computer property fields. | |
PowerView.Computer.Raw | |
The raw DirectoryServices.SearchResult object, if -Raw is enabled. | |
#> | |
[OutputType('PowerView.Computer')] | |
[OutputType('PowerView.Computer.Raw')] | |
[CmdletBinding()] | |
Param ( | |
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)] | |
[Alias('SamAccountName', 'Name', 'DNSHostName')] | |
[String[]] | |
$Identity, | |
[Switch] | |
$Unconstrained, | |
[Switch] | |
$TrustedToAuth, | |
[Switch] | |
$Printers, | |
[ValidateNotNullOrEmpty()] | |
[Alias('ServicePrincipalName')] | |
[String] | |
$SPN, | |
[ValidateNotNullOrEmpty()] | |
[String] | |
$OperatingSystem, | |
[ValidateNotNullOrEmpty()] | |
[String] | |
$ServicePack, | |
[ValidateNotNullOrEmpty()] | |
[String] | |
$SiteName, | |
[Switch] | |
$Ping, | |
[ValidateNotNullOrEmpty()] | |
[String] | |
$Domain, | |
[ValidateNotNullOrEmpty()] | |
[Alias('Filter')] | |
[String] | |
$LDAPFilter, | |
[ValidateNotNullOrEmpty()] | |
[String[]] | |
$Properties, | |
[ValidateNotNullOrEmpty()] | |
[Alias('ADSPath')] | |
[String] | |
$SearchBase, | |
[ValidateNotNullOrEmpty()] | |
[Alias('DomainController')] | |
[String] | |
$Server, | |
[ValidateSet('Base', 'OneLevel', 'Subtree')] | |
[String] | |
$SearchScope = 'Subtree', | |
[ValidateRange(1, 10000)] | |
[Int] | |
$ResultPageSize = 200, | |
[ValidateRange(1, 10000)] | |
[Int] | |
$ServerTimeLimit, | |
[ValidateSet('Dacl', 'Group', 'None', 'Owner', 'Sacl')] | |
[String] | |
$SecurityMasks, | |
[Switch] | |
$Tombstone, | |
[Alias('ReturnOne')] | |
[Switch] | |
$FindOne, | |
[Management.Automation.PSCredential] | |
[Management.Automation.CredentialAttribute()] | |
$Credential = [Management.Automation.PSCredential]::Empty, | |
[Switch] | |
$Raw | |
) | |
DynamicParam { | |
$UACValueNames = [Enum]::GetNames($UACEnum) | |
# add in the negations | |
$UACValueNames = $UACValueNames | ForEach-Object {$_; "NOT_$_"} | |
# create new dynamic parameter | |
New-DynamicParameter -Name UACFilter -ValidateSet $UACValueNames -Type ([array]) | |
} | |
BEGIN { | |
$SearcherArguments = @{} | |
if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain } | |
if ($PSBoundParameters['Properties']) { $SearcherArguments['Properties'] = $Properties } | |
if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase } | |
if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server } | |
if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope } | |
if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize } | |
if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit } | |
if ($PSBoundParameters['SecurityMasks']) { $SearcherArguments['SecurityMasks'] = $SecurityMasks } | |
if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone } | |
if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential } | |
$CompSearcher = Get-DomainSearcher @SearcherArguments | |
} | |
PROCESS { | |
#bind dynamic parameter to a friendly variable | |
if ($PSBoundParameters -and ($PSBoundParameters.Count -ne 0)) { | |
New-DynamicParameter -CreateVariables -BoundParameters $PSBoundParameters | |
} | |
if ($CompSearcher) { | |
$IdentityFilter = '' | |
$Filter = '' | |
$Identity | Where-Object {$_} | ForEach-Object { | |
$IdentityInstance = $_.Replace('(', '\28').Replace(')', '\29') | |
if ($IdentityInstance -match '^S-1-') { | |
$IdentityFilter += "(objectsid=$IdentityInstance)" | |
} | |
elseif ($IdentityInstance -match '^CN=') { | |
$IdentityFilter += "(distinguishedname=$IdentityInstance)" | |
if ((-not $PSBoundParameters['Domain']) -and (-not $PSBoundParameters['SearchBase'])) { | |
# if a -Domain isn't explicitly set, extract the object domain out of the distinguishedname | |
# and rebuild the domain searcher | |
$IdentityDomain = $IdentityInstance.SubString($IdentityInstance.IndexOf('DC=')) -replace 'DC=','' -replace ',','.' | |
Write-Verbose "[Get-DomainComputer] Extracted domain '$IdentityDomain' from '$IdentityInstance'" | |
$SearcherArguments['Domain'] = $IdentityDomain | |
$CompSearcher = Get-DomainSearcher @SearcherArguments | |
if (-not $CompSearcher) { | |
Write-Warning "[Get-DomainComputer] Unable to retrieve domain searcher for '$IdentityDomain'" | |
} | |
} | |
} | |
elseif ($IdentityInstance.Contains('.')) { | |
$IdentityFilter += "(|(name=$IdentityInstance)(dnshostname=$IdentityInstance))" | |
} | |
elseif ($IdentityInstance -imatch '^[0-9A-F]{8}-([0-9A-F]{4}-){3}[0-9A-F]{12}$') { | |
$GuidByteString = (([Guid]$IdentityInstance).ToByteArray() | ForEach-Object { '\' + $_.ToString('X2') }) -join '' | |
$IdentityFilter += "(objectguid=$GuidByteString)" | |
} | |
else { | |
$IdentityFilter += "(name=$IdentityInstance)" | |
} | |
} | |
if ($IdentityFilter -and ($IdentityFilter.Trim() -ne '') ) { | |
$Filter += "(|$IdentityFilter)" | |
} | |
if ($PSBoundParameters['Unconstrained']) { | |
Write-Verbose '[Get-DomainComputer] Searching for computers with for unconstrained delegation' | |
$Filter += '(userAccountControl:1.2.840.113556.1.4.803:=524288)' | |
} | |
if ($PSBoundParameters['TrustedToAuth']) { | |
Write-Verbose '[Get-DomainComputer] Searching for computers that are trusted to authenticate for other principals' | |
$Filter += '(msds-allowedtodelegateto=*)' | |
} | |
if ($PSBoundParameters['Printers']) { | |
Write-Verbose '[Get-DomainComputer] Searching for printers' | |
$Filter += '(objectCategory=printQueue)' | |
} | |
if ($PSBoundParameters['SPN']) { | |
Write-Verbose "[Get-DomainComputer] Searching for computers with SPN: $SPN" | |
$Filter += "(servicePrincipalName=$SPN)" | |
} | |
if ($PSBoundParameters['OperatingSystem']) { | |
Write-Verbose "[Get-DomainComputer] Searching for computers with operating system: $OperatingSystem" | |
$Filter += "(operatingsystem=$OperatingSystem)" | |
} | |
if ($PSBoundParameters['ServicePack']) { | |
Write-Verbose "[Get-DomainComputer] Searching for computers with service pack: $ServicePack" | |
$Filter += "(operatingsystemservicepack=$ServicePack)" | |
} | |
if ($PSBoundParameters['SiteName']) { | |
Write-Verbose "[Get-DomainComputer] Searching for computers with site name: $SiteName" | |
$Filter += "(serverreferencebl=$SiteName)" | |
} | |
if ($PSBoundParameters['LDAPFilter']) { | |
Write-Verbose "[Get-DomainComputer] Using additional LDAP filter: $LDAPFilter" | |
$Filter += "$LDAPFilter" | |
} | |
# build the LDAP filter for the dynamic UAC filter value | |
$UACFilter | Where-Object {$_} | ForEach-Object { | |
if ($_ -match 'NOT_.*') { | |
$UACField = $_.Substring(4) | |
$UACValue = [Int]($UACEnum::$UACField) | |
$Filter += "(!(userAccountControl:1.2.840.113556.1.4.803:=$UACValue))" | |
} | |
else { | |
$UACValue = [Int]($UACEnum::$_) | |
$Filter += "(userAccountControl:1.2.840.113556.1.4.803:=$UACValue)" | |
} | |
} | |
$CompSearcher.filter = "(&(samAccountType=805306369)$Filter)" | |
Write-Verbose "[Get-DomainComputer] Get-DomainComputer filter string: $($CompSearcher.filter)" | |
if ($PSBoundParameters['FindOne']) { $Results = $CompSearcher.FindOne() } | |
else { $Results = $CompSearcher.FindAll() } | |
$Results | Where-Object {$_} | ForEach-Object { | |
$Up = $True | |
if ($PSBoundParameters['Ping']) { | |
$Up = Test-Connection -Count 1 -Quiet -ComputerName $_.properties.dnshostname | |
} | |
if ($Up) { | |
if ($PSBoundParameters['Raw']) { | |
# return raw result objects | |
$Computer = $_ | |
$Computer.PSObject.TypeNames.Insert(0, 'PowerView.Computer.Raw') | |
} | |
else { | |
$Computer = Convert-LDAPProperty -Properties $_.Properties | |
$Computer.PSObject.TypeNames.Insert(0, 'PowerView.Computer') | |
} | |
$Computer | |
} | |
} | |
if ($Results) { | |
try { $Results.dispose() } | |
catch { | |
Write-Verbose "[Get-DomainComputer] Error disposing of the Results object: $_" | |
} | |
} | |
$CompSearcher.dispose() | |
} | |
} | |
} | |
function Get-DomainUser { | |
<# | |
.SYNOPSIS | |
Return all users or specific user objects in AD. | |
Author: Will Schroeder (@harmj0y) | |
License: BSD 3-Clause | |
Required Dependencies: Get-DomainSearcher, Convert-ADName, Convert-LDAPProperty | |
.DESCRIPTION | |
Builds a directory searcher object using Get-DomainSearcher, builds a custom | |
LDAP filter based on targeting/filter parameters, and searches for all objects | |
matching the criteria. To only return specific properties, use | |
"-Properties samaccountname,usnchanged,...". By default, all user objects for | |
the current domain are returned. | |
.PARAMETER Identity | |
A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=local), | |
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201). | |
Wildcards accepted. Also accepts DOMAIN\user format. | |
.PARAMETER SPN | |
Switch. Only return user objects with non-null service principal names. | |
.PARAMETER AdminCount | |
Switch. Return users with '(adminCount=1)' (meaning are/were privileged). | |
.PARAMETER AllowDelegation | |
Switch. Return user accounts that are not marked as 'sensitive and not allowed for delegation' | |
.PARAMETER DisallowDelegation | |
Switch. Return user accounts that are marked as 'sensitive and not allowed for delegation' | |
.PARAMETER TrustedToAuth | |
Switch. Return computer objects that are trusted to authenticate for other principals. | |
.PARAMETER KerberosPreauthNotRequired | |
Switch. Return user accounts with "Do not require Kerberos preauthentication" set. | |
.PARAMETER Domain | |
Specifies the domain to use for the query, defaults to the current domain. | |
.PARAMETER LDAPFilter | |
Specifies an LDAP query string that is used to filter Active Directory objects. | |
.PARAMETER Properties | |
Specifies the properties of the output object to retrieve from the server. | |
.PARAMETER SearchBase | |
The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local" | |
Useful for OU queries. | |
.PARAMETER Server | |
Specifies an Active Directory server (domain controller) to bind to. | |
.PARAMETER SearchScope | |
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree). | |
.PARAMETER ResultPageSize | |
Specifies the PageSize to set for the LDAP searcher object. | |
.PARAMETER ServerTimeLimit | |
Specifies the maximum amount of time the server spends searching. Default of 120 seconds. | |
.PARAMETER SecurityMasks | |
Specifies an option for examining security information of a directory object. | |
One of 'Dacl', 'Group', 'None', 'Owner', 'Sacl'. | |
.PARAMETER Tombstone | |
Switch. Specifies that the searcher should also return deleted/tombstoned objects. | |
.PARAMETER FindOne | |
Only return one result object. | |
.PARAMETER Credential | |
A [Management.Automation.PSCredential] object of alternate credentials | |
for connection to the target domain. | |
.PARAMETER Raw | |
Switch. Return raw results instead of translating the fields into a custom PSObject. | |
.EXAMPLE | |
Get-DomainUser -Domain testlab.local | |
Return all users for the testlab.local domain | |
.EXAMPLE | |
Get-DomainUser "S-1-5-21-890171859-3433809279-3366196753-1108","administrator" | |
Return the user with the given SID, as well as Administrator. | |
.EXAMPLE | |
'S-1-5-21-890171859-3433809279-3366196753-1114', 'CN=dfm,CN=Users,DC=testlab,DC=local','4c435dd7-dc58-4b14-9a5e-1fdb0e80d201','administrator' | Get-DomainUser -Properties samaccountname,lastlogoff | |
lastlogoff samaccountname | |
---------- -------------- | |
12/31/1600 4:00:00 PM dfm.a | |
12/31/1600 4:00:00 PM dfm | |
12/31/1600 4:00:00 PM harmj0y | |
12/31/1600 4:00:00 PM Administrator | |
.EXAMPLE | |
Get-DomainUser -SearchBase "LDAP://OU=secret,DC=testlab,DC=local" -AdminCount -AllowDelegation | |
Search the specified OU for privileged user (AdminCount = 1) that allow delegation | |
.EXAMPLE | |
Get-DomainUser -LDAPFilter '(!primarygroupid=513)' -Properties samaccountname,lastlogon | |
Search for users with a primary group ID other than 513 ('domain users') and only return samaccountname and lastlogon | |
.EXAMPLE | |
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force | |
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword) | |
Get-DomainUser -Credential $Cred | |
.EXAMPLE | |
Get-Domain | Select-Object -Expand name | |
testlab.local | |
Get-DomainUser dev\user1 -Verbose -Properties distinguishedname | |
VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local | |
VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=dev,DC=testlab,DC=local | |
VERBOSE: [Get-DomainUser] filter string: (&(samAccountType=805306368)(|(samAccountName=user1))) | |
distinguishedname | |
----------------- | |
CN=user1,CN=Users,DC=dev,DC=testlab,DC=local | |
.INPUTS | |
String | |
.OUTPUTS | |
PowerView.User | |
Custom PSObject with translated user property fields. | |
PowerView.User.Raw | |
The raw DirectoryServices.SearchResult object, if -Raw is enabled. | |
#> | |
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')] | |
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')] | |
[OutputType('PowerView.User')] | |
[OutputType('PowerView.User.Raw')] | |
[CmdletBinding(DefaultParameterSetName = 'AllowDelegation')] | |
Param( | |
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)] | |
[Alias('DistinguishedName', 'SamAccountName', 'Name', 'MemberDistinguishedName', 'MemberName')] | |
[String[]] | |
$Identity, | |
[Switch] | |
$SPN, | |
[Switch] | |
$AdminCount, | |
[Parameter(ParameterSetName = 'AllowDelegation')] | |
[Switch] | |
$AllowDelegation, | |
[Parameter(ParameterSetName = 'DisallowDelegation')] | |
[Switch] | |
$DisallowDelegation, | |
[Switch] | |
$TrustedToAuth, | |
[Switch] | |
$KerberosPreauthNotRequired, | |
[ValidateNotNullOrEmpty()] | |
[String] | |
$Domain, | |
[ValidateNotNullOrEmpty()] | |
[Alias('Filter')] | |
[String] | |
$LDAPFilter, | |
[ValidateNotNullOrEmpty()] | |
[String[]] | |
$Properties, | |
[ValidateNotNullOrEmpty()] | |
[Alias('ADSPath')] | |
[String] | |
$SearchBase, | |
[ValidateNotNullOrEmpty()] | |
[Alias('DomainController')] | |
[String] | |
$Server, | |
[ValidateSet('Base', 'OneLevel', 'Subtree')] | |
[String] | |
$SearchScope = 'Subtree', | |
[ValidateRange(1, 10000)] | |
[Int] | |
$ResultPageSize = 200, | |
[ValidateRange(1, 10000)] | |
[Int] | |
$ServerTimeLimit, | |
[ValidateSet('Dacl', 'Group', 'None', 'Owner', 'Sacl')] | |
[String] | |
$SecurityMasks, | |
[Switch] | |
$Tombstone, | |
[Alias('ReturnOne')] | |
[Switch] | |
$FindOne, | |
[Management.Automation.PSCredential] | |
[Management.Automation.CredentialAttribute()] | |
$Credential = [Management.Automation.PSCredential]::Empty, | |
[Switch] | |
$Raw | |
) | |
BEGIN { | |
$SearcherArguments = @{} | |
if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain } | |
if ($PSBoundParameters['Properties']) { $SearcherArguments['Properties'] = $Properties } | |
if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase } | |
if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server } | |
if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope } | |
if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize } | |
if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit } | |
if ($PSBoundParameters['SecurityMasks']) { $SearcherArguments['SecurityMasks'] = $SecurityMasks } | |
if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone } | |
if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential } | |
$UserSearcher = Get-DomainSearcher @SearcherArguments | |
} | |
PROCESS { | |
if ($UserSearcher) { | |
$IdentityFilter = '' | |
$Filter = '' | |
$Identity | Where-Object {$_} | ForEach-Object { | |
$IdentityInstance = $_ | |
if ($IdentityInstance -match '.+\\.+') { | |
$ConvertedIdentityInstance = $IdentityInstance | Convert-ADName -OutputType Canonical | |
if ($ConvertedIdentityInstance) { | |
$UserDomain = $ConvertedIdentityInstance.SubString(0, $ConvertedIdentityInstance.IndexOf('/')) | |
$UserName = $IdentityInstance.Split('\')[1] | |
$IdentityFilter += "(samAccountName=$UserName)" | |
$SearcherArguments['Domain'] = $UserDomain | |
Write-Verbose "[Get-DomainUser] Extracted domain '$UserDomain' from '$IdentityInstance'" | |
$UserSearcher = Get-DomainSearcher @SearcherArguments | |
} | |
} | |
elseif ($IdentityInstance -match '^S-1-.*') { | |
# SID format | |
$IdentityFilter += "(objectsid=$IdentityInstance)" | |
} | |
elseif ($IdentityInstance -match '^CN=.*') { | |
# distinguished names | |
$IdentityFilter += "(distinguishedname=$IdentityInstance)" | |
} | |
else { | |
try { | |
$GuidByteString = (-Join (([Guid]$IdentityInstance).ToByteArray() | ForEach-Object {$_.ToString('X').PadLeft(2,'0')})) -Replace '(..)','\$1' | |
$IdentityFilter += "(objectguid=$GuidByteString)" | |
} | |
catch { | |
$IdentityFilter += "(samAccountName=$IdentityInstance)" | |
} | |
} | |
} | |
if ($IdentityFilter -and ($IdentityFilter.Trim() -ne '') ) { | |
$Filter += "(|$IdentityFilter)" | |
} | |
if ($PSBoundParameters['SPN']) { | |
Write-Verbose '[Get-DomainUser] Searching for non-null service principal names' | |
$Filter += '(servicePrincipalName=*)' | |
} | |
if ($PSBoundParameters['AllowDelegation']) { | |
Write-Verbose '[Get-DomainUser] Searching 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 ($PSBoundParameters['DisallowDelegation']) { | |
Write-Verbose '[Get-DomainUser] Searching for users who are sensitive and not trusted for delegation' | |
$Filter += '(userAccountControl:1.2.840.113556.1.4.803:=1048574)' | |
} | |
if ($PSBoundParameters['AdminCount']) { | |
Write-Verbose '[Get-DomainUser] Searching for adminCount=1' | |
$Filter += '(admincount=1)' | |
} | |
if ($PSBoundParameters['TrustedToAuth']) { | |
Write-Verbose '[Get-DomainUser] Searching for users that are trusted to authenticate for other principals' | |
$Filter += '(msds-allowedtodelegateto=*)' | |
} | |
if ($PSBoundParameters['KerberosPreauthNotRequireduthNotRequired']) { | |
Write-Verbose '[Get-DomainUser] Searching for user accounts that do not require kerberos preauthenticate' | |
$Filter += '(userAccountControl:1.2.840.113556.1.4.803:=4194304)' | |
} | |
if ($PSBoundParameters['LDAPFilter']) { | |
Write-Verbose "[Get-DomainUser] Using additional LDAP filter: $LDAPFilter" | |
$Filter += "$LDAPFilter" | |
} | |
$UserSearcher.filter = "(&(samAccountType=805306368)$Filter)" | |
Write-Verbose "[Get-DomainUser] filter string: $($UserSearcher.filter)" | |
if ($PSBoundParameters['FindOne']) { $Results = $UserSearcher.FindOne() } | |
else { $Results = $UserSearcher.FindAll() } | |
$Results | Where-Object {$_} | ForEach-Object { | |
if ($PSBoundParameters['Raw']) { | |
# return raw result objects | |
$User = $_ | |
$User.PSObject.TypeNames.Insert(0, 'PowerView.User.Raw') | |
} | |
else { | |
$User = Convert-LDAPProperty -Properties $_.Properties | |
$User.PSObject.TypeNames.Insert(0, 'PowerView.User') | |
} | |
$User | |
} | |
if ($Results) { | |
try { $Results.dispose() } | |
catch { | |
Write-Verbose "[Get-DomainUser] Error disposing of the Results object: $_" | |
} | |
} | |
$UserSearcher.dispose() | |
} | |
} | |
} | |
function Get-Domain { | |
<# | |
.SYNOPSIS | |
Returns the domain object for the current (or specified) domain. | |
Author: Will Schroeder (@harmj0y) | |
License: BSD 3-Clause | |
Required Dependencies: None | |
.DESCRIPTION | |
Returns a System.DirectoryServices.ActiveDirectory.Domain object for the current | |
domain or the domain specified with -Domain X. | |
.PARAMETER Domain | |
Specifies the domain name to query for, defaults to the current domain. | |
.PARAMETER Credential | |
A [Management.Automation.PSCredential] object of alternate credentials | |
for connection to the target domain. | |
.EXAMPLE | |
Get-Domain -Domain testlab.local | |
.EXAMPLE | |
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force | |
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword) | |
Get-Domain -Credential $Cred | |
.OUTPUTS | |
System.DirectoryServices.ActiveDirectory.Domain | |
A complex .NET domain object. | |
.LINK | |
http://social.technet.microsoft.com/Forums/scriptcenter/en-US/0c5b3f83-e528-4d49-92a4-dee31f4b481c/finding-the-dn-of-the-the-domain-without-admodule-in-powershell?forum=ITCG | |
#> | |
[OutputType([System.DirectoryServices.ActiveDirectory.Domain])] | |
[CmdletBinding()] | |
Param( | |
[Parameter(Position = 0, ValueFromPipeline = $True)] | |
[ValidateNotNullOrEmpty()] | |
[String] | |
$Domain, | |
[Management.Automation.PSCredential] | |
[Management.Automation.CredentialAttribute()] | |
$Credential = [Management.Automation.PSCredential]::Empty | |
) | |
PROCESS { | |
if ($PSBoundParameters['Credential']) { | |
Write-Verbose '[Get-Domain] Using alternate credentials for Get-Domain' | |
if ($PSBoundParameters['Domain']) { | |
$TargetDomain = $Domain | |
} | |
else { | |
# if no domain is supplied, extract the logon domain from the PSCredential passed | |
$TargetDomain = $Credential.GetNetworkCredential().Domain | |
Write-Verbose "[Get-Domain] Extracted domain '$TargetDomain' from -Credential" | |
} | |
$DomainContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext('Domain', $TargetDomain, $Credential.UserName, $Credential.GetNetworkCredential().Password) | |
try { | |
[System.DirectoryServices.ActiveDirectory.Domain]::GetDomain($DomainContext) | |
} | |
catch { | |
Write-Verbose "[Get-Domain] The specified domain does '$TargetDomain' not exist, could not be contacted, there isn't an existing trust, or the specified credentials are invalid: $_" | |
} | |
} | |
elseif ($PSBoundParameters['Domain']) { | |
$DomainContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext('Domain', $Domain) | |
try { | |
[System.DirectoryServices.ActiveDirectory.Domain]::GetDomain($DomainContext) | |
} | |
catch { | |
Write-Verbose "[Get-Domain] The specified domain '$Domain' does not exist, could not be contacted, or there isn't an existing trust : $_" | |
} | |
} | |
else { | |
try { | |
[System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain() | |
} | |
catch { | |
Write-Verbose "[Get-Domain] Error retrieving the current domain: $_" | |
} | |
} | |
} | |
} | |
function Get-DomainController { | |
<# | |
.SYNOPSIS | |
Return the domain controllers for the current (or specified) domain. | |
Author: Will Schroeder (@harmj0y) | |
License: BSD 3-Clause | |
Required Dependencies: Get-DomainComputer, Get-Domain | |
.DESCRIPTION | |
Enumerates the domain controllers for the current or specified domain. | |
By default built in .NET methods are used. The -LDAP switch uses Get-DomainComputer | |
to search for domain controllers. | |
.PARAMETER Domain | |
The domain to query for domain controllers, defaults to the current domain. | |
.PARAMETER Server | |
Specifies an Active Directory server (domain controller) to bind to. | |
.PARAMETER LDAP | |
Switch. Use LDAP queries to determine the domain controllers instead of built in .NET methods. | |
.PARAMETER Credential | |
A [Management.Automation.PSCredential] object of alternate credentials | |
for connection to the target domain. | |
.EXAMPLE | |
Get-DomainController -Domain 'test.local' | |
Determine the domain controllers for 'test.local'. | |
.EXAMPLE | |
Get-DomainController -Domain 'test.local' -LDAP | |
Determine the domain controllers for 'test.local' using LDAP queries. | |
.EXAMPLE | |
'test.local' | Get-DomainController | |
Determine the domain controllers for 'test.local'. | |
.EXAMPLE | |
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force | |
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword) | |
Get-DomainController -Credential $Cred | |
.OUTPUTS | |
PowerView.Computer | |
Outputs custom PSObjects with details about the enumerated domain controller if -LDAP is specified. | |
System.DirectoryServices.ActiveDirectory.DomainController | |
If -LDAP isn't specified. | |
#> | |
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')] | |
[OutputType('PowerView.Computer')] | |
[OutputType('System.DirectoryServices.ActiveDirectory.DomainController')] | |
[CmdletBinding()] | |
Param( | |
[Parameter(Position = 0, ValueFromPipeline = $True)] | |
[String] | |
$Domain, | |
[ValidateNotNullOrEmpty()] | |
[Alias('DomainController')] | |
[String] | |
$Server, | |
[Switch] | |
$LDAP, | |
[Management.Automation.PSCredential] | |
[Management.Automation.CredentialAttribute()] | |
$Credential = [Management.Automation.PSCredential]::Empty | |
) | |
PROCESS { | |
$Arguments = @{} | |
if ($PSBoundParameters['Domain']) { $Arguments['Domain'] = $Domain } | |
if ($PSBoundParameters['Credential']) { $Arguments['Credential'] = $Credential } | |
if ($PSBoundParameters['LDAP'] -or $PSBoundParameters['Server']) { | |
if ($PSBoundParameters['Server']) { $Arguments['Server'] = $Server } | |
# UAC specification for domain controllers | |
$Arguments['LDAPFilter'] = '(userAccountControl:1.2.840.113556.1.4.803:=8192)' | |
Get-DomainComputer @Arguments | |
} | |
else { | |
$FoundDomain = Get-Domain @Arguments | |
if ($FoundDomain) { | |
$FoundDomain.DomainControllers | |
} | |
} | |
} | |
} | |
function Get-DomainObject { | |
<# | |
.SYNOPSIS | |
Return all (or specified) domain objects in AD. | |
Author: Will Schroeder (@harmj0y) | |
License: BSD 3-Clause | |
Required Dependencies: Get-DomainSearcher, Convert-LDAPProperty, Convert-ADName | |
.DESCRIPTION | |
Builds a directory searcher object using Get-DomainSearcher, builds a custom | |
LDAP filter based on targeting/filter parameters, and searches for all objects | |
matching the criteria. To only return specific properties, use | |
"-Properties samaccountname,usnchanged,...". By default, all objects for | |
the current domain are returned. | |
.PARAMETER Identity | |
A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=local), | |
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201). | |
Wildcards accepted. | |
.PARAMETER UACFilter | |
Dynamic parameter that accepts one or more values from $UACEnum, including | |
"NOT_X" negation forms. To see all possible values, run '0|ConvertFrom-UACValue -ShowAll'. | |
.PARAMETER Domain | |
Specifies the domain to use for the query, defaults to the current domain. | |
.PARAMETER LDAPFilter | |
Specifies an LDAP query string that is used to filter Active Directory objects. | |
.PARAMETER Properties | |
Specifies the properties of the output object to retrieve from the server. | |
.PARAMETER SearchBase | |
The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local" | |
Useful for OU queries. | |
.PARAMETER Server | |
Specifies an Active Directory server (domain controller) to bind to. | |
.PARAMETER SearchScope | |
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree). | |
.PARAMETER ResultPageSize | |
Specifies the PageSize to set for the LDAP searcher object. | |
.PARAMETER ServerTimeLimit | |
Specifies the maximum amount of time the server spends searching. Default of 120 seconds. | |
.PARAMETER SecurityMasks | |
Specifies an option for examining security information of a directory object. | |
One of 'Dacl', 'Group', 'None', 'Owner', 'Sacl'. | |
.PARAMETER Tombstone | |
Switch. Specifies that the searcher should also return deleted/tombstoned objects. | |
.PARAMETER FindOne | |
Only return one result object. | |
.PARAMETER Credential | |
A [Management.Automation.PSCredential] object of alternate credentials | |
for connection to the target domain. | |
.PARAMETER Raw | |
Switch. Return raw results instead of translating the fields into a custom PSObject. | |
.EXAMPLE | |
Get-DomainObject -Domain testlab.local | |
Return all objects for the testlab.local domain | |
.EXAMPLE | |
'S-1-5-21-890171859-3433809279-3366196753-1003', 'CN=dfm,CN=Users,DC=testlab,DC=local','b6a9a2fb-bbd5-4f28-9a09-23213cea6693','dfm.a' | Get-DomainObject -Properties distinguishedname | |
distinguishedname | |
----------------- | |
CN=PRIMARY,OU=Domain Controllers,DC=testlab,DC=local | |
CN=dfm,CN=Users,DC=testlab,DC=local | |
OU=OU3,DC=testlab,DC=local | |
CN=dfm (admin),CN=Users,DC=testlab,DC=local | |
.EXAMPLE | |
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force | |
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword) | |
Get-DomainObject -Credential $Cred -Identity 'windows1' | |
.EXAMPLE | |
Get-Domain | Select-Object -Expand name | |
testlab.local | |
'testlab\harmj0y','DEV\Domain Admins' | Get-DomainObject -Verbose -Properties distinguishedname | |
VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local | |
VERBOSE: [Get-DomainUser] Extracted domain 'testlab.local' from 'testlab\harmj0y' | |
VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local | |
VERBOSE: [Get-DomainObject] Get-DomainObject filter string: (&(|(samAccountName=harmj0y))) | |
distinguishedname | |
----------------- | |
CN=harmj0y,CN=Users,DC=testlab,DC=local | |
VERBOSE: [Get-DomainUser] Extracted domain 'dev.testlab.local' from 'DEV\Domain Admins' | |
VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=dev,DC=testlab,DC=local | |
VERBOSE: [Get-DomainObject] Get-DomainObject filter string: (&(|(samAccountName=Domain Admins))) | |
CN=Domain Admins,CN=Users,DC=dev,DC=testlab,DC=local | |
.OUTPUTS | |
PowerView.ADObject | |
Custom PSObject with translated AD object property fields. | |
PowerView.ADObject.Raw | |
The raw DirectoryServices.SearchResult object, if -Raw is enabled. | |
#> | |
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')] | |
[OutputType('PowerView.ADObject')] | |
[OutputType('PowerView.ADObject.Raw')] | |
[CmdletBinding()] | |
Param( | |
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)] | |
[Alias('DistinguishedName', 'SamAccountName', 'Name', 'MemberDistinguishedName', 'MemberName')] | |
[String[]] | |
$Identity, | |
[ValidateNotNullOrEmpty()] | |
[String] | |
$Domain, | |
[ValidateNotNullOrEmpty()] | |
[Alias('Filter')] | |
[String] | |
$LDAPFilter, | |
[ValidateNotNullOrEmpty()] | |
[String[]] | |
$Properties, | |
[ValidateNotNullOrEmpty()] | |
[Alias('ADSPath')] | |
[String] | |
$SearchBase, | |
[ValidateNotNullOrEmpty()] | |
[Alias('DomainController')] | |
[String] | |
$Server, | |
[ValidateSet('Base', 'OneLevel', 'Subtree')] | |
[String] | |
$SearchScope = 'Subtree', | |
[ValidateRange(1, 10000)] | |
[Int] | |
$ResultPageSize = 200, | |
[ValidateRange(1, 10000)] | |
[Int] | |
$ServerTimeLimit, | |
[ValidateSet('Dacl', 'Group', 'None', 'Owner', 'Sacl')] | |
[String] | |
$SecurityMasks, | |
[Switch] | |
$Tombstone, | |
[Alias('ReturnOne')] | |
[Switch] | |
$FindOne, | |
[Management.Automation.PSCredential] | |
[Management.Automation.CredentialAttribute()] | |
$Credential = [Management.Automation.PSCredential]::Empty, | |
[Switch] | |
$Raw | |
) | |
DynamicParam { | |
$UACValueNames = [Enum]::GetNames($UACEnum) | |
# add in the negations | |
$UACValueNames = $UACValueNames | ForEach-Object {$_; "NOT_$_"} | |
# create new dynamic parameter | |
New-DynamicParameter -Name UACFilter -ValidateSet $UACValueNames -Type ([array]) | |
} | |
BEGIN { | |
$SearcherArguments = @{} | |
if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain } | |
if ($PSBoundParameters['Properties']) { $SearcherArguments['Properties'] = $Properties } | |
if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase } | |
if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server } | |
if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope } | |
if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize } | |
if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit } | |
if ($PSBoundParameters['SecurityMasks']) { $SearcherArguments['SecurityMasks'] = $SecurityMasks } | |
if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone } | |
if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential } | |
$ObjectSearcher = Get-DomainSearcher @SearcherArguments | |
} | |
PROCESS { | |
#bind dynamic parameter to a friendly variable | |
if ($PSBoundParameters -and ($PSBoundParameters.Count -ne 0)) { | |
New-DynamicParameter -CreateVariables -BoundParameters $PSBoundParameters | |
} | |
if ($ObjectSearcher) { | |
$IdentityFilter = '' | |
$Filter = '' | |
$Identity | Where-Object {$_} | ForEach-Object { | |
$IdentityInstance = $_.Replace('(', '\28').Replace(')', '\29') | |
if ($IdentityInstance -match '^S-1-') { | |
$IdentityFilter += "(objectsid=$IdentityInstance)" | |
} | |
elseif ($IdentityInstance -match '^(CN|OU|DC)=') { | |
$IdentityFilter += "(distinguishedname=$IdentityInstance)" | |
if ((-not $PSBoundParameters['Domain']) -and (-not $PSBoundParameters['SearchBase'])) { | |
# if a -Domain isn't explicitly set, extract the object domain out of the distinguishedname | |
# and rebuild the domain searcher | |
$IdentityDomain = $IdentityInstance.SubString($IdentityInstance.IndexOf('DC=')) -replace 'DC=','' -replace ',','.' | |
Write-Verbose "[Get-DomainObject] Extracted domain '$IdentityDomain' from '$IdentityInstance'" | |
$SearcherArguments['Domain'] = $IdentityDomain | |
$ObjectSearcher = Get-DomainSearcher @SearcherArguments | |
if (-not $ObjectSearcher) { | |
Write-Warning "[Get-DomainObject] Unable to retrieve domain searcher for '$IdentityDomain'" | |
} | |
} | |
} | |
elseif ($IdentityInstance -imatch '^[0-9A-F]{8}-([0-9A-F]{4}-){3}[0-9A-F]{12}$') { | |
$GuidByteString = (([Guid]$IdentityInstance).ToByteArray() | ForEach-Object { '\' + $_.ToString('X2') }) -join '' | |
$IdentityFilter += "(objectguid=$GuidByteString)" | |
} | |
elseif ($IdentityInstance.Contains('\')) { | |
$ConvertedIdentityInstance = $IdentityInstance.Replace('\28', '(').Replace('\29', ')') | Convert-ADName -OutputType Canonical | |
if ($ConvertedIdentityInstance) { | |
$ObjectDomain = $ConvertedIdentityInstance.SubString(0, $ConvertedIdentityInstance.IndexOf('/')) | |
$ObjectName = $IdentityInstance.Split('\')[1] | |
$IdentityFilter += "(samAccountName=$ObjectName)" | |
$SearcherArguments['Domain'] = $ObjectDomain | |
Write-Verbose "[Get-DomainObject] Extracted domain '$ObjectDomain' from '$IdentityInstance'" | |
$ObjectSearcher = Get-DomainSearcher @SearcherArguments | |
} | |
} | |
elseif ($IdentityInstance.Contains('.')) { | |
$IdentityFilter += "(|(samAccountName=$IdentityInstance)(name=$IdentityInstance)(dnshostname=$IdentityInstance))" | |
} | |
else { | |
$IdentityFilter += "(|(samAccountName=$IdentityInstance)(name=$IdentityInstance)(displayname=$IdentityInstance))" | |
} | |
} | |
if ($IdentityFilter -and ($IdentityFilter.Trim() -ne '') ) { | |
$Filter += "(|$IdentityFilter)" | |
} | |
if ($PSBoundParameters['LDAPFilter']) { | |
Write-Verbose "[Get-DomainObject] Using additional LDAP filter: $LDAPFilter" | |
$Filter += "$LDAPFilter" | |
} | |
# build the LDAP filter for the dynamic UAC filter value | |
$UACFilter | Where-Object {$_} | ForEach-Object { | |
if ($_ -match 'NOT_.*') { | |
$UACField = $_.Substring(4) | |
$UACValue = [Int]($UACEnum::$UACField) | |
$Filter += "(!(userAccountControl:1.2.840.113556.1.4.803:=$UACValue))" | |
} | |
else { | |
$UACValue = [Int]($UACEnum::$_) | |
$Filter += "(userAccountControl:1.2.840.113556.1.4.803:=$UACValue)" | |
} | |
} | |
if ($Filter -and $Filter -ne '') { | |
$ObjectSearcher.filter = "(&$Filter)" | |
} | |
Write-Verbose "[Get-DomainObject] Get-DomainObject filter string: $($ObjectSearcher.filter)" | |
if ($PSBoundParameters['FindOne']) { $Results = $ObjectSearcher.FindOne() } | |
else { $Results = $ObjectSearcher.FindAll() } | |
$Results | Where-Object {$_} | ForEach-Object { | |
if ($PSBoundParameters['Raw']) { | |
# return raw result objects | |
$Object = $_ | |
$Object.PSObject.TypeNames.Insert(0, 'PowerView.ADObject.Raw') | |
} | |
else { | |
$Object = Convert-LDAPProperty -Properties $_.Properties | |
$Object.PSObject.TypeNames.Insert(0, 'PowerView.ADObject') | |
} | |
$Object | |
} | |
if ($Results) { | |
try { $Results.dispose() } | |
catch { | |
Write-Verbose "[Get-DomainObject] Error disposing of the Results object: $_" | |
} | |
} | |
$ObjectSearcher.dispose() | |
} | |
} | |
} | |
function Convert-ADName { | |
<# | |
.SYNOPSIS | |
Converts Active Directory object names between a variety of formats. | |
Author: Bill Stewart, Pasquale Lantella | |
Modifications: Will Schroeder (@harmj0y) | |
License: BSD 3-Clause | |
Required Dependencies: None | |
.DESCRIPTION | |
This function is heavily based on Bill Stewart's code and Pasquale Lantella's code (in LINK) | |
and translates Active Directory names between various formats using the NameTranslate COM object. | |
.PARAMETER Identity | |
Specifies the Active Directory object name to translate, of the following form: | |
DN short for 'distinguished name'; e.g., 'CN=Phineas Flynn,OU=Engineers,DC=fabrikam,DC=com' | |
Canonical canonical name; e.g., 'fabrikam.com/Engineers/Phineas Flynn' | |
NT4 domain\username; e.g., 'fabrikam\pflynn' | |
Display display name, e.g. 'pflynn' | |
DomainSimple simple domain name format, e.g. 'pflynn@fabrikam.com' | |
EnterpriseSimple simple enterprise name format, e.g. 'pflynn@fabrikam.com' | |
GUID GUID; e.g., '{95ee9fff-3436-11d1-b2b0-d15ae3ac8436}' | |
UPN user principal name; e.g., 'pflynn@fabrikam.com' | |
CanonicalEx extended canonical name format | |
SPN service principal name format; e.g. 'HTTP/kairomac.contoso.com' | |
SID Security Identifier; e.g., 'S-1-5-21-12986231-600641547-709122288-57999' | |
.PARAMETER OutputType | |
Specifies the output name type you want to convert to, which must be one of the following: | |
DN short for 'distinguished name'; e.g., 'CN=Phineas Flynn,OU=Engineers,DC=fabrikam,DC=com' | |
Canonical canonical name; e.g., 'fabrikam.com/Engineers/Phineas Flynn' | |
NT4 domain\username; e.g., 'fabrikam\pflynn' | |
Display display name, e.g. 'pflynn' | |
DomainSimple simple domain name format, e.g. 'pflynn@fabrikam.com' | |
EnterpriseSimple simple enterprise name format, e.g. 'pflynn@fabrikam.com' | |
GUID GUID; e.g., '{95ee9fff-3436-11d1-b2b0-d15ae3ac8436}' | |
UPN user principal name; e.g., 'pflynn@fabrikam.com' | |
CanonicalEx extended canonical name format, e.g. 'fabrikam.com/Users/Phineas Flynn' | |
SPN service principal name format; e.g. 'HTTP/kairomac.contoso.com' | |
.PARAMETER Domain | |
Specifies the domain to use for the translation, defaults to the current domain. | |
.PARAMETER Server | |
Specifies an Active Directory server (domain controller) to bind to for the translation. | |
.PARAMETER Credential | |
Specifies an alternate credential to use for the translation. | |
.EXAMPLE | |
Convert-ADName -Identity "TESTLAB\harmj0y" | |
harmj0y@testlab.local | |
.EXAMPLE | |
"TESTLAB\krbtgt", "CN=Administrator,CN=Users,DC=testlab,DC=local" | Convert-ADName -OutputType Canonical | |
testlab.local/Users/krbtgt | |
testlab.local/Users/Administrator | |
.EXAMPLE | |
Convert-ADName -OutputType dn -Identity 'TESTLAB\harmj0y' -Server PRIMARY.testlab.local | |
CN=harmj0y,CN=Users,DC=testlab,DC=local | |
.EXAMPLE | |
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force | |
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm', $SecPassword) | |
'S-1-5-21-890171859-3433809279-3366196753-1108' | Convert-ADNAme -Credential $Cred | |
TESTLAB\harmj0y | |
.INPUTS | |
String | |
Accepts one or more objects name strings on the pipeline. | |
.OUTPUTS | |
String | |
Outputs a string representing the converted name. | |
.LINK | |
http://windowsitpro.com/active-directory/translating-active-directory-object-names-between-formats | |
https://gallery.technet.microsoft.com/scriptcenter/Translating-Active-5c80dd67 | |
#> | |
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')] | |
[OutputType([String])] | |
[CmdletBinding()] | |
Param( | |
[Parameter(Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)] | |
[Alias('Name', 'ObjectName')] | |
[String[]] | |
$Identity, | |
[String] | |
[ValidateSet('DN', 'Canonical', 'NT4', 'Display', 'DomainSimple', 'EnterpriseSimple', 'GUID', 'Unknown', 'UPN', 'CanonicalEx', 'SPN')] | |
$OutputType, | |
[ValidateNotNullOrEmpty()] | |
[String] | |
$Domain, | |
[ValidateNotNullOrEmpty()] | |
[Alias('DomainController')] | |
[String] | |
$Server, | |
[Management.Automation.PSCredential] | |
[Management.Automation.CredentialAttribute()] | |
$Credential = [Management.Automation.PSCredential]::Empty | |
) | |
BEGIN { | |
$NameTypes = @{ | |
'DN' = 1 # CN=Phineas Flynn,OU=Engineers,DC=fabrikam,DC=com | |
'Canonical' = 2 # fabrikam.com/Engineers/Phineas Flynn | |
'NT4' = 3 # fabrikam\pflynn | |
'Display' = 4 # pflynn | |
'DomainSimple' = 5 # pflynn@fabrikam.com | |
'EnterpriseSimple' = 6 # pflynn@fabrikam.com | |
'GUID' = 7 # {95ee9fff-3436-11d1-b2b0-d15ae3ac8436} | |
'Unknown' = 8 # unknown type - let the server do translation | |
'UPN' = 9 # pflynn@fabrikam.com | |
'CanonicalEx' = 10 # fabrikam.com/Users/Phineas Flynn | |
'SPN' = 11 # HTTP/kairomac.contoso.com | |
'SID' = 12 # S-1-5-21-12986231-600641547-709122288-57999 | |
} | |
# accessor functions from Bill Stewart to simplify calls to NameTranslate | |
function Invoke-Method([__ComObject] $Object, [String] $Method, $Parameters) { | |
$Output = $Null | |
$Output = $Object.GetType().InvokeMember($Method, 'InvokeMethod', $NULL, $Object, $Parameters) | |
Write-Output $Output | |
} | |
function Get-Property([__ComObject] $Object, [String] $Property) { | |
$Object.GetType().InvokeMember($Property, 'GetProperty', $NULL, $Object, $NULL) | |
} | |
function Set-Property([__ComObject] $Object, [String] $Property, $Parameters) { | |
[Void] $Object.GetType().InvokeMember($Property, 'SetProperty', $NULL, $Object, $Parameters) | |
} | |
# https://msdn.microsoft.com/en-us/library/aa772266%28v=vs.85%29.aspx | |
if ($PSBoundParameters['Server']) { | |
$ADSInitType = 2 | |
$InitName = $Server | |
} | |
elseif ($PSBoundParameters['Domain']) { | |
$ADSInitType = 1 | |
$InitName = $Domain | |
} | |
elseif ($PSBoundParameters['Credential']) { | |
$Cred = $Credential.GetNetworkCredential() | |
$ADSInitType = 1 | |
$InitName = $Cred.Domain | |
} | |
else { | |
# if no domain or server is specified, default to GC initialization | |
$ADSInitType = 3 | |
$InitName = $Null | |
} | |
} | |
PROCESS { | |
ForEach ($TargetIdentity in $Identity) { | |
if (-not $PSBoundParameters['OutputType']) { | |
if ($TargetIdentity -match "^[A-Za-z]+\\[A-Za-z ]+") { | |
$ADSOutputType = $NameTypes['DomainSimple'] | |
} | |
else { | |
$ADSOutputType = $NameTypes['NT4'] | |
} | |
} | |
else { | |
$ADSOutputType = $NameTypes[$OutputType] | |
} | |
$Translate = New-Object -ComObject NameTranslate | |
if ($PSBoundParameters['Credential']) { | |
try { | |
$Cred = $Credential.GetNetworkCredential() | |
Invoke-Method $Translate 'InitEx' ( | |
$ADSInitType, | |
$InitName, | |
$Cred.UserName, | |
$Cred.Domain, | |
$Cred.Password | |
) | |
} | |
catch { | |
Write-Verbose "[Convert-ADName] Error initialiting translation for '$Identity' using alternate credentials : $_" | |
} | |
} | |
else { | |
try { | |
Invoke-Method $Translate 'Init' ( | |
$ADSInitType, | |
$InitName | |
) | |
} | |
catch { | |
Write-Verbose "[Convert-ADName] Error initialiting translation for '$Identity' : $_" | |
} | |
} | |
# always chase all referrals | |
Set-Property $Translate 'ChaseReferral' (0x60) | |
try { | |
# 8 = Unknown name type -> let the server do the work for us | |
Invoke-Method $Translate 'Set' (8, $TargetIdentity) | |
Invoke-Method $Translate 'Get' ($ADSOutputType) | |
} | |
catch [System.Management.Automation.MethodInvocationException] { | |
Write-Verbose "[Convert-ADName] Error translating '$TargetIdentity' : $($_.Exception.InnerException.Message)" | |
} | |
} | |
} | |
} | |
function Get-DomainSearcher { | |
<# | |
.SYNOPSIS | |
Helper used by various functions that builds a custom AD searcher object. | |
Author: Will Schroeder (@harmj0y) | |
License: BSD 3-Clause | |
Required Dependencies: Get-Domain | |
.DESCRIPTION | |
Takes a given domain and a number of customizations and returns a | |
System.DirectoryServices.DirectorySearcher object. This function is used | |
heavily by other LDAP/ADSI searcher functions (Verb-Domain*). | |
.PARAMETER Domain | |
Specifies the domain to use for the query, defaults to the current domain. | |
.PARAMETER LDAPFilter | |
Specifies an LDAP query string that is used to filter Active Directory objects. | |
.PARAMETER Properties | |
Specifies the properties of the output object to retrieve from the server. | |
.PARAMETER SearchBase | |
The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local" | |
Useful for OU queries. | |
.PARAMETER SearchBasePrefix | |
Specifies a prefix for the LDAP search string (i.e. "CN=Sites,CN=Configuration"). | |
.PARAMETER Server | |
Specifies an Active Directory server (domain controller) to bind to for the search. | |
.PARAMETER SearchScope | |
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree). | |
.PARAMETER ResultPageSize | |
Specifies the PageSize to set for the LDAP searcher object. | |
.PARAMETER ResultPageSize | |
Specifies the PageSize to set for the LDAP searcher object. | |
.PARAMETER ServerTimeLimit | |
Specifies the maximum amount of time the server spends searching. Default of 120 seconds. | |
.PARAMETER SecurityMasks | |
Specifies an option for examining security information of a directory object. | |
One of 'Dacl', 'Group', 'None', 'Owner', 'Sacl'. | |
.PARAMETER Tombstone | |
Switch. Specifies that the searcher should also return deleted/tombstoned objects. | |
.PARAMETER Credential | |
A [Management.Automation.PSCredential] object of alternate credentials | |
for connection to the target domain. | |
.EXAMPLE | |
Get-DomainSearcher -Domain testlab.local | |
Return a searcher for all objects in testlab.local. | |
.EXAMPLE | |
Get-DomainSearcher -Domain testlab.local -LDAPFilter '(samAccountType=805306368)' -Properties 'SamAccountName,lastlogon' | |
Return a searcher for user objects in testlab.local and only return the SamAccountName and LastLogon properties. | |
.EXAMPLE | |
Get-DomainSearcher -SearchBase "LDAP://OU=secret,DC=testlab,DC=local" | |
Return a searcher that searches through the specific ADS/LDAP search base (i.e. OU). | |
.OUTPUTS | |
System.DirectoryServices.DirectorySearcher | |
#> | |
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')] | |
[OutputType('System.DirectoryServices.DirectorySearcher')] | |
[CmdletBinding()] | |
Param( | |
[Parameter(ValueFromPipeline = $True)] | |
[ValidateNotNullOrEmpty()] | |
[String] | |
$Domain, | |
[ValidateNotNullOrEmpty()] | |
[Alias('Filter')] | |
[String] | |
$LDAPFilter, | |
[ValidateNotNullOrEmpty()] | |
[String[]] | |
$Properties, | |
[ValidateNotNullOrEmpty()] | |
[Alias('ADSPath')] | |
[String] | |
$SearchBase, | |
[ValidateNotNullOrEmpty()] | |
[String] | |
$SearchBasePrefix, | |
[ValidateNotNullOrEmpty()] | |
[Alias('DomainController')] | |
[String] | |
$Server, | |
[ValidateSet('Base', 'OneLevel', 'Subtree')] | |
[String] | |
$SearchScope = 'Subtree', | |
[ValidateRange(1, 10000)] | |
[Int] | |
$ResultPageSize = 200, | |
[ValidateRange(1, 10000)] | |
[Int] | |
$ServerTimeLimit = 120, | |
[ValidateSet('Dacl', 'Group', 'None', 'Owner', 'Sacl')] | |
[String] | |
$SecurityMasks, | |
[Switch] | |
$Tombstone, | |
[Management.Automation.PSCredential] | |
[Management.Automation.CredentialAttribute()] | |
$Credential = [Management.Automation.PSCredential]::Empty | |
) | |
PROCESS { | |
if ($PSBoundParameters['Domain']) { | |
$TargetDomain = $Domain | |
if ($ENV:USERDNSDOMAIN -and ($ENV:USERDNSDOMAIN.Trim() -ne '')) { | |
# see if we can grab the user DNS logon domain from environment variables | |
$UserDomain = $ENV:USERDNSDOMAIN | |
if ($ENV:LOGONSERVER -and ($ENV:LOGONSERVER.Trim() -ne '') -and $UserDomain) { | |
$BindServer = "$($ENV:LOGONSERVER -replace '\\','').$UserDomain" | |
} | |
} | |
} | |
elseif ($PSBoundParameters['Credential']) { | |
# if not -Domain is specified, but -Credential is, try to retrieve the current domain name with Get-Domain | |
$DomainObject = Get-Domain -Credential $Credential | |
$BindServer = ($DomainObject.PdcRoleOwner).Name | |
$TargetDomain = $DomainObject.Name | |
} | |
elseif ($ENV:USERDNSDOMAIN -and ($ENV:USERDNSDOMAIN.Trim() -ne '')) { | |
# see if we can grab the user DNS logon domain from environment variables | |
$TargetDomain = $ENV:USERDNSDOMAIN | |
if ($ENV:LOGONSERVER -and ($ENV:LOGONSERVER.Trim() -ne '') -and $TargetDomain) { | |
$BindServer = "$($ENV:LOGONSERVER -replace '\\','').$TargetDomain" | |
} | |
} | |
else { | |
# otherwise, resort to Get-Domain to retrieve the current domain object | |
write-verbose "get-domain" | |
$DomainObject = Get-Domain | |
$BindServer = ($DomainObject.PdcRoleOwner).Name | |
$TargetDomain = $DomainObject.Name | |
} | |
if ($PSBoundParameters['Server']) { | |
# if there's not a specified server to bind to, try to pull a logon server from ENV variables | |
$BindServer = $Server | |
} | |
$SearchString = 'LDAP://' | |
if ($BindServer -and ($BindServer.Trim() -ne '')) { | |
$SearchString += $BindServer | |
if ($TargetDomain) { | |
$SearchString += '/' | |
} | |
} | |
if ($PSBoundParameters['SearchBasePrefix']) { | |
$SearchString += $SearchBasePrefix + ',' | |
} | |
if ($PSBoundParameters['SearchBase']) { | |
if ($SearchBase -Match '^GC://') { | |
# if we're searching the global catalog, get the path in the right format | |
$DN = $SearchBase.ToUpper().Trim('/') | |
$SearchString = '' | |
} | |
else { | |
if ($SearchBase -match '^LDAP://') { | |
if ($SearchBase -match "LDAP://.+/.+") { | |
$SearchString = '' | |
$DN = $SearchBase | |
} | |
else { | |
$DN = $SearchBase.SubString(7) | |
} | |
} | |
else { | |
$DN = $SearchBase | |
} | |
} | |
} | |
else { | |
# transform the target domain name into a distinguishedName if an ADS search base is not specified | |
if ($TargetDomain -and ($TargetDomain.Trim() -ne '')) { | |
$DN = "DC=$($TargetDomain.Replace('.', ',DC='))" | |
} | |
} | |
$SearchString += $DN | |
Write-Verbose "[Get-DomainSearcher] search base: $SearchString" | |
if ($Credential -ne [Management.Automation.PSCredential]::Empty) { | |
Write-Verbose "[Get-DomainSearcher] Using alternate credentials for LDAP connection" | |
# bind to the inital search object using alternate credentials | |
$DomainObject = New-Object DirectoryServices.DirectoryEntry($SearchString, $Credential.UserName, $Credential.GetNetworkCredential().Password) | |
$Searcher = New-Object System.DirectoryServices.DirectorySearcher($DomainObject) | |
} | |
else { | |
# bind to the inital object using the current credentials | |
$Searcher = New-Object System.DirectoryServices.DirectorySearcher([ADSI]$SearchString) | |
} | |
$Searcher.PageSize = $ResultPageSize | |
$Searcher.SearchScope = $SearchScope | |
$Searcher.CacheResults = $False | |
$Searcher.ReferralChasing = [System.DirectoryServices.ReferralChasingOption]::All | |
if ($PSBoundParameters['ServerTimeLimit']) { | |
$Searcher.ServerTimeLimit = $ServerTimeLimit | |
} | |
if ($PSBoundParameters['Tombstone']) { | |
$Searcher.Tombstone = $True | |
} | |
if ($PSBoundParameters['LDAPFilter']) { | |
$Searcher.filter = $LDAPFilter | |
} | |
if ($PSBoundParameters['SecurityMasks']) { | |
$Searcher.SecurityMasks = Switch ($SecurityMasks) { | |
'Dacl' { [System.DirectoryServices.SecurityMasks]::Dacl } | |
'Group' { [System.DirectoryServices.SecurityMasks]::Group } | |
'None' { [System.DirectoryServices.SecurityMasks]::None } | |
'Owner' { [System.DirectoryServices.SecurityMasks]::Owner } | |
'Sacl' { [System.DirectoryServices.SecurityMasks]::Sacl } | |
} | |
} | |
if ($PSBoundParameters['Properties']) { | |
# handle an array of properties to load w/ the possibility of comma-separated strings | |
$PropertiesToLoad = $Properties| ForEach-Object { $_.Split(',') } | |
$Null = $Searcher.PropertiesToLoad.AddRange(($PropertiesToLoad)) | |
} | |
$Searcher | |
} | |
} | |
function Convert-LDAPProperty { | |
<# | |
.SYNOPSIS | |
Helper that converts specific LDAP property result fields and outputs | |
a custom psobject. | |
Author: Will Schroeder (@harmj0y) | |
License: BSD 3-Clause | |
Required Dependencies: None | |
.DESCRIPTION | |
Converts a set of raw LDAP properties results from ADSI/LDAP searches | |
into a proper PSObject. Used by several of the Get-Domain* function. | |
.PARAMETER Properties | |
Properties object to extract out LDAP fields for display. | |
.OUTPUTS | |
System.Management.Automation.PSCustomObject | |
A custom PSObject with LDAP hashtable properties translated. | |
#> | |
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')] | |
[OutputType('System.Management.Automation.PSCustomObject')] | |
[CmdletBinding()] | |
Param( | |
[Parameter(Mandatory = $True, ValueFromPipeline = $True)] | |
[ValidateNotNullOrEmpty()] | |
$Properties | |
) | |
$ObjectProperties = @{} | |
$Properties.PropertyNames | ForEach-Object { | |
if ($_ -ne 'adspath') { | |
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 'ntsecuritydescriptor') { | |
# $ObjectProperties[$_] = New-Object Security.AccessControl.RawSecurityDescriptor -ArgumentList $Properties[$_][0], 0 | |
$Descriptor = New-Object Security.AccessControl.RawSecurityDescriptor -ArgumentList $Properties[$_][0], 0 | |
if ($Descriptor.Owner) { | |
$ObjectProperties['Owner'] = $Descriptor.Owner | |
} | |
if ($Descriptor.Group) { | |
$ObjectProperties['Group'] = $Descriptor.Group | |
} | |
if ($Descriptor.DiscretionaryAcl) { | |
$ObjectProperties['DiscretionaryAcl'] = $Descriptor.DiscretionaryAcl | |
} | |
if ($Descriptor.SystemAcl) { | |
$ObjectProperties['SystemAcl'] = $Descriptor.SystemAcl | |
} | |
} | |
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 { | |
# otherwise just a string | |
$ObjectProperties[$_] = ([datetime]::FromFileTime(($Properties[$_][0]))) | |
} | |
} | |
elseif ($Properties[$_][0] -is [System.MarshalByRefObject]) { | |
# try to convert misc com objects | |
$Prop = $Properties[$_] | |
try { | |
$Temp = $Prop[$_][0] | |
[Int32]$High = $Temp.GetType().InvokeMember('HighPart', [System.Reflection.BindingFlags]::GetProperty, $Null, $Temp, $Null) | |
[Int32]$Low = $Temp.GetType().InvokeMember('LowPart', [System.Reflection.BindingFlags]::GetProperty, $Null, $Temp, $Null) | |
$ObjectProperties[$_] = [Int64]("0x{0:x8}{1:x8}" -f $High, $Low) | |
} | |
catch { | |
Write-Verbose "[Convert-LDAPProperty] error: $_" | |
$ObjectProperties[$_] = $Prop[$_] | |
} | |
} | |
elseif ($Properties[$_].count -eq 1) { | |
$ObjectProperties[$_] = $Properties[$_][0] | |
} | |
else { | |
$ObjectProperties[$_] = $Properties[$_] | |
} | |
} | |
} | |
try { | |
New-Object -TypeName PSObject -Property $ObjectProperties | |
} | |
catch { | |
Write-Warning "[Convert-LDAPProperty] Error parsing LDAP properties : $_" | |
} | |
} | |
function Get-ForestDomain { | |
<# | |
.SYNOPSIS | |
Return all domains for the current (or specified) forest. | |
Author: Will Schroeder (@harmj0y) | |
License: BSD 3-Clause | |
Required Dependencies: Get-Forest | |
.DESCRIPTION | |
Returns all domains for the current forest or the forest specified | |
by -Forest X. | |
.PARAMETER Forest | |
Specifies the forest name to query for domains. | |
.PARAMETER Credential | |
A [Management.Automation.PSCredential] object of alternate credentials | |
for connection to the target forest. | |
.EXAMPLE | |
Get-ForestDomain | |
.EXAMPLE | |
Get-ForestDomain -Forest external.local | |
.EXAMPLE | |
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force | |
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword) | |
Get-ForestDomain -Credential $Cred | |
.OUTPUTS | |
System.DirectoryServices.ActiveDirectory.Domain | |
#> | |
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')] | |
[OutputType('System.DirectoryServices.ActiveDirectory.Domain')] | |
[CmdletBinding()] | |
Param( | |
[Parameter(Position = 0, ValueFromPipeline = $True)] | |
[ValidateNotNullOrEmpty()] | |
[String] | |
$Forest, | |
[Management.Automation.PSCredential] | |
[Management.Automation.CredentialAttribute()] | |
$Credential = [Management.Automation.PSCredential]::Empty | |
) | |
PROCESS { | |
$Arguments = @{} | |
if ($PSBoundParameters['Forest']) { $Arguments['Forest'] = $Forest } | |
if ($PSBoundParameters['Credential']) { $Arguments['Credential'] = $Credential } | |
$ForestObject = Get-Forest @Arguments | |
if ($ForestObject) { | |
$ForestObject.Domains | |
} | |
} | |
} | |
function Get-DomainTrust { | |
<# | |
.SYNOPSIS | |
Return all domain trusts for the current domain or a specified domain. | |
Author: Will Schroeder (@harmj0y) | |
License: BSD 3-Clause | |
Required Dependencies: Get-Domain, Get-DomainSearcher, Get-DomainSID, PSReflect | |
.DESCRIPTION | |
This function will enumerate domain trust relationships for the current (or a remote) | |
domain using a number of methods. By default, and LDAP search using the filter | |
'(objectClass=trustedDomain)' is used- if any LDAP-appropriate parameters are specified | |
LDAP is used as well. If the -NET flag is specified, the .NET method | |
GetAllTrustRelationships() is used on the System.DirectoryServices.ActiveDirectory.Domain | |
object. If the -API flag is specified, the Win32 API DsEnumerateDomainTrusts() call is | |
used to enumerate instead. | |
.PARAMETER Domain | |
Specifies the domain to query for trusts, defaults to the current domain. | |
.PARAMETER API | |
Switch. Use an API call (DsEnumerateDomainTrusts) to enumerate the trusts instead of the built-in | |
.NET methods. | |
.PARAMETER NET | |
Switch. Use .NET queries to enumerate trusts instead of the default LDAP method. | |
.PARAMETER LDAPFilter | |
Specifies an LDAP query string that is used to filter Active Directory objects. | |
.PARAMETER Properties | |
Specifies the properties of the output object to retrieve from the server. | |
.PARAMETER SearchBase | |
The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local" | |
Useful for OU queries. | |
.PARAMETER Server | |
Specifies an Active Directory server (domain controller) to bind to. | |
.PARAMETER SearchScope | |
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree). | |
.PARAMETER ResultPageSize | |
Specifies the PageSize to set for the LDAP searcher object. | |
.PARAMETER ServerTimeLimit | |
Specifies the maximum amount of time the server spends searching. Default of 120 seconds. | |
.PARAMETER Tombstone | |
Switch. Specifies that the searcher should also return deleted/tombstoned objects. | |
.PARAMETER FindOne | |
Only return one result object. | |
.PARAMETER Credential | |
A [Management.Automation.PSCredential] object of alternate credentials | |
for connection to the target domain. | |
.EXAMPLE | |
Get-DomainTrust | |
Return domain trusts for the current domain using built in .LDAP methods. | |
.EXAMPLE | |
Get-DomainTrust -NET -Domain "prod.testlab.local" | |
Return domain trusts for the "prod.testlab.local" domain using .NET methods | |
.EXAMPLE | |
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force | |
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword) | |
Get-DomainTrust -Domain "prod.testlab.local" -Server "PRIMARY.testlab.local" -Credential $Cred | |
Return domain trusts for the "prod.testlab.local" domain enumerated through LDAP | |
queries, binding to the PRIMARY.testlab.local server for queries, and using the specified | |
alternate credenitals. | |
.EXAMPLE | |
Get-DomainTrust -API -Domain "prod.testlab.local" | |
Return domain trusts for the "prod.testlab.local" domain enumerated through API calls. | |
.OUTPUTS | |
PowerView.DomainTrust.LDAP | |
Custom PSObject with translated domain LDAP trust result fields (default). | |
PowerView.DomainTrust.NET | |
A TrustRelationshipInformationCollection returned when using .NET methods. | |
PowerView.DomainTrust.API | |
Custom PSObject with translated domain API trust result fields. | |
#> | |
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')] | |
[OutputType('PowerView.DomainTrust.NET')] | |
[OutputType('PowerView.DomainTrust.LDAP')] | |
[OutputType('PowerView.DomainTrust.API')] | |
[CmdletBinding(DefaultParameterSetName = 'LDAP')] | |
Param( | |
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)] | |
[Alias('Name')] | |
[ValidateNotNullOrEmpty()] | |
[String] | |
$Domain, | |
[Parameter(ParameterSetName = 'API')] | |
[Switch] | |
$API, | |
[Parameter(ParameterSetName = 'NET')] | |
[Switch] | |
$NET, | |
[Parameter(ParameterSetName = 'LDAP')] | |
[ValidateNotNullOrEmpty()] | |
[Alias('Filter')] | |
[String] | |
$LDAPFilter, | |
[Parameter(ParameterSetName = 'LDAP')] | |
[ValidateNotNullOrEmpty()] | |
[String[]] | |
$Properties, | |
[Parameter(ParameterSetName = 'LDAP')] | |
[ValidateNotNullOrEmpty()] | |
[Alias('ADSPath')] | |
[String] | |
$SearchBase, | |
[Parameter(ParameterSetName = 'LDAP')] | |
[Parameter(ParameterSetName = 'API')] | |
[ValidateNotNullOrEmpty()] | |
[Alias('DomainController')] | |
[String] | |
$Server, | |
[Parameter(ParameterSetName = 'LDAP')] | |
[ValidateSet('Base', 'OneLevel', 'Subtree')] | |
[String] | |
$SearchScope = 'Subtree', | |
[Parameter(ParameterSetName = 'LDAP')] | |
[ValidateRange(1, 10000)] | |
[Int] | |
$ResultPageSize = 200, | |
[Parameter(ParameterSetName = 'LDAP')] | |
[ValidateRange(1, 10000)] | |
[Int] | |
$ServerTimeLimit, | |
[Parameter(ParameterSetName = 'LDAP')] | |
[Switch] | |
$Tombstone, | |
[Alias('ReturnOne')] | |
[Switch] | |
$FindOne, | |
[Parameter(ParameterSetName = 'LDAP')] | |
[Management.Automation.PSCredential] | |
[Management.Automation.CredentialAttribute()] | |
$Credential = [Management.Automation.PSCredential]::Empty | |
) | |
BEGIN { | |
$TrustAttributes = @{ | |
[uint32]'0x00000001' = 'NON_TRANSITIVE' | |
[uint32]'0x00000002' = 'UPLEVEL_ONLY' | |
[uint32]'0x00000004' = 'FILTER_SIDS' | |
[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' | |
} | |
$LdapSearcherArguments = @{} | |
if ($PSBoundParameters['Domain']) { $LdapSearcherArguments['Domain'] = $Domain } | |
if ($PSBoundParameters['LDAPFilter']) { $LdapSearcherArguments['LDAPFilter'] = $LDAPFilter } | |
if ($PSBoundParameters['Properties']) { $LdapSearcherArguments['Properties'] = $Properties } | |
if ($PSBoundParameters['SearchBase']) { $LdapSearcherArguments['SearchBase'] = $SearchBase } | |
if ($PSBoundParameters['Server']) { $LdapSearcherArguments['Server'] = $Server } | |
if ($PSBoundParameters['SearchScope']) { $LdapSearcherArguments['SearchScope'] = $SearchScope } | |
if ($PSBoundParameters['ResultPageSize']) { $LdapSearcherArguments['ResultPageSize'] = $ResultPageSize } | |
if ($PSBoundParameters['ServerTimeLimit']) { $LdapSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit } | |
if ($PSBoundParameters['Tombstone']) { $LdapSearcherArguments['Tombstone'] = $Tombstone } | |
if ($PSBoundParameters['Credential']) { $LdapSearcherArguments['Credential'] = $Credential } | |
} | |
PROCESS { | |
if ($PsCmdlet.ParameterSetName -ne 'API') { | |
$NetSearcherArguments = @{} | |
if ($Domain -and $Domain.Trim() -ne '') { | |
$SourceDomain = $Domain | |
} | |
else { | |
if ($PSBoundParameters['Credential']) { | |
$SourceDomain = (Get-Domain -Credential $Credential).Name | |
} | |
else { | |
$SourceDomain = (Get-Domain).Name | |
} | |
} | |
} | |
elseif ($PsCmdlet.ParameterSetName -ne 'NET') { | |
if ($Domain -and $Domain.Trim() -ne '') { | |
$SourceDomain = $Domain | |
} | |
else { | |
$SourceDomain = $Env:USERDNSDOMAIN | |
} | |
} | |
if ($PsCmdlet.ParameterSetName -eq 'LDAP') { | |
# if we're searching for domain trusts through LDAP/ADSI | |
$TrustSearcher = Get-DomainSearcher @LdapSearcherArguments | |
$SourceSID = Get-DomainSID @NetSearcherArguments | |
if ($TrustSearcher) { | |
$TrustSearcher.Filter = '(objectClass=trustedDomain)' | |
if ($PSBoundParameters['FindOne']) { $Results = $TrustSearcher.FindOne() } | |
else { $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' } | |
} | |
$TrustType = Switch ($Props.trusttype) { | |
1 { 'WINDOWS_NON_ACTIVE_DIRECTORY' } | |
2 { 'WINDOWS_ACTIVE_DIRECTORY' } | |
3 { 'MIT' } | |
} | |
$Distinguishedname = $Props.distinguishedname[0] | |
$SourceNameIndex = $Distinguishedname.IndexOf('DC=') | |
if ($SourceNameIndex) { | |
$SourceDomain = $($Distinguishedname.SubString($SourceNameIndex)) -replace 'DC=','' -replace ',','.' | |
} | |
else { | |
$SourceDomain = "" | |
} | |
$TargetNameIndex = $Distinguishedname.IndexOf(',CN=System') | |
if ($SourceNameIndex) { | |
$TargetDomain = $Distinguishedname.SubString(3, $TargetNameIndex-3) | |
} | |
else { | |
$TargetDomain = "" | |
} | |
$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 'TargetName' $Props.name[0] | |
# $DomainTrust | Add-Member Noteproperty 'TargetGuid' "{$ObjectGuid}" | |
$DomainTrust | Add-Member Noteproperty 'TrustType' $TrustType | |
$DomainTrust | Add-Member Noteproperty 'TrustAttributes' $($TrustAttrib -join ',') | |
$DomainTrust | Add-Member Noteproperty 'TrustDirection' "$Direction" | |
$DomainTrust | Add-Member Noteproperty 'WhenCreated' $Props.whencreated[0] | |
$DomainTrust | Add-Member Noteproperty 'WhenChanged' $Props.whenchanged[0] | |
$DomainTrust.PSObject.TypeNames.Insert(0, 'PowerView.DomainTrust.LDAP') | |
$DomainTrust | |
} | |
if ($Results) { | |
try { $Results.dispose() } | |
catch { | |
Write-Verbose "[Get-DomainTrust] Error disposing of the Results object: $_" | |
} | |
} | |
$TrustSearcher.dispose() | |
} | |
} | |
elseif ($PsCmdlet.ParameterSetName -eq 'API') { | |
# if we're searching for domain trusts through Win32 API functions | |
if ($PSBoundParameters['Server']) { | |
$TargetDC = $Server | |
} | |
elseif ($Domain -and $Domain.Trim() -ne '') { | |
$TargetDC = $Domain | |
} | |
else { | |
# see https://msdn.microsoft.com/en-us/library/ms675976(v=vs.85).aspx for default NULL behavior | |
$TargetDC = $Null | |
} | |
# 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($TargetDC, $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 "[Get-DomainTrust] Error: $(([ComponentModel.Win32Exception] $LastError).Message)" | |
} | |
else { | |
$DomainTrust = New-Object PSObject | |
$DomainTrust | Add-Member Noteproperty 'SourceName' $SourceDomain | |
$DomainTrust | Add-Member Noteproperty 'TargetName' $Info.DnsDomainName | |
$DomainTrust | Add-Member Noteproperty 'TargetNetbiosName' $Info.NetbiosDomainName | |
$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 'TargetSid' $SidString | |
$DomainTrust | Add-Member Noteproperty 'TargetGuid' $Info.DomainGuid | |
$DomainTrust.PSObject.TypeNames.Insert(0, 'PowerView.DomainTrust.API') | |
$DomainTrust | |
} | |
} | |
# free up the result buffer | |
$Null = $Netapi32::NetApiBufferFree($PtrInfo) | |
} | |
else { | |
Write-Verbose "[Get-DomainTrust] Error: $(([ComponentModel.Win32Exception] $Result).Message)" | |
} | |
} | |
else { | |
# if we're searching for domain trusts through .NET methods | |
$FoundDomain = Get-Domain @NetSearcherArguments | |
if ($FoundDomain) { | |
$FoundDomain.GetAllTrustRelationships() | ForEach-Object { | |
$_.PSObject.TypeNames.Insert(0, 'PowerView.DomainTrust.NET') | |
$_ | |
} | |
} | |
} | |
} | |
} | |
function Get-ForestTrust { | |
<# | |
.SYNOPSIS | |
Return all forest trusts for the current forest or a specified forest. | |
Author: Will Schroeder (@harmj0y) | |
License: BSD 3-Clause | |
Required Dependencies: Get-Forest | |
.DESCRIPTION | |
This function will enumerate domain trust relationships for the current (or a remote) | |
forest using number of method using the .NET method GetAllTrustRelationships() on a | |
System.DirectoryServices.ActiveDirectory.Forest returned by Get-Forest. | |
.PARAMETER Forest | |
Specifies the forest to query for trusts, defaults to the current forest. | |
.PARAMETER Credential | |
A [Management.Automation.PSCredential] object of alternate credentials | |
for connection to the target domain. | |
.EXAMPLE | |
Get-ForestTrust | |
Return current forest trusts. | |
.EXAMPLE | |
Get-ForestTrust -Forest "external.local" | |
Return trusts for the "external.local" forest. | |
.EXAMPLE | |
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force | |
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword) | |
Get-ForestTrust -Forest "external.local" -Credential $Cred | |
Return trusts for the "external.local" forest using the specified alternate credenitals. | |
.OUTPUTS | |
PowerView.DomainTrust.NET | |
A TrustRelationshipInformationCollection returned when using .NET methods (default). | |
#> | |
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')] | |
[OutputType('PowerView.ForestTrust.NET')] | |
[CmdletBinding()] | |
Param( | |
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)] | |
[Alias('Name')] | |
[ValidateNotNullOrEmpty()] | |
[String] | |
$Forest, | |
[Management.Automation.PSCredential] | |
[Management.Automation.CredentialAttribute()] | |
$Credential = [Management.Automation.PSCredential]::Empty | |
) | |
PROCESS { | |
$NetForestArguments = @{} | |
if ($PSBoundParameters['Forest']) { $NetForestArguments['Forest'] = $Forest } | |
if ($PSBoundParameters['Credential']) { $NetForestArguments['Credential'] = $Credential } | |
$FoundForest = Get-Forest @NetForestArguments | |
if ($FoundForest) { | |
$FoundForest.GetAllTrustRelationships() | ForEach-Object { | |
$_.PSObject.TypeNames.Insert(0, 'PowerView.ForestTrust.NET') | |
$_ | |
} | |
} | |
} | |
} | |
function Invoke-UserImpersonation { | |
<# | |
.SYNOPSIS | |
Creates a new "runas /netonly" type logon and impersonates the token. | |
Author: Will Schroeder (@harmj0y) | |
License: BSD 3-Clause | |
Required Dependencies: PSReflect | |
.DESCRIPTION | |
This function uses LogonUser() with the LOGON32_LOGON_NEW_CREDENTIALS LogonType | |
to simulate "runas /netonly". The resulting token is then impersonated with | |
ImpersonateLoggedOnUser() and the token handle is returned for later usage | |
with Invoke-RevertToSelf. | |
.PARAMETER Credential | |
A [Management.Automation.PSCredential] object with alternate credentials | |
to impersonate in the current thread space. | |
.PARAMETER TokenHandle | |
An IntPtr TokenHandle returned by a previous Invoke-UserImpersonation. | |
If this is supplied, LogonUser() is skipped and only ImpersonateLoggedOnUser() | |
is executed. | |
.PARAMETER Quiet | |
Suppress any warnings about STA vs MTA. | |
.EXAMPLE | |
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force | |
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword) | |
Invoke-UserImpersonation -Credential $Cred | |
.OUTPUTS | |
IntPtr | |
The TokenHandle result from LogonUser. | |
#> | |
[OutputType([IntPtr])] | |
[CmdletBinding(DefaultParameterSetName = 'Credential')] | |
Param( | |
[Parameter(Mandatory = $True, ParameterSetName = 'Credential')] | |
[Management.Automation.PSCredential] | |
[Management.Automation.CredentialAttribute()] | |
$Credential, | |
[Parameter(Mandatory = $True, ParameterSetName = 'TokenHandle')] | |
[ValidateNotNull()] | |
[IntPtr] | |
$TokenHandle, | |
[Switch] | |
$Quiet | |
) | |
if (([System.Threading.Thread]::CurrentThread.GetApartmentState() -ne 'STA') -and (-not $PSBoundParameters['Quiet'])) { | |
Write-Warning "[Invoke-UserImpersonation] powershell.exe is not currently in a single-threaded apartment state, token impersonation may not work." | |
} | |
if ($PSBoundParameters['TokenHandle']) { | |
$LogonTokenHandle = $TokenHandle | |
} | |
else { | |
$LogonTokenHandle = [IntPtr]::Zero | |
$NetworkCredential = $Credential.GetNetworkCredential() | |
$UserDomain = $NetworkCredential.Domain | |
$UserName = $NetworkCredential.UserName | |
Write-Warning "[Invoke-UserImpersonation] Executing LogonUser() with user: $($UserDomain)\$($UserName)" | |
# LOGON32_LOGON_NEW_CREDENTIALS = 9, LOGON32_PROVIDER_WINNT50 = 3 | |
# this is to simulate "runas.exe /netonly" functionality | |
$Result = $Advapi32::LogonUser($UserName, $UserDomain, $NetworkCredential.Password, 9, 3, [ref]$LogonTokenHandle);$LastError = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error(); | |
if (-not $Result) { | |
throw "[Invoke-UserImpersonation] LogonUser() Error: $(([ComponentModel.Win32Exception] $LastError).Message)" | |
} | |
} | |
# actually impersonate the token from LogonUser() | |
$Result = $Advapi32::ImpersonateLoggedOnUser($LogonTokenHandle) | |
if (-not $Result) { | |
throw "[Invoke-UserImpersonation] ImpersonateLoggedOnUser() Error: $(([ComponentModel.Win32Exception] $LastError).Message)" | |
} | |
Write-Verbose "[Invoke-UserImpersonation] Alternate credentials successfully impersonated" | |
$LogonTokenHandle | |
} | |
function Get-Forest { | |
<# | |
.SYNOPSIS | |
Returns the forest object for the current (or specified) forest. | |
Author: Will Schroeder (@harmj0y) | |
License: BSD 3-Clause | |
Required Dependencies: ConvertTo-SID | |
.DESCRIPTION | |
Returns a System.DirectoryServices.ActiveDirectory.Forest object for the current | |
forest or the forest specified with -Forest X. | |
.PARAMETER Forest | |
The forest name to query for, defaults to the current forest. | |
.PARAMETER Credential | |
A [Management.Automation.PSCredential] object of alternate credentials | |
for connection to the target forest. | |
.EXAMPLE | |
Get-Forest -Forest external.domain | |
.EXAMPLE | |
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force | |
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword) | |
Get-Forest -Credential $Cred | |
.OUTPUTS | |
System.Management.Automation.PSCustomObject | |
Outputs a PSObject containing System.DirectoryServices.ActiveDirectory.Forest in addition | |
to the forest root domain SID. | |
#> | |
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')] | |
[OutputType('System.Management.Automation.PSCustomObject')] | |
[CmdletBinding()] | |
Param( | |
[Parameter(Position = 0, ValueFromPipeline = $True)] | |
[ValidateNotNullOrEmpty()] | |
[String] | |
$Forest, | |
[Management.Automation.PSCredential] | |
[Management.Automation.CredentialAttribute()] | |
$Credential = [Management.Automation.PSCredential]::Empty | |
) | |
PROCESS { | |
if ($PSBoundParameters['Credential']) { | |
Write-Verbose "[Get-Forest] Using alternate credentials for Get-Forest" | |
if ($PSBoundParameters['Forest']) { | |
$TargetForest = $Forest | |
} | |
else { | |
# if no domain is supplied, extract the logon domain from the PSCredential passed | |
$TargetForest = $Credential.GetNetworkCredential().Domain | |
Write-Verbose "[Get-Forest] Extracted domain '$Forest' from -Credential" | |
} | |
$ForestContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext('Forest', $TargetForest, $Credential.UserName, $Credential.GetNetworkCredential().Password) | |
try { | |
$ForestObject = [System.DirectoryServices.ActiveDirectory.Forest]::GetForest($ForestContext) | |
} | |
catch { | |
Write-Verbose "[Get-Forest] The specified forest '$TargetForest' does not exist, could not be contacted, there isn't an existing trust, or the specified credentials are invalid: $_" | |
$Null | |
} | |
} | |
elseif ($PSBoundParameters['Forest']) { | |
$ForestContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext('Forest', $Forest) | |
try { | |
$ForestObject = [System.DirectoryServices.ActiveDirectory.Forest]::GetForest($ForestContext) | |
} | |
catch { | |
Write-Verbose "[Get-Forest] 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 | |
if ($PSBoundParameters['Credential']) { | |
$ForestSid = (Get-DomainUser -Identity "krbtgt" -Domain $ForestObject.RootDomain.Name -Credential $Credential).objectsid | |
} | |
else { | |
$ForestSid = (Get-DomainUser -Identity "krbtgt" -Domain $ForestObject.RootDomain.Name).objectsid | |
} | |
$Parts = $ForestSid -Split '-' | |
$ForestSid = $Parts[0..$($Parts.length-2)] -join '-' | |
$ForestObject | Add-Member NoteProperty 'RootDomainSid' $ForestSid | |
$ForestObject | |
} | |
} | |
} | |
function Get-DomainSID { | |
<# | |
.SYNOPSIS | |
Returns the SID for the current domain or the specified domain. | |
Author: Will Schroeder (@harmj0y) | |
License: BSD 3-Clause | |
Required Dependencies: Get-DomainComputer | |
.DESCRIPTION | |
Returns the SID for the current domain or the specified domain by executing | |
Get-DomainComputer with the -LDAPFilter set to (userAccountControl:1.2.840.113556.1.4.803:=8192) | |
to search for domain controllers through LDAP. The SID of the returned domain controller | |
is then extracted. | |
.PARAMETER Domain | |
Specifies the domain to use for the query, defaults to the current domain. | |
.PARAMETER Server | |
Specifies an Active Directory server (domain controller) to bind to. | |
.PARAMETER Credential | |
A [Management.Automation.PSCredential] object of alternate credentials | |
for connection to the target domain. | |
.EXAMPLE | |
Get-DomainSID | |
.EXAMPLE | |
Get-DomainSID -Domain testlab.local | |
.EXAMPLE | |
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force | |
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword) | |
Get-DomainSID -Credential $Cred | |
.OUTPUTS | |
String | |
A string representing the specified domain SID. | |
#> | |
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')] | |
[OutputType([String])] | |
[CmdletBinding()] | |
Param( | |
[ValidateNotNullOrEmpty()] | |
[String] | |
$Domain, | |
[ValidateNotNullOrEmpty()] | |
[Alias('DomainController')] | |
[String] | |
$Server, | |
[Management.Automation.PSCredential] | |
[Management.Automation.CredentialAttribute()] | |
$Credential = [Management.Automation.PSCredential]::Empty | |
) | |
$SearcherArguments = @{ | |
'LDAPFilter' = '(userAccountControl:1.2.840.113556.1.4.803:=8192)' | |
} | |
if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain } | |
if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server } | |
if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential } | |
$DCSID = Get-DomainComputer @SearcherArguments -FindOne | Select-Object -First 1 -ExpandProperty objectsid | |
if ($DCSID) { | |
$DCSID.SubString(0, $DCSID.LastIndexOf('-')) | |
} | |
else { | |
Write-Verbose "[Get-DomainSID] Error extracting domain SID for '$Domain'" | |
} | |
} | |
function Get-DomainGroup { | |
<# | |
.SYNOPSIS | |
Return all groups or specific group objects in AD. | |
Author: Will Schroeder (@harmj0y) | |
License: BSD 3-Clause | |
Required Dependencies: Get-DomainSearcher, Get-DomainObject, Convert-ADName, Convert-LDAPProperty | |
.DESCRIPTION | |
Builds a directory searcher object using Get-DomainSearcher, builds a custom | |
LDAP filter based on targeting/filter parameters, and searches for all objects | |
matching the criteria. To only return specific properties, use | |
"-Properties samaccountname,usnchanged,...". By default, all group objects for | |
the current domain are returned. To return the groups a specific user/group is | |
a part of, use -MemberIdentity X to execute token groups enumeration. | |
.PARAMETER Identity | |
A SamAccountName (e.g. Group1), DistinguishedName (e.g. CN=group1,CN=Users,DC=testlab,DC=local), | |
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1114), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d202) | |
specifying the group to query for. Wildcards accepted. | |
.PARAMETER MemberIdentity | |
A SamAccountName (e.g. Group1), DistinguishedName (e.g. CN=group1,CN=Users,DC=testlab,DC=local), | |
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1114), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d202) | |
specifying the user/group member to query for group membership. | |
.PARAMETER AdminCount | |
Switch. Return users with '(adminCount=1)' (meaning are/were privileged). | |
.PARAMETER GroupScope | |
Specifies the scope (DomainLocal, Global, or Universal) of the group(s) to search for. | |
Also accepts NotDomainLocal, NotGloba, and NotUniversal as negations. | |
.PARAMETER GroupProperty | |
Specifies a specific property to search for when performing the group search. | |
Possible values are Security, Distribution, CreatedBySystem, and NotCreatedBySystem. | |
.PARAMETER Domain | |
Specifies the domain to use for the query, defaults to the current domain. | |
.PARAMETER LDAPFilter | |
Specifies an LDAP query string that is used to filter Active Directory objects. | |
.PARAMETER Properties | |
Specifies the properties of the output object to retrieve from the server. | |
.PARAMETER SearchBase | |
The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local" | |
Useful for OU queries. | |
.PARAMETER Server | |
Specifies an Active Directory server (domain controller) to bind to. | |
.PARAMETER SearchScope | |
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree). | |
.PARAMETER ResultPageSize | |
Specifies the PageSize to set for the LDAP searcher object. | |
.PARAMETER ServerTimeLimit | |
Specifies the maximum amount of time the server spends searching. Default of 120 seconds. | |
.PARAMETER SecurityMasks | |
Specifies an option for examining security information of a directory object. | |
One of 'Dacl', 'Group', 'None', 'Owner', 'Sacl'. | |
.PARAMETER Tombstone | |
Switch. Specifies that the searcher should also return deleted/tombstoned objects. | |
.PARAMETER FindOne | |
Only return one result object. | |
.PARAMETER Credential | |
A [Management.Automation.PSCredential] object of alternate credentials | |
for connection to the target domain. | |
.PARAMETER Raw | |
Switch. Return raw results instead of translating the fields into a custom PSObject. | |
.EXAMPLE | |
Get-DomainGroup | select samaccountname | |
samaccountname | |
-------------- | |
WinRMRemoteWMIUsers__ | |
Administrators | |
Users | |
Guests | |
Print Operators | |
Backup Operators | |
... | |
.EXAMPLE | |
Get-DomainGroup *admin* | select distinguishedname | |
distinguishedname | |
----------------- | |
CN=Administrators,CN=Builtin,DC=testlab,DC=local | |
CN=Hyper-V Administrators,CN=Builtin,DC=testlab,DC=local | |
CN=Schema Admins,CN=Users,DC=testlab,DC=local | |
CN=Enterprise Admins,CN=Users,DC=testlab,DC=local | |
CN=Domain Admins,CN=Users,DC=testlab,DC=local | |
CN=DnsAdmins,CN=Users,DC=testlab,DC=local | |
CN=Server Admins,CN=Users,DC=testlab,DC=local | |
CN=Desktop Admins,CN=Users,DC=testlab,DC=local | |
.EXAMPLE | |
Get-DomainGroup -Properties samaccountname -Identity 'S-1-5-21-890171859-3433809279-3366196753-1117' | fl | |
samaccountname | |
-------------- | |
Server Admins | |
.EXAMPLE | |
'CN=Desktop Admins,CN=Users,DC=testlab,DC=local' | Get-DomainGroup -Server primary.testlab.local -Verbose | |
VERBOSE: Get-DomainSearcher search string: LDAP://DC=testlab,DC=local | |
VERBOSE: Get-DomainGroup filter string: (&(objectCategory=group)(|(distinguishedname=CN=DesktopAdmins,CN=Users,DC=testlab,DC=local))) | |
usncreated : 13245 | |
grouptype : -2147483646 | |
samaccounttype : 268435456 | |
samaccountname : Desktop Admins | |
whenchanged : 8/10/2016 12:30:30 AM | |
objectsid : S-1-5-21-890171859-3433809279-3366196753-1118 | |
objectclass : {top, group} | |
cn : Desktop Admins | |
usnchanged : 13255 | |
dscorepropagationdata : 1/1/1601 12:00:00 AM | |
name : Desktop Admins | |
distinguishedname : CN=Desktop Admins,CN=Users,DC=testlab,DC=local | |
member : CN=Andy Robbins (admin),CN=Users,DC=testlab,DC=local | |
whencreated : 8/10/2016 12:29:43 AM | |
instancetype : 4 | |
objectguid : f37903ed-b333-49f4-abaa-46c65e9cca71 | |
objectcategory : CN=Group,CN=Schema,CN=Configuration,DC=testlab,DC=local | |
.EXAMPLE | |
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force | |
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword) | |
Get-DomainGroup -Credential $Cred | |
.EXAMPLE | |
Get-Domain | Select-Object -Expand name | |
testlab.local | |
'DEV\Domain Admins' | Get-DomainGroup -Verbose -Properties distinguishedname | |
VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local | |
VERBOSE: [Get-DomainGroup] Extracted domain 'dev.testlab.local' from 'DEV\Domain Admins' | |
VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=dev,DC=testlab,DC=local | |
VERBOSE: [Get-DomainGroup] filter string: (&(objectCategory=group)(|(samAccountName=Domain Admins))) | |
distinguishedname | |
----------------- | |
CN=Domain Admins,CN=Users,DC=dev,DC=testlab,DC=local | |
.OUTPUTS | |
PowerView.Group | |
Custom PSObject with translated group property fields. | |
#> | |
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')] | |
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')] | |
[OutputType('PowerView.Group')] | |
[CmdletBinding(DefaultParameterSetName = 'AllowDelegation')] | |
Param( | |
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)] | |
[Alias('DistinguishedName', 'SamAccountName', 'Name', 'MemberDistinguishedName', 'MemberName')] | |
[String[]] | |
$Identity, | |
[ValidateNotNullOrEmpty()] | |
[Alias('UserName')] | |
[String] | |
$MemberIdentity, | |
[Switch] | |
$AdminCount, | |
[ValidateSet('DomainLocal', 'NotDomainLocal', 'Global', 'NotGlobal', 'Universal', 'NotUniversal')] | |
[Alias('Scope')] | |
[String] | |
$GroupScope, | |
[ValidateSet('Security', 'Distribution', 'CreatedBySystem', 'NotCreatedBySystem')] | |
[String] | |
$GroupProperty, | |
[ValidateNotNullOrEmpty()] | |
[String] | |
$Domain, | |
[ValidateNotNullOrEmpty()] | |
[Alias('Filter')] | |
[String] | |
$LDAPFilter, | |
[ValidateNotNullOrEmpty()] | |
[String[]] | |
$Properties, | |
[ValidateNotNullOrEmpty()] | |
[Alias('ADSPath')] | |
[String] | |
$SearchBase, | |
[ValidateNotNullOrEmpty()] | |
[Alias('DomainController')] | |
[String] | |
$Server, | |
[ValidateSet('Base', 'OneLevel', 'Subtree')] | |
[String] | |
$SearchScope = 'Subtree', | |
[ValidateRange(1, 10000)] | |
[Int] | |
$ResultPageSize = 200, | |
[ValidateRange(1, 10000)] | |
[Int] | |
$ServerTimeLimit, | |
[ValidateSet('Dacl', 'Group', 'None', 'Owner', 'Sacl')] | |
[String] | |
$SecurityMasks, | |
[Switch] | |
$Tombstone, | |
[Alias('ReturnOne')] | |
[Switch] | |
$FindOne, | |
[Management.Automation.PSCredential] | |
[Management.Automation.CredentialAttribute()] | |
$Credential = [Management.Automation.PSCredential]::Empty, | |
[Switch] | |
$Raw | |
) | |
BEGIN { | |
$SearcherArguments = @{} | |
if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain } | |
if ($PSBoundParameters['Properties']) { $SearcherArguments['Properties'] = $Properties } | |
if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase } | |
if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server } | |
if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope } | |
if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize } | |
if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit } | |
if ($PSBoundParameters['SecurityMasks']) { $SearcherArguments['SecurityMasks'] = $SecurityMasks } | |
if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone } | |
if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential } | |
$GroupSearcher = Get-DomainSearcher @SearcherArguments | |
} | |
PROCESS { | |
if ($GroupSearcher) { | |
if ($PSBoundParameters['MemberIdentity']) { | |
if ($SearcherArguments['Properties']) { | |
$OldProperties = $SearcherArguments['Properties'] | |
} | |
$SearcherArguments['Identity'] = $MemberIdentity | |
$SearcherArguments['Raw'] = $True | |
Get-DomainObject @SearcherArguments | ForEach-Object { | |
# convert the user/group to a directory entry | |
$ObjectDirectoryEntry = $_.GetDirectoryEntry() | |
# cause the cache to calculate the token groups for the user/group | |
$ObjectDirectoryEntry.RefreshCache('tokenGroups') | |
$ObjectDirectoryEntry.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-.*') { | |
$SearcherArguments['Identity'] = $GroupSid | |
$SearcherArguments['Raw'] = $False | |
if ($OldProperties) { $SearcherArguments['Properties'] = $OldProperties } | |
$Group = Get-DomainObject @SearcherArguments | |
if ($Group) { | |
$Group.PSObject.TypeNames.Insert(0, 'PowerView.Group') | |
$Group | |
} | |
} | |
} | |
} | |
} | |
else { | |
$IdentityFilter = '' | |
$Filter = '' | |
$Identity | Where-Object {$_} | ForEach-Object { | |
$IdentityInstance = $_.Replace('(', '\28').Replace(')', '\29') | |
if ($IdentityInstance -match '^S-1-') { | |
$IdentityFilter += "(objectsid=$IdentityInstance)" | |
} | |
elseif ($IdentityInstance -match '^CN=') { | |
$IdentityFilter += "(distinguishedname=$IdentityInstance)" | |
if ((-not $PSBoundParameters['Domain']) -and (-not $PSBoundParameters['SearchBase'])) { | |
# if a -Domain isn't explicitly set, extract the object domain out of the distinguishedname | |
# and rebuild the domain searcher | |
$IdentityDomain = $IdentityInstance.SubString($IdentityInstance.IndexOf('DC=')) -replace 'DC=','' -replace ',','.' | |
Write-Verbose "[Get-DomainGroup] Extracted domain '$IdentityDomain' from '$IdentityInstance'" | |
$SearcherArguments['Domain'] = $IdentityDomain | |
$GroupSearcher = Get-DomainSearcher @SearcherArguments | |
if (-not $GroupSearcher) { | |
Write-Warning "[Get-DomainGroup] Unable to retrieve domain searcher for '$IdentityDomain'" | |
} | |
} | |
} | |
elseif ($IdentityInstance -imatch '^[0-9A-F]{8}-([0-9A-F]{4}-){3}[0-9A-F]{12}$') { | |
$GuidByteString = (([Guid]$IdentityInstance).ToByteArray() | ForEach-Object { '\' + $_.ToString('X2') }) -join '' | |
$IdentityFilter += "(objectguid=$GuidByteString)" | |
} | |
elseif ($IdentityInstance.Contains('\')) { | |
$ConvertedIdentityInstance = $IdentityInstance.Replace('\28', '(').Replace('\29', ')') | Convert-ADName -OutputType Canonical | |
if ($ConvertedIdentityInstance) { | |
$GroupDomain = $ConvertedIdentityInstance.SubString(0, $ConvertedIdentityInstance.IndexOf('/')) | |
$GroupName = $IdentityInstance.Split('\')[1] | |
$IdentityFilter += "(samAccountName=$GroupName)" | |
$SearcherArguments['Domain'] = $GroupDomain | |
Write-Verbose "[Get-DomainGroup] Extracted domain '$GroupDomain' from '$IdentityInstance'" | |
$GroupSearcher = Get-DomainSearcher @SearcherArguments | |
} | |
} | |
else { | |
$IdentityFilter += "(|(samAccountName=$IdentityInstance)(name=$IdentityInstance))" | |
} | |
} | |
if ($IdentityFilter -and ($IdentityFilter.Trim() -ne '') ) { | |
$Filter += "(|$IdentityFilter)" | |
} | |
if ($PSBoundParameters['AdminCount']) { | |
Write-Verbose '[Get-DomainGroup] Searching for adminCount=1' | |
$Filter += '(admincount=1)' | |
} | |
if ($PSBoundParameters['GroupScope']) { | |
$GroupScopeValue = $PSBoundParameters['GroupScope'] | |
$Filter = Switch ($GroupScopeValue) { | |
'DomainLocal' { '(groupType:1.2.840.113556.1.4.803:=4)' } | |
'NotDomainLocal' { '(!(groupType:1.2.840.113556.1.4.803:=4))' } | |
'Global' { '(groupType:1.2.840.113556.1.4.803:=2)' } | |
'NotGlobal' { '(!(groupType:1.2.840.113556.1.4.803:=2))' } | |
'Universal' { '(groupType:1.2.840.113556.1.4.803:=8)' } | |
'NotUniversal' { '(!(groupType:1.2.840.113556.1.4.803:=8))' } | |
} | |
Write-Verbose "[Get-DomainGroup] Searching for group scope '$GroupScopeValue'" | |
} | |
if ($PSBoundParameters['GroupProperty']) { | |
$GroupPropertyValue = $PSBoundParameters['GroupProperty'] | |
$Filter = Switch ($GroupPropertyValue) { | |
'Security' { '(groupType:1.2.840.113556.1.4.803:=2147483648)' } | |
'Distribution' { '(!(groupType:1.2.840.113556.1.4.803:=2147483648))' } | |
'CreatedBySystem' { '(groupType:1.2.840.113556.1.4.803:=1)' } | |
'NotCreatedBySystem' { '(!(groupType:1.2.840.113556.1.4.803:=1))' } | |
} | |
Write-Verbose "[Get-DomainGroup] Searching for group property '$GroupPropertyValue'" | |
} | |
if ($PSBoundParameters['LDAPFilter']) { | |
Write-Verbose "[Get-DomainGroup] Using additional LDAP filter: $LDAPFilter" | |
$Filter += "$LDAPFilter" | |
} | |
$GroupSearcher.filter = "(&(objectCategory=group)$Filter)" | |
Write-Verbose "[Get-DomainGroup] filter string: $($GroupSearcher.filter)" | |
if ($PSBoundParameters['FindOne']) { $Results = $GroupSearcher.FindOne() } | |
else { $Results = $GroupSearcher.FindAll() } | |
$Results | Where-Object {$_} | ForEach-Object { | |
if ($PSBoundParameters['Raw']) { | |
# return raw result objects | |
$Group = $_ | |
} | |
else { | |
$Group = Convert-LDAPProperty -Properties $_.Properties | |
} | |
$Group.PSObject.TypeNames.Insert(0, 'PowerView.Group') | |
$Group | |
} | |
if ($Results) { | |
try { $Results.dispose() } | |
catch { | |
Write-Verbose "[Get-DomainGroup] Error disposing of the Results object" | |
} | |
} | |
$GroupSearcher.dispose() | |
} | |
} | |
} | |
} | |
function Get-DomainGroupMember { | |
<# | |
.SYNOPSIS | |
Return the members of a specific domain group. | |
Author: Will Schroeder (@harmj0y) | |
License: BSD 3-Clause | |
Required Dependencies: Get-DomainSearcher, Get-DomainGroup, Get-DomainGroupMember, Convert-ADName, Get-DomainObject, ConvertFrom-SID | |
.DESCRIPTION | |
Builds a directory searcher object using Get-DomainSearcher, builds a custom | |
LDAP filter based on targeting/filter parameters, and searches for the specified | |
group matching the criteria. Each result is then rebound and the full user | |
or group object is returned. | |
.PARAMETER Identity | |
A SamAccountName (e.g. Group1), DistinguishedName (e.g. CN=group1,CN=Users,DC=testlab,DC=local), | |
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1114), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d202) | |
specifying the group to query for. Wildcards accepted. | |
.PARAMETER Domain | |
Specifies the domain to use for the query, defaults to the current domain. | |
.PARAMETER Recurse | |
Switch. If the group member is a group, recursively try to query its members as well. | |
.PARAMETER RecurseUsingMatchingRule | |
Switch. Use LDAP_MATCHING_RULE_IN_CHAIN in the LDAP search query to recurse. | |
Much faster than manual recursion, but doesn't reveal cross-domain groups, | |
and only returns user accounts (no nested group objects themselves). | |
.PARAMETER LDAPFilter | |
Specifies an LDAP query string that is used to filter Active Directory objects. | |
.PARAMETER SearchBase | |
The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local" | |
Useful for OU queries. | |
.PARAMETER Server | |
Specifies an Active Directory server (domain controller) to bind to. | |
.PARAMETER SearchScope | |
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree). | |
.PARAMETER ResultPageSize | |
Specifies the PageSize to set for the LDAP searcher object. | |
.PARAMETER ServerTimeLimit | |
Specifies the maximum amount of time the server spends searching. Default of 120 seconds. | |
.PARAMETER SecurityMasks | |
Specifies an option for examining security information of a directory object. | |
One of 'Dacl', 'Group', 'None', 'Owner', 'Sacl'. | |
.PARAMETER Tombstone | |
Switch. Specifies that the searcher should also return deleted/tombstoned objects. | |
.PARAMETER Credential | |
A [Management.Automation.PSCredential] object of alternate credentials | |
for connection to the target domain. | |
.EXAMPLE | |
Get-DomainGroupMember "Desktop Admins" | |
GroupDomain : testlab.local | |
GroupName : Desktop Admins | |
GroupDistinguishedName : CN=Desktop Admins,CN=Users,DC=testlab,DC=local | |
MemberDomain : testlab.local | |
MemberName : Testing Group | |
MemberDistinguishedName : CN=Testing Group,CN=Users,DC=testlab,DC=local | |
MemberObjectClass : group | |
MemberSID : S-1-5-21-890171859-3433809279-3366196753-1129 | |
GroupDomain : testlab.local | |
GroupName : Desktop Admins | |
GroupDistinguishedName : CN=Desktop Admins,CN=Users,DC=testlab,DC=local | |
MemberDomain : testlab.local | |
MemberName : arobbins.a | |
MemberDistinguishedName : CN=Andy Robbins (admin),CN=Users,DC=testlab,DC=local | |
MemberObjectClass : user | |
MemberSID : S-1-5-21-890171859-3433809279-3366196753-1112 | |
.EXAMPLE | |
'Desktop Admins' | Get-DomainGroupMember -Recurse | |
GroupDomain : testlab.local | |
GroupName : Desktop Admins | |
GroupDistinguishedName : CN=Desktop Admins,CN=Users,DC=testlab,DC=local | |
MemberDomain : testlab.local | |
MemberName : Testing Group | |
MemberDistinguishedName : CN=Testing Group,CN=Users,DC=testlab,DC=local | |
MemberObjectClass : group | |
MemberSID : S-1-5-21-890171859-3433809279-3366196753-1129 | |
GroupDomain : testlab.local | |
GroupName : Testing Group | |
GroupDistinguishedName : CN=Testing Group,CN=Users,DC=testlab,DC=local | |
MemberDomain : testlab.local | |
MemberName : harmj0y | |
MemberDistinguishedName : CN=harmj0y,CN=Users,DC=testlab,DC=local | |
MemberObjectClass : user | |
MemberSID : S-1-5-21-890171859-3433809279-3366196753-1108 | |
GroupDomain : testlab.local | |
GroupName : Desktop Admins | |
GroupDistinguishedName : CN=Desktop Admins,CN=Users,DC=testlab,DC=local | |
MemberDomain : testlab.local | |
MemberName : arobbins.a | |
MemberDistinguishedName : CN=Andy Robbins (admin),CN=Users,DC=testlab,DC=local | |
MemberObjectClass : user | |
MemberSID : S-1-5-21-890171859-3433809279-3366196753-1112 | |
.EXAMPLE | |
Get-DomainGroupMember -Domain testlab.local -Identity 'Desktop Admins' -RecurseUingMatchingRule | |
GroupDomain : testlab.local | |
GroupName : Desktop Admins | |
GroupDistinguishedName : CN=Desktop Admins,CN=Users,DC=testlab,DC=local | |
MemberDomain : testlab.local | |
MemberName : harmj0y | |
MemberDistinguishedName : CN=harmj0y,CN=Users,DC=testlab,DC=local | |
MemberObjectClass : user | |
MemberSID : S-1-5-21-890171859-3433809279-3366196753-1108 | |
GroupDomain : testlab.local | |
GroupName : Desktop Admins | |
GroupDistinguishedName : CN=Desktop Admins,CN=Users,DC=testlab,DC=local | |
MemberDomain : testlab.local | |
MemberName : arobbins.a | |
MemberDistinguishedName : CN=Andy Robbins (admin),CN=Users,DC=testlab,DC=local | |
MemberObjectClass : user | |
MemberSID : S-1-5-21-890171859-3433809279-3366196753-1112 | |
.EXAMPLE | |
Get-DomainGroup *admin* -Properties samaccountname | Get-DomainGroupMember | |
.EXAMPLE | |
'CN=Enterprise Admins,CN=Users,DC=testlab,DC=local', 'Domain Admins' | Get-DomainGroupMember | |
.EXAMPLE | |
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force | |
$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword) | |
Get-DomainGroupMember -Credential $Cred -Identity 'Domain Admins' | |
.EXAMPLE | |
Get-Domain | Select-Object -Expand name | |
testlab.local | |
'dev\domain admins' | Get-DomainGroupMember -Verbose | |
VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local | |
VERBOSE: [Get-DomainGroupMember] Extracted domain 'dev.testlab.local' from 'dev\domain admins' | |
VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=dev,DC=testlab,DC=local | |
VERBOSE: [Get-DomainGroupMember] Get-DomainGroupMember filter string: (&(objectCategory=group)(|(samAccountName=domain admins))) | |
VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=dev,DC=testlab,DC=local | |
VERBOSE: [Get-DomainObject] Get-DomainObject filter string: (&(|(distinguishedname=CN=user1,CN=Users,DC=dev,DC=testlab,DC=local))) | |
GroupDomain : dev.testlab.local | |
GroupName : Domain Admins | |
GroupDistinguishedName : CN=Domain Admins,CN=Users,DC=dev,DC=testlab,DC=local | |
MemberDomain : dev.testlab.local | |
MemberName : user1 | |
MemberDistinguishedName : CN=user1,CN=Users,DC=dev,DC=testlab,DC=local | |
MemberObjectClass : user | |
MemberSID : S-1-5-21-339048670-1233568108-4141518690-201108 | |
VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=dev,DC=testlab,DC=local | |
VERBOSE: [Get-DomainObject] Get-DomainObject filter string: (&(|(distinguishedname=CN=Administrator,CN=Users,DC=dev,DC=testlab,DC=local))) | |
GroupDomain : dev.testlab.local | |
GroupName : Domain Admins | |
GroupDistinguishedName : CN=Domain Admins,CN=Users,DC=dev,DC=testlab,DC=local | |
MemberDomain : dev.testlab.local | |
MemberName : Administrator | |
MemberDistinguishedName : CN=Administrator,CN=Users,DC=dev,DC=testlab,DC=local | |
MemberObjectClass : user | |
MemberSID : S-1-5-21-339048670-1233568108-4141518690-500 | |
.OUTPUTS | |
PowerView.GroupMember | |
Custom PSObject with translated group member property fields. | |
.LINK | |
http://www.powershellmagazine.com/2013/05/23/pstip-retrieve-group-membership-of-an-active-directory-group-recursively/ | |
#> | |
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')] | |
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')] | |
[OutputType('PowerView.GroupMember')] | |
[CmdletBinding(DefaultParameterSetName = 'None')] | |
Param( | |
[Parameter(Position = 0, Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)] | |
[Alias('DistinguishedName', 'SamAccountName', 'Name', 'MemberDistinguishedName', 'MemberName')] | |
[String[]] | |
$Identity, | |
[ValidateNotNullOrEmpty()] | |
[String] | |
$Domain, | |
[Parameter(ParameterSetName = 'ManualRecurse')] | |
[Switch] | |
$Recurse, | |
[Parameter(ParameterSetName = 'RecurseUsingMatchingRule')] | |
[Switch] | |
$RecurseUsingMatchingRule, | |
[ValidateNotNullOrEmpty()] | |
[Alias('Filter')] | |
[String] | |
$LDAPFilter, | |
[ValidateNotNullOrEmpty()] | |
[Alias('ADSPath')] | |
[String] | |
$SearchBase, | |
[ValidateNotNullOrEmpty()] | |
[Alias('DomainController')] | |
[String] | |
$Server, | |
[ValidateSet('Base', 'OneLevel', 'Subtree')] | |
[String] | |
$SearchScope = 'Subtree', | |
[ValidateRange(1, 10000)] | |
[Int] | |
$ResultPageSize = 200, | |
[ValidateRange(1, 10000)] | |
[Int] | |
$ServerTimeLimit, | |
[ValidateSet('Dacl', 'Group', 'None', 'Owner', 'Sacl')] | |
[String] | |
$SecurityMasks, | |
[Switch] | |
$Tombstone, | |
[Management.Automation.PSCredential] | |
[Management.Automation.CredentialAttribute()] | |
$Credential = [Management.Automation.PSCredential]::Empty | |
) | |
BEGIN { | |
$SearcherArguments = @{ | |
'Properties' = 'member,samaccountname,distinguishedname' | |
} | |
if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain } | |
if ($PSBoundParameters['LDAPFilter']) { $SearcherArguments['LDAPFilter'] = $LDAPFilter } | |
if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase } | |
if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server } | |
if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope } | |
if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize } | |
if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit } | |
if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone } | |
if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential } | |
$ADNameArguments = @{} | |
if ($PSBoundParameters['Domain']) { $ADNameArguments['Domain'] = $Domain } | |
if ($PSBoundParameters['Server']) { $ADNameArguments['Server'] = $Server } | |
if ($PSBoundParameters['Credential']) { $ADNameArguments['Credential'] = $Credential } | |
} | |
PROCESS { | |
$GroupSearcher = Get-DomainSearcher @SearcherArguments | |
if ($GroupSearcher) { | |
if ($PSBoundParameters['RecurseUsingMatchingRule']) { | |
$SearcherArguments['Identity'] = $Identity | |
$SearcherArguments['Raw'] = $True | |
$Group = Get-DomainGroup @SearcherArguments | |
if (-not $Group) { | |
Write-Warning "[Get-DomainGroupMember] Error searching for group with identity: $Identity" | |
} | |
else { | |
$GroupFoundName = $Group.properties.item('samaccountname')[0] | |
$GroupFoundDN = $Group.properties.item('distinguishedname')[0] | |
if ($PSBoundParameters['Domain']) { | |
$GroupFoundDomain = $Domain | |
} | |
else { | |
# if a domain isn't passed, try to extract it from the found group distinguished name | |
if ($GroupFoundDN) { | |
$GroupFoundDomain = $GroupFoundDN.SubString($GroupFoundDN.IndexOf('DC=')) -replace 'DC=','' -replace ',','.' | |
} | |
} | |
Write-Verbose "[Get-DomainGroupMember] Using LDAP matching rule to recurse on '$GroupFoundDN', only user accounts will be returned." | |
$GroupSearcher.filter = "(&(samAccountType=805306368)(memberof:1.2.840.113556.1.4.1941:=$GroupFoundDN))" | |
$GroupSearcher.PropertiesToLoad.AddRange(('distinguishedName')) | |
$Members = $GroupSearcher.FindAll() | ForEach-Object {$_.Properties.distinguishedname[0]} | |
} | |
$Null = $SearcherArguments.Remove('Raw') | |
} | |
else { | |
$IdentityFilter = '' | |
$Filter = '' | |
$Identity | Where-Object {$_} | ForEach-Object { | |
$IdentityInstance = $_.Replace('(', '\28').Replace(')', '\29') | |
if ($IdentityInstance -match '^S-1-') { | |
$IdentityFilter += "(objectsid=$IdentityInstance)" | |
} | |
elseif ($IdentityInstance -match '^CN=') { | |
$IdentityFilter += "(distinguishedname=$IdentityInstance)" | |
if ((-not $PSBoundParameters['Domain']) -and (-not $PSBoundParameters['SearchBase'])) { | |
# if a -Domain isn't explicitly set, extract the object domain out of the distinguishedname | |
# and rebuild the domain searcher | |
$IdentityDomain = $IdentityInstance.SubString($IdentityInstance.IndexOf('DC=')) -replace 'DC=','' -replace ',','.' | |
Write-Verbose "[Get-DomainGroupMember] Extracted domain '$IdentityDomain' from '$IdentityInstance'" | |
$SearcherArguments['Domain'] = $IdentityDomain | |
$GroupSearcher = Get-DomainSearcher @SearcherArguments | |
if (-not $GroupSearcher) { | |
Write-Warning "[Get-DomainGroupMember] Unable to retrieve domain searcher for '$IdentityDomain'" | |
} | |
} | |
} | |
elseif ($IdentityInstance -imatch '^[0-9A-F]{8}-([0-9A-F]{4}-){3}[0-9A-F]{12}$') { | |
$GuidByteString = (([Guid]$IdentityInstance).ToByteArray() | ForEach-Object { '\' + $_.ToString('X2') }) -join '' | |
$IdentityFilter += "(objectguid=$GuidByteString)" | |
} | |
elseif ($IdentityInstance.Contains('\')) { | |
$ConvertedIdentityInstance = $IdentityInstance.Replace('\28', '(').Replace('\29', ')') | Convert-ADName -OutputType Canonical | |
if ($ConvertedIdentityInstance) { | |
$GroupDomain = $ConvertedIdentityInstance.SubString(0, $ConvertedIdentityInstance.IndexOf('/')) | |
$GroupName = $IdentityInstance.Split('\')[1] | |
$IdentityFilter += "(samAccountName=$GroupName)" | |
$SearcherArguments['Domain'] = $GroupDomain | |
Write-Verbose "[Get-DomainGroupMember] Extracted domain '$GroupDomain' from '$IdentityInstance'" | |
$GroupSearcher = Get-DomainSearcher @SearcherArguments | |
} | |
} | |
else { | |
$IdentityFilter += "(samAccountName=$IdentityInstance)" | |
} | |
} | |
if ($IdentityFilter -and ($IdentityFilter.Trim() -ne '') ) { | |
$Filter += "(|$IdentityFilter)" | |
} | |
if ($PSBoundParameters['LDAPFilter']) { | |
Write-Verbose "[Get-DomainGroupMember] Using additional LDAP filter: $LDAPFilter" | |
$Filter += "$LDAPFilter" | |
} | |
$GroupSearcher.filter = "(&(objectCategory=group)$Filter)" | |
Write-Verbose "[Get-DomainGroupMember] Get-DomainGroupMember filter string: $($GroupSearcher.filter)" | |
try { | |
$Result = $GroupSearcher.FindOne() | |
} | |
catch { | |
Write-Warning "[Get-DomainGroupMember] Error searching for group with identity '$Identity': $_" | |
$Members = @() | |
} | |
$GroupFoundName = '' | |
$GroupFoundDN = '' | |
if ($Result) { | |
$Members = $Result.properties.item('member') | |
if ($Members.count -eq 0) { | |
# ranged searching, thanks @meatballs__ ! | |
$Finished = $False | |
$Bottom = 0 | |
$Top = 0 | |
while (-not $Finished) { | |
$Top = $Bottom + 1499 | |
$MemberRange="member;range=$Bottom-$Top" | |
$Bottom += 1500 | |
$Null = $GroupSearcher.PropertiesToLoad.Clear() | |
$Null = $GroupSearcher.PropertiesToLoad.Add("$MemberRange") | |
$Null = $GroupSearcher.PropertiesToLoad.Add('samaccountname') | |
$Null = $GroupSearcher.PropertiesToLoad.Add('distinguishedname') | |
try { | |
$Result = $GroupSearcher.FindOne() | |
$RangedProperty = $Result.Properties.PropertyNames -like "member;range=*" | |
$Members += $Result.Properties.item($RangedProperty) | |
$GroupFoundName = $Result.properties.item('samaccountname')[0] | |
$GroupFoundDN = $Result.properties.item('distinguishedname')[0] | |
if ($Members.count -eq 0) { | |
$Finished = $True | |
} | |
} | |
catch [System.Management.Automation.MethodInvocationException] { | |
$Finished = $True | |
} | |
} | |
} | |
else { | |
$GroupFoundName = $Result.properties.item('samaccountname')[0] | |
$GroupFoundDN = $Result.properties.item('distinguishedname')[0] | |
$Members += $Result.Properties.item($RangedProperty) | |
} | |
if ($PSBoundParameters['Domain']) { | |
$GroupFoundDomain = $Domain | |
} | |
else { | |
# if a domain isn't passed, try to extract it from the found group distinguished name | |
if ($GroupFoundDN) { | |
$GroupFoundDomain = $GroupFoundDN.SubString($GroupFoundDN.IndexOf('DC=')) -replace 'DC=','' -replace ',','.' | |
} | |
} | |
} | |
} | |
ForEach ($Member in $Members) { | |
if ($Recurse -and $UseMatchingRule) { | |
$Properties = $_.Properties | |
} | |
else { | |
$ObjectSearcherArguments = $SearcherArguments.Clone() | |
$ObjectSearcherArguments['Identity'] = $Member | |
$ObjectSearcherArguments['Raw'] = $True | |
$ObjectSearcherArguments['Properties'] = 'distinguishedname,cn,samaccountname,objectsid,objectclass' | |
$Object = Get-DomainObject @ObjectSearcherArguments | |
$Properties = $Object.Properties | |
} | |
if ($Properties) { | |
$GroupMember = New-Object PSObject | |
$GroupMember | Add-Member Noteproperty 'GroupDomain' $GroupFoundDomain | |
$GroupMember | Add-Member Noteproperty 'GroupName' $GroupFoundName | |
$GroupMember | Add-Member Noteproperty 'GroupDistinguishedName' $GroupFoundDN | |
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|S-1-5-21') { | |
try { | |
if (-not $MemberSID) { | |
$MemberSID = $Properties.cn[0] | |
} | |
$MemberSimpleName = Convert-ADName -Identity $MemberSID -OutputType 'DomainSimple' @ADNameArguments | |
if ($MemberSimpleName) { | |
$MemberDomain = $MemberSimpleName.Split('@')[1] | |
} | |
else { | |
Write-Warning "[Get-DomainGroupMember] Error converting $MemberDN" | |
$MemberDomain = $Null | |
} | |
} | |
catch { | |
Write-Warning "[Get-DomainGroupMember] 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 = ConvertFrom-SID -ObjectSID $Properties.cn[0] @ADNameArguments | |
} | |
catch { | |
# if there's a problem contacting the domain to resolve the SID | |
$MemberName = $Properties.cn[0] | |
} | |
} | |
if ($Properties.objectclass -match 'computer') { | |
$MemberObjectClass = 'computer' | |
} | |
elseif ($Properties.objectclass -match 'group') { | |
$MemberObjectClass = 'group' | |
} | |
elseif ($Properties.objectclass -match 'user') { | |
$MemberObjectClass = 'user' | |
} | |
else { | |
$MemberObjectClass = $Null | |
} | |
$GroupMember | Add-Member Noteproperty 'MemberDomain' $MemberDomain | |
$GroupMember | Add-Member Noteproperty 'MemberName' $MemberName | |
$GroupMember | Add-Member Noteproperty 'MemberDistinguishedName' $MemberDN | |
$GroupMember | Add-Member Noteproperty 'MemberObjectClass' $MemberObjectClass | |
$GroupMember | Add-Member Noteproperty 'MemberSID' $MemberSID | |
$GroupMember.PSObject.TypeNames.Insert(0, 'PowerView.GroupMember') | |
$GroupMember | |
# if we're doing manual recursion | |
if ($PSBoundParameters['Recurse'] -and $MemberDN -and ($MemberObjectClass -match 'group')) { | |
Write-Verbose "[Get-DomainGroupMember] Manually recursing on group: $MemberDN" | |
$SearcherArguments['Identity'] = $MemberDN | |
$Null = $SearcherArguments.Remove('Properties') | |
Get-DomainGroupMember @SearcherArguments | |
} | |
} | |
} | |
$GroupSearcher.dispose() | |
} | |
} | |
} | |
Function New-DynamicParameter { | |
<# | |
.SYNOPSIS | |
Helper function to simplify creating dynamic parameters. | |
Adapated from https://beatcracker.wordpress.com/2015/08/10/dynamic-parameters-validateset-and-enums/. | |
Originally released under the Microsoft Public License (Ms-PL). | |
.DESCRIPTION | |
Helper function to simplify creating dynamic parameters. | |
Example use cases: | |
Include parameters only if your environment dictates it | |
Include parameters depending on the value of a user-specified parameter | |
Provide tab completion and intellisense for parameters, depending on the environment | |
Please keep in mind that all dynamic parameters you create, will not have corresponding variables created. | |
Use New-DynamicParameter with 'CreateVariables' switch in your main code block, | |
('Process' for advanced functions) to create those variables. | |
Alternatively, manually reference $PSBoundParameters for the dynamic parameter value. | |
This function has two operating modes: | |
1. All dynamic parameters created in one pass using pipeline input to the function. This mode allows to create dynamic parameters en masse, | |
with one function call. There is no need to create and maintain custom RuntimeDefinedParameterDictionary. | |
2. Dynamic parameters are created by separate function calls and added to the RuntimeDefinedParameterDictionary you created beforehand. | |
Then you output this RuntimeDefinedParameterDictionary to the pipeline. This allows more fine-grained control of the dynamic parameters, | |
with custom conditions and so on. | |
.NOTES | |
Credits to jrich523 and ramblingcookiemonster for their initial code and inspiration: | |
https://github.com/RamblingCookieMonster/PowerShell/blob/master/New-DynamicParam.ps1 | |
http://ramblingcookiemonster.wordpress.com/2014/11/27/quick-hits-credentials-and-dynamic-parameters/ | |
http://jrich523.wordpress.com/2013/05/30/powershell-simple-way-to-add-dynamic-parameters-to-advanced-function/ | |
Credit to BM for alias and type parameters and their handling | |
.PARAMETER Name | |
Name of the dynamic parameter | |
.PARAMETER Type | |
Type for the dynamic parameter. Default is string | |
.PARAMETER Alias | |
If specified, one or more aliases to assign to the dynamic parameter | |
.PARAMETER Mandatory | |
If specified, set the Mandatory attribute for this dynamic parameter | |
.PARAMETER Position | |
If specified, set the Position attribute for this dynamic parameter | |
.PARAMETER HelpMessage | |
If specified, set the HelpMessage for this dynamic parameter | |
.PARAMETER DontShow | |
If specified, set the DontShow for this dynamic parameter. | |
This is the new PowerShell 4.0 attribute that hides parameter from tab-completion. | |
http://www.powershellmagazine.com/2013/07/29/pstip-hiding-parameters-from-tab-completion/ | |
.PARAMETER ValueFromPipeline | |
If specified, set the ValueFromPipeline attribute for this dynamic parameter | |
.PARAMETER ValueFromPipelineByPropertyName | |
If specified, set the ValueFromPipelineByPropertyName attribute for this dynamic parameter | |
.PARAMETER ValueFromRemainingArguments | |
If specified, set the ValueFromRemainingArguments attribute for this dynamic parameter | |
.PARAMETER ParameterSetName | |
If specified, set the ParameterSet attribute for this dynamic parameter. By default parameter is added to all parameters sets. | |
.PARAMETER AllowNull | |
If specified, set the AllowNull attribute of this dynamic parameter | |
.PARAMETER AllowEmptyString | |
If specified, set the AllowEmptyString attribute of this dynamic parameter | |
.PARAMETER AllowEmptyCollection | |
If specified, set the AllowEmptyCollection attribute of this dynamic parameter | |
.PARAMETER ValidateNotNull | |
If specified, set the ValidateNotNull attribute of this dynamic parameter | |
.PARAMETER ValidateNotNullOrEmpty | |
If specified, set the ValidateNotNullOrEmpty attribute of this dynamic parameter | |
.PARAMETER ValidateRange | |
If specified, set the ValidateRange attribute of this dynamic parameter | |
.PARAMETER ValidateLength | |
If specified, set the ValidateLength attribute of this dynamic parameter | |
.PARAMETER ValidatePattern | |
If specified, set the ValidatePattern attribute of this dynamic parameter | |
.PARAMETER ValidateScript | |
If specified, set the ValidateScript attribute of this dynamic parameter | |
.PARAMETER ValidateSet | |
If specified, set the ValidateSet attribute of this dynamic parameter | |
.PARAMETER Dictionary | |
If specified, add resulting RuntimeDefinedParameter to an existing RuntimeDefinedParameterDictionary. | |
Appropriate for custom dynamic parameters creation. | |
If not specified, create and return a RuntimeDefinedParameterDictionary | |
Appropriate for a simple dynamic parameter creation. | |
#> | |
[CmdletBinding(DefaultParameterSetName = 'DynamicParameter')] | |
Param ( | |
[Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')] | |
[ValidateNotNullOrEmpty()] | |
[string]$Name, | |
[Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')] | |
[System.Type]$Type = [int], | |
[Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')] | |
[string[]]$Alias, | |
[Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')] | |
[switch]$Mandatory, | |
[Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')] | |
[int]$Position, | |
[Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')] | |
[string]$HelpMessage, | |
[Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')] | |
[switch]$DontShow, | |
[Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')] | |
[switch]$ValueFromPipeline, | |
[Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')] | |
[switch]$ValueFromPipelineByPropertyName, | |
[Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')] | |
[switch]$ValueFromRemainingArguments, | |
[Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')] | |
[string]$ParameterSetName = '__AllParameterSets', | |
[Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')] | |
[switch]$AllowNull, | |
[Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')] | |
[switch]$AllowEmptyString, | |
[Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')] | |
[switch]$AllowEmptyCollection, | |
[Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')] | |
[switch]$ValidateNotNull, | |
[Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')] | |
[switch]$ValidateNotNullOrEmpty, | |
[Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')] | |
[ValidateCount(2,2)] | |
[int[]]$ValidateCount, | |
[Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')] | |
[ValidateCount(2,2)] | |
[int[]]$ValidateRange, | |
[Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')] | |
[ValidateCount(2,2)] | |
[int[]]$ValidateLength, | |
[Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')] | |
[ValidateNotNullOrEmpty()] | |
[string]$ValidatePattern, | |
[Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')] | |
[ValidateNotNullOrEmpty()] | |
[scriptblock]$ValidateScript, | |
[Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')] | |
[ValidateNotNullOrEmpty()] | |
[string[]]$ValidateSet, | |
[Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')] | |
[ValidateNotNullOrEmpty()] | |
[ValidateScript({ | |
if(!($_ -is [System.Management.Automation.RuntimeDefinedParameterDictionary])) | |
{ | |
Throw 'Dictionary must be a System.Management.Automation.RuntimeDefinedParameterDictionary object' | |
} | |
$true | |
})] | |
$Dictionary = $false, | |
[Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'CreateVariables')] | |
[switch]$CreateVariables, | |
[Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'CreateVariables')] | |
[ValidateNotNullOrEmpty()] | |
[ValidateScript({ | |
# System.Management.Automation.PSBoundParametersDictionary is an internal sealed class, | |
# so one can't use PowerShell's '-is' operator to validate type. | |
if($_.GetType().Name -notmatch 'Dictionary') { | |
Throw 'BoundParameters must be a System.Management.Automation.PSBoundParametersDictionary object' | |
} | |
$true | |
})] | |
$BoundParameters | |
) | |
Begin { | |
$InternalDictionary = New-Object -TypeName System.Management.Automation.RuntimeDefinedParameterDictionary | |
function _temp { [CmdletBinding()] Param() } | |
$CommonParameters = (Get-Command _temp).Parameters.Keys | |
} | |
Process { | |
if($CreateVariables) { | |
$BoundKeys = $BoundParameters.Keys | Where-Object { $CommonParameters -notcontains $_ } | |
ForEach($Parameter in $BoundKeys) { | |
if ($Parameter) { | |
Set-Variable -Name $Parameter -Value $BoundParameters.$Parameter -Scope 1 -Force | |
} | |
} | |
} | |
else { | |
$StaleKeys = @() | |
$StaleKeys = $PSBoundParameters.GetEnumerator() | | |
ForEach-Object { | |
if($_.Value.PSobject.Methods.Name -match '^Equals$') { | |
# If object has Equals, compare bound key and variable using it | |
if(!$_.Value.Equals((Get-Variable -Name $_.Key -ValueOnly -Scope 0))) { | |
$_.Key | |
} | |
} | |
else { | |
# If object doesn't has Equals (e.g. $null), fallback to the PowerShell's -ne operator | |
if($_.Value -ne (Get-Variable -Name $_.Key -ValueOnly -Scope 0)) { | |
$_.Key | |
} | |
} | |
} | |
if($StaleKeys) { | |
$StaleKeys | ForEach-Object {[void]$PSBoundParameters.Remove($_)} | |
} | |
# Since we rely solely on $PSBoundParameters, we don't have access to default values for unbound parameters | |
$UnboundParameters = (Get-Command -Name ($PSCmdlet.MyInvocation.InvocationName)).Parameters.GetEnumerator() | | |
# Find parameters that are belong to the current parameter set | |
Where-Object { $_.Value.ParameterSets.Keys -contains $PsCmdlet.ParameterSetName } | | |
Select-Object -ExpandProperty Key | | |
# Find unbound parameters in the current parameter set | |
Where-Object { $PSBoundParameters.Keys -notcontains $_ } | |
# Even if parameter is not bound, corresponding variable is created with parameter's default value (if specified) | |
$tmp = $null | |
ForEach ($Parameter in $UnboundParameters) { | |
$DefaultValue = Get-Variable -Name $Parameter -ValueOnly -Scope 0 | |
if(!$PSBoundParameters.TryGetValue($Parameter, [ref]$tmp) -and $DefaultValue) { | |
$PSBoundParameters.$Parameter = $DefaultValue | |
} | |
} | |
if($Dictionary) { | |
$DPDictionary = $Dictionary | |
} | |
else { | |
$DPDictionary = $InternalDictionary | |
} | |
# Shortcut for getting local variables | |
$GetVar = {Get-Variable -Name $_ -ValueOnly -Scope 0} | |
# Strings to match attributes and validation arguments | |
$AttributeRegex = '^(Mandatory|Position|ParameterSetName|DontShow|HelpMessage|ValueFromPipeline|ValueFromPipelineByPropertyName|ValueFromRemainingArguments)$' | |
$ValidationRegex = '^(AllowNull|AllowEmptyString|AllowEmptyCollection|ValidateCount|ValidateLength|ValidatePattern|ValidateRange|ValidateScript|ValidateSet|ValidateNotNull|ValidateNotNullOrEmpty)$' | |
$AliasRegex = '^Alias$' | |
$ParameterAttribute = New-Object -TypeName System.Management.Automation.ParameterAttribute | |
switch -regex ($PSBoundParameters.Keys) { | |
$AttributeRegex { | |
Try { | |
$ParameterAttribute.$_ = . $GetVar | |
} | |
Catch { | |
$_ | |
} | |
continue | |
} | |
} | |
if($DPDictionary.Keys -contains $Name) { | |
$DPDictionary.$Name.Attributes.Add($ParameterAttribute) | |
} | |
else { | |
$AttributeCollection = New-Object -TypeName Collections.ObjectModel.Collection[System.Attribute] | |
switch -regex ($PSBoundParameters.Keys) { | |
$ValidationRegex { | |
Try { | |
$ParameterOptions = New-Object -TypeName "System.Management.Automation.${_}Attribute" -ArgumentList (. $GetVar) -ErrorAction Stop | |
$AttributeCollection.Add($ParameterOptions) | |
} | |
Catch { $_ } | |
continue | |
} | |
$AliasRegex { | |
Try { | |
$ParameterAlias = New-Object -TypeName System.Management.Automation.AliasAttribute -ArgumentList (. $GetVar) -ErrorAction Stop | |
$AttributeCollection.Add($ParameterAlias) | |
continue | |
} | |
Catch { $_ } | |
} | |
} | |
$AttributeCollection.Add($ParameterAttribute) | |
$Parameter = New-Object -TypeName System.Management.Automation.RuntimeDefinedParameter -ArgumentList @($Name, $Type, $AttributeCollection) | |
$DPDictionary.Add($Name, $Parameter) | |
} | |
} | |
} | |
End { | |
if(!$CreateVariables -and !$Dictionary) { | |
$DPDictionary | |
} | |
} | |
} | |
function New-InMemoryModule { | |
<# | |
.SYNOPSIS | |
Creates an in-memory assembly and module | |
Author: Matthew Graeber (@mattifestation) | |
License: BSD 3-Clause | |
Required Dependencies: None | |
Optional Dependencies: None | |
.DESCRIPTION | |
When defining custom enums, structs, and unmanaged functions, it is | |
necessary to associate to an assembly module. This helper function | |
creates an in-memory module that can be passed to the 'enum', | |
'struct', and Add-Win32Type functions. | |
.PARAMETER ModuleName | |
Specifies the desired name for the in-memory assembly and module. If | |
ModuleName is not provided, it will default to a GUID. | |
.EXAMPLE | |
$Module = New-InMemoryModule -ModuleName Win32 | |
#> | |
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')] | |
[CmdletBinding()] | |
Param ( | |
[Parameter(Position = 0)] | |
[ValidateNotNullOrEmpty()] | |
[String] | |
$ModuleName = [Guid]::NewGuid().ToString() | |
) | |
$AppDomain = [Reflection.Assembly].Assembly.GetType('System.AppDomain').GetProperty('CurrentDomain').GetValue($null, @()) | |
$LoadedAssemblies = $AppDomain.GetAssemblies() | |
foreach ($Assembly in $LoadedAssemblies) { | |
if ($Assembly.FullName -and ($Assembly.FullName.Split(',')[0] -eq $ModuleName)) { | |
return $Assembly | |
} | |
} | |
$DynAssembly = New-Object Reflection.AssemblyName($ModuleName) | |
$Domain = $AppDomain | |
$AssemblyBuilder = $Domain.DefineDynamicAssembly($DynAssembly, 'Run') | |
$ModuleBuilder = $AssemblyBuilder.DefineDynamicModule($ModuleName, $False) | |
return $ModuleBuilder | |
} | |
# A helper function used to reduce typing while defining function | |
# prototypes for Add-Win32Type. | |
function func { | |
Param ( | |
[Parameter(Position = 0, Mandatory = $True)] | |
[String] | |
$DllName, | |
[Parameter(Position = 1, Mandatory = $True)] | |
[string] | |
$FunctionName, | |
[Parameter(Position = 2, Mandatory = $True)] | |
[Type] | |
$ReturnType, | |
[Parameter(Position = 3)] | |
[Type[]] | |
$ParameterTypes, | |
[Parameter(Position = 4)] | |
[Runtime.InteropServices.CallingConvention] | |
$NativeCallingConvention, | |
[Parameter(Position = 5)] | |
[Runtime.InteropServices.CharSet] | |
$Charset, | |
[String] | |
$EntryPoint, | |
[Switch] | |
$SetLastError | |
) | |
$Properties = @{ | |
DllName = $DllName | |
FunctionName = $FunctionName | |
ReturnType = $ReturnType | |
} | |
if ($ParameterTypes) { $Properties['ParameterTypes'] = $ParameterTypes } | |
if ($NativeCallingConvention) { $Properties['NativeCallingConvention'] = $NativeCallingConvention } | |
if ($Charset) { $Properties['Charset'] = $Charset } | |
if ($SetLastError) { $Properties['SetLastError'] = $SetLastError } | |
if ($EntryPoint) { $Properties['EntryPoint'] = $EntryPoint } | |
New-Object PSObject -Property $Properties | |
} | |
function Add-Win32Type | |
{ | |
<# | |
.SYNOPSIS | |
Creates a .NET type for an unmanaged Win32 function. | |
Author: Matthew Graeber (@mattifestation) | |
License: BSD 3-Clause | |
Required Dependencies: None | |
Optional Dependencies: func | |
.DESCRIPTION | |
Add-Win32Type enables you to easily interact with unmanaged (i.e. | |
Win32 unmanaged) functions in PowerShell. After providing | |
Add-Win32Type with a function signature, a .NET type is created | |
using reflection (i.e. csc.exe is never called like with Add-Type). | |
The 'func' helper function can be used to reduce typing when defining | |
multiple function definitions. | |
.PARAMETER DllName | |
The name of the DLL. | |
.PARAMETER FunctionName | |
The name of the target function. | |
.PARAMETER EntryPoint | |
The DLL export function name. This argument should be specified if the | |
specified function name is different than the name of the exported | |
function. | |
.PARAMETER ReturnType | |
The return type of the function. | |
.PARAMETER ParameterTypes | |
The function parameters. | |
.PARAMETER NativeCallingConvention | |
Specifies the native calling convention of the function. Defaults to | |
stdcall. | |
.PARAMETER Charset | |
If you need to explicitly call an 'A' or 'W' Win32 function, you can | |
specify the character set. | |
.PARAMETER SetLastError | |
Indicates whether the callee calls the SetLastError Win32 API | |
function before returning from the attributed method. | |
.PARAMETER Module | |
The in-memory module that will host the functions. Use | |
New-InMemoryModule to define an in-memory module. | |
.PARAMETER Namespace | |
An optional namespace to prepend to the type. Add-Win32Type defaults | |
to a namespace consisting only of the name of the DLL. | |
.EXAMPLE | |
$Mod = New-InMemoryModule -ModuleName Win32 | |
$FunctionDefinitions = @( | |
(func kernel32 GetProcAddress ([IntPtr]) @([IntPtr], [String]) -Charset Ansi -SetLastError), | |
(func kernel32 GetModuleHandle ([Intptr]) @([String]) -SetLastError), | |
(func ntdll RtlGetCurrentPeb ([IntPtr]) @()) | |
) | |
$Types = $FunctionDefinitions | Add-Win32Type -Module $Mod -Namespace 'Win32' | |
$Kernel32 = $Types['kernel32'] | |
$Ntdll = $Types['ntdll'] | |
$Ntdll::RtlGetCurrentPeb() | |
$ntdllbase = $Kernel32::GetModuleHandle('ntdll') | |
$Kernel32::GetProcAddress($ntdllbase, 'RtlGetCurrentPeb') | |
.NOTES | |
Inspired by Lee Holmes' Invoke-WindowsApi http://poshcode.org/2189 | |
When defining multiple function prototypes, it is ideal to provide | |
Add-Win32Type with an array of function signatures. That way, they | |
are all incorporated into the same in-memory module. | |
#> | |
[OutputType([Hashtable])] | |
Param( | |
[Parameter(Mandatory=$True, ValueFromPipelineByPropertyName=$True)] | |
[String] | |
$DllName, | |
[Parameter(Mandatory=$True, ValueFromPipelineByPropertyName=$True)] | |
[String] | |
$FunctionName, | |
[Parameter(ValueFromPipelineByPropertyName=$True)] | |
[String] | |
$EntryPoint, | |
[Parameter(Mandatory=$True, ValueFromPipelineByPropertyName=$True)] | |
[Type] | |
$ReturnType, | |
[Parameter(ValueFromPipelineByPropertyName=$True)] | |
[Type[]] | |
$ParameterTypes, | |
[Parameter(ValueFromPipelineByPropertyName=$True)] | |
[Runtime.InteropServices.CallingConvention] | |
$NativeCallingConvention = [Runtime.InteropServices.CallingConvention]::StdCall, | |
[Parameter(ValueFromPipelineByPropertyName=$True)] | |
[Runtime.InteropServices.CharSet] | |
$Charset = [Runtime.InteropServices.CharSet]::Auto, | |
[Parameter(ValueFromPipelineByPropertyName=$True)] | |
[Switch] | |
$SetLastError, | |
[Parameter(Mandatory=$True)] | |
[ValidateScript({($_ -is [Reflection.Emit.ModuleBuilder]) -or ($_ -is [Reflection.Assembly])})] | |
$Module, | |
[ValidateNotNull()] | |
[String] | |
$Namespace = '' | |
) | |
BEGIN | |
{ | |
$TypeHash = @{} | |
} | |
PROCESS | |
{ | |
if ($Module -is [Reflection.Assembly]) | |
{ | |
if ($Namespace) | |
{ | |
$TypeHash[$DllName] = $Module.GetType("$Namespace.$DllName") | |
} | |
else | |
{ | |
$TypeHash[$DllName] = $Module.GetType($DllName) | |
} | |
} | |
else | |
{ | |
# Define one type for each DLL | |
if (!$TypeHash.ContainsKey($DllName)) | |
{ | |
if ($Namespace) | |
{ | |
$TypeHash[$DllName] = $Module.DefineType("$Namespace.$DllName", 'Public,BeforeFieldInit') | |
} | |
else | |
{ | |
$TypeHash[$DllName] = $Module.DefineType($DllName, 'Public,BeforeFieldInit') | |
} | |
} | |
$Method = $TypeHash[$DllName].DefineMethod( | |
$FunctionName, | |
'Public,Static,PinvokeImpl', | |
$ReturnType, | |
$ParameterTypes) | |
# Make each ByRef parameter an Out parameter | |
$i = 1 | |
foreach($Parameter in $ParameterTypes) | |
{ | |
if ($Parameter.IsByRef) | |
{ | |
[void] $Method.DefineParameter($i, 'Out', $null) | |
} | |
$i++ | |
} | |
$DllImport = [Runtime.InteropServices.DllImportAttribute] | |
$SetLastErrorField = $DllImport.GetField('SetLastError') | |
$CallingConventionField = $DllImport.GetField('CallingConvention') | |
$CharsetField = $DllImport.GetField('CharSet') | |
$EntryPointField = $DllImport.GetField('EntryPoint') | |
if ($SetLastError) { $SLEValue = $True } else { $SLEValue = $False } | |
if ($PSBoundParameters['EntryPoint']) { $ExportedFuncName = $EntryPoint } else { $ExportedFuncName = $FunctionName } | |
# Equivalent to C# version of [DllImport(DllName)] | |
$Constructor = [Runtime.InteropServices.DllImportAttribute].GetConstructor([String]) | |
$DllImportAttribute = New-Object Reflection.Emit.CustomAttributeBuilder($Constructor, | |
$DllName, [Reflection.PropertyInfo[]] @(), [Object[]] @(), | |
[Reflection.FieldInfo[]] @($SetLastErrorField, | |
$CallingConventionField, | |
$CharsetField, | |
$EntryPointField), | |
[Object[]] @($SLEValue, | |
([Runtime.InteropServices.CallingConvention] $NativeCallingConvention), | |
([Runtime.InteropServices.CharSet] $Charset), | |
$ExportedFuncName)) | |
$Method.SetCustomAttribute($DllImportAttribute) | |
} | |
} | |
END | |
{ | |
if ($Module -is [Reflection.Assembly]) | |
{ | |
return $TypeHash | |
} | |
$ReturnTypes = @{} | |
foreach ($Key in $TypeHash.Keys) | |
{ | |
$Type = $TypeHash[$Key].CreateType() | |
$ReturnTypes[$Key] = $Type | |
} | |
return $ReturnTypes | |
} | |
} | |
function psenum { | |
<# | |
.SYNOPSIS | |
Creates an in-memory enumeration for use in your PowerShell session. | |
Author: Matthew Graeber (@mattifestation) | |
License: BSD 3-Clause | |
Required Dependencies: None | |
Optional Dependencies: None | |
.DESCRIPTION | |
The 'psenum' function facilitates the creation of enums entirely in | |
memory using as close to a "C style" as PowerShell will allow. | |
.PARAMETER Module | |
The in-memory module that will host the enum. Use | |
New-InMemoryModule to define an in-memory module. | |
.PARAMETER FullName | |
The fully-qualified name of the enum. | |
.PARAMETER Type | |
The type of each enum element. | |
.PARAMETER EnumElements | |
A hashtable of enum elements. | |
.PARAMETER Bitfield | |
Specifies that the enum should be treated as a bitfield. | |
.EXAMPLE | |
$Mod = New-InMemoryModule -ModuleName Win32 | |
$ImageSubsystem = psenum $Mod PE.IMAGE_SUBSYSTEM UInt16 @{ | |
UNKNOWN = 0 | |
NATIVE = 1 # Image doesn't require a subsystem. | |
WINDOWS_GUI = 2 # Image runs in the Windows GUI subsystem. | |
WINDOWS_CUI = 3 # Image runs in the Windows character subsystem. | |
OS2_CUI = 5 # Image runs in the OS/2 character subsystem. | |
POSIX_CUI = 7 # Image runs in the Posix character subsystem. | |
NATIVE_WINDOWS = 8 # Image is a native Win9x driver. | |
WINDOWS_CE_GUI = 9 # Image runs in the Windows CE subsystem. | |
EFI_APPLICATION = 10 | |
EFI_BOOT_SERVICE_DRIVER = 11 | |
EFI_RUNTIME_DRIVER = 12 | |
EFI_ROM = 13 | |
XBOX = 14 | |
WINDOWS_BOOT_APPLICATION = 16 | |
} | |
.NOTES | |
PowerShell purists may disagree with the naming of this function but | |
again, this was developed in such a way so as to emulate a "C style" | |
definition as closely as possible. Sorry, I'm not going to name it | |
New-Enum. :P | |
#> | |
[OutputType([Type])] | |
Param ( | |
[Parameter(Position = 0, Mandatory=$True)] | |
[ValidateScript({($_ -is [Reflection.Emit.ModuleBuilder]) -or ($_ -is [Reflection.Assembly])})] | |
$Module, | |
[Parameter(Position = 1, Mandatory=$True)] | |
[ValidateNotNullOrEmpty()] | |
[String] | |
$FullName, | |
[Parameter(Position = 2, Mandatory=$True)] | |
[Type] | |
$Type, | |
[Parameter(Position = 3, Mandatory=$True)] | |
[ValidateNotNullOrEmpty()] | |
[Hashtable] | |
$EnumElements, | |
[Switch] | |
$Bitfield | |
) | |
if ($Module -is [Reflection.Assembly]) | |
{ | |
return ($Module.GetType($FullName)) | |
} | |
$EnumType = $Type -as [Type] | |
$EnumBuilder = $Module.DefineEnum($FullName, 'Public', $EnumType) | |
if ($Bitfield) | |
{ | |
$FlagsConstructor = [FlagsAttribute].GetConstructor(@()) | |
$FlagsCustomAttribute = New-Object Reflection.Emit.CustomAttributeBuilder($FlagsConstructor, @()) | |
$EnumBuilder.SetCustomAttribute($FlagsCustomAttribute) | |
} | |
foreach ($Key in $EnumElements.Keys) | |
{ | |
# Apply the specified enum type to each element | |
$null = $EnumBuilder.DefineLiteral($Key, $EnumElements[$Key] -as $EnumType) | |
} | |
$EnumBuilder.CreateType() | |
} | |
# A helper function used to reduce typing while defining struct | |
# fields. | |
function field { | |
Param ( | |
[Parameter(Position = 0, Mandatory=$True)] | |
[UInt16] | |
$Position, | |
[Parameter(Position = 1, Mandatory=$True)] | |
[Type] | |
$Type, | |
[Parameter(Position = 2)] | |
[UInt16] | |
$Offset, | |
[Object[]] | |
$MarshalAs | |
) | |
@{ | |
Position = $Position | |
Type = $Type -as [Type] | |
Offset = $Offset | |
MarshalAs = $MarshalAs | |
} | |
} | |
function struct | |
{ | |
<# | |
.SYNOPSIS | |
Creates an in-memory struct for use in your PowerShell session. | |
Author: Matthew Graeber (@mattifestation) | |
License: BSD 3-Clause | |
Required Dependencies: None | |
Optional Dependencies: field | |
.DESCRIPTION | |
The 'struct' function facilitates the creation of structs entirely in | |
memory using as close to a "C style" as PowerShell will allow. Struct | |
fields are specified using a hashtable where each field of the struct | |
is comprosed of the order in which it should be defined, its .NET | |
type, and optionally, its offset and special marshaling attributes. | |
One of the features of 'struct' is that after your struct is defined, | |
it will come with a built-in GetSize method as well as an explicit | |
converter so that you can easily cast an IntPtr to the struct without | |
relying upon calling SizeOf and/or PtrToStructure in the Marshal | |
class. | |
.PARAMETER Module | |
The in-memory module that will host the struct. Use | |
New-InMemoryModule to define an in-memory module. | |
.PARAMETER FullName | |
The fully-qualified name of the struct. | |
.PARAMETER StructFields | |
A hashtable of fields. Use the 'field' helper function to ease | |
defining each field. | |
.PARAMETER PackingSize | |
Specifies the memory alignment of fields. | |
.PARAMETER ExplicitLayout | |
Indicates that an explicit offset for each field will be specified. | |
.EXAMPLE | |
$Mod = New-InMemoryModule -ModuleName Win32 | |
$ImageDosSignature = psenum $Mod PE.IMAGE_DOS_SIGNATURE UInt16 @{ | |
DOS_SIGNATURE = 0x5A4D | |
OS2_SIGNATURE = 0x454E | |
OS2_SIGNATURE_LE = 0x454C | |
VXD_SIGNATURE = 0x454C | |
} | |
$ImageDosHeader = struct $Mod PE.IMAGE_DOS_HEADER @{ | |
e_magic = field 0 $ImageDosSignature | |
e_cblp = field 1 UInt16 | |
e_cp = field 2 UInt16 | |
e_crlc = field 3 UInt16 | |
e_cparhdr = field 4 UInt16 | |
e_minalloc = field 5 UInt16 | |
e_maxalloc = field 6 UInt16 | |
e_ss = field 7 UInt16 | |
e_sp = field 8 UInt16 | |
e_csum = field 9 UInt16 | |
e_ip = field 10 UInt16 | |
e_cs = field 11 UInt16 | |
e_lfarlc = field 12 UInt16 | |
e_ovno = field 13 UInt16 | |
e_res = field 14 UInt16[] -MarshalAs @('ByValArray', 4) | |
e_oemid = field 15 UInt16 | |
e_oeminfo = field 16 UInt16 | |
e_res2 = field 17 UInt16[] -MarshalAs @('ByValArray', 10) | |
e_lfanew = field 18 Int32 | |
} | |
# Example of using an explicit layout in order to create a union. | |
$TestUnion = struct $Mod TestUnion @{ | |
field1 = field 0 UInt32 0 | |
field2 = field 1 IntPtr 0 | |
} -ExplicitLayout | |
.NOTES | |
PowerShell purists may disagree with the naming of this function but | |
again, this was developed in such a way so as to emulate a "C style" | |
definition as closely as possible. Sorry, I'm not going to name it | |
New-Struct. :P | |
#> | |
[OutputType([Type])] | |
Param ( | |
[Parameter(Position = 1, Mandatory=$True)] | |
[ValidateScript({($_ -is [Reflection.Emit.ModuleBuilder]) -or ($_ -is [Reflection.Assembly])})] | |
$Module, | |
[Parameter(Position = 2, Mandatory=$True)] | |
[ValidateNotNullOrEmpty()] | |
[String] | |
$FullName, | |
[Parameter(Position = 3, Mandatory=$True)] | |
[ValidateNotNullOrEmpty()] | |
[Hashtable] | |
$StructFields, | |
[Reflection.Emit.PackingSize] | |
$PackingSize = [Reflection.Emit.PackingSize]::Unspecified, | |
[Switch] | |
$ExplicitLayout | |
) | |
if ($Module -is [Reflection.Assembly]) | |
{ | |
return ($Module.GetType($FullName)) | |
} | |
[Reflection.TypeAttributes] $StructAttributes = 'AnsiClass, | |
Class, | |
Public, | |
Sealed, | |
BeforeFieldInit' | |
if ($ExplicitLayout) | |
{ | |
$StructAttributes = $StructAttributes -bor [Reflection.TypeAttributes]::ExplicitLayout | |
} | |
else | |
{ | |
$StructAttributes = $StructAttributes -bor [Reflection.TypeAttributes]::SequentialLayout | |
} | |
$StructBuilder = $Module.DefineType($FullName, $StructAttributes, [ValueType], $PackingSize) | |
$ConstructorInfo = [Runtime.InteropServices.MarshalAsAttribute].GetConstructors()[0] | |
$SizeConst = @([Runtime.InteropServices.MarshalAsAttribute].GetField('SizeConst')) | |
$Fields = New-Object Hashtable[]($StructFields.Count) | |
# Sort each field according to the orders specified | |
# Unfortunately, PSv2 doesn't have the luxury of the | |
# hashtable [Ordered] accelerator. | |
foreach ($Field in $StructFields.Keys) | |
{ | |
$Index = $StructFields[$Field]['Position'] | |
$Fields[$Index] = @{FieldName = $Field; Properties = $StructFields[$Field]} | |
} | |
foreach ($Field in $Fields) | |
{ | |
$FieldName = $Field['FieldName'] | |
$FieldProp = $Field['Properties'] | |
$Offset = $FieldProp['Offset'] | |
$Type = $FieldProp['Type'] | |
$MarshalAs = $FieldProp['MarshalAs'] | |
$NewField = $StructBuilder.DefineField($FieldName, $Type, 'Public') | |
if ($MarshalAs) | |
{ | |
$UnmanagedType = $MarshalAs[0] -as ([Runtime.InteropServices.UnmanagedType]) | |
if ($MarshalAs[1]) | |
{ | |
$Size = $MarshalAs[1] | |
$AttribBuilder = New-Object Reflection.Emit.CustomAttributeBuilder($ConstructorInfo, | |
$UnmanagedType, $SizeConst, @($Size)) | |
} | |
else | |
{ | |
$AttribBuilder = New-Object Reflection.Emit.CustomAttributeBuilder($ConstructorInfo, [Object[]] @($UnmanagedType)) | |
} | |
$NewField.SetCustomAttribute($AttribBuilder) | |
} | |
if ($ExplicitLayout) { $NewField.SetOffset($Offset) } | |
} | |
# Make the struct aware of its own size. | |
# No more having to call [Runtime.InteropServices.Marshal]::SizeOf! | |
$SizeMethod = $StructBuilder.DefineMethod('GetSize', | |
'Public, Static', | |
[Int], | |
[Type[]] @()) | |
$ILGenerator = $SizeMethod.GetILGenerator() | |
# Thanks for the help, Jason Shirk! | |
$ILGenerator.Emit([Reflection.Emit.OpCodes]::Ldtoken, $StructBuilder) | |
$ILGenerator.Emit([Reflection.Emit.OpCodes]::Call, | |
[Type].GetMethod('GetTypeFromHandle')) | |
$ILGenerator.Emit([Reflection.Emit.OpCodes]::Call, | |
[Runtime.InteropServices.Marshal].GetMethod('SizeOf', [Type[]] @([Type]))) | |
$ILGenerator.Emit([Reflection.Emit.OpCodes]::Ret) | |
# Allow for explicit casting from an IntPtr | |
# No more having to call [Runtime.InteropServices.Marshal]::PtrToStructure! | |
$ImplicitConverter = $StructBuilder.DefineMethod('op_Implicit', | |
'PrivateScope, Public, Static, HideBySig, SpecialName', | |
$StructBuilder, | |
[Type[]] @([IntPtr])) | |
$ILGenerator2 = $ImplicitConverter.GetILGenerator() | |
$ILGenerator2.Emit([Reflection.Emit.OpCodes]::Nop) | |
$ILGenerator2.Emit([Reflection.Emit.OpCodes]::Ldarg_0) | |
$ILGenerator2.Emit([Reflection.Emit.OpCodes]::Ldtoken, $StructBuilder) | |
$ILGenerator2.Emit([Reflection.Emit.OpCodes]::Call, | |
[Type].GetMethod('GetTypeFromHandle')) | |
$ILGenerator2.Emit([Reflection.Emit.OpCodes]::Call, | |
[Runtime.InteropServices.Marshal].GetMethod('PtrToStructure', [Type[]] @([IntPtr], [Type]))) | |
$ILGenerator2.Emit([Reflection.Emit.OpCodes]::Unbox_Any, $StructBuilder) | |
$ILGenerator2.Emit([Reflection.Emit.OpCodes]::Ret) | |
$StructBuilder.CreateType() | |
} | |
$Mod = New-InMemoryModule -ModuleName Win32 | |
# used to parse the 'samAccountType' property for users/computers/groups | |
$SamAccountTypeEnum = psenum $Mod PowerView.SamAccountTypeEnum UInt32 @{ | |
DOMAIN_OBJECT = '0x00000000' | |
GROUP_OBJECT = '0x10000000' | |
NON_SECURITY_GROUP_OBJECT = '0x10000001' | |
ALIAS_OBJECT = '0x20000000' | |
NON_SECURITY_ALIAS_OBJECT = '0x20000001' | |
USER_OBJECT = '0x30000000' | |
MACHINE_ACCOUNT = '0x30000001' | |
TRUST_ACCOUNT = '0x30000002' | |
APP_BASIC_GROUP = '0x40000000' | |
APP_QUERY_GROUP = '0x40000001' | |
ACCOUNT_TYPE_MAX = '0x7fffffff' | |
} | |
# used to parse the 'grouptype' property for groups | |
$GroupTypeEnum = psenum $Mod PowerView.GroupTypeEnum UInt32 @{ | |
CREATED_BY_SYSTEM = '0x00000001' | |
GLOBAL_SCOPE = '0x00000002' | |
DOMAIN_LOCAL_SCOPE = '0x00000004' | |
UNIVERSAL_SCOPE = '0x00000008' | |
APP_BASIC = '0x00000010' | |
APP_QUERY = '0x00000020' | |
SECURITY = '0x80000000' | |
} -Bitfield | |
# used to parse the 'userAccountControl' property for users/groups | |
$UACEnum = psenum $Mod PowerView.UACEnum UInt32 @{ | |
SCRIPT = 1 | |
ACCOUNTDISABLE = 2 | |
HOMEDIR_REQUIRED = 8 | |
LOCKOUT = 16 | |
PASSWD_NOTREQD = 32 | |
PASSWD_CANT_CHANGE = 64 | |
ENCRYPTED_TEXT_PWD_ALLOWED = 128 | |
TEMP_DUPLICATE_ACCOUNT = 256 | |
NORMAL_ACCOUNT = 512 | |
INTERDOMAIN_TRUST_ACCOUNT = 2048 | |
WORKSTATION_TRUST_ACCOUNT = 4096 | |
SERVER_TRUST_ACCOUNT = 8192 | |
DONT_EXPIRE_PASSWORD = 65536 | |
MNS_LOGON_ACCOUNT = 131072 | |
SMARTCARD_REQUIRED = 262144 | |
TRUSTED_FOR_DELEGATION = 524288 | |
NOT_DELEGATED = 1048576 | |
USE_DES_KEY_ONLY = 2097152 | |
DONT_REQ_PREAUTH = 4194304 | |
PASSWORD_EXPIRED = 8388608 | |
TRUSTED_TO_AUTH_FOR_DELEGATION = 16777216 | |
PARTIAL_SECRETS_ACCOUNT = 67108864 | |
} -Bitfield | |
# 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 PowerView.RDPSessionInfo @{ | |
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 PowerView.ShareInfo @{ | |
Name = field 0 String -MarshalAs @('LPWStr') | |
Type = field 1 UInt32 | |
Remark = field 2 String -MarshalAs @('LPWStr') | |
} | |
# the NetWkstaUserEnum result structure | |
$WKSTA_USER_INFO_1 = struct $Mod PowerView.LoggedOnUserInfo @{ | |
UserName = field 0 String -MarshalAs @('LPWStr') | |
LogonDomain = field 1 String -MarshalAs @('LPWStr') | |
AuthDomains = field 2 String -MarshalAs @('LPWStr') | |
LogonServer = field 3 String -MarshalAs @('LPWStr') | |
} | |
# the NetSessionEnum result structure | |
$SESSION_INFO_10 = struct $Mod PowerView.SessionInfo @{ | |
CName = field 0 String -MarshalAs @('LPWStr') | |
UserName = field 1 String -MarshalAs @('LPWStr') | |
Time = field 2 UInt32 | |
IdleTime = 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 NetLocalGroupEnum result structure | |
$LOCALGROUP_INFO_1 = struct $Mod LOCALGROUP_INFO_1 @{ | |
lgrpi1_name = field 0 String -MarshalAs @('LPWStr') | |
lgrpi1_comment = field 1 String -MarshalAs @('LPWStr') | |
} | |
# 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 | |
} | |
# used by WNetAddConnection2W | |
$NETRESOURCEW = struct $Mod NETRESOURCEW @{ | |
dwScope = field 0 UInt32 | |
dwType = field 1 UInt32 | |
dwDisplayType = field 2 UInt32 | |
dwUsage = field 3 UInt32 | |
lpLocalName = field 4 String -MarshalAs @('LPWStr') | |
lpRemoteName = field 5 String -MarshalAs @('LPWStr') | |
lpComment = field 6 String -MarshalAs @('LPWStr') | |
lpProvider = field 7 String -MarshalAs @('LPWStr') | |
} | |
# all of the Win32 API functions we need | |
$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 NetLocalGroupEnum ([Int]) @([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 advapi32 LogonUser ([Bool]) @([String], [String], [String], [UInt32], [UInt32], [IntPtr].MakeByRefType()) -SetLastError), | |
(func advapi32 ImpersonateLoggedOnUser ([Bool]) @([IntPtr]) -SetLastError), | |
(func advapi32 RevertToSelf ([Bool]) @() -SetLastError), | |
(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])), | |
(func Mpr WNetAddConnection2W ([Int]) @($NETRESOURCEW, [String], [String], [UInt32])), | |
(func Mpr WNetCancelConnection2 ([Int]) @([String], [Int], [Bool])), | |
(func kernel32 CloseHandle ([Bool]) @([IntPtr]) -SetLastError) | |
) | |
$Types = $FunctionDefinitions | Add-Win32Type -Module $Mod -Namespace 'Win32' | |
$Netapi32 = $Types['netapi32'] | |
$Advapi32 = $Types['advapi32'] | |
$Wtsapi32 = $Types['wtsapi32'] | |
$Mpr = $Types['Mpr'] | |
$Kernel32 = $Types['kernel32'] |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment