#requires -version 2 <# LAPSToolkit Uses many functions from PowerView URL: https://github.com/PowerShellMafia/PowerSploit/blob/master/Recon/PowerView.ps1 Author: Will Schroeder (@harmj0y) Credits: Will Schroeder (@harmj0y), Sean Metcalf (@pyrotek3), Matt Graeber (@mattifestation), Karl Fosaaen (@kfosaaen) #> ######################################################## # # Functions from PowerView # Author: Will Schroeder (@harmj0y) # ######################################################## filter Export-PowerViewCSV { <# .SYNOPSIS This helper exports an -InputObject to a .csv in a thread-safe manner using a mutex. This is so the various multi-threaded functions in PowerView has a thread-safe way to export output to the same file. Based partially on Dmitry Sotnikov's Export-CSV code at http://poshcode.org/1590 .LINK http://poshcode.org/1590 http://dmitrysotnikov.wordpress.com/2010/01/19/Export-Csv-append/ #> Param( [Parameter(Mandatory=$True, ValueFromPipeline=$True, ValueFromPipelineByPropertyName=$True)] [System.Management.Automation.PSObject[]] $InputObject, [Parameter(Mandatory=$True, Position=0)] [String] [ValidateNotNullOrEmpty()] $OutFile ) $ObjectCSV = $InputObject | ConvertTo-Csv -NoTypeInformation # mutex so threaded code doesn't stomp on the output file $Mutex = New-Object System.Threading.Mutex $False,'CSVMutex'; $Null = $Mutex.WaitOne() if (Test-Path -Path $OutFile) { # hack to skip the first line of output if the file already exists $ObjectCSV | ForEach-Object { $Start=$True }{ if ($Start) {$Start=$False} else {$_} } | Out-File -Encoding 'ASCII' -Append -FilePath $OutFile } else { $ObjectCSV | Out-File -Encoding 'ASCII' -Append -FilePath $OutFile } $Mutex.ReleaseMutex() } filter Convert-SidToName { <# .SYNOPSIS Converts a security identifier (SID) to a group/user name. .PARAMETER SID The SID to convert. .EXAMPLE PS C:\> Convert-SidToName S-1-5-21-2620891829-2411261497-1773853088-1105 #> [CmdletBinding()] param( [Parameter(Mandatory=$True, ValueFromPipeline=$True)] [String] [ValidatePattern('^S-1-.*')] $SID ) try { $SID2 = $SID.trim('*') # try to resolve any built-in SIDs first # from https://support.microsoft.com/en-us/kb/243330 Switch ($SID2) { 'S-1-0' { 'Null Authority' } 'S-1-0-0' { 'Nobody' } 'S-1-1' { 'World Authority' } 'S-1-1-0' { 'Everyone' } 'S-1-2' { 'Local Authority' } 'S-1-2-0' { 'Local' } 'S-1-2-1' { 'Console Logon ' } 'S-1-3' { 'Creator Authority' } 'S-1-3-0' { 'Creator Owner' } 'S-1-3-1' { 'Creator Group' } 'S-1-3-2' { 'Creator Owner Server' } 'S-1-3-3' { 'Creator Group Server' } 'S-1-3-4' { 'Owner Rights' } 'S-1-4' { 'Non-unique Authority' } 'S-1-5' { 'NT Authority' } 'S-1-5-1' { 'Dialup' } 'S-1-5-2' { 'Network' } 'S-1-5-3' { 'Batch' } 'S-1-5-4' { 'Interactive' } 'S-1-5-6' { 'Service' } 'S-1-5-7' { 'Anonymous' } 'S-1-5-8' { 'Proxy' } 'S-1-5-9' { 'Enterprise Domain Controllers' } 'S-1-5-10' { 'Principal Self' } 'S-1-5-11' { 'Authenticated Users' } 'S-1-5-12' { 'Restricted Code' } 'S-1-5-13' { 'Terminal Server Users' } 'S-1-5-14' { 'Remote Interactive Logon' } 'S-1-5-15' { 'This Organization ' } 'S-1-5-17' { 'This Organization ' } 'S-1-5-18' { 'Local System' } 'S-1-5-19' { 'NT Authority' } 'S-1-5-20' { 'NT Authority' } 'S-1-5-80-0' { 'All Services ' } 'S-1-5-32-544' { 'BUILTIN\Administrators' } 'S-1-5-32-545' { 'BUILTIN\Users' } 'S-1-5-32-546' { 'BUILTIN\Guests' } 'S-1-5-32-547' { 'BUILTIN\Power Users' } 'S-1-5-32-548' { 'BUILTIN\Account Operators' } 'S-1-5-32-549' { 'BUILTIN\Server Operators' } 'S-1-5-32-550' { 'BUILTIN\Print Operators' } 'S-1-5-32-551' { 'BUILTIN\Backup Operators' } 'S-1-5-32-552' { 'BUILTIN\Replicators' } 'S-1-5-32-554' { 'BUILTIN\Pre-Windows 2000 Compatible Access' } 'S-1-5-32-555' { 'BUILTIN\Remote Desktop Users' } 'S-1-5-32-556' { 'BUILTIN\Network Configuration Operators' } 'S-1-5-32-557' { 'BUILTIN\Incoming Forest Trust Builders' } 'S-1-5-32-558' { 'BUILTIN\Performance Monitor Users' } 'S-1-5-32-559' { 'BUILTIN\Performance Log Users' } 'S-1-5-32-560' { 'BUILTIN\Windows Authorization Access Group' } 'S-1-5-32-561' { 'BUILTIN\Terminal Server License Servers' } 'S-1-5-32-562' { 'BUILTIN\Distributed COM Users' } 'S-1-5-32-569' { 'BUILTIN\Cryptographic Operators' } 'S-1-5-32-573' { 'BUILTIN\Event Log Readers' } 'S-1-5-32-574' { 'BUILTIN\Certificate Service DCOM Access' } 'S-1-5-32-575' { 'BUILTIN\RDS Remote Access Servers' } 'S-1-5-32-576' { 'BUILTIN\RDS Endpoint Servers' } 'S-1-5-32-577' { 'BUILTIN\RDS Management Servers' } 'S-1-5-32-578' { 'BUILTIN\Hyper-V Administrators' } 'S-1-5-32-579' { 'BUILTIN\Access Control Assistance Operators' } 'S-1-5-32-580' { 'BUILTIN\Access Control Assistance Operators' } Default { $Obj = (New-Object System.Security.Principal.SecurityIdentifier($SID2)) $Obj.Translate( [System.Security.Principal.NTAccount]).Value } } } catch { Write-Debug "Invalid SID: $SID" $SID } } filter Convert-ADName { <# .SYNOPSIS Converts user/group names from NT4 (DOMAIN\user) or domainSimple (user@domain.com) to canonical format (domain.com/Users/user) or NT4. Based on Bill Stewart's code from this article: http://windowsitpro.com/active-directory/translating-active-directory-object-names-between-formats .PARAMETER ObjectName The user/group name to convert. .PARAMETER InputType The InputType of the user/group name ("NT4","Simple","Canonical"). .PARAMETER OutputType The OutputType of the user/group name ("NT4","Simple","Canonical"). .EXAMPLE PS C:\> Convert-ADName -ObjectName "dev\dfm" Returns "dev.testlab.local/Users/Dave" .EXAMPLE PS C:\> Convert-SidToName "S-..." | Convert-ADName Returns the canonical name for the resolved SID. .LINK http://windowsitpro.com/active-directory/translating-active-directory-object-names-between-formats #> [CmdletBinding()] param( [Parameter(Mandatory=$True, ValueFromPipeline=$True)] [String] $ObjectName, [String] [ValidateSet("NT4","Simple","Canonical")] $InputType, [String] [ValidateSet("NT4","Simple","Canonical")] $OutputType ) $NameTypes = @{ "Canonical" = 2 "NT4" = 3 "Simple" = 5 } if(!$PSBoundParameters['InputType']) { if( ($ObjectName.split('/')).Count -eq 2 ) { $ObjectName = $ObjectName.replace('/', '\') } if($ObjectName -match "^[A-Za-z]+\\[A-Za-z ]+$") { $InputType = 'NT4' } elseif($ObjectName -match "^[A-Za-z ]+@[A-Za-z\.]+") { $InputType = 'Simple' } elseif($ObjectName -match "^[A-Za-z\.]+/[A-Za-z]+/[A-Za-z/ ]+") { $InputType = 'Canonical' } else { Write-Warning "Can not identify InType for $ObjectName" return $ObjectName } } elseif($InputType -eq 'NT4') { $ObjectName = $ObjectName.replace('/', '\') } if(!$PSBoundParameters['OutputType']) { $OutputType = Switch($InputType) { 'NT4' {'Canonical'} 'Simple' {'NT4'} 'Canonical' {'NT4'} } } # try to extract the domain from the given format $Domain = Switch($InputType) { 'NT4' { $ObjectName.split("\")[0] } 'Simple' { $ObjectName.split("@")[1] } 'Canonical' { $ObjectName.split("/")[0] } } # Accessor functions to simplify calls to NameTranslate function Invoke-Method([__ComObject] $Object, [String] $Method, $Parameters) { $Output = $Object.GetType().InvokeMember($Method, "InvokeMethod", $Null, $Object, $Parameters) if ( $Output ) { $Output } } function Set-Property([__ComObject] $Object, [String] $Property, $Parameters) { [Void] $Object.GetType().InvokeMember($Property, "SetProperty", $Null, $Object, $Parameters) } $Translate = New-Object -ComObject NameTranslate try { Invoke-Method $Translate "Init" (1, $Domain) } catch [System.Management.Automation.MethodInvocationException] { Write-Debug "Error with translate init in Convert-ADName: $_" } Set-Property $Translate "ChaseReferral" (0x60) try { Invoke-Method $Translate "Set" ($NameTypes[$InputType], $ObjectName) (Invoke-Method $Translate "Get" ($NameTypes[$OutputType])) } catch [System.Management.Automation.MethodInvocationException] { Write-Debug "Error with translate Set/Get in Convert-ADName: $_" } } function ConvertFrom-UACValue { <# .SYNOPSIS Converts a UAC int value to human readable form. .PARAMETER Value The int UAC value to convert. .PARAMETER ShowAll Show all UAC values, with a + indicating the value is currently set. .EXAMPLE PS C:\> ConvertFrom-UACValue -Value 66176 Convert the UAC value 66176 to human readable format. .EXAMPLE PS C:\> Get-NetUser jason | select useraccountcontrol | ConvertFrom-UACValue Convert the UAC value for 'jason' to human readable format. .EXAMPLE PS C:\> Get-NetUser jason | select useraccountcontrol | ConvertFrom-UACValue -ShowAll Convert the UAC value for 'jason' to human readable format, showing all possible UAC values. #> [CmdletBinding()] param( [Parameter(Mandatory=$True, ValueFromPipeline=$True)] $Value, [Switch] $ShowAll ) begin { # values from https://support.microsoft.com/en-us/kb/305144 $UACValues = New-Object System.Collections.Specialized.OrderedDictionary $UACValues.Add("SCRIPT", 1) $UACValues.Add("ACCOUNTDISABLE", 2) $UACValues.Add("HOMEDIR_REQUIRED", 8) $UACValues.Add("LOCKOUT", 16) $UACValues.Add("PASSWD_NOTREQD", 32) $UACValues.Add("PASSWD_CANT_CHANGE", 64) $UACValues.Add("ENCRYPTED_TEXT_PWD_ALLOWED", 128) $UACValues.Add("TEMP_DUPLICATE_ACCOUNT", 256) $UACValues.Add("NORMAL_ACCOUNT", 512) $UACValues.Add("INTERDOMAIN_TRUST_ACCOUNT", 2048) $UACValues.Add("WORKSTATION_TRUST_ACCOUNT", 4096) $UACValues.Add("SERVER_TRUST_ACCOUNT", 8192) $UACValues.Add("DONT_EXPIRE_PASSWORD", 65536) $UACValues.Add("MNS_LOGON_ACCOUNT", 131072) $UACValues.Add("SMARTCARD_REQUIRED", 262144) $UACValues.Add("TRUSTED_FOR_DELEGATION", 524288) $UACValues.Add("NOT_DELEGATED", 1048576) $UACValues.Add("USE_DES_KEY_ONLY", 2097152) $UACValues.Add("DONT_REQ_PREAUTH", 4194304) $UACValues.Add("PASSWORD_EXPIRED", 8388608) $UACValues.Add("TRUSTED_TO_AUTH_FOR_DELEGATION", 16777216) $UACValues.Add("PARTIAL_SECRETS_ACCOUNT", 67108864) } process { $ResultUACValues = New-Object System.Collections.Specialized.OrderedDictionary if($Value -is [Int]) { $IntValue = $Value } elseif ($Value -is [PSCustomObject]) { if($Value.useraccountcontrol) { $IntValue = $Value.useraccountcontrol } } else { Write-Warning "Invalid object input for -Value : $Value" return $Null } if($ShowAll) { foreach ($UACValue in $UACValues.GetEnumerator()) { if( ($IntValue -band $UACValue.Value) -eq $UACValue.Value) { $ResultUACValues.Add($UACValue.Name, "$($UACValue.Value)+") } else { $ResultUACValues.Add($UACValue.Name, "$($UACValue.Value)") } } } else { foreach ($UACValue in $UACValues.GetEnumerator()) { if( ($IntValue -band $UACValue.Value) -eq $UACValue.Value) { $ResultUACValues.Add($UACValue.Name, "$($UACValue.Value)") } } } $ResultUACValues } } filter Get-Proxy { <# .SYNOPSIS Enumerates the proxy server and WPAD conents for the current user. .PARAMETER ComputerName The computername to enumerate proxy settings on, defaults to local host. .EXAMPLE PS C:\> Get-Proxy Returns the current proxy settings. #> param( [Parameter(ValueFromPipeline=$True)] [ValidateNotNullOrEmpty()] [String] $ComputerName = $ENV:COMPUTERNAME ) try { $Reg = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('CurrentUser', $ComputerName) $RegKey = $Reg.OpenSubkey("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Internet Settings") $ProxyServer = $RegKey.GetValue('ProxyServer') $AutoConfigURL = $RegKey.GetValue('AutoConfigURL') $Wpad = "" if($AutoConfigURL -and ($AutoConfigURL -ne "")) { try { $Wpad = (New-Object Net.Webclient).DownloadString($AutoConfigURL) } catch { Write-Warning "Error connecting to AutoConfigURL : $AutoConfigURL" } } if($ProxyServer -or $AutoConfigUrl) { $Properties = @{ 'ProxyServer' = $ProxyServer 'AutoConfigURL' = $AutoConfigURL 'Wpad' = $Wpad } New-Object -TypeName PSObject -Property $Properties } else { Write-Warning "No proxy settings found for $ComputerName" } } catch { Write-Warning "Error enumerating proxy settings for $ComputerName : $_" } } function Get-PathAcl { <# .SYNOPSIS Enumerates the ACL for a given file path. .PARAMETER Path The local/remote path to enumerate the ACLs for. .PARAMETER Recurse If any ACL results are groups, recurse and retrieve user membership. .EXAMPLE PS C:\> Get-PathAcl "\\SERVER\Share\" Returns ACLs for the given UNC share. #> [CmdletBinding()] param( [Parameter(Mandatory=$True, ValueFromPipeline=$True)] [String] $Path, [Switch] $Recurse ) begin { function Convert-FileRight { # From http://stackoverflow.com/questions/28029872/retrieving-security-descriptor-and-getting-number-for-filesystemrights [CmdletBinding()] param( [Int] $FSR ) $AccessMask = @{ [uint32]'0x80000000' = 'GenericRead' [uint32]'0x40000000' = 'GenericWrite' [uint32]'0x20000000' = 'GenericExecute' [uint32]'0x10000000' = 'GenericAll' [uint32]'0x02000000' = 'MaximumAllowed' [uint32]'0x01000000' = 'AccessSystemSecurity' [uint32]'0x00100000' = 'Synchronize' [uint32]'0x00080000' = 'WriteOwner' [uint32]'0x00040000' = 'WriteDAC' [uint32]'0x00020000' = 'ReadControl' [uint32]'0x00010000' = 'Delete' [uint32]'0x00000100' = 'WriteAttributes' [uint32]'0x00000080' = 'ReadAttributes' [uint32]'0x00000040' = 'DeleteChild' [uint32]'0x00000020' = 'Execute/Traverse' [uint32]'0x00000010' = 'WriteExtendedAttributes' [uint32]'0x00000008' = 'ReadExtendedAttributes' [uint32]'0x00000004' = 'AppendData/AddSubdirectory' [uint32]'0x00000002' = 'WriteData/AddFile' [uint32]'0x00000001' = 'ReadData/ListDirectory' } $SimplePermissions = @{ [uint32]'0x1f01ff' = 'FullControl' [uint32]'0x0301bf' = 'Modify' [uint32]'0x0200a9' = 'ReadAndExecute' [uint32]'0x02019f' = 'ReadAndWrite' [uint32]'0x020089' = 'Read' [uint32]'0x000116' = 'Write' } $Permissions = @() # get simple permission $Permissions += $SimplePermissions.Keys | % { if (($FSR -band $_) -eq $_) { $SimplePermissions[$_] $FSR = $FSR -band (-not $_) } } # get remaining extended permissions $Permissions += $AccessMask.Keys | ? { $FSR -band $_ } | % { $AccessMask[$_] } ($Permissions | ?{$_}) -join "," } } process { try { $ACL = Get-Acl -Path $Path $ACL.GetAccessRules($true,$true,[System.Security.Principal.SecurityIdentifier]) | ForEach-Object { $Names = @() if ($_.IdentityReference -match '^S-1-5-21-[0-9]+-[0-9]+-[0-9]+-[0-9]+') { $Object = Get-ADObject -SID $_.IdentityReference $Names = @() $SIDs = @($Object.objectsid) if ($Recurse -and (@('268435456','268435457','536870912','536870913') -contains $Object.samAccountType)) { $SIDs += Get-NetGroupMember -SID $Object.objectsid | Select-Object -ExpandProperty MemberSid } $SIDs | ForEach-Object { $Names += ,@($_, (Convert-SidToName $_)) } } else { $Names += ,@($_.IdentityReference.Value, (Convert-SidToName $_.IdentityReference.Value)) } ForEach($Name in $Names) { $Out = New-Object PSObject $Out | Add-Member Noteproperty 'Path' $Path $Out | Add-Member Noteproperty 'FileSystemRights' (Convert-FileRight -FSR $_.FileSystemRights.value__) $Out | Add-Member Noteproperty 'IdentityReference' $Name[1] $Out | Add-Member Noteproperty 'IdentitySID' $Name[0] $Out | Add-Member Noteproperty 'AccessControlType' $_.AccessControlType $Out } } } catch { Write-Warning $_ } } } filter Get-NameField { <# .SYNOPSIS Helper that attempts to extract appropriate field names from passed computer objects. .PARAMETER Object The passed object to extract name fields from. .PARAMETER DnsHostName A DnsHostName to extract through ValueFromPipelineByPropertyName. .PARAMETER Name A Name to extract through ValueFromPipelineByPropertyName. .EXAMPLE PS C:\> Get-NetComputer -FullData | Get-NameField #> [CmdletBinding()] param( [Parameter(ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)] [Object] $Object, [Parameter(ValueFromPipelineByPropertyName = $True)] [String] $DnsHostName, [Parameter(ValueFromPipelineByPropertyName = $True)] [String] $Name ) if($PSBoundParameters['DnsHostName']) { $DnsHostName } elseif($PSBoundParameters['Name']) { $Name } elseif($Object) { if ( [bool]($Object.PSobject.Properties.name -match "dnshostname") ) { # objects from Get-NetComputer $Object.dnshostname } elseif ( [bool]($Object.PSobject.Properties.name -match "name") ) { # objects from Get-NetDomainController $Object.name } else { # strings and catch alls $Object } } else { return $Null } } function Convert-LDAPProperty { <# .SYNOPSIS Helper that converts specific LDAP property result fields. Used by several of the Get-Net* function. .PARAMETER Properties Properties object to extract out LDAP fields for display. #> param( [Parameter(Mandatory=$True, ValueFromPipeline=$True)] [ValidateNotNullOrEmpty()] $Properties ) $ObjectProperties = @{} $Properties.PropertyNames | ForEach-Object { if (($_ -eq "objectsid") -or ($_ -eq "sidhistory")) { # convert the SID to a string $ObjectProperties[$_] = (New-Object System.Security.Principal.SecurityIdentifier($Properties[$_][0],0)).Value } elseif($_ -eq "objectguid") { # convert the GUID to a string $ObjectProperties[$_] = (New-Object Guid (,$Properties[$_][0])).Guid } elseif( ($_ -eq "lastlogon") -or ($_ -eq "lastlogontimestamp") -or ($_ -eq "pwdlastset") -or ($_ -eq "lastlogoff") -or ($_ -eq "badPasswordTime") ) { # convert timestamps if ($Properties[$_][0] -is [System.MarshalByRefObject]) { # if we have a System.__ComObject $Temp = $Properties[$_][0] [Int32]$High = $Temp.GetType().InvokeMember("HighPart", [System.Reflection.BindingFlags]::GetProperty, $null, $Temp, $null) [Int32]$Low = $Temp.GetType().InvokeMember("LowPart", [System.Reflection.BindingFlags]::GetProperty, $null, $Temp, $null) $ObjectProperties[$_] = ([datetime]::FromFileTime([Int64]("0x{0:x8}{1:x8}" -f $High, $Low))) } else { $ObjectProperties[$_] = ([datetime]::FromFileTime(($Properties[$_][0]))) } } elseif($Properties[$_][0] -is [System.MarshalByRefObject]) { # try to convert misc com objects $Prop = $Properties[$_] try { $Temp = $Prop[$_][0] Write-Verbose $_ [Int32]$High = $Temp.GetType().InvokeMember("HighPart", [System.Reflection.BindingFlags]::GetProperty, $null, $Temp, $null) [Int32]$Low = $Temp.GetType().InvokeMember("LowPart", [System.Reflection.BindingFlags]::GetProperty, $null, $Temp, $null) $ObjectProperties[$_] = [Int64]("0x{0:x8}{1:x8}" -f $High, $Low) } catch { $ObjectProperties[$_] = $Prop[$_] } } elseif($Properties[$_].count -eq 1) { $ObjectProperties[$_] = $Properties[$_][0] } else { $ObjectProperties[$_] = $Properties[$_] } } New-Object -TypeName PSObject -Property $ObjectProperties } filter Get-DomainSearcher { <# .SYNOPSIS Helper used by various functions that takes an ADSpath and domain specifier and builds the correct ADSI searcher object. .PARAMETER Domain The domain to use for the query, defaults to the current domain. .PARAMETER DomainController Domain controller to reflect LDAP queries through. .PARAMETER ADSpath The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local" Useful for OU queries. .PARAMETER ADSprefix Prefix to set for the searcher (like "CN=Sites,CN=Configuration") .PARAMETER PageSize The PageSize to set for the LDAP searcher object. .PARAMETER Credential A [Management.Automation.PSCredential] object of alternate credentials for connection to the target domain. .EXAMPLE PS C:\> Get-DomainSearcher -Domain testlab.local .EXAMPLE PS C:\> Get-DomainSearcher -Domain testlab.local -DomainController SECONDARY.dev.testlab.local #> param( [Parameter(ValueFromPipeline=$True)] [String] $Domain, [String] $DomainController, [String] $ADSpath, [String] $ADSprefix, [ValidateRange(1,10000)] [Int] $PageSize = 200, [Management.Automation.PSCredential] $Credential ) if(!$Credential) { if(!$Domain){ $Domain = (Get-NetDomain).name } elseif(!$DomainController) { try { # if there's no -DomainController specified, try to pull the primary DC # to reflect queries through $DomainController = ((Get-NetDomain).PdcRoleOwner).Name } catch { throw "Get-DomainSearcher: Error in retrieving PDC for current domain" } } } elseif (!$DomainController) { try { $DomainController = ((Get-NetDomain -Credential $Credential).PdcRoleOwner).Name } catch { throw "Get-DomainSearcher: Error in retrieving PDC for current domain" } if(!$DomainController) { throw "Get-DomainSearcher: Error in retrieving PDC for current domain" } } $SearchString = "LDAP://" if($DomainController) { $SearchString += $DomainController if($Domain){ $SearchString += "/" } } if($ADSprefix) { $SearchString += $ADSprefix + "," } if($ADSpath) { if($ADSpath -like "GC://*") { # if we're searching the global catalog $DN = $AdsPath $SearchString = "" } else { if($ADSpath -like "LDAP://*") { if($ADSpath -match "LDAP://.+/.+") { $SearchString = "" } else { $ADSpath = $ADSpath.Substring(7) } } $DN = $ADSpath } } else { if($Domain -and ($Domain.Trim() -ne "")) { $DN = "DC=$($Domain.Replace('.', ',DC='))" } } $SearchString += $DN Write-Verbose "Get-DomainSearcher search string: $SearchString" if($Credential) { Write-Verbose "Using alternate credentials for LDAP connection" $DomainObject = New-Object DirectoryServices.DirectoryEntry($SearchString, $Credential.UserName, $Credential.GetNetworkCredential().Password) $Searcher = New-Object System.DirectoryServices.DirectorySearcher($DomainObject) } else { $Searcher = New-Object System.DirectoryServices.DirectorySearcher([ADSI]$SearchString) } $Searcher.PageSize = $PageSize $Searcher } filter Get-NetDomain { <# .SYNOPSIS Returns a given domain object. .PARAMETER Domain 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 PS C:\> Get-NetDomain -Domain testlab.local .EXAMPLE PS C:\> "testlab.local" | Get-NetDomain .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 #> param( [Parameter(ValueFromPipeline=$True)] [String] $Domain, [Management.Automation.PSCredential] $Credential ) if($Credential) { Write-Verbose "Using alternate credentials for Get-NetDomain" if(!$Domain) { # if no domain is supplied, extract the logon domain from the PSCredential passed $Domain = $Credential.GetNetworkCredential().Domain Write-Verbose "Extracted domain '$Domain' from -Credential" } $DomainContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext('Domain', $Domain, $Credential.UserName, $Credential.GetNetworkCredential().Password) try { [System.DirectoryServices.ActiveDirectory.Domain]::GetDomain($DomainContext) } catch { Write-Warning "The specified domain does '$Domain' not exist, could not be contacted, there isn't an existing trust, or the specified credentials are invalid." $Null } } elseif($Domain) { $DomainContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext('Domain', $Domain) try { [System.DirectoryServices.ActiveDirectory.Domain]::GetDomain($DomainContext) } catch { Write-Warning "The specified domain '$Domain' does not exist, could not be contacted, or there isn't an existing trust." $Null } } else { [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain() } } filter Get-NetForest { <# .SYNOPSIS Returns a given forest object. .PARAMETER Forest The forest 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 PS C:\> Get-NetForest -Forest external.domain .EXAMPLE PS C:\> "external.domain" | Get-NetForest #> param( [Parameter(ValueFromPipeline=$True)] [String] $Forest, [Management.Automation.PSCredential] $Credential ) if($Credential) { Write-Verbose "Using alternate credentials for Get-NetForest" if(!$Forest) { # if no domain is supplied, extract the logon domain from the PSCredential passed $Forest = $Credential.GetNetworkCredential().Domain Write-Verbose "Extracted domain '$Forest' from -Credential" } $ForestContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext('Forest', $Forest, $Credential.UserName, $Credential.GetNetworkCredential().Password) try { $ForestObject = [System.DirectoryServices.ActiveDirectory.Forest]::GetForest($ForestContext) } catch { Write-Warning "The specified forest '$Forest' does not exist, could not be contacted, there isn't an existing trust, or the specified credentials are invalid." $Null } } elseif($Forest) { $ForestContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext('Forest', $Forest) try { $ForestObject = [System.DirectoryServices.ActiveDirectory.Forest]::GetForest($ForestContext) } catch { Write-Warning "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 try { $ForestSid = (New-Object System.Security.Principal.NTAccount($ForestObject.RootDomain,"krbtgt")).Translate([System.Security.Principal.SecurityIdentifier]).Value $Parts = $ForestSid -Split "-" $ForestSid = $Parts[0..$($Parts.length-2)] -join "-" $ForestObject | Add-Member NoteProperty 'RootDomainSid' $ForestSid } catch { Write-Verbose "Couldn't translate SID for Forest" $ForestSid = "" } $ForestObject } } filter Get-NetForestDomain { <# .SYNOPSIS Return all domains for a given forest. .PARAMETER Forest The forest name to query domain for. .PARAMETER Credential A [Management.Automation.PSCredential] object of alternate credentials for connection to the target domain. .EXAMPLE PS C:\> Get-NetForestDomain .EXAMPLE PS C:\> Get-NetForestDomain -Forest external.local #> param( [Parameter(ValueFromPipeline=$True)] [String] $Forest, [Management.Automation.PSCredential] $Credential ) $ForestObject = Get-NetForest -Forest $Forest -Credential $Credential if($ForestObject) { $ForestObject.Domains } } filter Get-NetForestCatalog { <# .SYNOPSIS Return all global catalogs for a given forest. .PARAMETER Forest The forest name to query domain for. .PARAMETER Credential A [Management.Automation.PSCredential] object of alternate credentials for connection to the target domain. .EXAMPLE PS C:\> Get-NetForestCatalog #> param( [Parameter(ValueFromPipeline=$True)] [String] $Forest, [Management.Automation.PSCredential] $Credential ) $ForestObject = Get-NetForest -Forest $Forest -Credential $Credential if($ForestObject) { $ForestObject.FindAllGlobalCatalogs() } } filter Get-NetDomainController { <# .SYNOPSIS Return the current domain controllers for the active domain. .PARAMETER Domain The domain to query for domain controllers, defaults to the current domain. .PARAMETER DomainController Domain controller to reflect LDAP queries through. .PARAMETER LDAP Switch. Use LDAP queries to determine the domain controllers. .PARAMETER Credential A [Management.Automation.PSCredential] object of alternate credentials for connection to the target domain. .EXAMPLE PS C:\> Get-NetDomainController -Domain 'test.local' Determine the domain controllers for 'test.local'. .EXAMPLE PS C:\> Get-NetDomainController -Domain 'test.local' -LDAP Determine the domain controllers for 'test.local' using LDAP queries. .EXAMPLE PS C:\> 'test.local' | Get-NetDomainController Determine the domain controllers for 'test.local'. #> [CmdletBinding()] param( [Parameter(ValueFromPipeline=$True)] [String] $Domain, [String] $DomainController, [Switch] $LDAP, [Management.Automation.PSCredential] $Credential ) if($LDAP -or $DomainController) { # filter string to return all domain controllers Get-NetComputer -Domain $Domain -DomainController $DomainController -Credential $Credential -FullData -Filter '(userAccountControl:1.2.840.113556.1.4.803:=8192)' } else { $FoundDomain = Get-NetDomain -Domain $Domain -Credential $Credential if($FoundDomain) { $Founddomain.DomainControllers } } } function Get-ObjectAcl { <# .SYNOPSIS Returns the ACLs associated with a specific active directory object. Thanks Sean Metcalf (@pyrotek3) for the idea and guidance. .PARAMETER SamAccountName Object name to filter for. .PARAMETER Name Object name to filter for. .PARAMETER DistinguishedName Object distinguished name to filter for. .PARAMETER ResolveGUIDs Switch. Resolve GUIDs to their display names. .PARAMETER Filter A customized ldap filter string to use, e.g. "(description=*admin*)" .PARAMETER ADSpath The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local" Useful for OU queries. .PARAMETER ADSprefix Prefix to set for the searcher (like "CN=Sites,CN=Configuration") .PARAMETER RightsFilter Only return results with the associated rights, "All", "ResetPassword","WriteMembers" .PARAMETER Domain The domain to use for the query, defaults to the current domain. .PARAMETER DomainController Domain controller to reflect LDAP queries through. .PARAMETER PageSize The PageSize to set for the LDAP searcher object. .PARAMETER Credential A [Management.Automation.PSCredential] object of alternate credentials for connection to the target domain. .EXAMPLE PS C:\> Get-ObjectAcl -SamAccountName matt.admin -domain testlab.local Get the ACLs for the matt.admin user in the testlab.local domain .EXAMPLE PS C:\> Get-ObjectAcl -SamAccountName matt.admin -domain testlab.local -ResolveGUIDs Get the ACLs for the matt.admin user in the testlab.local domain and resolve relevant GUIDs to their display names. .EXAMPLE PS C:\> Get-NetOU -FullData | Get-ObjectAcl -ResolveGUIDs Enumerate the ACL permissions for all OUs in the domain. #> [CmdletBinding()] Param ( [Parameter(ValueFromPipelineByPropertyName=$True)] [String] $SamAccountName, [Parameter(ValueFromPipelineByPropertyName=$True)] [String] $Name = "*", [Parameter(ValueFromPipelineByPropertyName=$True)] [String] $DistinguishedName = "*", [Switch] $ResolveGUIDs, [String] $Filter, [String] $ADSpath, [String] $ADSprefix, [String] [ValidateSet("All","ResetPassword","WriteMembers")] $RightsFilter, [String] $Domain, [String] $DomainController, [ValidateRange(1,10000)] [Int] $PageSize = 200, [Management.Automation.PSCredential] $Credential ) begin { $Searcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -ADSpath $ADSpath -ADSprefix $ADSprefix -PageSize $PageSize -Credential $Credential # get a GUID -> name mapping if($ResolveGUIDs) { $GUIDs = Get-GUIDMap -Domain $Domain -DomainController $DomainController -PageSize $PageSize -Credential $Credential } } process { if ($Searcher) { if($SamAccountName) { $Searcher.filter="(&(samaccountname=$SamAccountName)(name=$Name)(distinguishedname=$DistinguishedName)$Filter)" } else { $Searcher.filter="(&(name=$Name)(distinguishedname=$DistinguishedName)$Filter)" } try { $Results = $Searcher.FindAll() $Results | Where-Object {$_} | ForEach-Object { if($Credential) { $Object = New-Object -TypeName System.DirectoryServices.DirectoryEntry($_.path, $($Credential.UserName),$($Credential.GetNetworkCredential().password)) } else { $Object = [adsi]($_.path) } if($Object.distinguishedname) { $Access = $Object.PsBase.ObjectSecurity.access $Access | ForEach-Object { $_ | Add-Member NoteProperty 'ObjectDN' $Object.distinguishedname[0] if($Object.objectsid[0]){ $S = (New-Object System.Security.Principal.SecurityIdentifier($Object.objectsid[0],0)).Value } else { $S = $Null } $_ | Add-Member NoteProperty 'ObjectSID' $S $_ } } } | ForEach-Object { if($RightsFilter) { $GuidFilter = Switch ($RightsFilter) { "ResetPassword" { "00299570-246d-11d0-a768-00aa006e0529" } "WriteMembers" { "bf9679c0-0de6-11d0-a285-00aa003049e2" } Default { "00000000-0000-0000-0000-000000000000"} } if($_.ObjectType -eq $GuidFilter) { $_ } } else { $_ } } | ForEach-Object { if($GUIDs) { # if we're resolving GUIDs, map them them to the resolved hash table $AclProperties = @{} $_.psobject.properties | ForEach-Object { if( ($_.Name -eq 'ObjectType') -or ($_.Name -eq 'InheritedObjectType') ) { try { $AclProperties[$_.Name] = $GUIDS[$_.Value.toString()] } catch { $AclProperties[$_.Name] = $_.Value } } else { $AclProperties[$_.Name] = $_.Value } } New-Object -TypeName PSObject -Property $AclProperties } else { $_ } } $Results.dispose() $Searcher.dispose() } catch { Write-Warning $_ } } } } function Add-ObjectAcl { <# .SYNOPSIS Adds an ACL for a specific active directory object. AdminSDHolder ACL approach from Sean Metcalf (@pyrotek3) https://adsecurity.org/?p=1906 ACE setting method adapted from https://social.technet.microsoft.com/Forums/windowsserver/en-US/df3bfd33-c070-4a9c-be98-c4da6e591a0a/forum-faq-using-powershell-to-assign-permissions-on-active-directory-objects. 'ResetPassword' doesn't need to know the user's current password 'WriteMembers' allows for the modification of group membership .PARAMETER TargetSamAccountName Target object name to filter for. .PARAMETER TargetName Target object name to filter for. .PARAMETER TargetDistinguishedName Target object distinguished name to filter for. .PARAMETER TargetFilter A customized ldap filter string to use to find a target, e.g. "(description=*admin*)" .PARAMETER TargetADSpath The LDAP source for the target, e.g. "LDAP://OU=secret,DC=testlab,DC=local" .PARAMETER TargetADSprefix Prefix to set for the target searcher (like "CN=Sites,CN=Configuration") .PARAMETER PrincipalSID The SID of the principal object to add for access. .PARAMETER PrincipalName The name of the principal object to add for access. .PARAMETER PrincipalSamAccountName The samAccountName of the principal object to add for access. .PARAMETER Rights Rights to add for the principal, "All","ResetPassword","WriteMembers","DCSync" .PARAMETER Domain The domain to use for the target query, defaults to the current domain. .PARAMETER DomainController Domain controller to reflect LDAP queries through. .PARAMETER PageSize The PageSize to set for the LDAP searcher object. .EXAMPLE Add-ObjectAcl -TargetSamAccountName matt -PrincipalSamAccountName john Grants 'john' all full access rights to the 'matt' account. .EXAMPLE Add-ObjectAcl -TargetSamAccountName matt -PrincipalSamAccountName john -Rights ResetPassword Grants 'john' the right to reset the password for the 'matt' account. .LINK https://adsecurity.org/?p=1906 https://social.technet.microsoft.com/Forums/windowsserver/en-US/df3bfd33-c070-4a9c-be98-c4da6e591a0a/forum-faq-using-powershell-to-assign-permissions-on-active-directory-objects?forum=winserverpowershell #> [CmdletBinding()] Param ( [String] $TargetSamAccountName, [String] $TargetName = "*", [Alias('DN')] [String] $TargetDistinguishedName = "*", [String] $TargetFilter, [String] $TargetADSpath, [String] $TargetADSprefix, [String] [ValidatePattern('^S-1-5-21-[0-9]+-[0-9]+-[0-9]+-[0-9]+')] $PrincipalSID, [String] $PrincipalName, [String] $PrincipalSamAccountName, [String] [ValidateSet("All","ResetPassword","WriteMembers","DCSync")] $Rights = "All", [String] $RightsGUID, [String] $Domain, [String] $DomainController, [ValidateRange(1,10000)] [Int] $PageSize = 200 ) begin { $Searcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -ADSpath $TargetADSpath -ADSprefix $TargetADSprefix -PageSize $PageSize if(!$PrincipalSID) { $Principal = Get-ADObject -Domain $Domain -DomainController $DomainController -Name $PrincipalName -SamAccountName $PrincipalSamAccountName -PageSize $PageSize if(!$Principal) { throw "Error resolving principal" } $PrincipalSID = $Principal.objectsid } if(!$PrincipalSID) { throw "Error resolving principal" } } process { if ($Searcher) { if($TargetSamAccountName) { $Searcher.filter="(&(samaccountname=$TargetSamAccountName)(name=$TargetName)(distinguishedname=$TargetDistinguishedName)$TargetFilter)" } else { $Searcher.filter="(&(name=$TargetName)(distinguishedname=$TargetDistinguishedName)$TargetFilter)" } try { $Searcher.FindAll() | Where-Object {$_} | ForEach-Object { # adapted from https://social.technet.microsoft.com/Forums/windowsserver/en-US/df3bfd33-c070-4a9c-be98-c4da6e591a0a/forum-faq-using-powershell-to-assign-permissions-on-active-directory-objects $TargetDN = $_.Properties.distinguishedname $Identity = [System.Security.Principal.IdentityReference] ([System.Security.Principal.SecurityIdentifier]$PrincipalSID) $InheritanceType = [System.DirectoryServices.ActiveDirectorySecurityInheritance] "None" $ControlType = [System.Security.AccessControl.AccessControlType] "Allow" $ACEs = @() if($RightsGUID) { $GUIDs = @($RightsGUID) } else { $GUIDs = Switch ($Rights) { # ResetPassword doesn't need to know the user's current password "ResetPassword" { "00299570-246d-11d0-a768-00aa006e0529" } # allows for the modification of group membership "WriteMembers" { "bf9679c0-0de6-11d0-a285-00aa003049e2" } # 'DS-Replication-Get-Changes' = 1131f6aa-9c07-11d1-f79f-00c04fc2dcd2 # 'DS-Replication-Get-Changes-All' = 1131f6ad-9c07-11d1-f79f-00c04fc2dcd2 # 'DS-Replication-Get-Changes-In-Filtered-Set' = 89e95b76-444d-4c62-991a-0facbeda640c # when applied to a domain's ACL, allows for the use of DCSync "DCSync" { "1131f6aa-9c07-11d1-f79f-00c04fc2dcd2", "1131f6ad-9c07-11d1-f79f-00c04fc2dcd2", "89e95b76-444d-4c62-991a-0facbeda640c"} } } if($GUIDs) { foreach($GUID in $GUIDs) { $NewGUID = New-Object Guid $GUID $ADRights = [System.DirectoryServices.ActiveDirectoryRights] "ExtendedRight" $ACEs += New-Object System.DirectoryServices.ActiveDirectoryAccessRule $Identity,$ADRights,$ControlType,$NewGUID,$InheritanceType } } else { # deault to GenericAll rights $ADRights = [System.DirectoryServices.ActiveDirectoryRights] "GenericAll" $ACEs += New-Object System.DirectoryServices.ActiveDirectoryAccessRule $Identity,$ADRights,$ControlType,$InheritanceType } Write-Verbose "Granting principal $PrincipalSID '$Rights' on $($_.Properties.distinguishedname)" try { # add all the new ACEs to the specified object ForEach ($ACE in $ACEs) { Write-Verbose "Granting principal $PrincipalSID '$($ACE.ObjectType)' rights on $($_.Properties.distinguishedname)" $Object = [adsi]($_.path) $Object.PsBase.ObjectSecurity.AddAccessRule($ACE) $Object.PsBase.commitchanges() } } catch { Write-Warning "Error granting principal $PrincipalSID '$Rights' on $TargetDN : $_" } } } catch { Write-Warning "Error: $_" } } } } filter Get-GUIDMap { <# .SYNOPSIS Helper to build a hash table of [GUID] -> resolved names Heavily adapted from http://blogs.technet.com/b/ashleymcglone/archive/2013/03/25/active-directory-ou-permissions-report-free-powershell-script-download.aspx .PARAMETER Domain The domain to use for the query, defaults to the current domain. .PARAMETER DomainController Domain controller to reflect LDAP queries through. .PARAMETER PageSize The PageSize to set for the LDAP searcher object. .LINK http://blogs.technet.com/b/ashleymcglone/archive/2013/03/25/active-directory-ou-permissions-report-free-powershell-script-download.aspx #> [CmdletBinding()] Param ( [Parameter(ValueFromPipeline=$True)] [String] $Domain, [String] $DomainController, [ValidateRange(1,10000)] [Int] $PageSize = 200, [Management.Automation.PSCredential] $Credential ) $GUIDs = @{'00000000-0000-0000-0000-000000000000' = 'All'} $SchemaPath = (Get-NetForest -Credential $Credential).schema.name $SchemaSearcher = Get-DomainSearcher -ADSpath $SchemaPath -Domain $Domain -DomainController $DomainController -PageSize $PageSize -Credential $Credential if($SchemaSearcher) { $SchemaSearcher.filter = "(schemaIDGUID=*)" try { $SchemaSearcher.FindAll() | Where-Object {$_} | ForEach-Object { # convert the GUID $GUIDs[(New-Object Guid (,$_.properties.schemaidguid[0])).Guid] = $_.properties.name[0] } } catch { Write-Debug "Error in building GUID map: $_" } } $RightsSearcher = Get-DomainSearcher -ADSpath $SchemaPath.replace("Schema","Extended-Rights") -Domain $Domain -DomainController $DomainController -PageSize $PageSize -Credential $Credential if ($RightsSearcher) { $RightsSearcher.filter = "(objectClass=controlAccessRight)" try { $RightsSearcher.FindAll() | Where-Object {$_} | ForEach-Object { # convert the GUID $GUIDs[$_.properties.rightsguid[0].toString()] = $_.properties.name[0] } } catch { Write-Debug "Error in building GUID map: $_" } } $GUIDs } function Get-NetComputer { <# .SYNOPSIS This function utilizes adsisearcher to query the current AD context for current computer objects. Based off of Carlos Perez's Audit.psm1 script in Posh-SecMod (link below). .PARAMETER ComputerName Return computers with a specific name, wildcards accepted. .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 Filter A customized ldap filter string to use, e.g. "(description=*admin*)" .PARAMETER Printers Switch. Return only printers. .PARAMETER Ping Switch. Ping each host to ensure it's up before enumerating. .PARAMETER FullData Switch. Return full computer objects instead of just system names (the default). .PARAMETER Domain The domain to query for computers, defaults to the current domain. .PARAMETER DomainController Domain controller to reflect LDAP queries through. .PARAMETER ADSpath The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local" Useful for OU queries. .PARAMETER SiteName The AD Site name to search for computers. .PARAMETER Unconstrained Switch. Return computer objects that have unconstrained delegation. .PARAMETER PageSize The PageSize to set for the LDAP searcher object. .PARAMETER Credential A [Management.Automation.PSCredential] object of alternate credentials for connection to the target domain. .EXAMPLE PS C:\> Get-NetComputer Returns the current computers in current domain. .EXAMPLE PS C:\> Get-NetComputer -SPN mssql* Returns all MS SQL servers on the domain. .EXAMPLE PS C:\> Get-NetComputer -Domain testing Returns the current computers in 'testing' domain. .EXAMPLE PS C:\> Get-NetComputer -Domain testing -FullData Returns full computer objects in the 'testing' domain. .LINK https://github.com/darkoperator/Posh-SecMod/blob/master/Audit/Audit.psm1 #> [CmdletBinding()] Param ( [Parameter(ValueFromPipeline=$True)] [Alias('HostName')] [String] $ComputerName = '*', [String] $SPN, [String] $OperatingSystem, [String] $ServicePack, [String] $Filter, [Switch] $Printers, [Switch] $Ping, [Switch] $FullData, [String] $Domain, [String] $DomainController, [String] $ADSpath, [String] $SiteName, [Switch] $Unconstrained, [ValidateRange(1,10000)] [Int] $PageSize = 200, [Management.Automation.PSCredential] $Credential ) begin { # so this isn't repeated if multiple computer names are passed on the pipeline $CompSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -ADSpath $ADSpath -PageSize $PageSize -Credential $Credential } process { if ($CompSearcher) { # if we're checking for unconstrained delegation if($Unconstrained) { Write-Verbose "Searching for computers with for unconstrained delegation" $Filter += "(userAccountControl:1.2.840.113556.1.4.803:=524288)" } # set the filters for the seracher if it exists if($Printers) { Write-Verbose "Searching for printers" # $CompSearcher.filter="(&(objectCategory=printQueue)$Filter)" $Filter += "(objectCategory=printQueue)" } if($SPN) { Write-Verbose "Searching for computers with SPN: $SPN" $Filter += "(servicePrincipalName=$SPN)" } if($OperatingSystem) { $Filter += "(operatingsystem=$OperatingSystem)" } if($ServicePack) { $Filter += "(operatingsystemservicepack=$ServicePack)" } if($SiteName) { $Filter += "(serverreferencebl=$SiteName)" } $CompFilter = "(&(sAMAccountType=805306369)(dnshostname=$ComputerName)$Filter)" Write-Verbose "Get-NetComputer filter : '$CompFilter'" $CompSearcher.filter = $CompFilter try { $CompSearcher.FindAll() | Where-Object {$_} | ForEach-Object { $Up = $True if($Ping) { # TODO: how can these results be piped to ping for a speedup? $Up = Test-Connection -Count 1 -Quiet -ComputerName $_.properties.dnshostname } if($Up) { # return full data objects if ($FullData) { # convert/process the LDAP fields for each result Convert-LDAPProperty -Properties $_.Properties } else { # otherwise we're just returning the DNS host name $_.properties.dnshostname } } } } catch { Write-Warning "Error: $_" } } } } function Get-NetOU { <# .SYNOPSIS Gets a list of all current OUs in a domain. .PARAMETER OUName The OU name to query for, wildcards accepted. .PARAMETER GUID Only return OUs with the specified GUID in their gplink property. .PARAMETER Domain The domain to query for OUs, defaults to the current domain. .PARAMETER DomainController Domain controller to reflect LDAP queries through. .PARAMETER ADSpath The LDAP source to search through. .PARAMETER FullData Switch. Return full OU objects instead of just object names (the default). .PARAMETER PageSize The PageSize to set for the LDAP searcher object. .PARAMETER Credential A [Management.Automation.PSCredential] object of alternate credentials for connection to the target domain. .EXAMPLE PS C:\> Get-NetOU Returns the current OUs in the domain. .EXAMPLE PS C:\> Get-NetOU -OUName *admin* -Domain testlab.local Returns all OUs with "admin" in their name in the testlab.local domain. .EXAMPLE PS C:\> Get-NetOU -GUID 123-... Returns all OUs with linked to the specified group policy object. .EXAMPLE PS C:\> "*admin*","*server*" | Get-NetOU Get the full OU names for the given search terms piped on the pipeline. #> [CmdletBinding()] Param ( [Parameter(ValueFromPipeline=$True)] [String] $OUName = '*', [String] $GUID, [String] $Domain, [String] $DomainController, [String] $ADSpath, [Switch] $FullData, [ValidateRange(1,10000)] [Int] $PageSize = 200, [Management.Automation.PSCredential] $Credential ) begin { $OUSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $ADSpath -PageSize $PageSize } process { if ($OUSearcher) { if ($GUID) { # if we're filtering for a GUID in .gplink $OUSearcher.filter="(&(objectCategory=organizationalUnit)(name=$OUName)(gplink=*$GUID*))" } else { $OUSearcher.filter="(&(objectCategory=organizationalUnit)(name=$OUName))" } try { $OUSearcher.FindAll() | Where-Object {$_} | ForEach-Object { if ($FullData) { # convert/process the LDAP fields for each result Convert-LDAPProperty -Properties $_.Properties } else { # otherwise just returning the ADS paths of the OUs $_.properties.adspath } } } catch { Write-Warning $_ } } } } function Get-NetGroup { <# .SYNOPSIS Gets a list of all current groups in a domain, or all the groups a given user/group object belongs to. .PARAMETER GroupName The group name to query for, wildcards accepted. .PARAMETER SID The group SID to query for. .PARAMETER UserName The user name (or group name) to query for all effective groups of. .PARAMETER Filter A customized ldap filter string to use, e.g. "(description=*admin*)" .PARAMETER Domain The domain to query for groups, defaults to the current domain. .PARAMETER DomainController Domain controller to reflect LDAP queries through. .PARAMETER ADSpath The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local" Useful for OU queries. .PARAMETER AdminCount Switch. Return group with adminCount=1. .PARAMETER FullData Switch. Return full group objects instead of just object names (the default). .PARAMETER RawSids Switch. Return raw SIDs when using "Get-NetGroup -UserName X" .PARAMETER PageSize The PageSize to set for the LDAP searcher object. .PARAMETER Credential A [Management.Automation.PSCredential] object of alternate credentials for connection to the target domain. .EXAMPLE PS C:\> Get-NetGroup Returns the current groups in the domain. .EXAMPLE PS C:\> Get-NetGroup -GroupName *admin* Returns all groups with "admin" in their group name. .EXAMPLE PS C:\> Get-NetGroup -Domain testing -FullData Returns full group data objects in the 'testing' domain #> [CmdletBinding()] param( [Parameter(ValueFromPipeline=$True)] [String] $GroupName = '*', [String] $SID, [String] $UserName, [String] $Filter, [String] $Domain, [String] $DomainController, [String] $ADSpath, [Switch] $AdminCount, [Switch] $FullData, [Switch] $RawSids, [ValidateRange(1,10000)] [Int] $PageSize = 200, [Management.Automation.PSCredential] $Credential ) begin { $GroupSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $ADSpath -PageSize $PageSize } process { if($GroupSearcher) { if($AdminCount) { Write-Verbose "Checking for adminCount=1" $Filter += "(admincount=1)" } if ($UserName) { # get the raw user object $User = Get-ADObject -SamAccountName $UserName -Domain $Domain -DomainController $DomainController -Credential $Credential -ReturnRaw -PageSize $PageSize # convert the user to a directory entry $UserDirectoryEntry = $User.GetDirectoryEntry() # cause the cache to calculate the token groups for the user $UserDirectoryEntry.RefreshCache("tokenGroups") $UserDirectoryEntry.TokenGroups | ForEach-Object { # convert the token group sid $GroupSid = (New-Object System.Security.Principal.SecurityIdentifier($_,0)).Value # ignore the built in users and default domain user group if(!($GroupSid -match '^S-1-5-32-545|-513$')) { if($FullData) { Get-ADObject -SID $GroupSid -PageSize $PageSize -Domain $Domain -DomainController $DomainController -Credential $Credential } else { if($RawSids) { $GroupSid } else { Convert-SidToName $GroupSid } } } } } else { if ($SID) { $GroupSearcher.filter = "(&(objectCategory=group)(objectSID=$SID)$Filter)" } else { $GroupSearcher.filter = "(&(objectCategory=group)(name=$GroupName)$Filter)" } $GroupSearcher.FindAll() | Where-Object {$_} | ForEach-Object { # if we're returning full data objects if ($FullData) { # convert/process the LDAP fields for each result Convert-LDAPProperty -Properties $_.Properties } else { # otherwise we're just returning the group name $_.properties.samaccountname } } } } } } function Get-NetGroupMember { <# .SYNOPSIS This function users [ADSI] and LDAP to query the current AD context or trusted domain for users in a specified group. If no GroupName is specified, it defaults to querying the "Domain Admins" group. This is a replacement for "net group 'name' /domain" .PARAMETER GroupName The group name to query for users. .PARAMETER SID The Group SID to query for users. If not given, it defaults to 512 "Domain Admins" .PARAMETER Filter A customized ldap filter string to use, e.g. "(description=*admin*)" .PARAMETER Domain The domain to query for group users, defaults to the current domain. .PARAMETER DomainController Domain controller to reflect LDAP queries through. .PARAMETER ADSpath The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local" Useful for OU queries. .PARAMETER FullData Switch. Returns full data objects instead of just group/users. .PARAMETER Recurse Switch. If the group member is a group, recursively try to query its members as well. .PARAMETER UseMatchingRule Switch. Use LDAP_MATCHING_RULE_IN_CHAIN in the LDAP search query when -Recurse is specified. Much faster than manual recursion, but doesn't reveal cross-domain groups. .PARAMETER PageSize The PageSize to set for the LDAP searcher object. .PARAMETER Credential A [Management.Automation.PSCredential] object of alternate credentials for connection to the target domain. .EXAMPLE PS C:\> Get-NetGroupMember Returns the usernames that of members of the "Domain Admins" domain group. .EXAMPLE PS C:\> Get-NetGroupMember -Domain testing -GroupName "Power Users" Returns the usernames that of members of the "Power Users" group in the 'testing' domain. .LINK http://www.powershellmagazine.com/2013/05/23/pstip-retrieve-group-membership-of-an-active-directory-group-recursively/ #> [CmdletBinding()] param( [Parameter(ValueFromPipeline=$True)] [String] $GroupName, [String] $SID, [String] $Domain, [String] $DomainController, [String] $ADSpath, [Switch] $FullData, [Switch] $Recurse, [Switch] $UseMatchingRule, [ValidateRange(1,10000)] [Int] $PageSize = 200, [Management.Automation.PSCredential] $Credential ) begin { # so this isn't repeated if users are passed on the pipeline $GroupSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $ADSpath -PageSize $PageSize if(!$DomainController) { $DomainController = ((Get-NetDomain -Credential $Credential).PdcRoleOwner).Name } if(!$Domain) { $Domain = Get-NetDomain -Credential $Credential } } process { if ($GroupSearcher) { if ($Recurse -and $UseMatchingRule) { # resolve the group to a distinguishedname if ($GroupName) { $Group = Get-NetGroup -GroupName $GroupName -Domain $Domain -DomainController $DomainController -Credential $Credential -FullData -PageSize $PageSize } elseif ($SID) { $Group = Get-NetGroup -SID $SID -Domain $Domain -DomainController $DomainController -Credential $Credential -FullData -PageSize $PageSize } else { # default to domain admins $SID = (Get-DomainSID -Domain $Domain -Credential $Credential) + "-512" $Group = Get-NetGroup -SID $SID -Domain $Domain -DomainController $DomainController -Credential $Credential -FullData -PageSize $PageSize } $GroupDN = $Group.distinguishedname $GroupFoundName = $Group.name if ($GroupDN) { $GroupSearcher.filter = "(&(samAccountType=805306368)(memberof:1.2.840.113556.1.4.1941:=$GroupDN)$Filter)" $GroupSearcher.PropertiesToLoad.AddRange(('distinguishedName','samaccounttype','lastlogon','lastlogontimestamp','dscorepropagationdata','objectsid','whencreated','badpasswordtime','accountexpires','iscriticalsystemobject','name','usnchanged','objectcategory','description','codepage','instancetype','countrycode','distinguishedname','cn','admincount','logonhours','objectclass','logoncount','usncreated','useraccountcontrol','objectguid','primarygroupid','lastlogoff','samaccountname','badpwdcount','whenchanged','memberof','pwdlastset','adspath')) $Members = $GroupSearcher.FindAll() $GroupFoundName = $GroupName } else { Write-Error "Unable to find Group" } } else { if ($GroupName) { $GroupSearcher.filter = "(&(objectCategory=group)(name=$GroupName)$Filter)" } elseif ($SID) { $GroupSearcher.filter = "(&(objectCategory=group)(objectSID=$SID)$Filter)" } else { # default to domain admins $SID = (Get-DomainSID -Domain $Domain -Credential $Credential) + "-512" $GroupSearcher.filter = "(&(objectCategory=group)(objectSID=$SID)$Filter)" } $GroupSearcher.FindAll() | ForEach-Object { try { if (!($_) -or !($_.properties) -or !($_.properties.name)) { continue } $GroupFoundName = $_.properties.name[0] $Members = @() if ($_.properties.member.Count -eq 0) { $Finished = $False $Bottom = 0 $Top = 0 while(!$Finished) { $Top = $Bottom + 1499 $MemberRange="member;range=$Bottom-$Top" $Bottom += 1500 $GroupSearcher.PropertiesToLoad.Clear() [void]$GroupSearcher.PropertiesToLoad.Add("$MemberRange") try { $Result = $GroupSearcher.FindOne() if ($Result) { $RangedProperty = $_.Properties.PropertyNames -like "member;range=*" $Results = $_.Properties.item($RangedProperty) if ($Results.count -eq 0) { $Finished = $True } else { $Results | ForEach-Object { $Members += $_ } } } else { $Finished = $True } } catch [System.Management.Automation.MethodInvocationException] { $Finished = $True } } } else { $Members = $_.properties.member } } catch { Write-Verbose $_ } } } $Members | Where-Object {$_} | ForEach-Object { # if we're doing the LDAP_MATCHING_RULE_IN_CHAIN recursion if ($Recurse -and $UseMatchingRule) { $Properties = $_.Properties } else { if($DomainController) { $Result = [adsi]"LDAP://$DomainController/$_" } else { $Result = [adsi]"LDAP://$_" } if($Result){ $Properties = $Result.Properties } } if($Properties) { $IsGroup = @('268435456','268435457','536870912','536870913') -contains $Properties.samaccounttype if ($FullData) { $GroupMember = Convert-LDAPProperty -Properties $Properties } else { $GroupMember = New-Object PSObject } $GroupMember | Add-Member Noteproperty 'GroupDomain' $Domain $GroupMember | Add-Member Noteproperty 'GroupName' $GroupFoundName try { $MemberDN = $Properties.distinguishedname[0] # extract the FQDN from the Distinguished Name $MemberDomain = $MemberDN.subString($MemberDN.IndexOf("DC=")) -replace 'DC=','' -replace ',','.' } catch { $MemberDN = $Null $MemberDomain = $Null } if ($Properties.samaccountname) { # forest users have the samAccountName set $MemberName = $Properties.samaccountname[0] } else { # external trust users have a SID, so convert it try { $MemberName = Convert-SidToName $Properties.cn[0] } catch { # if there's a problem contacting the domain to resolve the SID $MemberName = $Properties.cn } } if($Properties.objectSid) { $MemberSid = ((New-Object System.Security.Principal.SecurityIdentifier $Properties.objectSid[0],0).Value) } else { $MemberSid = $Null } $GroupMember | Add-Member Noteproperty 'MemberDomain' $MemberDomain $GroupMember | Add-Member Noteproperty 'MemberName' $MemberName $GroupMember | Add-Member Noteproperty 'MemberSid' $MemberSid $GroupMember | Add-Member Noteproperty 'IsGroup' $IsGroup $GroupMember | Add-Member Noteproperty 'MemberDN' $MemberDN $GroupMember # if we're doing manual recursion if ($Recurse -and !$UseMatchingRule -and $IsGroup -and $MemberName) { if($FullData) { Get-NetGroupMember -FullData -Domain $MemberDomain -DomainController $DomainController -Credential $Credential -GroupName $MemberName -Recurse -PageSize $PageSize } else { Get-NetGroupMember -Domain $MemberDomain -DomainController $DomainController -Credential $Credential -GroupName $MemberName -Recurse -PageSize $PageSize } } } } } } } function Get-NetUser { <# .SYNOPSIS Query information for a given user or users in the domain using ADSI and LDAP. Another -Domain can be specified to query for users across a trust. Replacement for "net users /domain" .PARAMETER UserName Username filter string, wildcards accepted. .PARAMETER Domain The domain to query for users, defaults to the current domain. .PARAMETER DomainController Domain controller to reflect LDAP queries through. .PARAMETER ADSpath The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local" Useful for OU queries. .PARAMETER Filter A customized ldap filter string to use, e.g. "(description=*admin*)" .PARAMETER AdminCount Switch. Return users with adminCount=1. .PARAMETER SPN Switch. Only return user objects with non-null service principal names. .PARAMETER Unconstrained Switch. Return users that have unconstrained delegation. .PARAMETER AllowDelegation Switch. Return user accounts that are not marked as 'sensitive and not allowed for delegation' .PARAMETER PageSize The PageSize to set for the LDAP searcher object. .PARAMETER Credential A [Management.Automation.PSCredential] object of alternate credentials for connection to the target domain. .EXAMPLE PS C:\> Get-NetUser -Domain testing .EXAMPLE PS C:\> Get-NetUser -ADSpath "LDAP://OU=secret,DC=testlab,DC=local" #> param( [Parameter(Position=0, ValueFromPipeline=$True)] [String] $UserName, [String] $Domain, [String] $DomainController, [String] $ADSpath, [String] $Filter, [Switch] $SPN, [Switch] $AdminCount, [Switch] $Unconstrained, [Switch] $AllowDelegation, [ValidateRange(1,10000)] [Int] $PageSize = 200, [Management.Automation.PSCredential] $Credential ) begin { # so this isn't repeated if users are passed on the pipeline $UserSearcher = Get-DomainSearcher -Domain $Domain -ADSpath $ADSpath -DomainController $DomainController -PageSize $PageSize -Credential $Credential } process { if($UserSearcher) { # if we're checking for unconstrained delegation if($Unconstrained) { Write-Verbose "Checking for unconstrained delegation" $Filter += "(userAccountControl:1.2.840.113556.1.4.803:=524288)" } if($AllowDelegation) { Write-Verbose "Checking for users who can be delegated" # negation of "Accounts that are sensitive and not trusted for delegation" $Filter += "(!(userAccountControl:1.2.840.113556.1.4.803:=1048574))" } if($AdminCount) { Write-Verbose "Checking for adminCount=1" $Filter += "(admincount=1)" } # check if we're using a username filter or not if($UserName) { # samAccountType=805306368 indicates user objects $UserSearcher.filter="(&(samAccountType=805306368)(samAccountName=$UserName)$Filter)" } elseif($SPN) { $UserSearcher.filter="(&(samAccountType=805306368)(servicePrincipalName=*)$Filter)" } else { # filter is something like "(samAccountName=*blah*)" if specified $UserSearcher.filter="(&(samAccountType=805306368)$Filter)" } $Results = $UserSearcher.FindAll() $Results | Where-Object {$_} | ForEach-Object { # convert/process the LDAP fields for each result $User = Convert-LDAPProperty -Properties $_.Properties $User.PSObject.TypeNames.Add('PowerView.User') $User } $Results.dispose() $UserSearcher.dispose() } } } ######################################################## # # LAPS functions below. # ######################################################## function Find-AdmPwdExtendedRights { <# .SYNOPSIS This function leverages Get-ObjectAcl to query the domain for all computer objects with LAPS enabled, then parses each ExtendedRight ACL assignment to determine which users can read the ms-Mcs-AdmPwd attribute and if it was specifically delegated by the system administrator. Credits to @harmj0y and @pyrotek3 for research Author: @leoloobeek .PARAMETER Domain The domain to use for the query, defaults to the current domain. .PARAMETER DomainController Domain controller to reflect LDAP queries through. .PARAMETER Filter Filter to apply to LDAP queries, defaults to all computers with LAPS enabled. .PARAMETER ExcludeDelegated Switch. Only show users with "All Extended Rights". Default False. .PARAMETER PageSize The PageSize to set for the LDAP searcher object. .PARAMETER Credential A [Management.Automation.PSCredential] object of alternate credentials for connection to the target domain. NOTE: You must use FQDN of domain: testlab.local\user, not testlab\user .EXAMPLE PS C:\> Find-AdmPwdExtendedRights Description ----------- Get users who can read the ms-Mcs-AdmPwd confidential attribute for all LAPS enabled computers Reason: Delegated System admin delegated this group to view the password Reason: All This user/group has all extended rights permission and can view the password .EXAMPLE PS C:\> Find-AdmPwdExtendedRights -ComputerName victim1.testlab.local Description ----------- Only retrieves ExtendedRights for the computer specified .EXAMPLE PS C:\> Find-AdmPwdExtendedRights -Credential testlab.local\user -Domain testlab.local -DomainController 192.168.1.1 Description ----------- Retrieve LAPS ACL information from computer not connected to testlab.local domain. .LINK https://adsecurity.org/?p=1790 https://blogs.msdn.microsoft.com/laps/2015/07/17/laps-and-permission-to-join-computer-to-domain/ http://www.harmj0y.net/blog/redteaming/abusing-gpo-permissions/ #> [CmdletBinding()] param( [String] $Domain, [String] $DomainController, [String] $ComputerName, [String] $Filter = "(objectCategory=Computer)(ms-mcs-admpwdexpirationtime=*)", [Switch] $ExcludeDelegated, [ValidateRange(1,10000)] [Int] $PageSize = 200, [Management.Automation.PSCredential] $Credential ) begin { if($ComputerName) { $LAPSFilter = "$Filter(dNSHostName=$ComputerName)" } else { $LAPSFilter = "$Filter" } Write-Verbose "Retrieving all ExtendedRight ACLs for domain $Domain" $ExtendedRights = Get-ObjectAcl -ResolveGUIDs -Filter $LAPSFilter -Domain $Domain -DomainController $DomainController -Credential $Credential -PageSize $PageSize | Where-Object { $_.ActiveDirectoryRights -match "ExtendedRight" } # Build a hash table of DN and hostnames $CompMap = @{} $ComputerObjects = Get-NetComputer -Filter "(ms-mcs-admpwdexpirationtime=*)" -FullData -Domain $Domain -DomainController $DomainController -Credential $Credential | ForEach-Object { $CompMap.Add($_.distinguishedname, $_.dnshostname) } if($Credential){ # Build a hash table of SIDs and user/group Write-Verbose "Retrieving all users and groups to resolve SIDs when using PSCredential" $SIDMap = @{} Get-NetUser -Domain $Domain -DomainController $DomainController -Credential $Credential | ForEach-Object { $SIDMap.Add($_.objectsid, $_.samaccountname) } Get-NetGroup -FullData -Domain $Domain -DomainController $DomainController -Credential $Credential | ForEach-Object { $SIDMap.Add($_.objectsid, $_.samaccountname) } } } process { $ExtendedRights | ForEach-Object { $ComputerName = $CompMap[$_.ObjectDN] Write-Verbose "Parsing ACLs for $ComputerName" $Identity = $_.IdentityReference if($_.ObjectType -match "ms-Mcs-AdmPwd" -and !($ExcludeDelegated)) { $Reason = "Delegated" } elseif($_.ObjectType -match "All" -and $_.IdentityReference -notmatch "BUILTIN") { $Reason = "All" } else { return } # Map the SID to user/group if not on domain if($Credential) { if($SIDMap.Contains($Identity.ToString())) { $Identity = $SIDMap[$Identity.ToString()] } } $ExtendedRightUser = New-Object PSObject $ExtendedRightUser | Add-Member Noteproperty 'ComputerName' "$ComputerName" $ExtendedRightUser | Add-Member Noteproperty 'Identity' "$Identity" $ExtendedRightUser | Add-Member Noteproperty 'Reason' "$Reason" $ExtendedRightUser } } } function Find-LAPSDelegatedGroups { <# .SYNOPSIS This function uses Get-NetOU to query the domain for all OUs and finds the groups the system admin delegated to read the ms-Mcs-AdmPwd attribute. This will not find users with special permissions that can also read the attribute (i.e. users configured with the All Extended Rights permission) Credits to @harmj0y and @pyrotek3 for research Author: @leoloobeek .PARAMETER Domain The domain to use for the query, defaults to the current domain. .PARAMETER DomainController Domain controller to reflect LDAP queries through. .PARAMETER PageSize The PageSize to set for the LDAP searcher object. .PARAMETER Credential A [Management.Automation.PSCredential] object of alternate credentials for connection to the target domain. NOTE: You must use FQDN of domain: testlab.local\user, not testlab\user .EXAMPLE PS C:\> Find-LAPSDelegatedGroups Description ----------- Retrieves the groups delegated to read the ms-Mcs-AdmPwd for each OU .LINK http://www.harmj0y.net/blog/powershell/running-laps-with-powerview/ https://adsecurity.org/?p=1790 #> [CmdletBinding()] param( [String] $DomainController, [String] $Domain, [ValidateRange(1,10000)] [Int] $PageSize = 200, [Management.Automation.PSCredential] $Credential ) # Next few lines taken from http://www.harmj0y.net/blog/powershell/running-laps-with-powerview/ Get-NetOU -Domain $Domain -DomainController $DomainController -Credential $Credential -FullData | Get-ObjectAcl -Domain $Domain -DomainController $DomainController -Credential $Credential -ResolveGUIDs | Where-Object { ($_.ObjectType -like 'ms-Mcs-AdmPwd') -and ($_.ActiveDirectoryRights -match 'ReadProperty') } | ForEach-Object { $dn = $_.ObjectDN $ir = $_.IdentityReference $DelegatedGroup = New-Object PSObject $DelegatedGroup | Add-Member NoteProperty 'OrgUnit' "$dn" $DelegatedGroup | Add-Member Noteproperty 'Delegated Groups' "$ir" $DelegatedGroup } } function Get-LAPSComputers { <# .SYNOPSIS Retrieves all computers with LAPS enabled. Passwords for the accounts are displayed if the user has access to view the ms-Mcs-AdmPwd attribute. Similar to @kfosaaen's Get-LAPSPasswords but leverages @harmj0y's PowerView functions and can be used to find computers with LAPS enabled even if user does not have permissions to view password. Note: Parameters are taken from Get-NetComputer as this function is essentially a wrapper around it. .PARAMETER ComputerName Return computers with a specific name, wildcards accepted. .PARAMETER SPN Return computers with a specific service principal name, wildcards accepted. .PARAMETER Domain The domain to query for computers, defaults to the current domain. .PARAMETER DomainController Domain controller to reflect LDAP queries through. .PARAMETER ADSpath The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local" Useful for OU queries. .PARAMETER SiteName The AD Site name to search for computers. .PARAMETER Unconstrained Switch. Return computer objects that have unconstrained delegation. .PARAMETER PageSize The PageSize to set for the LDAP searcher object. .PARAMETER Credential A [Management.Automation.PSCredential] object of alternate credentials for connection to the target domain. NOTE: You must use FQDN of domain: testlab.local\user, not testlab\user .EXAMPLE PS C:\> Get-LAPSComputers Description ----------- Retreives all computer objects from domain with LAPS enabled and displays the time the password expires and the password if the user has read access. .LINK https://github.com/kfosaaen/Get-LAPSPasswords https://blog.netspi.com/running-laps-around-cleartext-passwords/ #> [CmdletBinding()] Param ( [Parameter(ValueFromPipeline=$True)] [Alias('HostName')] [String] $ComputerName = '*', [String] $SPN, [String] $Domain, [String] $DomainController, [String] $ADSpath, [String] $SiteName, [Switch] $Unconstrained, [ValidateRange(1,10000)] [Int] $PageSize = 200, [Management.Automation.PSCredential] $Credential ) process { Get-NetComputer -FullData -Filter "(ms-mcs-admpwdexpirationtime=*)" @PSBoundParameters | ForEach-Object { $HostName = $_.dnshostname $Password = $_."ms-mcs-admpwd" # epoch conversion code taken directly from https://github.com/kfosaaen/Get-LAPSPasswords/blob/master/Get-LAPSPasswords.ps1 If ($_."ms-MCS-AdmPwdExpirationTime" -ge 0) { $CurrentExpiration = $([datetime]::FromFileTime([convert]::ToInt64($_."ms-MCS-AdmPwdExpirationTime",10))) } Else{ $CurrentExpiration = "N/A" } $Computer = New-Object PSObject $Computer | Add-Member NoteProperty 'ComputerName' "$HostName" $Computer | Add-Member Noteproperty 'Password' "$Password" $Computer | Add-Member Noteproperty 'Expiration' "$CurrentExpiration" $Computer } } }