aboutsummaryrefslogtreecommitdiff
path: root/Recon/PowerView.ps1
diff options
context:
space:
mode:
Diffstat (limited to 'Recon/PowerView.ps1')
-rw-r--r--Recon/PowerView.ps14132
1 files changed, 2628 insertions, 1504 deletions
diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1
index 57a5789..e6f6233 100644
--- a/Recon/PowerView.ps1
+++ b/Recon/PowerView.ps1
@@ -714,11 +714,13 @@ function struct
#
########################################################
-function Export-PowerViewCSV {
+filter Export-PowerViewCSV {
<#
.SYNOPSIS
- This function exports to a .csv in a thread-safe manner.
+ 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
@@ -729,231 +731,84 @@ function Export-PowerViewCSV {
http://dmitrysotnikov.wordpress.com/2010/01/19/Export-Csv-append/
#>
Param(
- [Parameter(Mandatory=$True, ValueFromPipeline=$True,
- ValueFromPipelineByPropertyName=$True)]
- [System.Management.Automation.PSObject]
+ [Parameter(Mandatory=$True, ValueFromPipeline=$True, ValueFromPipelineByPropertyName=$True)]
+ [System.Management.Automation.PSObject[]]
$InputObject,
[Parameter(Mandatory=$True, Position=0)]
- [Alias('PSPath')]
[String]
+ [ValidateNotNullOrEmpty()]
$OutFile
)
- process {
-
- $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()
+ $ObjectCSV = $InputObject | ConvertTo-Csv -NoTypeInformation
- 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 so threaded code doesn't stomp on the output file
+ $Mutex = New-Object System.Threading.Mutex $False,'CSVMutex';
+ $Null = $Mutex.WaitOne()
- $Mutex.ReleaseMutex()
+ 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
}
-}
-
-
-# stolen directly from http://obscuresecurity.blogspot.com/2014/05/touch.html
-function Set-MacAttribute {
-<#
- .SYNOPSIS
-
- Sets the modified, accessed and created (Mac) attributes for a file based on another file or input.
-
- PowerSploit Function: Set-MacAttribute
- Author: Chris Campbell (@obscuresec)
- License: BSD 3-Clause
- Required Dependencies: None
- Optional Dependencies: None
- Version: 1.0.0
-
- .DESCRIPTION
-
- Set-MacAttribute sets one or more Mac attributes and returns the new attribute values of the file.
-
- .EXAMPLE
-
- PS C:\> Set-MacAttribute -FilePath c:\test\newfile -OldFilePath c:\test\oldfile
-
- .EXAMPLE
-
- PS C:\> Set-MacAttribute -FilePath c:\demo\test.xt -All "01/03/2006 12:12 pm"
-
- .EXAMPLE
-
- PS C:\> Set-MacAttribute -FilePath c:\demo\test.txt -Modified "01/03/2006 12:12 pm" -Accessed "01/03/2006 12:11 pm" -Created "01/03/2006 12:10 pm"
-
- .LINK
-
- http://www.obscuresec.com/2014/05/touch.html
-#>
- [CmdletBinding(DefaultParameterSetName = 'Touch')]
- Param (
-
- [Parameter(Position = 1,Mandatory = $True)]
- [ValidateScript({Test-Path -Path $_ })]
- [String]
- $FilePath,
-
- [Parameter(ParameterSetName = 'Touch')]
- [ValidateScript({Test-Path -Path $_ })]
- [String]
- $OldFilePath,
-
- [Parameter(ParameterSetName = 'Individual')]
- [DateTime]
- $Modified,
-
- [Parameter(ParameterSetName = 'Individual')]
- [DateTime]
- $Accessed,
-
- [Parameter(ParameterSetName = 'Individual')]
- [DateTime]
- $Created,
-
- [Parameter(ParameterSetName = 'All')]
- [DateTime]
- $AllMacAttributes
- )
-
- #Helper function that returns an object with the MAC attributes of a file.
- function Get-MacAttribute {
-
- param($OldFileName)
-
- if (!(Test-Path -Path $OldFileName)) {Throw 'File Not Found'}
- $FileInfoObject = (Get-Item $OldFileName)
-
- $ObjectProperties = @{'Modified' = ($FileInfoObject.LastWriteTime);
- 'Accessed' = ($FileInfoObject.LastAccessTime);
- 'Created' = ($FileInfoObject.CreationTime)};
- $ResultObject = New-Object -TypeName PSObject -Property $ObjectProperties
- Return $ResultObject
- }
-
- $FileInfoObject = (Get-Item -Path $FilePath)
-
- if ($PSBoundParameters['AllMacAttributes']) {
- $Modified = $AllMacAttributes
- $Accessed = $AllMacAttributes
- $Created = $AllMacAttributes
- }
-
- if ($PSBoundParameters['OldFilePath']) {
- $CopyFileMac = (Get-MacAttribute $OldFilePath)
- $Modified = $CopyFileMac.Modified
- $Accessed = $CopyFileMac.Accessed
- $Created = $CopyFileMac.Created
+ else {
+ $ObjectCSV | Out-File -Encoding 'ASCII' -Append -FilePath $OutFile
}
- if ($Modified) {$FileInfoObject.LastWriteTime = $Modified}
- if ($Accessed) {$FileInfoObject.LastAccessTime = $Accessed}
- if ($Created) {$FileInfoObject.CreationTime = $Created}
-
- Return (Get-MacAttribute $FilePath)
+ $Mutex.ReleaseMutex()
}
-function Copy-ClonedFile {
+filter Get-IPAddress {
<#
.SYNOPSIS
- Copy a source file to a destination location, matching any MAC
- properties as appropriate.
-
- .PARAMETER SourceFile
-
- Source file to copy.
-
- .PARAMETER DestFile
-
- Destination file path to copy file to.
+ Resolves a given hostename to its associated IPv4 address.
+ If no hostname is provided, it defaults to returning
+ the IP address of the localhost.
.EXAMPLE
- PS C:\> Copy-ClonedFile -SourceFile program.exe -DestFile \\WINDOWS7\tools\program.exe
+ PS C:\> Get-IPAddress -ComputerName SERVER
- Copy the local program.exe binary to a remote location, matching the MAC properties of the remote exe.
-
- .LINK
-
- http://obscuresecurity.blogspot.com/2014/05/touch.html
-#>
-
- param(
- [Parameter(Mandatory = $True)]
- [String]
- [ValidateNotNullOrEmpty()]
- $SourceFile,
-
- [Parameter(Mandatory = $True)]
- [String]
- [ValidateNotNullOrEmpty()]
- $DestFile
- )
-
- # clone the MAC properties
- Set-MacAttribute -FilePath $SourceFile -OldFilePath $DestFile
-
- # copy the file off
- Copy-Item -Path $SourceFile -Destination $DestFile
-}
-
-
-function Get-IPAddress {
-<#
- .SYNOPSIS
-
- This function resolves a given hostename to its associated IPv4
- address. If no hostname is provided, it defaults to returning
- the IP address of the local host the script be being run on.
+ Return the IPv4 address of 'SERVER'
.EXAMPLE
- PS C:\> Get-IPAddress -ComputerName SERVER
-
- Return the IPv4 address of 'SERVER'
+ PS C:\> Get-Content .\hostnames.txt | Get-IPAddress
+
+ Get the IP addresses of all hostnames in an input file.
#>
[CmdletBinding()]
param(
- [Parameter(Position=0,ValueFromPipeline=$True)]
+ [Parameter(Position=0, ValueFromPipeline=$True)]
[Alias('HostName')]
[String]
- $ComputerName = ''
+ $ComputerName = $Env:ComputerName
)
- process {
- try {
- # get the IP resolution of this specified hostname
- $Results = @(([Net.Dns]::GetHostEntry($ComputerName)).AddressList)
-
- if ($Results.Count -ne 0) {
- ForEach ($Result in $Results) {
- # make sure the returned result is IPv4
- if ($Result.AddressFamily -eq 'InterNetwork') {
- $Result.IPAddressToString
- }
- }
+
+ try {
+ # extract the computer name from whatever object was passed on the pipeline
+ $Computer = $ComputerName | Get-NameField
+
+ # get the IP resolution of this specified hostname
+ @(([Net.Dns]::GetHostEntry($Computer)).AddressList) | ForEach-Object {
+ if ($_.AddressFamily -eq 'InterNetwork') {
+ $Out = New-Object PSObject
+ $Out | Add-Member Noteproperty 'ComputerName' $Computer
+ $Out | Add-Member Noteproperty 'IPAddress' $_.IPAddressToString
+ $Out
}
}
- catch {
- Write-Verbose -Message 'Could not resolve host to an IP Address.'
- }
}
- end {}
+ catch {
+ Write-Verbose -Message 'Could not resolve host to an IP Address.'
+ }
}
-function Convert-NameToSid {
+filter Convert-NameToSid {
<#
.SYNOPSIS
@@ -973,38 +828,43 @@ function Convert-NameToSid {
#>
[CmdletBinding()]
param(
- [Parameter(Mandatory=$True,ValueFromPipeline=$True)]
+ [Parameter(Mandatory=$True, ValueFromPipeline=$True)]
[String]
[Alias('Name')]
$ObjectName,
[String]
- $Domain = (Get-NetDomain).Name
+ $Domain
)
- process {
-
- $ObjectName = $ObjectName -replace "/","\"
-
- if($ObjectName.contains("\")) {
- # if we get a DOMAIN\user format, auto convert it
- $Domain = $ObjectName.split("\")[0]
- $ObjectName = $ObjectName.split("\")[1]
- }
+ $ObjectName = $ObjectName -Replace "/","\"
+
+ if($ObjectName.Contains("\")) {
+ # if we get a DOMAIN\user format, auto convert it
+ $Domain = $ObjectName.Split("\")[0]
+ $ObjectName = $ObjectName.Split("\")[1]
+ }
+ elseif(!$Domain) {
+ $Domain = (Get-NetDomain).Name
+ }
- try {
- $Obj = (New-Object System.Security.Principal.NTAccount($Domain,$ObjectName))
- $Obj.Translate([System.Security.Principal.SecurityIdentifier]).Value
- }
- catch {
- Write-Verbose "Invalid object/name: $Domain\$ObjectName"
- $Null
- }
+ try {
+ $Obj = (New-Object System.Security.Principal.NTAccount($Domain, $ObjectName))
+ $SID = $Obj.Translate([System.Security.Principal.SecurityIdentifier]).Value
+
+ $Out = New-Object PSObject
+ $Out | Add-Member Noteproperty 'ObjectName' $ObjectName
+ $Out | Add-Member Noteproperty 'SID' $SID
+ $Out
+ }
+ catch {
+ Write-Verbose "Invalid object/name: $Domain\$ObjectName"
+ $Null
}
}
-function Convert-SidToName {
+filter Convert-SidToName {
<#
.SYNOPSIS
@@ -1020,211 +880,217 @@ function Convert-SidToName {
#>
[CmdletBinding()]
param(
- [Parameter(Mandatory=$True,ValueFromPipeline=$True)]
+ [Parameter(Mandatory=$True, ValueFromPipeline=$True)]
[String]
+ [ValidatePattern('^S-1-.*')]
$SID
)
- process {
- try {
- $SID2 = $SID.trim('*')
+ 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
- }
+ # 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-Warning "Invalid SID: $SID"
- $SID
- }
+ }
+ catch {
+ Write-Debug "Invalid SID: $SID"
+ $SID
}
}
-function Convert-NT4toCanonical {
+filter Convert-ADName {
<#
.SYNOPSIS
- Converts a user/group NT4 name (i.e. dev/john) to canonical format.
+ 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, needs to be in 'DOMAIN\user' format.
+ 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-NT4toCanonical -ObjectName "dev\dfm"
+ 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)]
+ [Parameter(Mandatory=$True, ValueFromPipeline=$True)]
+ [String]
+ $ObjectName,
+
+ [String]
+ [ValidateSet("NT4","Simple","Canonical")]
+ $InputType,
+
[String]
- $ObjectName
+ [ValidateSet("NT4","Simple","Canonical")]
+ $OutputType
)
- process {
+ $NameTypes = @{
+ "Canonical" = 2
+ "NT4" = 3
+ "Simple" = 5
+ }
- $ObjectName = $ObjectName -replace "/","\"
-
- if($ObjectName.contains("\")) {
- # if we get a DOMAIN\user format, try to extract the domain
- $Domain = $ObjectName.split("\")[0]
+ if(!$PSBoundParameters['InputType']) {
+ if( ($ObjectName.split('/')).Count -eq 2 ) {
+ $ObjectName = $ObjectName.replace('/', '\')
}
- # 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 }
+ if($ObjectName -match "^[A-Za-z]+\\[A-Za-z ]+$") {
+ $InputType = 'NT4'
}
- function Set-Property([__ComObject] $Object, [String] $Property, $Parameters) {
- [Void] $Object.GetType().InvokeMember($Property, "SetProperty", $Null, $Object, $Parameters)
+ elseif($ObjectName -match "^[A-Za-z ]+@[A-Za-z\.]+") {
+ $InputType = 'Simple'
}
-
- $Translate = New-Object -ComObject NameTranslate
-
- try {
- Invoke-Method $Translate "Init" (1, $Domain)
+ elseif($ObjectName -match "^[A-Za-z\.]+/[A-Za-z]+/[A-Za-z/ ]+") {
+ $InputType = 'Canonical'
}
- catch [System.Management.Automation.MethodInvocationException] {
- Write-Debug "Error with translate init in Convert-NT4toCanonical: $_"
+ else {
+ Write-Warning "Can not identify InType for $ObjectName"
+ return $ObjectName
}
+ }
+ elseif($InputType -eq 'NT4') {
+ $ObjectName = $ObjectName.replace('/', '\')
+ }
- Set-Property $Translate "ChaseReferral" (0x60)
-
- try {
- Invoke-Method $Translate "Set" (3, $ObjectName)
- (Invoke-Method $Translate "Get" (2))
- }
- catch [System.Management.Automation.MethodInvocationException] {
- Write-Debug "Error with translate Set/Get in Convert-NT4toCanonical: $_"
+ if(!$PSBoundParameters['OutputType']) {
+ $OutputType = Switch($InputType) {
+ 'NT4' {'Canonical'}
+ 'Simple' {'NT4'}
+ 'Canonical' {'NT4'}
}
}
-}
-
-
-function Convert-CanonicaltoNT4 {
-<#
- .SYNOPSIS
-
- Converts a user@fqdn to NT4 format.
-
- .PARAMETER ObjectName
-
- The user/group name to convert, needs to be in 'DOMAIN\user' format.
-
- .LINK
-
- http://windowsitpro.com/active-directory/translating-active-directory-object-names-between-formats
-#>
-
- [CmdletBinding()]
- param(
- [String] $ObjectName
- )
- $Domain = ($ObjectName -split "@")[1]
-
- $ObjectName = $ObjectName -replace "/","\"
+ # 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 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)
+ function Set-Property([__ComObject] $Object, [String] $Property, $Parameters) {
+ [Void] $Object.GetType().InvokeMember($Property, "SetProperty", $Null, $Object, $Parameters)
}
- $Translate = New-Object -comobject NameTranslate
+ $Translate = New-Object -ComObject NameTranslate
try {
Invoke-Method $Translate "Init" (1, $Domain)
}
- catch [System.Management.Automation.MethodInvocationException] { }
+ catch [System.Management.Automation.MethodInvocationException] {
+ Write-Debug "Error with translate init in Convert-ADName: $_"
+ }
Set-Property $Translate "ChaseReferral" (0x60)
try {
- Invoke-Method $Translate "Set" (5, $ObjectName)
- (Invoke-Method $Translate "Get" (3))
+ 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: $_"
}
- catch [System.Management.Automation.MethodInvocationException] { $_ }
}
@@ -1264,7 +1130,7 @@ function ConvertFrom-UACValue {
[CmdletBinding()]
param(
- [Parameter(ValueFromPipeline=$True)]
+ [Parameter(Mandatory=$True, ValueFromPipeline=$True)]
$Value,
[Switch]
@@ -1272,7 +1138,6 @@ function ConvertFrom-UACValue {
)
begin {
-
# values from https://support.microsoft.com/en-us/kb/305144
$UACValues = New-Object System.Collections.Specialized.OrderedDictionary
$UACValues.Add("SCRIPT", 1)
@@ -1297,7 +1162,6 @@ function ConvertFrom-UACValue {
$UACValues.Add("PASSWORD_EXPIRED", 8388608)
$UACValues.Add("TRUSTED_TO_AUTH_FOR_DELEGATION", 16777216)
$UACValues.Add("PARTIAL_SECRETS_ACCOUNT", 67108864)
-
}
process {
@@ -1307,40 +1171,39 @@ function ConvertFrom-UACValue {
if($Value -is [Int]) {
$IntValue = $Value
}
-
- if ($Value -is [PSCustomObject]) {
+ elseif ($Value -is [PSCustomObject]) {
if($Value.useraccountcontrol) {
$IntValue = $Value.useraccountcontrol
}
}
+ else {
+ Write-Warning "Invalid object input for -Value : $Value"
+ return $Null
+ }
- if($IntValue) {
-
- 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)")
- }
+ 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)")
- }
- }
+ }
+ else {
+ foreach ($UACValue in $UACValues.GetEnumerator()) {
+ if( ($IntValue -band $UACValue.Value) -eq $UACValue.Value) {
+ $ResultUACValues.Add($UACValue.Name, "$($UACValue.Value)")
+ }
}
}
-
$ResultUACValues
}
}
-function Get-Proxy {
+filter Get-Proxy {
<#
.SYNOPSIS
@@ -1363,52 +1226,66 @@ function Get-Proxy {
$ComputerName = $ENV:COMPUTERNAME
)
- process {
- 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')
+ 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')
- if($AutoConfigURL -and ($AutoConfigURL -ne "")) {
- try {
- $Wpad = (New-Object Net.Webclient).DownloadString($AutoConfigURL)
- }
- catch {
- $Wpad = ""
- }
+ $Wpad = ""
+ if($AutoConfigURL -and ($AutoConfigURL -ne "")) {
+ try {
+ $Wpad = (New-Object Net.Webclient).DownloadString($AutoConfigURL)
}
- else {
- $Wpad = ""
+ catch {
+ Write-Warning "Error connecting to AutoConfigURL : $AutoConfigURL"
}
-
- if($ProxyServer -or $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"
+ $Properties = @{
+ 'ProxyServer' = $ProxyServer
+ 'AutoConfigURL' = $AutoConfigURL
+ 'Wpad' = $Wpad
}
+
+ New-Object -TypeName PSObject -Property $Properties
}
- catch {
- Write-Warning "Error enumerating proxy settings for $ComputerName"
+ 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]
+ [String]
$Path,
[Switch]
@@ -1491,7 +1368,7 @@ function Get-PathAcl {
$Names = @()
$SIDs = @($Object.objectsid)
- if ($Recurse -and ($Object.samAccountType -ne "805306368")) {
+ if ($Recurse -and (@('268435456','268435457','536870912','536870913') -contains $Object.samAccountType)) {
$SIDs += Get-NetGroupMember -SID $Object.objectsid | Select-Object -ExpandProperty MemberSid
}
@@ -1521,41 +1398,83 @@ function Get-PathAcl {
}
-function Get-NameField {
- # function that attempts to extract the appropriate field name
- # from various passed objects. This is so functions can have
- # multiple types of objects passed on the pipeline.
+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(Mandatory=$True,ValueFromPipeline=$True)]
- $Object
+ [Parameter(ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
+ [Object]
+ $Object,
+
+ [Parameter(ValueFromPipelineByPropertyName = $True)]
+ [String]
+ $DnsHostName,
+
+ [Parameter(ValueFromPipelineByPropertyName = $True)]
+ [String]
+ $Name
)
- process {
- if($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
- }
+
+ 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 {
- return $Null
+ # strings and catch alls
+ $Object
}
}
+ else {
+ return $Null
+ }
}
function Convert-LDAPProperty {
- # helper to convert specific LDAP property result fields
+<#
+ .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)]
+ [Parameter(Mandatory=$True, ValueFromPipeline=$True)]
[ValidateNotNullOrEmpty()]
$Properties
)
@@ -1585,7 +1504,7 @@ function Convert-LDAPProperty {
}
}
elseif($Properties[$_][0] -is [System.MarshalByRefObject]) {
- # convert misc com objects
+ # try to convert misc com objects
$Prop = $Properties[$_]
try {
$Temp = $Prop[$_][0]
@@ -1617,7 +1536,7 @@ function Convert-LDAPProperty {
#
########################################################
-function Get-DomainSearcher {
+filter Get-DomainSearcher {
<#
.SYNOPSIS
@@ -1645,6 +1564,11 @@ function Get-DomainSearcher {
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
@@ -1654,8 +1578,8 @@ function Get-DomainSearcher {
PS C:\> Get-DomainSearcher -Domain testlab.local -DomainController SECONDARY.dev.testlab.local
#>
- [CmdletBinding()]
param(
+ [Parameter(ValueFromPipeline=$True)]
[String]
$Domain,
@@ -1670,14 +1594,17 @@ function Get-DomainSearcher {
[ValidateRange(1,10000)]
[Int]
- $PageSize = 200
+ $PageSize = 200,
+
+ [Management.Automation.PSCredential]
+ $Credential
)
- if(!$Domain) {
- $Domain = (Get-NetDomain).name
- }
- else {
- if(!$DomainController) {
+ 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
@@ -1688,12 +1615,28 @@ function Get-DomainSearcher {
}
}
}
+ 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 + "/"
+ $SearchString += $DomainController
+ if($Domain){
+ $SearchString += "/"
+ }
}
+
if($ADSprefix) {
$SearchString += $ADSprefix + ","
}
@@ -1701,30 +1644,45 @@ function Get-DomainSearcher {
if($ADSpath) {
if($ADSpath -like "GC://*") {
# if we're searching the global catalog
- $DistinguishedName = $AdsPath
+ $DN = $AdsPath
$SearchString = ""
}
else {
if($ADSpath -like "LDAP://*") {
- $ADSpath = $ADSpath.Substring(7)
+ if($ADSpath -match "LDAP://.+/.+") {
+ $SearchString = ""
+ }
+ else {
+ $ADSpath = $ADSpath.Substring(7)
+ }
}
- $DistinguishedName = $ADSpath
+ $DN = $ADSpath
}
}
else {
- $DistinguishedName = "DC=$($Domain.Replace('.', ',DC='))"
+ if($Domain -and ($Domain.Trim() -ne "")) {
+ $DN = "DC=$($Domain.Replace('.', ',DC='))"
+ }
}
- $SearchString += $DistinguishedName
+ $SearchString += $DN
Write-Verbose "Get-DomainSearcher search string: $SearchString"
- $Searcher = New-Object System.DirectoryServices.DirectorySearcher([ADSI]$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
}
-function Get-NetDomain {
+filter Get-NetDomain {
<#
.SYNOPSIS
@@ -1734,41 +1692,70 @@ function Get-NetDomain {
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
#>
- [CmdletBinding()]
param(
[Parameter(ValueFromPipeline=$True)]
[String]
- $Domain
+ $Domain,
+
+ [Management.Automation.PSCredential]
+ $Credential
)
- process {
- if($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
- }
+ 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"
}
- else {
- [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()
+
+ $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()
+ }
}
-function Get-NetForest {
+filter Get-NetForest {
<#
.SYNOPSIS
@@ -1778,47 +1765,76 @@ function Get-NetForest {
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
#>
- [CmdletBinding()]
param(
[Parameter(ValueFromPipeline=$True)]
[String]
- $Forest
+ $Forest,
+
+ [Management.Automation.PSCredential]
+ $Credential
)
- process {
- if($Forest) {
- $ForestContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext('Forest', $Forest)
- try {
- $ForestObject = [System.DirectoryServices.ActiveDirectory.Forest]::GetForest($ForestContext)
- }
- catch {
- Write-Debug "The specified forest $Forest does not exist, could not be contacted, or there isn't an existing trust."
- $Null
- }
+ 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"
}
- else {
- # otherwise use the current forest
- $ForestObject = [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest()
+
+ $ForestContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext('Forest', $Forest, $Credential.UserName, $Credential.GetNetworkCredential().Password)
+
+ try {
+ $ForestObject = [System.DirectoryServices.ActiveDirectory.Forest]::GetForest($ForestContext)
}
-
- if($ForestObject) {
- # get the SID of the forest root
- $ForestSid = (New-Object System.Security.Principal.NTAccount($ForestObject.RootDomain,"krbtgt")).Translate([System.Security.Principal.SecurityIdentifier]).Value
- $Parts = $ForestSid -Split "-"
- $ForestSid = $Parts[0..$($Parts.length-2)] -join "-"
- $ForestObject | Add-Member NoteProperty 'RootDomainSid' $ForestSid
- $ForestObject
+ 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
+ $ForestSid = (New-Object System.Security.Principal.NTAccount($ForestObject.RootDomain,"krbtgt")).Translate([System.Security.Principal.SecurityIdentifier]).Value
+ $Parts = $ForestSid -Split "-"
+ $ForestSid = $Parts[0..$($Parts.length-2)] -join "-"
+ $ForestObject | Add-Member NoteProperty 'RootDomainSid' $ForestSid
+ $ForestObject
+ }
}
-function Get-NetForestDomain {
+filter Get-NetForestDomain {
<#
.SYNOPSIS
@@ -1828,9 +1844,10 @@ function Get-NetForestDomain {
The forest name to query domain for.
- .PARAMETER Domain
+ .PARAMETER Credential
- Return domains that match this term/wildcard.
+ A [Management.Automation.PSCredential] object of alternate credentials
+ for connection to the target domain.
.EXAMPLE
@@ -1841,39 +1858,24 @@ function Get-NetForestDomain {
PS C:\> Get-NetForestDomain -Forest external.local
#>
- [CmdletBinding()]
param(
[Parameter(ValueFromPipeline=$True)]
[String]
$Forest,
- [String]
- $Domain
+ [Management.Automation.PSCredential]
+ $Credential
)
- process {
- if($Domain) {
- # try to detect a wild card so we use -like
- if($Domain.Contains('*')) {
- (Get-NetForest -Forest $Forest).Domains | Where-Object {$_.Name -like $Domain}
- }
- else {
- # match the exact domain name if there's not a wildcard
- (Get-NetForest -Forest $Forest).Domains | Where-Object {$_.Name.ToLower() -eq $Domain.ToLower()}
- }
- }
- else {
- # return all domains
- $ForestObject = Get-NetForest -Forest $Forest
- if($ForestObject) {
- $ForestObject.Domains
- }
- }
+ $ForestObject = Get-NetForest -Forest $Forest -Credential $Credential
+
+ if($ForestObject) {
+ $ForestObject.Domains
}
}
-function Get-NetForestCatalog {
+filter Get-NetForestCatalog {
<#
.SYNOPSIS
@@ -1883,28 +1885,34 @@ function Get-NetForestCatalog {
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
#>
-
- [CmdletBinding()]
+
param(
[Parameter(ValueFromPipeline=$True)]
[String]
- $Forest
+ $Forest,
+
+ [Management.Automation.PSCredential]
+ $Credential
)
- process {
- $ForestObject = Get-NetForest -Forest $Forest
- if($ForestObject) {
- $ForestObject.FindAllGlobalCatalogs()
- }
+ $ForestObject = Get-NetForest -Forest $Forest -Credential $Credential
+
+ if($ForestObject) {
+ $ForestObject.FindAllGlobalCatalogs()
}
}
-function Get-NetDomainController {
+filter Get-NetDomainController {
<#
.SYNOPSIS
@@ -1922,9 +1930,28 @@ function Get-NetDomainController {
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:\> Get-NetDomainController -Domain test
+ PS C:\> 'test.local' | Get-NetDomainController
+
+ Determine the domain controllers for 'test.local'.
#>
[CmdletBinding()]
@@ -1937,20 +1964,20 @@ function Get-NetDomainController {
$DomainController,
[Switch]
- $LDAP
+ $LDAP,
+
+ [Management.Automation.PSCredential]
+ $Credential
)
- process {
- if($LDAP -or $DomainController) {
- # filter string to return all domain controllers
- Get-NetComputer -Domain $Domain -DomainController $DomainController -FullData -Filter '(userAccountControl:1.2.840.113556.1.4.803:=8192)'
- }
- else {
- $FoundDomain = Get-NetDomain -Domain $Domain
-
- if($FoundDomain) {
- $Founddomain.DomainControllers
- }
+ 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
}
}
}
@@ -2012,6 +2039,11 @@ function Get-NetUser {
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
@@ -2021,9 +2053,8 @@ function Get-NetUser {
PS C:\> Get-NetUser -ADSpath "LDAP://OU=secret,DC=testlab,DC=local"
#>
- [CmdletBinding()]
param(
- [Parameter(ValueFromPipeline=$True)]
+ [Parameter(Position=0, ValueFromPipeline=$True)]
[String]
$UserName,
@@ -2053,12 +2084,15 @@ function Get-NetUser {
[ValidateRange(1,10000)]
[Int]
- $PageSize = 200
+ $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
+ $UserSearcher = Get-DomainSearcher -Domain $Domain -ADSpath $ADSpath -DomainController $DomainController -PageSize $PageSize -Credential $Credential
}
process {
@@ -2395,6 +2429,11 @@ function Get-UserProperty {
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-UserProperty -Domain testing
@@ -2426,22 +2465,25 @@ function Get-UserProperty {
[ValidateRange(1,10000)]
[Int]
- $PageSize = 200
+ $PageSize = 200,
+
+ [Management.Automation.PSCredential]
+ $Credential
)
if($Properties) {
# extract out the set of all properties for each object
$Properties = ,"name" + $Properties
- Get-NetUser -Domain $Domain -DomainController $DomainController -PageSize $PageSize | Select-Object -Property $Properties
+ Get-NetUser -Domain $Domain -DomainController $DomainController -PageSize $PageSize -Credential $Credential | Select-Object -Property $Properties
}
else {
# extract out just the property names
- Get-NetUser -Domain $Domain -DomainController $DomainController -PageSize $PageSize | Select-Object -First 1 | Get-Member -MemberType *Property | Select-Object -Property 'Name'
+ Get-NetUser -Domain $Domain -DomainController $DomainController -PageSize $PageSize -Credential $Credential | Select-Object -First 1 | Get-Member -MemberType *Property | Select-Object -Property 'Name'
}
}
-function Find-UserField {
+filter Find-UserField {
<#
.SYNOPSIS
@@ -2476,6 +2518,11 @@ function Find-UserField {
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:\> Find-UserField -SearchField info -SearchTerm backup
@@ -2503,16 +2550,17 @@ function Find-UserField {
[ValidateRange(1,10000)]
[Int]
- $PageSize = 200
- )
+ $PageSize = 200,
- process {
- Get-NetUser -ADSpath $ADSpath -Domain $Domain -DomainController $DomainController -Filter "($SearchField=*$SearchTerm*)" -PageSize $PageSize | Select-Object samaccountname,$SearchField
- }
+ [Management.Automation.PSCredential]
+ $Credential
+ )
+
+ Get-NetUser -ADSpath $ADSpath -Domain $Domain -DomainController $DomainController -Credential $Credential -Filter "($SearchField=*$SearchTerm*)" -PageSize $PageSize | Select-Object samaccountname,$SearchField
}
-function Get-UserEvent {
+filter Get-UserEvent {
<#
.SYNOPSIS
@@ -2534,7 +2582,12 @@ function Get-UserEvent {
.PARAMETER DateStart
Filter out all events before this date. Default: 5 days
-
+
+ .PARAMETER Credential
+
+ A [Management.Automation.PSCredential] object of alternate credentials
+ for connection to the target domain.
+
.EXAMPLE
PS C:\> Get-UserEvent -ComputerName DomainController.testlab.local
@@ -2545,6 +2598,7 @@ function Get-UserEvent {
#>
Param(
+ [Parameter(ValueFromPipeline=$True)]
[String]
$ComputerName = $Env:ComputerName,
@@ -2553,7 +2607,10 @@ function Get-UserEvent {
$EventType = "logon",
[DateTime]
- $DateStart=[DateTime]::Today.AddDays(-5)
+ $DateStart = [DateTime]::Today.AddDays(-5),
+
+ [Management.Automation.PSCredential]
+ $Credential
)
if($EventType.ToLower() -like "logon") {
@@ -2566,8 +2623,25 @@ function Get-UserEvent {
[Int32[]]$ID = @(4624, 4768)
}
- #grab all events matching our filter for the specified host
- Get-WinEvent -ComputerName $ComputerName -FilterHashTable @{ LogName = 'Security'; ID=$ID; StartTime=$DateStart} -ErrorAction SilentlyContinue | ForEach-Object {
+ if($Credential) {
+ Write-Verbose "Using alternative credentials"
+ $Arguments = @{
+ 'ComputerName' = $ComputerName;
+ 'Credential' = $Credential;
+ 'FilterHashTable' = @{ LogName = 'Security'; ID=$ID; StartTime=$DateStart};
+ 'ErrorAction' = 'SilentlyContinue';
+ }
+ }
+ else {
+ $Arguments = @{
+ 'ComputerName' = $ComputerName;
+ 'FilterHashTable' = @{ LogName = 'Security'; ID=$ID; StartTime=$DateStart};
+ 'ErrorAction' = 'SilentlyContinue';
+ }
+ }
+
+ # grab all events matching our filter for the specified host
+ Get-WinEvent @Arguments | ForEach-Object {
if($ID -contains 4624) {
# first parse and check the logon event type. This could be later adapted and tested for RDP logons (type 10)
@@ -2763,7 +2837,7 @@ function Get-ObjectAcl {
)
begin {
- $Searcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -ADSpath $ADSpath -ADSprefix $ADSprefix -PageSize $PageSize
+ $Searcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -ADSpath $ADSpath -ADSprefix $ADSprefix -PageSize $PageSize
# get a GUID -> name mapping
if($ResolveGUIDs) {
@@ -2783,12 +2857,13 @@ function Get-ObjectAcl {
}
try {
- $Searcher.FindAll() | Where-Object {$_} | Foreach-Object {
+ $Searcher.FindAll() | Where-Object {$_} | ForEach-Object {
$Object = [adsi]($_.path)
+
if($Object.distinguishedname) {
$Access = $Object.PsBase.ObjectSecurity.access
$Access | ForEach-Object {
- $_ | Add-Member NoteProperty 'ObjectDN' ($Object.distinguishedname[0])
+ $_ | Add-Member NoteProperty 'ObjectDN' $Object.distinguishedname[0]
if($Object.objectsid[0]){
$S = (New-Object System.Security.Principal.SecurityIdentifier($Object.objectsid[0],0)).Value
@@ -2813,7 +2888,7 @@ function Get-ObjectAcl {
else {
$_
}
- } | Foreach-Object {
+ } | ForEach-Object {
if($GUIDs) {
# if we're resolving GUIDs, map them them to the resolved hash table
$AclProperties = @{}
@@ -3005,7 +3080,7 @@ function Add-ObjectAcl {
}
try {
- $Searcher.FindAll() | Where-Object {$_} | Foreach-Object {
+ $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
@@ -3170,6 +3245,7 @@ function Invoke-ACLScanner {
} | Where-Object {
# check for any ACLs with SIDs > -1000
try {
+ # TODO: change this to a regex for speedup?
[int]($_.IdentitySid.split("-")[-1]) -ge 1000
}
catch {}
@@ -3180,7 +3256,7 @@ function Invoke-ACLScanner {
}
-function Get-GUIDMap {
+filter Get-GUIDMap {
<#
.SYNOPSIS
@@ -3207,6 +3283,7 @@ function Get-GUIDMap {
[CmdletBinding()]
Param (
+ [Parameter(ValueFromPipeline=$True)]
[String]
$Domain,
@@ -3233,10 +3310,10 @@ function Get-GUIDMap {
}
catch {
Write-Debug "Error in building GUID map: $_"
- }
+ }
}
- $RightsSearcher = Get-DomainSearcher -ADSpath $SchemaPath.replace("Schema","Extended-Rights") -DomainController $DomainController -PageSize $PageSize
+ $RightsSearcher = Get-DomainSearcher -ADSpath $SchemaPath.replace("Schema","Extended-Rights") -DomainController $DomainController -PageSize $PageSize -Credential $Credential
if ($RightsSearcher) {
$RightsSearcher.filter = "(objectClass=controlAccessRight)"
try {
@@ -3306,6 +3383,10 @@ function Get-NetComputer {
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
@@ -3315,6 +3396,11 @@ function Get-NetComputer {
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
@@ -3381,17 +3467,23 @@ function Get-NetComputer {
[String]
$ADSpath,
+ [String]
+ $SiteName,
+
[Switch]
$Unconstrained,
[ValidateRange(1,10000)]
[Int]
- $PageSize = 200
+ $PageSize = 200,
+
+ [Management.Automation.PSCredential]
+ $Credential
)
begin {
- # so this isn't repeated if users are passed on the pipeline
- $CompSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -ADSpath $ADSpath -PageSize $PageSize
+ # 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 {
@@ -3419,8 +3511,13 @@ function Get-NetComputer {
if($ServicePack) {
$Filter += "(operatingsystemservicepack=$ServicePack)"
}
+ if($SiteName) {
+ $Filter += "(serverreferencebl=$SiteName)"
+ }
- $CompSearcher.filter = "(&(sAMAccountType=805306369)(dnshostname=$ComputerName)$Filter)"
+ $CompFilter = "(&(sAMAccountType=805306369)(dnshostname=$ComputerName)$Filter)"
+ Write-Verbose "Get-NetComputer filter : '$CompFilter'"
+ $CompSearcher.filter = $CompFilter
try {
@@ -3496,6 +3593,11 @@ function Get-ADObject {
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-ADObject -SID "S-1-5-21-2620891829-2411261497-1773853088-1110"
@@ -3538,7 +3640,10 @@ function Get-ADObject {
[ValidateRange(1,10000)]
[Int]
- $PageSize = 200
+ $PageSize = 200,
+
+ [Management.Automation.PSCredential]
+ $Credential
)
process {
if($SID) {
@@ -3546,7 +3651,7 @@ function Get-ADObject {
try {
$Name = Convert-SidToName $SID
if($Name) {
- $Canonical = Convert-NT4toCanonical -ObjectName $Name
+ $Canonical = Convert-ADName -ObjectName $Name -InputType NT4 -OutputType Canonical
if($Canonical) {
$Domain = $Canonical.split("/")[0]
}
@@ -3562,10 +3667,9 @@ function Get-ADObject {
}
}
- $ObjectSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -ADSpath $ADSpath -PageSize $PageSize
+ $ObjectSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $ADSpath -PageSize $PageSize
if($ObjectSearcher) {
-
if($SID) {
$ObjectSearcher.filter = "(&(objectsid=$SID)$Filter)"
}
@@ -3642,6 +3746,11 @@ function Set-ADObject {
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:\> Set-ADObject -SamAccountName matt.admin -PropertyName countrycode -PropertyValue 0
@@ -3689,7 +3798,10 @@ function Set-ADObject {
[ValidateRange(1,10000)]
[Int]
- $PageSize = 200
+ $PageSize = 200,
+
+ [Management.Automation.PSCredential]
+ $Credential
)
$Arguments = @{
@@ -3700,6 +3812,7 @@ function Set-ADObject {
'DomainController' = $DomainController
'Filter' = $Filter
'PageSize' = $PageSize
+ 'Credential' = $Credential
}
# splat the appropriate arguments to Get-ADObject
$RawObject = Get-ADObject -ReturnRaw @Arguments
@@ -3765,6 +3878,11 @@ function Invoke-DowngradeAccount {
Switch. Unset the reversible encryption flag and force password reset flag.
+ .PARAMETER Credential
+
+ A [Management.Automation.PSCredential] object of alternate credentials
+ for connection to the target domain.
+
.EXAMPLE
PS> Invoke-DowngradeAccount -SamAccountName jason
@@ -3780,10 +3898,11 @@ function Invoke-DowngradeAccount {
[CmdletBinding()]
Param (
- [Parameter(Position=0,ValueFromPipeline=$True)]
+ [Parameter(ParameterSetName = 'SamAccountName', Position=0, ValueFromPipeline=$True)]
[String]
$SamAccountName,
+ [Parameter(ParameterSetName = 'Name')]
[String]
$Name,
@@ -3797,7 +3916,10 @@ function Invoke-DowngradeAccount {
$Filter,
[Switch]
- $Repair
+ $Repair,
+
+ [Management.Automation.PSCredential]
+ $Credential
)
process {
@@ -3807,6 +3929,7 @@ function Invoke-DowngradeAccount {
'Domain' = $Domain
'DomainController' = $DomainController
'Filter' = $Filter
+ 'Credential' = $Credential
}
# splat the appropriate arguments to Get-ADObject
@@ -3868,6 +3991,11 @@ function Get-ComputerProperty {
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-ComputerProperty -Domain testing
@@ -3899,17 +4027,20 @@ function Get-ComputerProperty {
[ValidateRange(1,10000)]
[Int]
- $PageSize = 200
+ $PageSize = 200,
+
+ [Management.Automation.PSCredential]
+ $Credential
)
if($Properties) {
# extract out the set of all properties for each object
$Properties = ,"name" + $Properties | Sort-Object -Unique
- Get-NetComputer -Domain $Domain -DomainController $DomainController -FullData -PageSize $PageSize | Select-Object -Property $Properties
+ Get-NetComputer -Domain $Domain -DomainController $DomainController -Credential $Credential -FullData -PageSize $PageSize | Select-Object -Property $Properties
}
else {
# extract out just the property names
- Get-NetComputer -Domain $Domain -DomainController $DomainController -FullData -PageSize $PageSize | Select-Object -first 1 | Get-Member -MemberType *Property | Select-Object -Property "Name"
+ Get-NetComputer -Domain $Domain -DomainController $DomainController -Credential $Credential -FullData -PageSize $PageSize | Select-Object -first 1 | Get-Member -MemberType *Property | Select-Object -Property "Name"
}
}
@@ -3949,6 +4080,11 @@ function Find-ComputerField {
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:\> Find-ComputerField -SearchTerm backup -SearchField info
@@ -3978,11 +4114,14 @@ function Find-ComputerField {
[ValidateRange(1,10000)]
[Int]
- $PageSize = 200
+ $PageSize = 200,
+
+ [Management.Automation.PSCredential]
+ $Credential
)
process {
- Get-NetComputer -ADSpath $ADSpath -Domain $Domain -DomainController $DomainController -FullData -Filter "($SearchField=*$SearchTerm*)" -PageSize $PageSize | Select-Object samaccountname,$SearchField
+ Get-NetComputer -ADSpath $ADSpath -Domain $Domain -DomainController $DomainController -Credential $Credential -FullData -Filter "($SearchField=*$SearchTerm*)" -PageSize $PageSize | Select-Object samaccountname,$SearchField
}
}
@@ -4021,6 +4160,11 @@ function Get-NetOU {
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
@@ -4037,7 +4181,13 @@ function Get-NetOU {
PS C:\> Get-NetOU -GUID 123-...
- Returns all OUs with linked to the specified group policy object.
+ 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()]
@@ -4063,11 +4213,14 @@ function Get-NetOU {
[ValidateRange(1,10000)]
[Int]
- $PageSize = 200
+ $PageSize = 200,
+
+ [Management.Automation.PSCredential]
+ $Credential
)
begin {
- $OUSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -ADSpath $ADSpath -PageSize $PageSize
+ $OUSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $ADSpath -PageSize $PageSize
}
process {
if ($OUSearcher) {
@@ -4079,16 +4232,21 @@ function Get-NetOU {
$OUSearcher.filter="(&(objectCategory=organizationalUnit)(name=$OUName))"
}
- $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
+ 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 $_
+ }
}
}
}
@@ -4128,6 +4286,11 @@ function Get-NetSite {
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-NetSite -Domain testlab.local -FullData
@@ -4158,11 +4321,18 @@ function Get-NetSite {
[ValidateRange(1,10000)]
[Int]
- $PageSize = 200
+ $PageSize = 200,
+
+ [Management.Automation.PSCredential]
+ $Credential
)
begin {
- $SiteSearcher = Get-DomainSearcher -ADSpath $ADSpath -Domain $Domain -DomainController $DomainController -ADSprefix "CN=Sites,CN=Configuration" -PageSize $PageSize
+ if(!$Domain) {
+ $Domain = Get-NetDomain -Credential $Credential
+ }
+
+ $SiteSearcher = Get-DomainSearcher -ADSpath $ADSpath -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSprefix "CN=Sites,CN=Configuration" -PageSize $PageSize
}
process {
if($SiteSearcher) {
@@ -4225,6 +4395,11 @@ function Get-NetSubnet {
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-NetSubnet
@@ -4258,11 +4433,18 @@ function Get-NetSubnet {
[ValidateRange(1,10000)]
[Int]
- $PageSize = 200
+ $PageSize = 200,
+
+ [Management.Automation.PSCredential]
+ $Credential
)
begin {
- $SubnetSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -ADSpath $ADSpath -ADSprefix "CN=Subnets,CN=Sites,CN=Configuration" -PageSize $PageSize
+ if(!$Domain) {
+ $Domain = Get-NetDomain -Credential $Credential
+ }
+
+ $SubnetSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $ADSpath -ADSprefix "CN=Subnets,CN=Sites,CN=Configuration" -PageSize $PageSize
}
process {
@@ -4390,6 +4572,11 @@ function Get-NetGroup {
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
@@ -4444,11 +4631,14 @@ function Get-NetGroup {
[ValidateRange(1,10000)]
[Int]
- $PageSize = 200
+ $PageSize = 200,
+
+ [Management.Automation.PSCredential]
+ $Credential
)
begin {
- $GroupSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -ADSpath $ADSpath -PageSize $PageSize
+ $GroupSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $ADSpath -PageSize $PageSize
}
process {
@@ -4461,7 +4651,7 @@ function Get-NetGroup {
if ($UserName) {
# get the raw user object
- $User = Get-ADObject -SamAccountName $UserName -Domain $Domain -DomainController $DomainController -ReturnRaw -PageSize $PageSize
+ $User = Get-ADObject -SamAccountName $UserName -Domain $Domain -DomainController $DomainController -Credential $Credential -ReturnRaw -PageSize $PageSize
# convert the user to a directory entry
$UserDirectoryEntry = $User.GetDirectoryEntry()
@@ -4469,14 +4659,14 @@ function Get-NetGroup {
# cause the cache to calculate the token groups for the user
$UserDirectoryEntry.RefreshCache("tokenGroups")
- $UserDirectoryEntry.TokenGroups | Foreach-Object {
+ $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
+ Get-ADObject -SID $GroupSid -PageSize $PageSize -Domain $Domain -DomainController $DomainController -Credential $Credential
}
else {
if($RawSids) {
@@ -4565,6 +4755,11 @@ function Get-NetGroupMember {
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
@@ -4592,7 +4787,7 @@ function Get-NetGroupMember {
$SID,
[String]
- $Domain = (Get-NetDomain).Name,
+ $Domain,
[String]
$DomainController,
@@ -4611,15 +4806,22 @@ function Get-NetGroupMember {
[ValidateRange(1,10000)]
[Int]
- $PageSize = 200
+ $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 -ADSpath $ADSpath -PageSize $PageSize
+ $GroupSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $ADSpath -PageSize $PageSize
if(!$DomainController) {
- $DomainController = ((Get-NetDomain).PdcRoleOwner).Name
+ $DomainController = ((Get-NetDomain -Credential $Credential).PdcRoleOwner).Name
+ }
+
+ if(!$Domain) {
+ $Domain = Get-NetDomain -Credential $Credential
}
}
@@ -4630,15 +4832,15 @@ function Get-NetGroupMember {
if ($Recurse -and $UseMatchingRule) {
# resolve the group to a distinguishedname
if ($GroupName) {
- $Group = Get-NetGroup -GroupName $GroupName -Domain $Domain -FullData -PageSize $PageSize
+ $Group = Get-NetGroup -GroupName $GroupName -Domain $Domain -DomainController $DomainController -Credential $Credential -FullData -PageSize $PageSize
}
elseif ($SID) {
- $Group = Get-NetGroup -SID $SID -Domain $Domain -FullData -PageSize $PageSize
+ $Group = Get-NetGroup -SID $SID -Domain $Domain -DomainController $DomainController -Credential $Credential -FullData -PageSize $PageSize
}
else {
# default to domain admins
- $SID = (Get-DomainSID -Domain $Domain) + "-512"
- $Group = Get-NetGroup -SID $SID -Domain $Domain -FullData -PageSize $PageSize
+ $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
@@ -4663,7 +4865,7 @@ function Get-NetGroupMember {
}
else {
# default to domain admins
- $SID = (Get-DomainSID -Domain $Domain) + "-512"
+ $SID = (Get-DomainSID -Domain $Domain -Credential $Credential) + "-512"
$GroupSearcher.filter = "(&(objectCategory=group)(objectSID=$SID)$Filter)"
}
@@ -4736,12 +4938,7 @@ function Get-NetGroupMember {
if($Properties) {
- if($Properties.samaccounttype -notmatch '805306368') {
- $IsGroup = $True
- }
- else {
- $IsGroup = $False
- }
+ $IsGroup = @('268435456','268435457','536870912','536870913') -contains $Properties.samaccounttype
if ($FullData) {
$GroupMember = Convert-LDAPProperty -Properties $Properties
@@ -4795,7 +4992,12 @@ function Get-NetGroupMember {
# if we're doing manual recursion
if ($Recurse -and !$UseMatchingRule -and $IsGroup -and $MemberName) {
- Get-NetGroupMember -FullData -Domain $MemberDomain -DomainController $DomainController -GroupName $MemberName -Recurse -PageSize $PageSize
+ 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
+ }
}
}
@@ -4828,6 +5030,11 @@ function Get-NetFileServer {
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-NetFileServer
@@ -4854,7 +5061,10 @@ function Get-NetFileServer {
[ValidateRange(1,10000)]
[Int]
- $PageSize = 200
+ $PageSize = 200,
+
+ [Management.Automation.PSCredential]
+ $Credential
)
function SplitPath {
@@ -4869,13 +5079,13 @@ function Get-NetFileServer {
}
}
- Get-NetUser -Domain $Domain -DomainController $DomainController -PageSize $PageSize | Where-Object {$_} | Where-Object {
+ Get-NetUser -Domain $Domain -DomainController $DomainController -Credential $Credential -PageSize $PageSize | Where-Object {$_} | Where-Object {
# filter for any target users
if($TargetUsers) {
$TargetUsers -Match $_.samAccountName
}
else { $True }
- } | Foreach-Object {
+ } | ForEach-Object {
# split out every potential file server path
if($_.homedirectory) {
SplitPath($_.homedirectory)
@@ -4920,16 +5130,21 @@ function Get-DFSshare {
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-DFSshare
-
+
Returns all distributed file system shares for the current domain.
.EXAMPLE
PS C:\> Get-DFSshare -Domain test
-
+
Returns all distributed file system shares for the 'test' domain.
#>
@@ -4950,9 +5165,191 @@ function Get-DFSshare {
[ValidateRange(1,10000)]
[Int]
- $PageSize = 200
+ $PageSize = 200,
+
+ [Management.Automation.PSCredential]
+ $Credential
)
+ function Parse-Pkt {
+ [CmdletBinding()]
+ param(
+ [byte[]]
+ $Pkt
+ )
+
+ $bin = $Pkt
+ $blob_version = [bitconverter]::ToUInt32($bin[0..3],0)
+ $blob_element_count = [bitconverter]::ToUInt32($bin[4..7],0)
+ #Write-Host "Element Count: " $blob_element_count
+ $offset = 8
+ #https://msdn.microsoft.com/en-us/library/cc227147.aspx
+ $object_list = @()
+ for($i=1; $i -le $blob_element_count; $i++){
+ $blob_name_size_start = $offset
+ $blob_name_size_end = $offset + 1
+ $blob_name_size = [bitconverter]::ToUInt16($bin[$blob_name_size_start..$blob_name_size_end],0)
+ #Write-Host "Blob name size: " $blob_name_size
+ $blob_name_start = $blob_name_size_end + 1
+ $blob_name_end = $blob_name_start + $blob_name_size - 1
+ $blob_name = [System.Text.Encoding]::Unicode.GetString($bin[$blob_name_start..$blob_name_end])
+ #Write-Host "Blob Name: " $blob_name
+ $blob_data_size_start = $blob_name_end + 1
+ $blob_data_size_end = $blob_data_size_start + 3
+ $blob_data_size = [bitconverter]::ToUInt32($bin[$blob_data_size_start..$blob_data_size_end],0)
+ #Write-Host "blob data size: " $blob_data_size
+ $blob_data_start = $blob_data_size_end + 1
+ $blob_data_end = $blob_data_start + $blob_data_size - 1
+ $blob_data = $bin[$blob_data_start..$blob_data_end]
+ switch -wildcard ($blob_name) {
+ "\siteroot" { }
+ "\domainroot*" {
+ # Parse DFSNamespaceRootOrLinkBlob object. Starts with variable length DFSRootOrLinkIDBlob which we parse first...
+ # DFSRootOrLinkIDBlob
+ $root_or_link_guid_start = 0
+ $root_or_link_guid_end = 15
+ $root_or_link_guid = [byte[]]$blob_data[$root_or_link_guid_start..$root_or_link_guid_end]
+ $guid = New-Object Guid(,$root_or_link_guid) # should match $guid_str
+ $prefix_size_start = $root_or_link_guid_end + 1
+ $prefix_size_end = $prefix_size_start + 1
+ $prefix_size = [bitconverter]::ToUInt16($blob_data[$prefix_size_start..$prefix_size_end],0)
+ $prefix_start = $prefix_size_end + 1
+ $prefix_end = $prefix_start + $prefix_size - 1
+ $prefix = [System.Text.Encoding]::Unicode.GetString($blob_data[$prefix_start..$prefix_end])
+ #write-host "Prefix: " $prefix
+ $short_prefix_size_start = $prefix_end + 1
+ $short_prefix_size_end = $short_prefix_size_start + 1
+ $short_prefix_size = [bitconverter]::ToUInt16($blob_data[$short_prefix_size_start..$short_prefix_size_end],0)
+ $short_prefix_start = $short_prefix_size_end + 1
+ $short_prefix_end = $short_prefix_start + $short_prefix_size - 1
+ $short_prefix = [System.Text.Encoding]::Unicode.GetString($blob_data[$short_prefix_start..$short_prefix_end])
+ #write-host "Short Prefix: " $short_prefix
+ $type_start = $short_prefix_end + 1
+ $type_end = $type_start + 3
+ $type = [bitconverter]::ToUInt32($blob_data[$type_start..$type_end],0)
+ #write-host $type
+ $state_start = $type_end + 1
+ $state_end = $state_start + 3
+ $state = [bitconverter]::ToUInt32($blob_data[$state_start..$state_end],0)
+ #write-host $state
+ $comment_size_start = $state_end + 1
+ $comment_size_end = $comment_size_start + 1
+ $comment_size = [bitconverter]::ToUInt16($blob_data[$comment_size_start..$comment_size_end],0)
+ $comment_start = $comment_size_end + 1
+ $comment_end = $comment_start + $comment_size - 1
+ if ($comment_size -gt 0) {
+ $comment = [System.Text.Encoding]::Unicode.GetString($blob_data[$comment_start..$comment_end])
+ #Write-Host $comment
+ }
+ $prefix_timestamp_start = $comment_end + 1
+ $prefix_timestamp_end = $prefix_timestamp_start + 7
+ # https://msdn.microsoft.com/en-us/library/cc230324.aspx FILETIME
+ $prefix_timestamp = $blob_data[$prefix_timestamp_start..$prefix_timestamp_end] #dword lowDateTime #dword highdatetime
+ $state_timestamp_start = $prefix_timestamp_end + 1
+ $state_timestamp_end = $state_timestamp_start + 7
+ $state_timestamp = $blob_data[$state_timestamp_start..$state_timestamp_end]
+ $comment_timestamp_start = $state_timestamp_end + 1
+ $comment_timestamp_end = $comment_timestamp_start + 7
+ $comment_timestamp = $blob_data[$comment_timestamp_start..$comment_timestamp_end]
+ $version_start = $comment_timestamp_end + 1
+ $version_end = $version_start + 3
+ $version = [bitconverter]::ToUInt32($blob_data[$version_start..$version_end],0)
+
+ #write-host $version
+ if ($version -ne 3)
+ {
+ #write-host "error"
+ }
+
+ # Parse rest of DFSNamespaceRootOrLinkBlob here
+ $dfs_targetlist_blob_size_start = $version_end + 1
+ $dfs_targetlist_blob_size_end = $dfs_targetlist_blob_size_start + 3
+ $dfs_targetlist_blob_size = [bitconverter]::ToUInt32($blob_data[$dfs_targetlist_blob_size_start..$dfs_targetlist_blob_size_end],0)
+ #write-host $dfs_targetlist_blob_size
+ $dfs_targetlist_blob_start = $dfs_targetlist_blob_size_end + 1
+ $dfs_targetlist_blob_end = $dfs_targetlist_blob_start + $dfs_targetlist_blob_size - 1
+ $dfs_targetlist_blob = $blob_data[$dfs_targetlist_blob_start..$dfs_targetlist_blob_end]
+ $reserved_blob_size_start = $dfs_targetlist_blob_end + 1
+ $reserved_blob_size_end = $reserved_blob_size_start + 3
+ $reserved_blob_size = [bitconverter]::ToUInt32($blob_data[$reserved_blob_size_start..$reserved_blob_size_end],0)
+ #write-host $reserved_blob_size
+ $reserved_blob_start = $reserved_blob_size_end + 1
+ $reserved_blob_end = $reserved_blob_start + $reserved_blob_size - 1
+ $reserved_blob = $blob_data[$reserved_blob_start..$reserved_blob_end]
+ $referral_ttl_start = $reserved_blob_end + 1
+ $referral_ttl_end = $referral_ttl_start + 3
+ $referral_ttl = [bitconverter]::ToUInt32($blob_data[$referral_ttl_start..$referral_ttl_end],0)
+
+ #Parse DFSTargetListBlob
+ $target_count_start = 0
+ $target_count_end = $target_count_start + 3
+ $target_count = [bitconverter]::ToUInt32($dfs_targetlist_blob[$target_count_start..$target_count_end],0)
+ $t_offset = $target_count_end + 1
+ #write-host $target_count
+
+ for($j=1; $j -le $target_count; $j++){
+ $target_entry_size_start = $t_offset
+ $target_entry_size_end = $target_entry_size_start + 3
+ $target_entry_size = [bitconverter]::ToUInt32($dfs_targetlist_blob[$target_entry_size_start..$target_entry_size_end],0)
+ #write-host $target_entry_size
+ $target_time_stamp_start = $target_entry_size_end + 1
+ $target_time_stamp_end = $target_time_stamp_start + 7
+ # FILETIME again or special if priority rank and priority class 0
+ $target_time_stamp = $dfs_targetlist_blob[$target_time_stamp_start..$target_time_stamp_end]
+ $target_state_start = $target_time_stamp_end + 1
+ $target_state_end = $target_state_start + 3
+ $target_state = [bitconverter]::ToUInt32($dfs_targetlist_blob[$target_state_start..$target_state_end],0)
+ #write-host $target_state
+ $target_type_start = $target_state_end + 1
+ $target_type_end = $target_type_start + 3
+ $target_type = [bitconverter]::ToUInt32($dfs_targetlist_blob[$target_type_start..$target_type_end],0)
+ #write-host $target_type
+ $server_name_size_start = $target_type_end + 1
+ $server_name_size_end = $server_name_size_start + 1
+ $server_name_size = [bitconverter]::ToUInt16($dfs_targetlist_blob[$server_name_size_start..$server_name_size_end],0)
+ #write-host $server_name_size
+ $server_name_start = $server_name_size_end + 1
+ $server_name_end = $server_name_start + $server_name_size - 1
+ $server_name = [System.Text.Encoding]::Unicode.GetString($dfs_targetlist_blob[$server_name_start..$server_name_end])
+ #write-host $server_name
+ $share_name_size_start = $server_name_end + 1
+ $share_name_size_end = $share_name_size_start + 1
+ $share_name_size = [bitconverter]::ToUInt16($dfs_targetlist_blob[$share_name_size_start..$share_name_size_end],0)
+ $share_name_start = $share_name_size_end + 1
+ $share_name_end = $share_name_start + $share_name_size - 1
+ $share_name = [System.Text.Encoding]::Unicode.GetString($dfs_targetlist_blob[$share_name_start..$share_name_end])
+ #write-host $share_name
+ $target_list += "\\$server_name\$share_name"
+ $t_offset = $share_name_end + 1
+ }
+ }
+ }
+ $offset = $blob_data_end + 1
+ $dfs_pkt_properties = @{
+ 'Name' = $blob_name
+ 'Prefix' = $prefix
+ 'TargetList' = $target_list
+ }
+ $object_list += New-Object -TypeName PSObject -Property $dfs_pkt_properties
+ $prefix = $null
+ $blob_name = $null
+ $target_list = $null
+ }
+
+ $servers = @()
+ $object_list | ForEach-Object {
+ #write-host $_.Name;
+ #write-host $_.TargetList
+ if ($_.TargetList) {
+ $_.TargetList | ForEach-Object {
+ $servers += $_.split("\")[2]
+ }
+ }
+ }
+
+ $servers
+ }
+
function Get-DFSshareV1 {
[CmdletBinding()]
param(
@@ -4965,12 +5362,15 @@ function Get-DFSshare {
[String]
$ADSpath,
- [ValidateRange(1,10000)]
+ [ValidateRange(1,10000)]
[Int]
- $PageSize = 200
+ $PageSize = 200,
+
+ [Management.Automation.PSCredential]
+ $Credential
)
- $DFSsearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -ADSpath $ADSpath -PageSize $PageSize
+ $DFSsearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $ADSpath -PageSize $PageSize
if($DFSsearcher) {
$DFSshares = @()
@@ -4980,6 +5380,7 @@ function Get-DFSshare {
$DFSSearcher.FindAll() | Where-Object {$_} | ForEach-Object {
$Properties = $_.Properties
$RemoteNames = $Properties.remoteservername
+ $Pkt = $Properties.pkt
$DFSshares += $RemoteNames | ForEach-Object {
try {
@@ -4992,9 +5393,22 @@ function Get-DFSshare {
}
}
}
+
+ if($pkt -and $pkt[0]) {
+ Parse-Pkt $pkt[0] | ForEach-Object {
+ # If a folder doesn't have a redirection it will
+ # have a target like
+ # \\null\TestNameSpace\folder\.DFSFolderLink so we
+ # do actually want to match on "null" rather than
+ # $null
+ if ($_ -ne "null") {
+ New-Object -TypeName PSObject -Property @{'Name'=$Properties.name[0];'RemoteServerName'=$_}
+ }
+ }
+ }
}
catch {
- Write-Warning "Get-DFSshareV2 error : $_"
+ Write-Warning "Get-DFSshareV1 error : $_"
}
$DFSshares | Sort-Object -Property "RemoteServerName"
}
@@ -5014,10 +5428,13 @@ function Get-DFSshare {
[ValidateRange(1,10000)]
[Int]
- $PageSize = 200
+ $PageSize = 200,
+
+ [Management.Automation.PSCredential]
+ $Credential
)
- $DFSsearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -ADSpath $ADSpath -PageSize $PageSize
+ $DFSsearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $ADSpath -PageSize $PageSize
if($DFSsearcher) {
$DFSshares = @()
@@ -5052,15 +5469,15 @@ function Get-DFSshare {
}
$DFSshares = @()
-
+
if ( ($Version -eq "all") -or ($Version.endsWith("1")) ) {
- $DFSshares += Get-DFSshareV1 -Domain $Domain -DomainController $DomainController -ADSpath $ADSpath -PageSize $PageSize
+ $DFSshares += Get-DFSshareV1 -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $ADSpath -PageSize $PageSize
}
if ( ($Version -eq "all") -or ($Version.endsWith("2")) ) {
- $DFSshares += Get-DFSshareV2 -Domain $Domain -DomainController $DomainController -ADSpath $ADSpath -PageSize $PageSize
+ $DFSshares += Get-DFSshareV2 -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $ADSpath -PageSize $PageSize
}
- $DFSshares | Sort-Object -Property "RemoteServerName"
+ $DFSshares | Sort-Object -Property ("RemoteServerName","Name") -Unique
}
@@ -5130,42 +5547,38 @@ function Get-GptTmpl {
$SectionsFinal = @{}
try {
+ Write-Verbose "Parsing $GptTmplPath"
- if(Test-Path $GptTmplPath) {
-
- Write-Verbose "Parsing $GptTmplPath"
+ Get-Content $GptTmplPath -ErrorAction Stop | ForEach-Object {
+ if ($_ -match '\[') {
+ # this signifies that we're starting a new section
+ $SectionName = $_.trim('[]') -replace ' ',''
+ }
+ elseif($_ -match '=') {
+ $Parts = $_.split('=')
+ $PropertyName = $Parts[0].trim()
+ $PropertyValues = $Parts[1].trim()
- Get-Content $GptTmplPath -ErrorAction Stop | Foreach-Object {
- if ($_ -match '\[') {
- # this signifies that we're starting a new section
- $SectionName = $_.trim('[]') -replace ' ',''
+ if($PropertyValues -match ',') {
+ $PropertyValues = $PropertyValues.split(',')
}
- elseif($_ -match '=') {
- $Parts = $_.split('=')
- $PropertyName = $Parts[0].trim()
- $PropertyValues = $Parts[1].trim()
-
- if($PropertyValues -match ',') {
- $PropertyValues = $PropertyValues.split(',')
- }
- if(!$SectionsTemp[$SectionName]) {
- $SectionsTemp.Add($SectionName, @{})
- }
-
- # add the parsed property into the relevant Section name
- $SectionsTemp[$SectionName].Add( $PropertyName, $PropertyValues )
+ if(!$SectionsTemp[$SectionName]) {
+ $SectionsTemp.Add($SectionName, @{})
}
- }
- ForEach ($Section in $SectionsTemp.keys) {
- # transform each nested hash table into a custom object
- $SectionsFinal[$Section] = New-Object PSObject -Property $SectionsTemp[$Section]
+ # add the parsed property into the relevant Section name
+ $SectionsTemp[$SectionName].Add( $PropertyName, $PropertyValues )
}
+ }
- # transform the parent hash table into a custom object
- New-Object PSObject -Property $SectionsFinal
+ ForEach ($Section in $SectionsTemp.keys) {
+ # transform each nested hash table into a custom object
+ $SectionsFinal[$Section] = New-Object PSObject -Property $SectionsTemp[$Section]
}
+
+ # transform the parent hash table into a custom object
+ New-Object PSObject -Property $SectionsFinal
}
catch {
Write-Debug "Error parsing $GptTmplPath : $_"
@@ -5175,7 +5588,7 @@ function Get-GptTmpl {
end {
if($UsePSDrive -and $RandDrive) {
Write-Verbose "Removing temp PSDrive $RandDrive"
- Get-PSDrive -Name $RandDrive -ErrorAction SilentlyContinue | Remove-PSDrive
+ Get-PSDrive -Name $RandDrive -ErrorAction SilentlyContinue | Remove-PSDrive -Force
}
}
}
@@ -5198,7 +5611,6 @@ function Get-GroupsXML {
.PARAMETER UsePSDrive
Switch. Mount the target groups.xml folder path as a temporary PSDrive.
-
#>
[CmdletBinding()]
@@ -5239,10 +5651,8 @@ function Get-GroupsXML {
process {
- # parse the Groups.xml file if it exists
- if(Test-Path $GroupsXMLPath) {
-
- [xml] $GroupsXMLcontent = Get-Content $GroupsXMLPath
+ try {
+ [xml] $GroupsXMLcontent = Get-Content $GroupsXMLPath -ErrorAction Stop
# process all group properties in the XML
$GroupsXMLcontent | Select-Xml "//Group" | Select-Object -ExpandProperty node | ForEach-Object {
@@ -5308,18 +5718,20 @@ function Get-GroupsXML {
}
}
}
+ catch {
+ Write-Debug "Error parsing $GptTmplPath : $_"
+ }
}
end {
if($UsePSDrive -and $RandDrive) {
Write-Verbose "Removing temp PSDrive $RandDrive"
- Get-PSDrive -Name $RandDrive -ErrorAction SilentlyContinue | Remove-PSDrive
+ Get-PSDrive -Name $RandDrive -ErrorAction SilentlyContinue | Remove-PSDrive -Force
}
}
}
-
function Get-NetGPO {
<#
.SYNOPSIS
@@ -5334,6 +5746,10 @@ function Get-NetGPO {
The GPO display name to query for, wildcards accepted.
+ .PARAMETER ComputerName
+
+ Return all GPO objects applied to a given computer (FQDN).
+
.PARAMETER Domain
The domain to query for GPOs, defaults to the current domain.
@@ -5351,6 +5767,11 @@ function Get-NetGPO {
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-NetGPO -Domain testlab.local
@@ -5367,6 +5788,9 @@ function Get-NetGPO {
$DisplayName,
[String]
+ $ComputerName,
+
+ [String]
$Domain,
[String]
@@ -5377,26 +5801,325 @@ function Get-NetGPO {
[ValidateRange(1,10000)]
[Int]
- $PageSize = 200
+ $PageSize = 200,
+ [Management.Automation.PSCredential]
+ $Credential
)
begin {
- $GPOSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -ADSpath $ADSpath -PageSize $PageSize
+ $GPOSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $ADSpath -PageSize $PageSize
}
process {
if ($GPOSearcher) {
- if($DisplayName) {
- $GPOSearcher.filter="(&(objectCategory=groupPolicyContainer)(displayname=$DisplayName))"
+
+ if($ComputerName) {
+ $GPONames = @()
+ $Computers = Get-NetComputer -ComputerName $ComputerName -Domain $Domain -DomainController $DomainController -FullData -PageSize $PageSize
+
+ if(!$Computers) {
+ throw "Computer $ComputerName in domain '$Domain' not found! Try a fully qualified host name"
+ }
+
+ # get the given computer's OU
+ $ComputerOUs = @()
+ ForEach($Computer in $Computers) {
+ # extract all OUs a computer is a part of
+ $DN = $Computer.distinguishedname
+
+ $ComputerOUs += $DN.split(",") | ForEach-Object {
+ if($_.startswith("OU=")) {
+ $DN.substring($DN.indexof($_))
+ }
+ }
+ }
+
+ Write-Verbose "ComputerOUs: $ComputerOUs"
+
+ # find all the GPOs linked to the computer's OU
+ ForEach($ComputerOU in $ComputerOUs) {
+ $GPONames += Get-NetOU -Domain $Domain -DomainController $DomainController -ADSpath $ComputerOU -FullData -PageSize $PageSize | ForEach-Object {
+ # get any GPO links
+ write-verbose "blah: $($_.name)"
+ $_.gplink.split("][") | ForEach-Object {
+ if ($_.startswith("LDAP")) {
+ $_.split(";")[0]
+ }
+ }
+ }
+ }
+
+ Write-Verbose "GPONames: $GPONames"
+
+ # find any GPOs linked to the site for the given computer
+ $ComputerSite = (Get-SiteName -ComputerName $ComputerName).SiteName
+ if($ComputerSite -and ($ComputerSite -ne 'ERROR')) {
+ $GPONames += Get-NetSite -SiteName $ComputerSite -FullData | ForEach-Object {
+ if($_.gplink) {
+ $_.gplink.split("][") | ForEach-Object {
+ if ($_.startswith("LDAP")) {
+ $_.split(";")[0]
+ }
+ }
+ }
+ }
+ }
+
+ $GPONames | Where-Object{$_ -and ($_ -ne '')} | ForEach-Object {
+
+ # use the gplink as an ADS path to enumerate all GPOs for the computer
+ $GPOSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $_ -PageSize $PageSize
+ $GPOSearcher.filter="(&(objectCategory=groupPolicyContainer)(name=$GPOname))"
+
+ try {
+ $GPOSearcher.FindAll() | Where-Object {$_} | ForEach-Object {
+ $Out = Convert-LDAPProperty -Properties $_.Properties
+ $Out | Add-Member Noteproperty 'ComputerName' $ComputerName
+ $Out
+ }
+ }
+ catch {
+ Write-Warning $_
+ }
+ }
}
+
else {
- $GPOSearcher.filter="(&(objectCategory=groupPolicyContainer)(name=$GPOname))"
+ if($DisplayName) {
+ $GPOSearcher.filter="(&(objectCategory=groupPolicyContainer)(displayname=$DisplayName))"
+ }
+ else {
+ $GPOSearcher.filter="(&(objectCategory=groupPolicyContainer)(name=$GPOname))"
+ }
+
+ try {
+ $GPOSearcher.FindAll() | Where-Object {$_} | ForEach-Object {
+ # convert/process the LDAP fields for each result
+ Convert-LDAPProperty -Properties $_.Properties
+ }
+ }
+ catch {
+ Write-Warning $_
+ }
}
+ }
+ }
+}
- $GPOSearcher.FindAll() | Where-Object {$_} | ForEach-Object {
- # convert/process the LDAP fields for each result
- Convert-LDAPProperty -Properties $_.Properties
+
+function New-GPOImmediateTask {
+<#
+ .SYNOPSIS
+
+ Builds an 'Immediate' schtask to push out through a specified GPO.
+
+ .PARAMETER TaskName
+
+ Name for the schtask to recreate. Required.
+
+ .PARAMETER Command
+
+ The command to execute with the task, defaults to 'powershell'
+
+ .PARAMETER CommandArguments
+
+ The arguments to supply to the -Command being launched.
+
+ .PARAMETER TaskDescription
+
+ An optional description for the task.
+
+ .PARAMETER TaskAuthor
+
+ The displayed author of the task, defaults to ''NT AUTHORITY\System'
+
+ .PARAMETER TaskModifiedDate
+
+ The displayed modified date for the task, defaults to 30 days ago.
+
+ .PARAMETER GPOname
+
+ The GPO name to build the task for.
+
+ .PARAMETER GPODisplayName
+
+ The GPO display name to build the task for.
+
+ .PARAMETER Domain
+
+ The domain to query for the GPOs, 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://cn={8FF59D28-15D7-422A-BCB7-2AE45724125A},cn=policies,cn=system,DC=dev,DC=testlab,DC=local"
+
+ .PARAMETER Credential
+
+ A [Management.Automation.PSCredential] object of alternate credentials
+ for connection to the target.
+
+ .EXAMPLE
+
+ PS> New-GPOImmediateTask -TaskName Debugging -GPODisplayName SecurePolicy -CommandArguments '-c "123 | Out-File C:\Temp\debug.txt"' -Force
+
+ Create an immediate schtask that executes the specified PowerShell arguments and
+ push it out to the 'SecurePolicy' GPO, skipping the confirmation prompt.
+
+ .EXAMPLE
+
+ PS> New-GPOImmediateTask -GPODisplayName SecurePolicy -Remove -Force
+
+ Remove all schtasks from the 'SecurePolicy' GPO, skipping the confirmation prompt.
+#>
+ [CmdletBinding(DefaultParameterSetName = 'Create')]
+ Param (
+ [Parameter(ParameterSetName = 'Create', Mandatory = $True)]
+ [String]
+ [ValidateNotNullOrEmpty()]
+ $TaskName,
+
+ [Parameter(ParameterSetName = 'Create')]
+ [String]
+ [ValidateNotNullOrEmpty()]
+ $Command = 'powershell',
+
+ [Parameter(ParameterSetName = 'Create')]
+ [String]
+ [ValidateNotNullOrEmpty()]
+ $CommandArguments,
+
+ [Parameter(ParameterSetName = 'Create')]
+ [String]
+ [ValidateNotNullOrEmpty()]
+ $TaskDescription = '',
+
+ [Parameter(ParameterSetName = 'Create')]
+ [String]
+ [ValidateNotNullOrEmpty()]
+ $TaskAuthor = 'NT AUTHORITY\System',
+
+ [Parameter(ParameterSetName = 'Create')]
+ [String]
+ [ValidateNotNullOrEmpty()]
+ $TaskModifiedDate = (Get-Date (Get-Date).AddDays(-30) -Format u).trim("Z"),
+
+ [Parameter(ParameterSetName = 'Create')]
+ [Parameter(ParameterSetName = 'Remove')]
+ [String]
+ $GPOname,
+
+ [Parameter(ParameterSetName = 'Create')]
+ [Parameter(ParameterSetName = 'Remove')]
+ [String]
+ $GPODisplayName,
+
+ [Parameter(ParameterSetName = 'Create')]
+ [Parameter(ParameterSetName = 'Remove')]
+ [String]
+ $Domain,
+
+ [Parameter(ParameterSetName = 'Create')]
+ [Parameter(ParameterSetName = 'Remove')]
+ [String]
+ $DomainController,
+
+ [Parameter(ParameterSetName = 'Create')]
+ [Parameter(ParameterSetName = 'Remove')]
+ [String]
+ $ADSpath,
+
+ [Parameter(ParameterSetName = 'Create')]
+ [Parameter(ParameterSetName = 'Remove')]
+ [Switch]
+ $Force,
+
+ [Parameter(ParameterSetName = 'Remove')]
+ [Switch]
+ $Remove,
+
+ [Parameter(ParameterSetName = 'Create')]
+ [Parameter(ParameterSetName = 'Remove')]
+ [Management.Automation.PSCredential]
+ $Credential
+ )
+
+ # build the XML spec for our 'immediate' scheduled task
+ $TaskXML = '<?xml version="1.0" encoding="utf-8"?><ScheduledTasks clsid="{CC63F200-7309-4ba0-B154-A71CD118DBCC}"><ImmediateTaskV2 clsid="{9756B581-76EC-4169-9AFC-0CA8D43ADB5F}" name="'+$TaskName+'" image="0" changed="'+$TaskModifiedDate+'" uid="{'+$([guid]::NewGuid())+'}" userContext="0" removePolicy="0"><Properties action="C" name="'+$TaskName+'" runAs="NT AUTHORITY\System" logonType="S4U"><Task version="1.3"><RegistrationInfo><Author>'+$TaskAuthor+'</Author><Description>'+$TaskDescription+'</Description></RegistrationInfo><Principals><Principal id="Author"><UserId>NT AUTHORITY\System</UserId><RunLevel>HighestAvailable</RunLevel><LogonType>S4U</LogonType></Principal></Principals><Settings><IdleSettings><Duration>PT10M</Duration><WaitTimeout>PT1H</WaitTimeout><StopOnIdleEnd>true</StopOnIdleEnd><RestartOnIdle>false</RestartOnIdle></IdleSettings><MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy><DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries><StopIfGoingOnBatteries>true</StopIfGoingOnBatteries><AllowHardTerminate>false</AllowHardTerminate><StartWhenAvailable>true</StartWhenAvailable><AllowStartOnDemand>false</AllowStartOnDemand><Enabled>true</Enabled><Hidden>true</Hidden><ExecutionTimeLimit>PT0S</ExecutionTimeLimit><Priority>7</Priority><DeleteExpiredTaskAfter>PT0S</DeleteExpiredTaskAfter><RestartOnFailure><Interval>PT15M</Interval><Count>3</Count></RestartOnFailure></Settings><Actions Context="Author"><Exec><Command>'+$Command+'</Command><Arguments>'+$CommandArguments+'</Arguments></Exec></Actions><Triggers><TimeTrigger><StartBoundary>%LocalTimeXmlEx%</StartBoundary><EndBoundary>%LocalTimeXmlEx%</EndBoundary><Enabled>true</Enabled></TimeTrigger></Triggers></Task></Properties></ImmediateTaskV2></ScheduledTasks>'
+
+ if (!$PSBoundParameters['GPOname'] -and !$PSBoundParameters['GPODisplayName']) {
+ Write-Warning 'Either -GPOName or -GPODisplayName must be specified'
+ return
+ }
+
+ # eunmerate the specified GPO(s)
+ $GPOs = Get-NetGPO -GPOname $GPOname -DisplayName $GPODisplayName -Domain $Domain -DomainController $DomainController -ADSpath $ADSpath -Credential $Credential
+
+ if(!$GPOs) {
+ Write-Warning 'No GPO found.'
+ return
+ }
+
+ $GPOs | ForEach-Object {
+ $ProcessedGPOName = $_.Name
+ try {
+ Write-Verbose "Trying to weaponize GPO: $ProcessedGPOName"
+
+ # map a network drive as New-PSDrive/New-Item/etc. don't accept -Credential properly :(
+ if($Credential) {
+ Write-Verbose "Mapping '$($_.gpcfilesyspath)' to network drive N:\"
+ $Path = $_.gpcfilesyspath.TrimEnd('\')
+ $Net = New-Object -ComObject WScript.Network
+ $Net.MapNetworkDrive("N:", $Path, $False, $Credential.UserName, $Credential.GetNetworkCredential().Password)
+ $TaskPath = "N:\Machine\Preferences\ScheduledTasks\"
+ }
+ else {
+ $TaskPath = $_.gpcfilesyspath + "\Machine\Preferences\ScheduledTasks\"
+ }
+
+ if($Remove) {
+ if(!(Test-Path "$TaskPath\ScheduledTasks.xml")) {
+ Throw "Scheduled task doesn't exist at $TaskPath\ScheduledTasks.xml"
+ }
+
+ if (!$Force -and !$psCmdlet.ShouldContinue('Do you want to continue?',"Removing schtask at $TaskPath\ScheduledTasks.xml")) {
+ return
+ }
+
+ Remove-Item -Path "$TaskPath\ScheduledTasks.xml" -Force
+ }
+ else {
+ if (!$Force -and !$psCmdlet.ShouldContinue('Do you want to continue?',"Creating schtask at $TaskPath\ScheduledTasks.xml")) {
+ return
+ }
+
+ # create the folder if it doesn't exist
+ $Null = New-Item -ItemType Directory -Force -Path $TaskPath
+
+ if(Test-Path "$TaskPath\ScheduledTasks.xml") {
+ Throw "Scheduled task already exists at $TaskPath\ScheduledTasks.xml !"
+ }
+
+ $TaskXML | Set-Content -Encoding ASCII -Path "$TaskPath\ScheduledTasks.xml"
+ }
+
+ if($Credential) {
+ Write-Verbose "Removing mounted drive at N:\"
+ $Net = New-Object -ComObject WScript.Network
+ $Net.RemoveNetworkDrive("N:")
+ }
+ }
+ catch {
+ Write-Warning "Error for GPO $ProcessedGPOName : $_"
+ if($Credential) {
+ Write-Verbose "Removing mounted drive at N:\"
+ $Net = New-Object -ComObject WScript.Network
+ $Net.RemoveNetworkDrive("N:")
}
}
}
@@ -5479,7 +6202,7 @@ function Get-NetGPOGroup {
)
# get every GPO from the specified domain with restricted groups set
- Get-NetGPO -GPOName $GPOname -DisplayName $GPOname -Domain $Domain -DomainController $DomainController -ADSpath $ADSpath -PageSize $PageSize | Foreach-Object {
+ Get-NetGPO -GPOName $GPOname -DisplayName $GPOname -Domain $Domain -DomainController $DomainController -ADSpath $ADSpath -PageSize $PageSize | ForEach-Object {
$Memberof = $Null
$Members = $Null
@@ -5500,33 +6223,44 @@ function Get-NetGPOGroup {
$Memberof = $Inf.GroupMembership | Get-Member *Memberof | ForEach-Object { $Inf.GroupMembership.($_.name) } | ForEach-Object { $_.trim('*') }
$Members = $Inf.GroupMembership | Get-Member *Members | ForEach-Object { $Inf.GroupMembership.($_.name) } | ForEach-Object { $_.trim('*') }
- # only return an object if Members are found
- if ($Members -or $Memberof) {
-
- # if there is no Memberof defined, assume local admins
- if(!$Memberof) {
- $Memberof = 'S-1-5-32-544'
+ if(!$Members) {
+ try {
+ $MembersRaw = $Inf.GroupMembership | Get-Member *Members | Select-Object -ExpandProperty Name
+ $Members = ($MembersRaw -split "__")[0].trim("*")
+ }
+ catch {
+ $MembersRaw = ''
}
+ }
- if($ResolveSids) {
- $Memberof = $Memberof | ForEach-Object {Convert-SidToName $_}
- $Members = $Members | ForEach-Object {Convert-SidToName $_}
+ if(!$Memberof) {
+ try {
+ $MemberofRaw = $Inf.GroupMembership | Get-Member *Memberof | Select-Object -ExpandProperty Name
+ $Memberof = ($MemberofRaw -split "__")[0].trim("*")
+ }
+ catch {
+ $Memberof = ''
}
+ }
- if($Memberof -isnot [system.array]) {$Memberof = @($Memberof)}
- if($Members -isnot [system.array]) {$Members = @($Members)}
+ if($ResolveSids) {
+ $Memberof = $Memberof | ForEach-Object { Convert-SidToName $_ }
+ $Members = $Members | ForEach-Object { Convert-SidToName $_ }
+ }
- $GPOProperties = @{
- 'GPODisplayName' = $GPODisplayName
- 'GPOName' = $GPOName
- 'GPOPath' = $GPOPath
- 'Filters' = $Null
- 'MemberOf' = $Memberof
- 'Members' = $Members
- }
+ if($Memberof -isnot [System.Array]) {$Memberof = @($Memberof)}
+ if($Members -isnot [System.Array]) {$Members = @($Members)}
- New-Object -TypeName PSObject -Property $GPOProperties
+ $GPOProperties = @{
+ 'GPODisplayName' = $GPODisplayName
+ 'GPOName' = $GPOName
+ 'GPOPath' = $GPOPath
+ 'Filters' = $Null
+ 'MemberOf' = $Memberof
+ 'Members' = $Members
}
+
+ New-Object -TypeName PSObject -Property $GPOProperties
}
$ParseArgs = @{
@@ -5647,7 +6381,7 @@ function Find-GPOLocation {
$TargetSid = $UserSid
$ObjectSamAccountName = $User.samaccountname
- $ObjectDistName = $User.distinguishedname
+ $TargetObjects = $UserSid
}
elseif($GroupName) {
@@ -5660,19 +6394,19 @@ function Find-GPOLocation {
$TargetSid = $GroupSid
$ObjectSamAccountName = $Group.samaccountname
- $ObjectDistName = $Group.distinguishedname
+ $TargetObjects = $GroupSid
}
else {
- throw "-UserName or -GroupName must be specified!"
+ $TargetSid = '*'
}
if($LocalGroup -like "*Admin*") {
- $LocalSID = "S-1-5-32-544"
+ $LocalSID = 'S-1-5-32-544'
}
elseif ( ($LocalGroup -like "*RDP*") -or ($LocalGroup -like "*Remote*") ) {
- $LocalSID = "S-1-5-32-555"
+ $LocalSID = 'S-1-5-32-555'
}
- elseif ($LocalGroup -like "S-1-5*") {
+ elseif ($LocalGroup -like "S-1-5-*") {
$LocalSID = $LocalGroup
}
else {
@@ -5681,15 +6415,16 @@ function Find-GPOLocation {
Write-Verbose "LocalSid: $LocalSID"
Write-Verbose "TargetSid: $TargetSid"
- Write-Verbose "TargetObjectDistName: $ObjectDistName"
- if($TargetSid -isnot [system.array]) { $TargetSid = @($TargetSid) }
+ if($TargetSid -ne '*') {
+ if($TargetSid -isnot [System.Array]) { $TargetSid = @($TargetSid) }
- # use the tokenGroups approach from Get-NetGroup to get all effective
- # security SIDs this object is a part of
- $TargetSid += Get-NetGroup -Domain $Domain -DomainController $DomainController -PageSize $PageSize -UserName $ObjectSamAccountName -RawSids
+ # use the tokenGroups approach from Get-NetGroup to get all effective
+ # security SIDs this object is a part of
+ $TargetSid += Get-NetGroup -Domain $Domain -DomainController $DomainController -PageSize $PageSize -UserName $ObjectSamAccountName -RawSids
- if($TargetSid -isnot [system.array]) { $TargetSid = @($TargetSid) }
+ if($TargetSid -isnot [System.Array]) { [System.Array]$TargetSid = [System.Array]@($TargetSid) }
+ }
Write-Verbose "Effective target sids: $TargetSid"
@@ -5703,104 +6438,108 @@ function Find-GPOLocation {
# get all GPO groups, and filter on ones that match our target SID list
# and match the target local sid memberof list
$GPOgroups = Get-NetGPOGroup @GPOGroupArgs | ForEach-Object {
-
if ($_.members) {
$_.members = $_.members | Where-Object {$_} | ForEach-Object {
- if($_ -match "S-1-5") {
+ if($_ -match '^S-1-.*') {
$_
}
else {
# if there are any plain group names, try to resolve them to sids
- Convert-NameToSid -ObjectName $_ -Domain $Domain
+ (Convert-NameToSid -ObjectName $_ -Domain $Domain).SID
}
- }
+ } | Sort-Object -Unique
# stop PowerShell 2.0's string stupid unboxing
- if($_.members -isnot [system.array]) { $_.members = @($_.members) }
- if($_.memberof -isnot [system.array]) { $_.memberof = @($_.memberof) }
-
- if($_.members) {
- try {
- # only return groups that contain a target sid
-
- # TODO: fix stupid weird "-DifferenceObject" is null error
- if( (Compare-Object -ReferenceObject $_.members -DifferenceObject $TargetSid -IncludeEqual -ExcludeDifferent) ) {
- if ($_.memberof -contains $LocalSid) {
- $_
- }
- }
- }
- catch {
- Write-Debug "Error comparing members and $TargetSid : $_"
+ if($_.members -isnot [System.Array]) { $_.members = @($_.members) }
+ if($_.memberof -isnot [System.Array]) { $_.memberof = @($_.memberof) }
+
+ # check if the memberof contains the sid of the local account we're searching for
+ Write-Verbose "memberof: $($_.memberof)"
+ if ($_.memberof -contains $LocalSid) {
+ # check if there's an overlap between the members field and the set of target sids
+ # if $TargetSid = *, then return all results
+ if ( ($TargetSid -eq '*') -or ($_.members | Where-Object {$_} | Where-Object { $TargetSid -Contains $_ })) {
+ $_
}
}
}
}
- Write-Verbose "GPOgroups: $GPOgroups"
$ProcessedGUIDs = @{}
# process the matches and build the result objects
$GPOgroups | Where-Object {$_} | ForEach-Object {
$GPOguid = $_.GPOName
+ $GPOMembers = $_.Members
+
+ if(!$TargetObjects) {
+ # if the * wildcard was used, set the ObjectDistName as the GPO member sid set
+ $TargetObjects = $GPOMembers
+ }
if( -not $ProcessedGUIDs[$GPOguid] ) {
$GPOname = $_.GPODisplayName
$Filters = $_.Filters
- # find any OUs that have this GUID applied
+ # find any OUs that have this GUID applied and then retrieve any computers from the OU
Get-NetOU -Domain $Domain -DomainController $DomainController -GUID $GPOguid -FullData -PageSize $PageSize | ForEach-Object {
if($Filters) {
# filter for computer name/org unit if a filter is specified
# TODO: handle other filters?
- $OUComputers = Get-NetComputer -ADSpath $_.ADSpath -FullData -PageSize $PageSize | Where-Object {
+ $OUComputers = Get-NetComputer -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $_.ADSpath -FullData -PageSize $PageSize | Where-Object {
$_.adspath -match ($Filters.Value)
} | ForEach-Object { $_.dnshostname }
}
else {
- $OUComputers = Get-NetComputer -ADSpath $_.ADSpath -PageSize $PageSize
+ $OUComputers = Get-NetComputer -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $_.ADSpath -PageSize $PageSize
}
- $GPOLocation = New-Object PSObject
- $GPOLocation | Add-Member Noteproperty 'ObjectName' $ObjectDistName
- $GPOLocation | Add-Member Noteproperty 'GPOname' $GPOname
- $GPOLocation | Add-Member Noteproperty 'GPOguid' $GPOguid
- $GPOLocation | Add-Member Noteproperty 'ContainerName' $_.distinguishedname
- $GPOLocation | Add-Member Noteproperty 'Computers' $OUComputers
- $GPOLocation
+ ForEach ($TargetSid in $TargetObjects) {
+
+ $Object = Get-ADObject -SID $TargetSid -Domain $Domain -DomainController $DomainController $_ -PageSize $PageSize
+
+ $IsGroup = @('268435456','268435457','536870912','536870913') -contains $Object.samaccounttype
+
+ $GPOLocation = New-Object PSObject
+ $GPOLocation | Add-Member Noteproperty 'ObjectName' $Object.samaccountname
+ $GPOLocation | Add-Member Noteproperty 'ObjectDN' $Object.distinguishedname
+ $GPOLocation | Add-Member Noteproperty 'ObjectSID' $Object.objectsid
+ $GPOLocation | Add-Member Noteproperty 'IsGroup' $IsGroup
+ $GPOLocation | Add-Member Noteproperty 'GPOname' $GPOname
+ $GPOLocation | Add-Member Noteproperty 'GPOguid' $GPOguid
+ $GPOLocation | Add-Member Noteproperty 'ContainerName' $_.distinguishedname
+ $GPOLocation | Add-Member Noteproperty 'Computers' $OUComputers
+ $GPOLocation
+ }
}
# find any sites that have this GUID applied
- # TODO: fix, this isn't the correct way to query computers from a site...
- # Get-NetSite -GUID $GPOguid -FullData | Foreach-Object {
- # if($Filters) {
- # # filter for computer name/org unit if a filter is specified
- # # TODO: handle other filters?
- # $SiteComptuers = Get-NetComputer -ADSpath $_.ADSpath -FullData | ? {
- # $_.adspath -match ($Filters.Value)
- # } | Foreach-Object {$_.dnshostname}
- # }
- # else {
- # $SiteComptuers = Get-NetComputer -ADSpath $_.ADSpath
- # }
-
- # $SiteComptuers = Get-NetComputer -ADSpath $_.ADSpath
- # $out = New-Object PSObject
- # $out | Add-Member Noteproperty 'Object' $ObjectDistName
- # $out | Add-Member Noteproperty 'GPOname' $GPOname
- # $out | Add-Member Noteproperty 'GPOguid' $GPOguid
- # $out | Add-Member Noteproperty 'ContainerName' $_.distinguishedname
- # $out | Add-Member Noteproperty 'Computers' $OUComputers
- # $out
- # }
+ Get-NetSite -Domain $Domain -DomainController $DomainController -GUID $GPOguid -PageSize $PageSize -FullData | ForEach-Object {
+
+ ForEach ($TargetSid in $TargetObjects) {
+ $Object = Get-ADObject -SID $TargetSid -Domain $Domain -DomainController $DomainController $_ -PageSize $PageSize
+
+ $IsGroup = @('268435456','268435457','536870912','536870913') -contains $Object.samaccounttype
+
+ $AppliedSite = New-Object PSObject
+ $AppliedSite | Add-Member Noteproperty 'ObjectName' $Object.samaccountname
+ $AppliedSite | Add-Member Noteproperty 'ObjectDN' $Object.distinguishedname
+ $AppliedSite | Add-Member Noteproperty 'ObjectSID' $Object.objectsid
+ $AppliedSite | Add-Member Noteproperty 'IsGroup' $IsGroup
+ $AppliedSite | Add-Member Noteproperty 'GPOname' $GPOname
+ $AppliedSite | Add-Member Noteproperty 'GPOguid' $GPOguid
+ $AppliedSite | Add-Member Noteproperty 'ContainerName' $_.distinguishedname
+ $AppliedSite | Add-Member Noteproperty 'Computers' $_.siteobjectbl
+ $AppliedSite
+ }
+ }
# mark off this GPO GUID so we don't process it again if there are dupes
$ProcessedGUIDs[$GPOguid] = $True
}
}
-
}
@@ -5897,23 +6636,51 @@ function Find-GPOComputerAdmin {
Throw "-ComputerName or -OUName must be provided"
}
+ $GPOGroups = @()
+
if($ComputerName) {
$Computers = Get-NetComputer -ComputerName $ComputerName -Domain $Domain -DomainController $DomainController -FullData -PageSize $PageSize
if(!$Computers) {
- throw "Computer $Computer in domain '$Domain' not found!"
+ throw "Computer $ComputerName in domain '$Domain' not found! Try a fully qualified host name"
}
+ $TargetOUs = @()
ForEach($Computer in $Computers) {
# extract all OUs a computer is a part of
$DN = $Computer.distinguishedname
- $TargetOUs = $DN.split(",") | Foreach-Object {
+ $TargetOUs += $DN.split(",") | ForEach-Object {
if($_.startswith("OU=")) {
$DN.substring($DN.indexof($_))
}
}
}
+
+ # enumerate any linked GPOs for the computer's site
+ $ComputerSite = (Get-SiteName -ComputerName $ComputerName).SiteName
+ if($ComputerSite -and ($ComputerSite -ne 'ERROR')) {
+ $GPOGroups += Get-NetSite -SiteName $ComputerSite -FullData | ForEach-Object {
+ if($_.gplink) {
+ $_.gplink.split("][") | ForEach-Object {
+ if ($_.startswith("LDAP")) {
+ $_.split(";")[0]
+ }
+ }
+ }
+ } | ForEach-Object {
+ $GPOGroupArgs = @{
+ 'Domain' = $Domain
+ 'DomainController' = $DomainController
+ 'ADSpath' = $_
+ 'UsePSDrive' = $UsePSDrive
+ 'PageSize' = $PageSize
+ }
+
+ # for each GPO link, get any locally set user/group SIDs
+ Get-NetGPOGroup @GPOGroupArgs
+ }
+ }
}
else {
$TargetOUs = @($OUName)
@@ -5921,19 +6688,19 @@ function Find-GPOComputerAdmin {
Write-Verbose "Target OUs: $TargetOUs"
- $TargetOUs | Where-Object {$_} | Foreach-Object {
-
- $OU = $_
+ $TargetOUs | Where-Object {$_} | ForEach-Object {
# for each OU the computer is a part of, get the full OU object
- $GPOgroups = Get-NetOU -Domain $Domain -DomainController $DomainController -ADSpath $_ -FullData -PageSize $PageSize | Foreach-Object {
+ $GPOgroups += Get-NetOU -Domain $Domain -DomainController $DomainController -ADSpath $_ -FullData -PageSize $PageSize | ForEach-Object {
# and then get any GPO links
- $_.gplink.split("][") | Foreach-Object {
- if ($_.startswith("LDAP")) {
- $_.split(";")[0]
+ if($_.gplink) {
+ $_.gplink.split("][") | ForEach-Object {
+ if ($_.startswith("LDAP")) {
+ $_.split(";")[0]
+ }
}
}
- } | Foreach-Object {
+ } | ForEach-Object {
$GPOGroupArgs = @{
'Domain' = $Domain
'DomainController' = $DomainController
@@ -5945,69 +6712,77 @@ function Find-GPOComputerAdmin {
# for each GPO link, get any locally set user/group SIDs
Get-NetGPOGroup @GPOGroupArgs
}
+ }
- # for each found GPO group, resolve the SIDs of the members
- $GPOgroups | Where-Object {$_} | Foreach-Object {
- $GPO = $_
- $GPO.members | Foreach-Object {
+ # for each found GPO group, resolve the SIDs of the members
+ $GPOgroups | Where-Object {$_} | ForEach-Object {
+ $GPO = $_
- # resolvethis SID to a domain object
- $Object = Get-ADObject -Domain $Domain -DomainController $DomainController $_ -PageSize $PageSize
+ if ($GPO.members) {
+ $GPO.members = $GPO.members | Where-Object {$_} | ForEach-Object {
+ if($_ -match '^S-1-.*') {
+ $_
+ }
+ else {
+ # if there are any plain group names, try to resolve them to sids
+ (Convert-NameToSid -ObjectName $_ -Domain $Domain).SID
+ }
+ } | Sort-Object -Unique
+ }
- $GPOComputerAdmin = New-Object PSObject
- $GPOComputerAdmin | Add-Member Noteproperty 'ComputerName' $ComputerName
- $GPOComputerAdmin | Add-Member Noteproperty 'OU' $OU
- $GPOComputerAdmin | Add-Member Noteproperty 'GPODisplayName' $GPO.GPODisplayName
- $GPOComputerAdmin | Add-Member Noteproperty 'GPOPath' $GPO.GPOPath
- $GPOComputerAdmin | Add-Member Noteproperty 'ObjectName' $Object.name
- $GPOComputerAdmin | Add-Member Noteproperty 'ObjectDN' $Object.distinguishedname
- $GPOComputerAdmin | Add-Member Noteproperty 'ObjectSID' $_
- $GPOComputerAdmin | Add-Member Noteproperty 'IsGroup' $($Object.samaccounttype -notmatch '805306368')
- $GPOComputerAdmin
+ $GPO.members | ForEach-Object {
- # if we're recursing and the current result object is a group
- if($Recurse -and $GPOComputerAdmin.isGroup) {
+ # resolve this SID to a domain object
+ $Object = Get-ADObject -Domain $Domain -DomainController $DomainController -PageSize $PageSize -SID $_
- Get-NetGroupMember -SID $_ -FullData -Recurse -PageSize $PageSize | Foreach-Object {
+ $IsGroup = @('268435456','268435457','536870912','536870913') -contains $Object.samaccounttype
- $MemberDN = $_.distinguishedName
+ $GPOComputerAdmin = New-Object PSObject
+ $GPOComputerAdmin | Add-Member Noteproperty 'ComputerName' $ComputerName
+ $GPOComputerAdmin | Add-Member Noteproperty 'GPODisplayName' $GPO.GPODisplayName
+ $GPOComputerAdmin | Add-Member Noteproperty 'GPOPath' $GPO.GPOPath
+ $GPOComputerAdmin | Add-Member Noteproperty 'ObjectName' $Object.samaccountname
+ $GPOComputerAdmin | Add-Member Noteproperty 'ObjectDN' $Object.distinguishedname
+ $GPOComputerAdmin | Add-Member Noteproperty 'ObjectSID' $_
+ $GPOComputerAdmin | Add-Member Noteproperty 'IsGroup' $IsGroup
+ $GPOComputerAdmin
- # extract the FQDN from the Distinguished Name
- $MemberDomain = $MemberDN.subString($MemberDN.IndexOf("DC=")) -replace 'DC=','' -replace ',','.'
+ # if we're recursing and the current result object is a group
+ if($Recurse -and $GPOComputerAdmin.isGroup) {
- if ($_.samAccountType -ne "805306368") {
- $MemberIsGroup = $True
- }
- else {
- $MemberIsGroup = $False
- }
+ Get-NetGroupMember -Domain $Domain -DomainController $DomainController -SID $_ -FullData -Recurse -PageSize $PageSize | ForEach-Object {
+
+ $MemberDN = $_.distinguishedName
+
+ # extract the FQDN from the Distinguished Name
+ $MemberDomain = $MemberDN.subString($MemberDN.IndexOf("DC=")) -replace 'DC=','' -replace ',','.'
- if ($_.samAccountName) {
- # forest users have the samAccountName set
- $MemberName = $_.samAccountName
+ $MemberIsGroup = @('268435456','268435457','536870912','536870913') -contains $_.samaccounttype
+
+ if ($_.samAccountName) {
+ # forest users have the samAccountName set
+ $MemberName = $_.samAccountName
+ }
+ else {
+ # external trust users have a SID, so convert it
+ try {
+ $MemberName = Convert-SidToName $_.cn
}
- else {
- # external trust users have a SID, so convert it
- try {
- $MemberName = Convert-SidToName $_.cn
- }
- catch {
- # if there's a problem contacting the domain to resolve the SID
- $MemberName = $_.cn
- }
+ catch {
+ # if there's a problem contacting the domain to resolve the SID
+ $MemberName = $_.cn
}
-
- $GPOComputerAdmin = New-Object PSObject
- $GPOComputerAdmin | Add-Member Noteproperty 'ComputerName' $ComputerName
- $GPOComputerAdmin | Add-Member Noteproperty 'OU' $OU
- $GPOComputerAdmin | Add-Member Noteproperty 'GPODisplayName' $GPO.GPODisplayName
- $GPOComputerAdmin | Add-Member Noteproperty 'GPOPath' $GPO.GPOPath
- $GPOComputerAdmin | Add-Member Noteproperty 'ObjectName' $MemberName
- $GPOComputerAdmin | Add-Member Noteproperty 'ObjectDN' $MemberDN
- $GPOComputerAdmin | Add-Member Noteproperty 'ObjectSID' $_.objectsid
- $GPOComputerAdmin | Add-Member Noteproperty 'IsGroup' $MemberIsGroup
- $GPOComputerAdmin
}
+
+ $GPOComputerAdmin = New-Object PSObject
+ $GPOComputerAdmin | Add-Member Noteproperty 'ComputerName' $ComputerName
+ $GPOComputerAdmin | Add-Member Noteproperty 'GPODisplayName' $GPO.GPODisplayName
+ $GPOComputerAdmin | Add-Member Noteproperty 'GPOPath' $GPO.GPOPath
+ $GPOComputerAdmin | Add-Member Noteproperty 'ObjectName' $MemberName
+ $GPOComputerAdmin | Add-Member Noteproperty 'ObjectDN' $MemberDN
+ $GPOComputerAdmin | Add-Member Noteproperty 'ObjectSID' $_.objectsid
+ $GPOComputerAdmin | Add-Member Noteproperty 'IsGroup' $MemberIsGroup
+ $GPOComputerAdmin
}
}
}
@@ -6047,9 +6822,15 @@ function Get-DomainPolicy {
.EXAMPLE
- PS C:\> Get-NetGPO
+ PS C:\> Get-DomainPolicy
+
+ Returns the domain policy for the current domain.
+
+ .EXAMPLE
+
+ PS C:\> Get-DomainPolicy -Source DC -DomainController MASTER.testlab.local
- Returns the GPOs in the current domain.
+ Returns the policy for the MASTER.testlab.local domain controller.
#>
[CmdletBinding()]
@@ -6103,25 +6884,25 @@ function Get-DomainPolicy {
}
# parse the GptTmpl.inf
- Get-GptTmpl @ParseArgs | Foreach-Object {
+ Get-GptTmpl @ParseArgs | ForEach-Object {
if($ResolveSids) {
# if we're resolving sids in PrivilegeRights to names
$Policy = New-Object PSObject
- $_.psobject.properties | Foreach-Object {
+ $_.psobject.properties | ForEach-Object {
if( $_.Name -eq 'PrivilegeRights') {
$PrivilegeRights = New-Object PSObject
# for every nested SID member of PrivilegeRights, try to
# unpack everything and resolve the SIDs as appropriate
- $_.Value.psobject.properties | Foreach-Object {
+ $_.Value.psobject.properties | ForEach-Object {
- $Sids = $_.Value | Foreach-Object {
+ $Sids = $_.Value | ForEach-Object {
try {
if($_ -isnot [System.Array]) {
Convert-SidToName $_
}
else {
- $_ | Foreach-Object { Convert-SidToName $_ }
+ $_ | ForEach-Object { Convert-SidToName $_ }
}
}
catch {
@@ -6184,6 +6965,11 @@ function Get-NetLocalGroup {
Switch. If the local member member is a domain group, recursively try to resolve its members to get a list of domain users who can access this machine.
+ .PARAMETER API
+
+ Switch. Use API calls instead of the WinNT service provider. Less information,
+ but the results are faster.
+
.EXAMPLE
PS C:\> Get-NetLocalGroup
@@ -6198,7 +6984,7 @@ function Get-NetLocalGroup {
.EXAMPLE
- PS C:\> Get-NetLocalGroup -ComputerName WINDOWS7 -Resurse
+ PS C:\> Get-NetLocalGroup -ComputerName WINDOWS7 -Recurse
Returns all effective local/domain users/groups that can access WINDOWS7 with
local administrative privileges.
@@ -6209,42 +6995,52 @@ function Get-NetLocalGroup {
Returns all local groups on the WINDOWS7 host.
+ .EXAMPLE
+
+ PS C:\> "WINDOWS7", "WINDOWSSP" | Get-NetLocalGroup -API
+
+ Returns all local groups on the the passed hosts using API calls instead of the
+ WinNT service provider.
+
.LINK
http://stackoverflow.com/questions/21288220/get-all-local-members-and-groups-displayed-together
http://msdn.microsoft.com/en-us/library/aa772211(VS.85).aspx
#>
- [CmdletBinding()]
+ [CmdletBinding(DefaultParameterSetName = 'WinNT')]
param(
- [Parameter(ValueFromPipeline=$True)]
+ [Parameter(ParameterSetName = 'API', Position=0, ValueFromPipeline=$True)]
+ [Parameter(ParameterSetName = 'WinNT', Position=0, ValueFromPipeline=$True)]
[Alias('HostName')]
- [String]
+ [String[]]
$ComputerName = 'localhost',
+ [Parameter(ParameterSetName = 'WinNT')]
+ [Parameter(ParameterSetName = 'API')]
[ValidateScript({Test-Path -Path $_ })]
[Alias('HostList')]
[String]
$ComputerFile,
+ [Parameter(ParameterSetName = 'WinNT')]
+ [Parameter(ParameterSetName = 'API')]
[String]
$GroupName = 'Administrators',
+ [Parameter(ParameterSetName = 'WinNT')]
[Switch]
$ListGroups,
+ [Parameter(ParameterSetName = 'WinNT')]
[Switch]
- $Recurse
+ $Recurse,
+
+ [Parameter(ParameterSetName = 'API')]
+ [Switch]
+ $API
)
- begin {
- if ((-not $ListGroups) -and (-not $GroupName)) {
- # resolve the SID for the local admin group - this should usually default to "Administrators"
- $ObjSID = New-Object System.Security.Principal.SecurityIdentifier('S-1-5-32-544')
- $Objgroup = $ObjSID.Translate( [System.Security.Principal.NTAccount])
- $GroupName = ($Objgroup.Value).Split('\')[1]
- }
- }
process {
$Servers = @()
@@ -6255,140 +7051,273 @@ function Get-NetLocalGroup {
}
else {
# otherwise assume a single host name
- $Servers += Get-NameField -Object $ComputerName
+ $Servers += $ComputerName | Get-NameField
}
# query the specified group using the WINNT provider, and
# extract fields as appropriate from the results
ForEach($Server in $Servers) {
- try {
- if($ListGroups) {
- # if we're listing the group names on a remote server
- $Computer = [ADSI]"WinNT://$Server,computer"
-
- $Computer.psbase.children | Where-Object { $_.psbase.schemaClassName -eq 'group' } | ForEach-Object {
- $Group = New-Object PSObject
- $Group | Add-Member Noteproperty 'Server' $Server
- $Group | Add-Member Noteproperty 'Group' ($_.name[0])
- $Group | Add-Member Noteproperty 'SID' ((New-Object System.Security.Principal.SecurityIdentifier $_.objectsid[0],0).Value)
- $Group | Add-Member Noteproperty 'Description' ($_.Description[0])
- $Group
- }
- }
- else {
- # otherwise we're listing the group members
- $Members = @($([ADSI]"WinNT://$Server/$GroupName").psbase.Invoke('Members'))
- $Members | ForEach-Object {
+ if($API) {
+ # if we're using the Netapi32 NetLocalGroupGetMembers API call to
+ # get the local group information
+
+ # arguments for NetLocalGroupGetMembers
+ $QueryLevel = 2
+ $PtrInfo = [IntPtr]::Zero
+ $EntriesRead = 0
+ $TotalRead = 0
+ $ResumeHandle = 0
- $Member = New-Object PSObject
- $Member | Add-Member Noteproperty 'Server' $Server
+ # get the local user information
+ $Result = $Netapi32::NetLocalGroupGetMembers($Server, $GroupName, $QueryLevel, [ref]$PtrInfo, -1, [ref]$EntriesRead, [ref]$TotalRead, [ref]$ResumeHandle)
- $AdsPath = ($_.GetType().InvokeMember('Adspath', 'GetProperty', $Null, $_, $Null)).Replace('WinNT://', '')
+ # Locate the offset of the initial intPtr
+ $Offset = $PtrInfo.ToInt64()
- # try to translate the NT4 domain to a FQDN if possible
- $Name = Convert-NT4toCanonical -ObjectName $AdsPath
- if($Name) {
- $FQDN = $Name.split("/")[0]
- $ObjName = $AdsPath.split("/")[-1]
- $Name = "$FQDN/$ObjName"
- $IsDomain = $True
+ Write-Debug "NetLocalGroupGetMembers result for $Server : $Result"
+ $LocalUsers = @()
+
+ # 0 = success
+ if (($Result -eq 0) -and ($Offset -gt 0)) {
+
+ # Work out how mutch to increment the pointer by finding out the size of the structure
+ $Increment = $LOCALGROUP_MEMBERS_INFO_2::GetSize()
+
+ # parse all the result structures
+ for ($i = 0; ($i -lt $EntriesRead); $i++) {
+ # create a new int ptr at the given offset and cast
+ # the pointer as our result structure
+ $NewIntPtr = New-Object System.Intptr -ArgumentList $Offset
+ $Info = $NewIntPtr -as $LOCALGROUP_MEMBERS_INFO_2
+
+ $SidString = ""
+ $Result = $Advapi32::ConvertSidToStringSid($Info.lgrmi2_sid, [ref]$SidString)
+ Write-Debug "Result of ConvertSidToStringSid: $Result"
+
+ if($Result -eq 0) {
+ # error codes - http://msdn.microsoft.com/en-us/library/windows/desktop/ms681382(v=vs.85).aspx
+ $Err = $Kernel32::GetLastError()
+ Write-Error "ConvertSidToStringSid LastError: $Err"
}
else {
- $Name = $AdsPath
- $IsDomain = $False
- }
+ $LocalUser = New-Object PSObject
+ $LocalUser | Add-Member Noteproperty 'ComputerName' $Server
+ $LocalUser | Add-Member Noteproperty 'AccountName' $Info.lgrmi2_domainandname
+ $LocalUser | Add-Member Noteproperty 'SID' $SidString
- $Member | Add-Member Noteproperty 'AccountName' $Name
+ $IsGroup = $($Info.lgrmi2_sidusage -eq 'SidTypeGroup')
+ $LocalUser | Add-Member Noteproperty 'IsGroup' $IsGroup
- # translate the binary sid to a string
- $Member | Add-Member Noteproperty 'SID' ((New-Object System.Security.Principal.SecurityIdentifier($_.GetType().InvokeMember('ObjectSID', 'GetProperty', $Null, $_, $Null),0)).Value)
+ $Offset = $NewIntPtr.ToInt64()
+ $Offset += $Increment
- # if the account is local, check if it's disabled, if it's domain, always print $False
- # TODO: fix this occasinal error?
- $Member | Add-Member Noteproperty 'Disabled' $( if(-not $IsDomain) { try { $_.GetType().InvokeMember('AccountDisabled', 'GetProperty', $Null, $_, $Null) } catch { 'ERROR' } } else { $False } )
+ $LocalUsers += $LocalUser
+ }
+ }
+
+ # free up the result buffer
+ $Null = $Netapi32::NetApiBufferFree($PtrInfo)
- # check if the member is a group
- $IsGroup = ($_.GetType().InvokeMember('Class', 'GetProperty', $Null, $_, $Null) -eq 'group')
- $Member | Add-Member Noteproperty 'IsGroup' $IsGroup
- $Member | Add-Member Noteproperty 'IsDomain' $IsDomain
- if($IsGroup) {
- $Member | Add-Member Noteproperty 'LastLogin' ""
+ # try to extract out the machine SID by using the -500 account as a reference
+ $MachineSid = $LocalUsers | Where-Object {$_.SID -like '*-500'}
+ $Parts = $MachineSid.SID.Split('-')
+ $MachineSid = $Parts[0..($Parts.Length -2)] -join '-'
+
+ $LocalUsers | ForEach-Object {
+ if($_.SID -match $MachineSid) {
+ $_ | Add-Member Noteproperty 'IsDomain' $False
}
else {
- try {
- $Member | Add-Member Noteproperty 'LastLogin' ( $_.GetType().InvokeMember('LastLogin', 'GetProperty', $Null, $_, $Null))
- }
- catch {
- $Member | Add-Member Noteproperty 'LastLogin' ""
- }
+ $_ | Add-Member Noteproperty 'IsDomain' $True
}
- $Member
+ }
+ $LocalUsers
+ }
+ else
+ {
+ switch ($Result) {
+ (5) {Write-Debug 'The user does not have access to the requested information.'}
+ (124) {Write-Debug 'The value specified for the level parameter is not valid.'}
+ (87) {Write-Debug 'The specified parameter is not valid.'}
+ (234) {Write-Debug 'More entries are available. Specify a large enough buffer to receive all entries.'}
+ (8) {Write-Debug 'Insufficient memory is available.'}
+ (2312) {Write-Debug 'A session does not exist with the computer name.'}
+ (2351) {Write-Debug 'The computer name is not valid.'}
+ (2221) {Write-Debug 'Username not found.'}
+ (53) {Write-Debug 'Hostname could not be found'}
+ }
+ }
+ }
- # if the result is a group domain object and we're recursing,
- # try to resolve all the group member results
- if($Recurse -and $IsDomain -and $IsGroup) {
+ else {
+ # otherwise we're using the WinNT service provider
+ try {
+ if($ListGroups) {
+ # if we're listing the group names on a remote server
+ $Computer = [ADSI]"WinNT://$Server,computer"
+
+ $Computer.psbase.children | Where-Object { $_.psbase.schemaClassName -eq 'group' } | ForEach-Object {
+ $Group = New-Object PSObject
+ $Group | Add-Member Noteproperty 'Server' $Server
+ $Group | Add-Member Noteproperty 'Group' ($_.name[0])
+ $Group | Add-Member Noteproperty 'SID' ((New-Object System.Security.Principal.SecurityIdentifier $_.objectsid[0],0).Value)
+ $Group | Add-Member Noteproperty 'Description' ($_.Description[0])
+ $Group
+ }
+ }
+ else {
+ # otherwise we're listing the group members
+ $Members = @($([ADSI]"WinNT://$Server/$GroupName,group").psbase.Invoke('Members'))
+
+ $Members | ForEach-Object {
+
+ $Member = New-Object PSObject
+ $Member | Add-Member Noteproperty 'ComputerName' $Server
+
+ $AdsPath = ($_.GetType().InvokeMember('Adspath', 'GetProperty', $Null, $_, $Null)).Replace('WinNT://', '')
+
+ # try to translate the NT4 domain to a FQDN if possible
+ $Name = Convert-ADName -ObjectName $AdsPath -InputType 'NT4' -OutputType 'Canonical'
+
+ if($Name) {
+ $FQDN = $Name.split("/")[0]
+ $ObjName = $AdsPath.split("/")[-1]
+ $Name = "$FQDN/$ObjName"
+ $IsDomain = $True
+ }
+ else {
+ $Name = $AdsPath
+ $IsDomain = $False
+ }
- $FQDN = $Name.split("/")[0]
- $GroupName = $Name.split("/")[1].trim()
+ $Member | Add-Member Noteproperty 'AccountName' $Name
- Get-NetGroupMember -GroupName $GroupName -Domain $FQDN -FullData -Recurse | ForEach-Object {
+ if($IsDomain) {
+ # translate the binary sid to a string
+ $Member | Add-Member Noteproperty 'SID' ((New-Object System.Security.Principal.SecurityIdentifier($_.GetType().InvokeMember('ObjectSID', 'GetProperty', $Null, $_, $Null),0)).Value)
- $Member = New-Object PSObject
- $Member | Add-Member Noteproperty 'Server' "$FQDN/$($_.GroupName)"
+ $Member | Add-Member Noteproperty 'Description' ""
+ $Member | Add-Member Noteproperty 'Disabled' $False
- $MemberDN = $_.distinguishedName
- # extract the FQDN from the Distinguished Name
- $MemberDomain = $MemberDN.subString($MemberDN.IndexOf("DC=")) -replace 'DC=','' -replace ',','.'
+ # check if the member is a group
+ $IsGroup = ($_.GetType().InvokeMember('Class', 'GetProperty', $Null, $_, $Null) -eq 'group')
+ $Member | Add-Member Noteproperty 'IsGroup' $IsGroup
+ $Member | Add-Member Noteproperty 'IsDomain' $IsDomain
- if ($_.samAccountType -ne "805306368") {
- $MemberIsGroup = $True
+ if($IsGroup) {
+ $Member | Add-Member Noteproperty 'LastLogin' $Null
}
else {
- $MemberIsGroup = $False
+ try {
+ $Member | Add-Member Noteproperty 'LastLogin' ( $_.GetType().InvokeMember('LastLogin', 'GetProperty', $Null, $_, $Null))
+ }
+ catch {
+ $Member | Add-Member Noteproperty 'LastLogin' $Null
+ }
}
+ $Member | Add-Member Noteproperty 'PwdLastSet' ""
+ $Member | Add-Member Noteproperty 'PwdExpired' ""
+ $Member | Add-Member Noteproperty 'UserFlags' ""
+ }
+ else {
+ # repull this user object so we can ensure correct information
+ $LocalUser = $([ADSI] "WinNT://$AdsPath")
+
+ # translate the binary sid to a string
+ $Member | Add-Member Noteproperty 'SID' ((New-Object System.Security.Principal.SecurityIdentifier($LocalUser.objectSid.value,0)).Value)
+
+ $Member | Add-Member Noteproperty 'Description' ($LocalUser.Description[0])
+
+ # UAC flags of 0x2 mean the account is disabled
+ $Member | Add-Member Noteproperty 'Disabled' $(($LocalUser.userFlags.value -band 2) -eq 2)
+
+ # check if the member is a group
+ $Member | Add-Member Noteproperty 'IsGroup' ($LocalUser.SchemaClassName -like 'group')
+ $Member | Add-Member Noteproperty 'IsDomain' $IsDomain
- if ($_.samAccountName) {
- # forest users have the samAccountName set
- $MemberName = $_.samAccountName
+ if($IsGroup) {
+ $Member | Add-Member Noteproperty 'LastLogin' ""
}
else {
try {
- # external trust users have a SID, so convert it
+ $Member | Add-Member Noteproperty 'LastLogin' ( $LocalUser.LastLogin[0])
+ }
+ catch {
+ $Member | Add-Member Noteproperty 'LastLogin' ""
+ }
+ }
+
+ $Member | Add-Member Noteproperty 'PwdLastSet' ( (Get-Date).AddSeconds(-$LocalUser.PasswordAge[0]))
+ $Member | Add-Member Noteproperty 'PwdExpired' ( $LocalUser.PasswordExpired[0] -eq '1')
+ $Member | Add-Member Noteproperty 'UserFlags' ( $LocalUser.UserFlags[0] )
+ }
+ $Member
+
+ # if the result is a group domain object and we're recursing,
+ # try to resolve all the group member results
+ if($Recurse -and $IsDomain -and $IsGroup) {
+
+ $FQDN = $Name.split("/")[0]
+ $GroupName = $Name.split("/")[1].trim()
+
+ Get-NetGroupMember -GroupName $GroupName -Domain $FQDN -FullData -Recurse | ForEach-Object {
+
+ $Member = New-Object PSObject
+ $Member | Add-Member Noteproperty 'ComputerName' "$FQDN/$($_.GroupName)"
+
+ $MemberDN = $_.distinguishedName
+ # extract the FQDN from the Distinguished Name
+ $MemberDomain = $MemberDN.subString($MemberDN.IndexOf("DC=")) -replace 'DC=','' -replace ',','.'
+
+ $MemberIsGroup = @('268435456','268435457','536870912','536870913') -contains $_.samaccounttype
+
+ if ($_.samAccountName) {
+ # forest users have the samAccountName set
+ $MemberName = $_.samAccountName
+ }
+ else {
try {
- $MemberName = Convert-SidToName $_.cn
+ # external trust users have a SID, so convert it
+ try {
+ $MemberName = Convert-SidToName $_.cn
+ }
+ catch {
+ # if there's a problem contacting the domain to resolve the SID
+ $MemberName = $_.cn
+ }
}
catch {
- # if there's a problem contacting the domain to resolve the SID
- $MemberName = $_.cn
+ Write-Debug "Error resolving SID : $_"
}
}
- catch {
- Write-Debug "Error resolving SID : $_"
- }
- }
- $Member | Add-Member Noteproperty 'AccountName' "$MemberDomain/$MemberName"
- $Member | Add-Member Noteproperty 'SID' $_.objectsid
- $Member | Add-Member Noteproperty 'Disabled' $False
- $Member | Add-Member Noteproperty 'IsGroup' $MemberIsGroup
- $Member | Add-Member Noteproperty 'IsDomain' $True
- $Member | Add-Member Noteproperty 'LastLogin' ''
- $Member
+ $Member | Add-Member Noteproperty 'AccountName' "$MemberDomain/$MemberName"
+ $Member | Add-Member Noteproperty 'SID' $_.objectsid
+ $Member | Add-Member Noteproperty 'Description' $_.description
+ $Member | Add-Member Noteproperty 'Disabled' $False
+ $Member | Add-Member Noteproperty 'IsGroup' $MemberIsGroup
+ $Member | Add-Member Noteproperty 'IsDomain' $True
+ $Member | Add-Member Noteproperty 'LastLogin' ''
+ $Member | Add-Member Noteproperty 'PwdLastSet' $_.pwdLastSet
+ $Member | Add-Member Noteproperty 'PwdExpired' ''
+ $Member | Add-Member Noteproperty 'UserFlags' $_.userAccountControl
+ $Member
+ }
}
}
}
}
- }
- catch {
- Write-Warning "[!] Error: $_"
+ catch {
+ Write-Warning "[!] Error: $_"
+ }
}
}
}
}
-function Get-NetShare {
+filter Get-NetShare {
<#
.SYNOPSIS
@@ -6403,7 +7332,8 @@ function Get-NetShare {
.OUTPUTS
SHARE_INFO_1 structure. A representation of the SHARE_INFO_1
- result structure which includes the name and note for each share.
+ result structure which includes the name and note for each share,
+ with the ComputerName added.
.EXAMPLE
@@ -6416,82 +7346,87 @@ function Get-NetShare {
PS C:\> Get-NetShare -ComputerName sqlserver
Returns active shares on the 'sqlserver' host
+
+ .EXAMPLE
+
+ PS C:\> Get-NetComputer | Get-NetShare
+
+ Returns all shares for all computers in the domain.
+
+ .LINK
+
+ http://www.powershellmagazine.com/2014/09/25/easily-defining-enums-structs-and-win32-functions-in-memory/
#>
[CmdletBinding()]
param(
[Parameter(ValueFromPipeline=$True)]
[Alias('HostName')]
- [String]
+ [Object[]]
+ [ValidateNotNullOrEmpty()]
$ComputerName = 'localhost'
)
- begin {
- if ($PSBoundParameters['Debug']) {
- $DebugPreference = 'Continue'
- }
- }
+ # extract the computer name from whatever object was passed on the pipeline
+ $Computer = $ComputerName | Get-NameField
- process {
-
- # process multiple host object types from the pipeline
- $ComputerName = Get-NameField -Object $ComputerName
-
- # arguments for NetShareEnum
- $QueryLevel = 1
- $PtrInfo = [IntPtr]::Zero
- $EntriesRead = 0
- $TotalRead = 0
- $ResumeHandle = 0
+ # arguments for NetShareEnum
+ $QueryLevel = 1
+ $PtrInfo = [IntPtr]::Zero
+ $EntriesRead = 0
+ $TotalRead = 0
+ $ResumeHandle = 0
- # get the share information
- $Result = $Netapi32::NetShareEnum($ComputerName, $QueryLevel, [ref]$PtrInfo, -1, [ref]$EntriesRead, [ref]$TotalRead, [ref]$ResumeHandle)
+ # get the share information
+ $Result = $Netapi32::NetShareEnum($Computer, $QueryLevel, [ref]$PtrInfo, -1, [ref]$EntriesRead, [ref]$TotalRead, [ref]$ResumeHandle)
- # Locate the offset of the initial intPtr
- $Offset = $PtrInfo.ToInt64()
+ # Locate the offset of the initial intPtr
+ $Offset = $PtrInfo.ToInt64()
- Write-Debug "Get-NetShare result: $Result"
+ Write-Debug "Get-NetShare result for $Computer : $Result"
- # 0 = success
- if (($Result -eq 0) -and ($Offset -gt 0)) {
+ # 0 = success
+ if (($Result -eq 0) -and ($Offset -gt 0)) {
- # Work out how mutch to increment the pointer by finding out the size of the structure
- $Increment = $SHARE_INFO_1::GetSize()
+ # Work out how mutch to increment the pointer by finding out the size of the structure
+ $Increment = $SHARE_INFO_1::GetSize()
- # parse all the result structures
- for ($i = 0; ($i -lt $EntriesRead); $i++) {
- # create a new int ptr at the given offset and cast
- # the pointer as our result structure
- $NewIntPtr = New-Object System.Intptr -ArgumentList $Offset
- $Info = $NewIntPtr -as $SHARE_INFO_1
- # return all the sections of the structure
- $Info | Select-Object *
- $Offset = $NewIntPtr.ToInt64()
- $Offset += $Increment
- }
+ # parse all the result structures
+ for ($i = 0; ($i -lt $EntriesRead); $i++) {
+ # create a new int ptr at the given offset and cast
+ # the pointer as our result structure
+ $NewIntPtr = New-Object System.Intptr -ArgumentList $Offset
+ $Info = $NewIntPtr -as $SHARE_INFO_1
- # free up the result buffer
- $Null = $Netapi32::NetApiBufferFree($PtrInfo)
+ # return all the sections of the structure
+ $Shares = $Info | Select-Object *
+ $Shares | Add-Member Noteproperty 'ComputerName' $Computer
+ $Offset = $NewIntPtr.ToInt64()
+ $Offset += $Increment
+ $Shares
}
- else
- {
- switch ($Result) {
- (5) {Write-Debug 'The user does not have access to the requested information.'}
- (124) {Write-Debug 'The value specified for the level parameter is not valid.'}
- (87) {Write-Debug 'The specified parameter is not valid.'}
- (234) {Write-Debug 'More entries are available. Specify a large enough buffer to receive all entries.'}
- (8) {Write-Debug 'Insufficient memory is available.'}
- (2312) {Write-Debug 'A session does not exist with the computer name.'}
- (2351) {Write-Debug 'The computer name is not valid.'}
- (2221) {Write-Debug 'Username not found.'}
- (53) {Write-Debug 'Hostname could not be found'}
- }
+
+ # free up the result buffer
+ $Null = $Netapi32::NetApiBufferFree($PtrInfo)
+ }
+ else
+ {
+ switch ($Result) {
+ (5) {Write-Debug 'The user does not have access to the requested information.'}
+ (124) {Write-Debug 'The value specified for the level parameter is not valid.'}
+ (87) {Write-Debug 'The specified parameter is not valid.'}
+ (234) {Write-Debug 'More entries are available. Specify a large enough buffer to receive all entries.'}
+ (8) {Write-Debug 'Insufficient memory is available.'}
+ (2312) {Write-Debug 'A session does not exist with the computer name.'}
+ (2351) {Write-Debug 'The computer name is not valid.'}
+ (2221) {Write-Debug 'Username not found.'}
+ (53) {Write-Debug 'Hostname could not be found'}
}
}
}
-function Get-NetLoggedon {
+filter Get-NetLoggedon {
<#
.SYNOPSIS
@@ -6505,7 +7440,8 @@ function Get-NetLoggedon {
.OUTPUTS
WKSTA_USER_INFO_1 structure. A representation of the WKSTA_USER_INFO_1
- result structure which includes the username and domain of logged on users.
+ result structure which includes the username and domain of logged on users,
+ with the ComputerName added.
.EXAMPLE
@@ -6519,6 +7455,12 @@ function Get-NetLoggedon {
Returns users actively logged onto the 'sqlserver' host.
+ .EXAMPLE
+
+ PS C:\> Get-NetComputer | Get-NetLoggedon
+
+ Returns all logged on userse for all computers in the domain.
+
.LINK
http://www.powershellmagazine.com/2014/09/25/easily-defining-enums-structs-and-win32-functions-in-memory/
@@ -6528,78 +7470,71 @@ function Get-NetLoggedon {
param(
[Parameter(ValueFromPipeline=$True)]
[Alias('HostName')]
- [String]
+ [Object[]]
+ [ValidateNotNullOrEmpty()]
$ComputerName = 'localhost'
)
- begin {
- if ($PSBoundParameters['Debug']) {
- $DebugPreference = 'Continue'
- }
- }
-
- process {
-
- # process multiple host object types from the pipeline
- $ComputerName = Get-NameField -Object $ComputerName
+ # extract the computer name from whatever object was passed on the pipeline
+ $Computer = $ComputerName | Get-NameField
- # Declare the reference variables
- $QueryLevel = 1
- $PtrInfo = [IntPtr]::Zero
- $EntriesRead = 0
- $TotalRead = 0
- $ResumeHandle = 0
+ # Declare the reference variables
+ $QueryLevel = 1
+ $PtrInfo = [IntPtr]::Zero
+ $EntriesRead = 0
+ $TotalRead = 0
+ $ResumeHandle = 0
- # get logged on user information
- $Result = $Netapi32::NetWkstaUserEnum($ComputerName, $QueryLevel, [ref]$PtrInfo, -1, [ref]$EntriesRead, [ref]$TotalRead, [ref]$ResumeHandle)
+ # get logged on user information
+ $Result = $Netapi32::NetWkstaUserEnum($Computer, $QueryLevel, [ref]$PtrInfo, -1, [ref]$EntriesRead, [ref]$TotalRead, [ref]$ResumeHandle)
- # Locate the offset of the initial intPtr
- $Offset = $PtrInfo.ToInt64()
+ # Locate the offset of the initial intPtr
+ $Offset = $PtrInfo.ToInt64()
- Write-Debug "Get-NetLoggedon result: $Result"
+ Write-Debug "Get-NetLoggedon result for $Computer : $Result"
- # 0 = success
- if (($Result -eq 0) -and ($Offset -gt 0)) {
+ # 0 = success
+ if (($Result -eq 0) -and ($Offset -gt 0)) {
- # Work out how mutch to increment the pointer by finding out the size of the structure
- $Increment = $WKSTA_USER_INFO_1::GetSize()
+ # Work out how mutch to increment the pointer by finding out the size of the structure
+ $Increment = $WKSTA_USER_INFO_1::GetSize()
- # parse all the result structures
- for ($i = 0; ($i -lt $EntriesRead); $i++) {
- # create a new int ptr at the given offset and cast
- # the pointer as our result structure
- $NewIntPtr = New-Object System.Intptr -ArgumentList $Offset
- $Info = $NewIntPtr -as $WKSTA_USER_INFO_1
+ # parse all the result structures
+ for ($i = 0; ($i -lt $EntriesRead); $i++) {
+ # create a new int ptr at the given offset and cast
+ # the pointer as our result structure
+ $NewIntPtr = New-Object System.Intptr -ArgumentList $Offset
+ $Info = $NewIntPtr -as $WKSTA_USER_INFO_1
- # return all the sections of the structure
- $Info | Select-Object *
- $Offset = $NewIntPtr.ToInt64()
- $Offset += $Increment
-
- }
-
- # free up the result buffer
- $Null = $Netapi32::NetApiBufferFree($PtrInfo)
+ # return all the sections of the structure
+ $LoggedOn = $Info | Select-Object *
+ $LoggedOn | Add-Member Noteproperty 'ComputerName' $Computer
+ $Offset = $NewIntPtr.ToInt64()
+ $Offset += $Increment
+ $LoggedOn
}
- else
- {
- switch ($Result) {
- (5) {Write-Debug 'The user does not have access to the requested information.'}
- (124) {Write-Debug 'The value specified for the level parameter is not valid.'}
- (87) {Write-Debug 'The specified parameter is not valid.'}
- (234) {Write-Debug 'More entries are available. Specify a large enough buffer to receive all entries.'}
- (8) {Write-Debug 'Insufficient memory is available.'}
- (2312) {Write-Debug 'A session does not exist with the computer name.'}
- (2351) {Write-Debug 'The computer name is not valid.'}
- (2221) {Write-Debug 'Username not found.'}
- (53) {Write-Debug 'Hostname could not be found'}
- }
+
+ # free up the result buffer
+ $Null = $Netapi32::NetApiBufferFree($PtrInfo)
+ }
+ else
+ {
+ switch ($Result) {
+ (5) {Write-Debug 'The user does not have access to the requested information.'}
+ (124) {Write-Debug 'The value specified for the level parameter is not valid.'}
+ (87) {Write-Debug 'The specified parameter is not valid.'}
+ (234) {Write-Debug 'More entries are available. Specify a large enough buffer to receive all entries.'}
+ (8) {Write-Debug 'Insufficient memory is available.'}
+ (2312) {Write-Debug 'A session does not exist with the computer name.'}
+ (2351) {Write-Debug 'The computer name is not valid.'}
+ (2221) {Write-Debug 'Username not found.'}
+ (53) {Write-Debug 'Hostname could not be found'}
}
}
}
-function Get-NetSession {
+filter Get-NetSession {
<#
.SYNOPSIS
@@ -6619,7 +7554,7 @@ function Get-NetSession {
SESSION_INFO_10 structure. A representation of the SESSION_INFO_10
result structure which includes the host and username associated
- with active sessions.
+ with active sessions, with the ComputerName added.
.EXAMPLE
@@ -6633,6 +7568,12 @@ function Get-NetSession {
Returns active sessions on the 'sqlserver' host.
+ .EXAMPLE
+
+ PS C:\> Get-NetDomainController | Get-NetSession
+
+ Returns active sessions on all domain controllers.
+
.LINK
http://www.powershellmagazine.com/2014/09/25/easily-defining-enums-structs-and-win32-functions-in-memory/
@@ -6642,80 +7583,73 @@ function Get-NetSession {
param(
[Parameter(ValueFromPipeline=$True)]
[Alias('HostName')]
- [String]
+ [Object[]]
+ [ValidateNotNullOrEmpty()]
$ComputerName = 'localhost',
[String]
$UserName = ''
)
- begin {
- if ($PSBoundParameters['Debug']) {
- $DebugPreference = 'Continue'
- }
- }
-
- process {
-
- # process multiple host object types from the pipeline
- $ComputerName = Get-NameField -Object $ComputerName
-
- # arguments for NetSessionEnum
- $QueryLevel = 10
- $PtrInfo = [IntPtr]::Zero
- $EntriesRead = 0
- $TotalRead = 0
- $ResumeHandle = 0
+ # extract the computer name from whatever object was passed on the pipeline
+ $Computer = $ComputerName | Get-NameField
- # get session information
- $Result = $Netapi32::NetSessionEnum($ComputerName, '', $UserName, $QueryLevel, [ref]$PtrInfo, -1, [ref]$EntriesRead, [ref]$TotalRead, [ref]$ResumeHandle)
+ # arguments for NetSessionEnum
+ $QueryLevel = 10
+ $PtrInfo = [IntPtr]::Zero
+ $EntriesRead = 0
+ $TotalRead = 0
+ $ResumeHandle = 0
- # Locate the offset of the initial intPtr
- $Offset = $PtrInfo.ToInt64()
+ # get session information
+ $Result = $Netapi32::NetSessionEnum($Computer, '', $UserName, $QueryLevel, [ref]$PtrInfo, -1, [ref]$EntriesRead, [ref]$TotalRead, [ref]$ResumeHandle)
- Write-Debug "Get-NetSession result: $Result"
+ # Locate the offset of the initial intPtr
+ $Offset = $PtrInfo.ToInt64()
- # 0 = success
- if (($Result -eq 0) -and ($Offset -gt 0)) {
+ Write-Debug "Get-NetSession result for $Computer : $Result"
- # Work out how mutch to increment the pointer by finding out the size of the structure
- $Increment = $SESSION_INFO_10::GetSize()
+ # 0 = success
+ if (($Result -eq 0) -and ($Offset -gt 0)) {
- # parse all the result structures
- for ($i = 0; ($i -lt $EntriesRead); $i++) {
- # create a new int ptr at the given offset and cast
- # the pointer as our result structure
- $NewIntPtr = New-Object System.Intptr -ArgumentList $Offset
- $Info = $NewIntPtr -as $SESSION_INFO_10
+ # Work out how mutch to increment the pointer by finding out the size of the structure
+ $Increment = $SESSION_INFO_10::GetSize()
- # return all the sections of the structure
- $Info | Select-Object *
- $Offset = $NewIntPtr.ToInt64()
- $Offset += $Increment
+ # parse all the result structures
+ for ($i = 0; ($i -lt $EntriesRead); $i++) {
+ # create a new int ptr at the given offset and cast
+ # the pointer as our result structure
+ $NewIntPtr = New-Object System.Intptr -ArgumentList $Offset
+ $Info = $NewIntPtr -as $SESSION_INFO_10
- }
- # free up the result buffer
- $Null = $Netapi32::NetApiBufferFree($PtrInfo)
+ # return all the sections of the structure
+ $Sessions = $Info | Select-Object *
+ $Sessions | Add-Member Noteproperty 'ComputerName' $Computer
+ $Offset = $NewIntPtr.ToInt64()
+ $Offset += $Increment
+ $Sessions
}
- else
- {
- switch ($Result) {
- (5) {Write-Debug 'The user does not have access to the requested information.'}
- (124) {Write-Debug 'The value specified for the level parameter is not valid.'}
- (87) {Write-Debug 'The specified parameter is not valid.'}
- (234) {Write-Debug 'More entries are available. Specify a large enough buffer to receive all entries.'}
- (8) {Write-Debug 'Insufficient memory is available.'}
- (2312) {Write-Debug 'A session does not exist with the computer name.'}
- (2351) {Write-Debug 'The computer name is not valid.'}
- (2221) {Write-Debug 'Username not found.'}
- (53) {Write-Debug 'Hostname could not be found'}
- }
+ # free up the result buffer
+ $Null = $Netapi32::NetApiBufferFree($PtrInfo)
+ }
+ else
+ {
+ switch ($Result) {
+ (5) {Write-Debug 'The user does not have access to the requested information.'}
+ (124) {Write-Debug 'The value specified for the level parameter is not valid.'}
+ (87) {Write-Debug 'The specified parameter is not valid.'}
+ (234) {Write-Debug 'More entries are available. Specify a large enough buffer to receive all entries.'}
+ (8) {Write-Debug 'Insufficient memory is available.'}
+ (2312) {Write-Debug 'A session does not exist with the computer name.'}
+ (2351) {Write-Debug 'The computer name is not valid.'}
+ (2221) {Write-Debug 'Username not found.'}
+ (53) {Write-Debug 'Hostname could not be found'}
}
}
}
-function Get-NetRDPSession {
+filter Get-NetRDPSession {
<#
.SYNOPSIS
@@ -6742,132 +7676,130 @@ function Get-NetRDPSession {
PS C:\> Get-NetRDPSession -ComputerName "sqlserver"
Returns active RDP/terminal sessions on the 'sqlserver' host.
+
+ .EXAMPLE
+
+ PS C:\> Get-NetDomainController | Get-NetRDPSession
+
+ Returns active RDP/terminal sessions on all domain controllers.
#>
[CmdletBinding()]
param(
[Parameter(ValueFromPipeline=$True)]
[Alias('HostName')]
- [String]
+ [Object[]]
+ [ValidateNotNullOrEmpty()]
$ComputerName = 'localhost'
)
-
- begin {
- if ($PSBoundParameters['Debug']) {
- $DebugPreference = 'Continue'
- }
- }
-
- process {
- # process multiple host object types from the pipeline
- $ComputerName = Get-NameField -Object $ComputerName
+ # extract the computer name from whatever object was passed on the pipeline
+ $Computer = $ComputerName | Get-NameField
- # open up a handle to the Remote Desktop Session host
- $Handle = $Wtsapi32::WTSOpenServerEx($ComputerName)
+ # open up a handle to the Remote Desktop Session host
+ $Handle = $Wtsapi32::WTSOpenServerEx($Computer)
- # if we get a non-zero handle back, everything was successful
- if ($Handle -ne 0) {
+ # if we get a non-zero handle back, everything was successful
+ if ($Handle -ne 0) {
- Write-Debug "WTSOpenServerEx handle: $Handle"
+ Write-Debug "WTSOpenServerEx handle: $Handle"
- # arguments for WTSEnumerateSessionsEx
- $ppSessionInfo = [IntPtr]::Zero
- $pCount = 0
-
- # get information on all current sessions
- $Result = $Wtsapi32::WTSEnumerateSessionsEx($Handle, [ref]1, 0, [ref]$ppSessionInfo, [ref]$pCount)
+ # arguments for WTSEnumerateSessionsEx
+ $ppSessionInfo = [IntPtr]::Zero
+ $pCount = 0
+
+ # get information on all current sessions
+ $Result = $Wtsapi32::WTSEnumerateSessionsEx($Handle, [ref]1, 0, [ref]$ppSessionInfo, [ref]$pCount)
- # Locate the offset of the initial intPtr
- $Offset = $ppSessionInfo.ToInt64()
+ # Locate the offset of the initial intPtr
+ $Offset = $ppSessionInfo.ToInt64()
- Write-Debug "WTSEnumerateSessionsEx result: $Result"
- Write-Debug "pCount: $pCount"
+ Write-Debug "WTSEnumerateSessionsEx result: $Result"
+ Write-Debug "pCount: $pCount"
- if (($Result -ne 0) -and ($Offset -gt 0)) {
+ if (($Result -ne 0) -and ($Offset -gt 0)) {
- # Work out how mutch to increment the pointer by finding out the size of the structure
- $Increment = $WTS_SESSION_INFO_1::GetSize()
+ # Work out how mutch to increment the pointer by finding out the size of the structure
+ $Increment = $WTS_SESSION_INFO_1::GetSize()
- # parse all the result structures
- for ($i = 0; ($i -lt $pCount); $i++) {
-
- # create a new int ptr at the given offset and cast
- # the pointer as our result structure
- $NewIntPtr = New-Object System.Intptr -ArgumentList $Offset
- $Info = $NewIntPtr -as $WTS_SESSION_INFO_1
+ # parse all the result structures
+ for ($i = 0; ($i -lt $pCount); $i++) {
+
+ # create a new int ptr at the given offset and cast
+ # the pointer as our result structure
+ $NewIntPtr = New-Object System.Intptr -ArgumentList $Offset
+ $Info = $NewIntPtr -as $WTS_SESSION_INFO_1
- $RDPSession = New-Object PSObject
+ $RDPSession = New-Object PSObject
- if ($Info.pHostName) {
- $RDPSession | Add-Member Noteproperty 'ComputerName' $Info.pHostName
- }
- else {
- # if no hostname returned, use the specified hostname
- $RDPSession | Add-Member Noteproperty 'ComputerName' $ComputerName
- }
+ if ($Info.pHostName) {
+ $RDPSession | Add-Member Noteproperty 'ComputerName' $Info.pHostName
+ }
+ else {
+ # if no hostname returned, use the specified hostname
+ $RDPSession | Add-Member Noteproperty 'ComputerName' $Computer
+ }
- $RDPSession | Add-Member Noteproperty 'SessionName' $Info.pSessionName
+ $RDPSession | Add-Member Noteproperty 'SessionName' $Info.pSessionName
- if ($(-not $Info.pDomainName) -or ($Info.pDomainName -eq '')) {
- # if a domain isn't returned just use the username
- $RDPSession | Add-Member Noteproperty 'UserName' "$($Info.pUserName)"
- }
- else {
- $RDPSession | Add-Member Noteproperty 'UserName' "$($Info.pDomainName)\$($Info.pUserName)"
- }
+ if ($(-not $Info.pDomainName) -or ($Info.pDomainName -eq '')) {
+ # if a domain isn't returned just use the username
+ $RDPSession | Add-Member Noteproperty 'UserName' "$($Info.pUserName)"
+ }
+ else {
+ $RDPSession | Add-Member Noteproperty 'UserName' "$($Info.pDomainName)\$($Info.pUserName)"
+ }
- $RDPSession | Add-Member Noteproperty 'ID' $Info.SessionID
- $RDPSession | Add-Member Noteproperty 'State' $Info.State
+ $RDPSession | Add-Member Noteproperty 'ID' $Info.SessionID
+ $RDPSession | Add-Member Noteproperty 'State' $Info.State
- $ppBuffer = [IntPtr]::Zero
- $pBytesReturned = 0
+ $ppBuffer = [IntPtr]::Zero
+ $pBytesReturned = 0
- # query for the source client IP with WTSQuerySessionInformation
- # https://msdn.microsoft.com/en-us/library/aa383861(v=vs.85).aspx
- $Result2 = $Wtsapi32::WTSQuerySessionInformation($Handle, $Info.SessionID, 14, [ref]$ppBuffer, [ref]$pBytesReturned)
+ # query for the source client IP with WTSQuerySessionInformation
+ # https://msdn.microsoft.com/en-us/library/aa383861(v=vs.85).aspx
+ $Result2 = $Wtsapi32::WTSQuerySessionInformation($Handle, $Info.SessionID, 14, [ref]$ppBuffer, [ref]$pBytesReturned)
- $Offset2 = $ppBuffer.ToInt64()
- $NewIntPtr2 = New-Object System.Intptr -ArgumentList $Offset2
- $Info2 = $NewIntPtr2 -as $WTS_CLIENT_ADDRESS
+ $Offset2 = $ppBuffer.ToInt64()
+ $NewIntPtr2 = New-Object System.Intptr -ArgumentList $Offset2
+ $Info2 = $NewIntPtr2 -as $WTS_CLIENT_ADDRESS
- $SourceIP = $Info2.Address
- if($SourceIP[2] -ne 0) {
- $SourceIP = [String]$SourceIP[2]+"."+[String]$SourceIP[3]+"."+[String]$SourceIP[4]+"."+[String]$SourceIP[5]
- }
- else {
- $SourceIP = $Null
- }
+ $SourceIP = $Info2.Address
+ if($SourceIP[2] -ne 0) {
+ $SourceIP = [String]$SourceIP[2]+"."+[String]$SourceIP[3]+"."+[String]$SourceIP[4]+"."+[String]$SourceIP[5]
+ }
+ else {
+ $SourceIP = $Null
+ }
- $RDPSession | Add-Member Noteproperty 'SourceIP' $SourceIP
- $RDPSession
+ $RDPSession | Add-Member Noteproperty 'SourceIP' $SourceIP
+ $RDPSession
- # free up the memory buffer
- $Null = $Wtsapi32::WTSFreeMemory($ppBuffer)
+ # free up the memory buffer
+ $Null = $Wtsapi32::WTSFreeMemory($ppBuffer)
- $Offset += $Increment
- }
- # free up the memory result buffer
- $Null = $Wtsapi32::WTSFreeMemoryEx(2, $ppSessionInfo, $pCount)
+ $Offset += $Increment
}
- # Close off the service handle
- $Null = $Wtsapi32::WTSCloseServer($Handle)
- }
- else {
- # otherwise it failed - get the last error
- # error codes - http://msdn.microsoft.com/en-us/library/windows/desktop/ms681382(v=vs.85).aspx
- $Err = $Kernel32::GetLastError()
- Write-Verbuse "LastError: $Err"
+ # free up the memory result buffer
+ $Null = $Wtsapi32::WTSFreeMemoryEx(2, $ppSessionInfo, $pCount)
}
+ # Close off the service handle
+ $Null = $Wtsapi32::WTSCloseServer($Handle)
+ }
+ else {
+ # otherwise it failed - get the last error
+ # error codes - http://msdn.microsoft.com/en-us/library/windows/desktop/ms681382(v=vs.85).aspx
+ $Err = $Kernel32::GetLastError()
+ Write-Verbose "LastError: $Err"
}
}
-function Invoke-CheckLocalAdminAccess {
+filter Invoke-CheckLocalAdminAccess {
<#
.SYNOPSIS
- This function will use the OpenSCManagerW Win32API call to to establish
+ This function will use the OpenSCManagerW Win32API call to establish
a handle to the remote host. If this succeeds, the current user context
has local administrator acess to the target.
@@ -6890,6 +7822,12 @@ function Invoke-CheckLocalAdminAccess {
Returns active sessions on the local host.
+ .EXAMPLE
+
+ PS C:\> Get-NetComputer | Invoke-CheckLocalAdminAccess
+
+ Sees what machines in the domain the current user has access to.
+
.LINK
https://github.com/rapid7/metasploit-framework/blob/master/modules/post/windows/gather/local_admin_search_enum.rb
@@ -6899,46 +7837,119 @@ function Invoke-CheckLocalAdminAccess {
[CmdletBinding()]
param(
[Parameter(ValueFromPipeline=$True)]
- [String]
[Alias('HostName')]
+ [Object[]]
+ [ValidateNotNullOrEmpty()]
$ComputerName = 'localhost'
)
- begin {
- if ($PSBoundParameters['Debug']) {
- $DebugPreference = 'Continue'
- }
+ # extract the computer name from whatever object was passed on the pipeline
+ $Computer = $ComputerName | Get-NameField
+
+ # 0xF003F - SC_MANAGER_ALL_ACCESS
+ # http://msdn.microsoft.com/en-us/library/windows/desktop/ms685981(v=vs.85).aspx
+ $Handle = $Advapi32::OpenSCManagerW("\\$Computer", 'ServicesActive', 0xF003F)
+
+ Write-Debug "Invoke-CheckLocalAdminAccess handle: $Handle"
+
+ $IsAdmin = New-Object PSObject
+ $IsAdmin | Add-Member Noteproperty 'ComputerName' $Computer
+
+ # if we get a non-zero handle back, everything was successful
+ if ($Handle -ne 0) {
+ # Close off the service handle
+ $Null = $Advapi32::CloseServiceHandle($Handle)
+ $IsAdmin | Add-Member Noteproperty 'IsAdmin' $True
+ }
+ else {
+ # otherwise it failed - get the last error
+ # error codes - http://msdn.microsoft.com/en-us/library/windows/desktop/ms681382(v=vs.85).aspx
+ $Err = $Kernel32::GetLastError()
+ Write-Debug "Invoke-CheckLocalAdminAccess LastError: $Err"
+ $IsAdmin | Add-Member Noteproperty 'IsAdmin' $False
}
- process {
+ $IsAdmin
+}
- # process multiple host object types from the pipeline
- $ComputerName = Get-NameField -Object $ComputerName
- # 0xF003F - SC_MANAGER_ALL_ACCESS
- # http://msdn.microsoft.com/en-us/library/windows/desktop/ms685981(v=vs.85).aspx
- $Handle = $Advapi32::OpenSCManagerW("\\$ComputerName", 'ServicesActive', 0xF003F)
+filter Get-SiteName {
+<#
+ .SYNOPSIS
- Write-Debug "Invoke-CheckLocalAdminAccess handle: $Handle"
+ This function will use the DsGetSiteName Win32API call to look up the
+ name of the site where a specified computer resides.
- # if we get a non-zero handle back, everything was successful
- if ($Handle -ne 0) {
- # Close off the service handle
- $Null = $Advapi32::CloseServiceHandle($Handle)
- $True
- }
- else {
- # otherwise it failed - get the last error
- # error codes - http://msdn.microsoft.com/en-us/library/windows/desktop/ms681382(v=vs.85).aspx
- $Err = $Kernel32::GetLastError()
- Write-Debug "Invoke-CheckLocalAdminAccess LastError: $Err"
- $False
- }
+ .PARAMETER ComputerName
+
+ The hostname to look the site up for, default to localhost.
+
+ .EXAMPLE
+
+ PS C:\> Get-SiteName -ComputerName WINDOWS1
+
+ Returns the site for WINDOWS1.testlab.local.
+
+ .EXAMPLE
+
+ PS C:\> Get-NetComputer | Invoke-CheckLocalAdminAccess
+
+ Returns the sites for every machine in AD.
+#>
+ [CmdletBinding()]
+ param(
+ [Parameter(ValueFromPipeline=$True)]
+ [Alias('HostName')]
+ [Object[]]
+ [ValidateNotNullOrEmpty()]
+ $ComputerName = $Env:ComputerName
+ )
+
+ # extract the computer name from whatever object was passed on the pipeline
+ $Computer = $ComputerName | Get-NameField
+
+ # if we get an IP address, try to resolve the IP to a hostname
+ if($Computer -match '^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$') {
+ $IPAddress = $Computer
+ $Computer = [System.Net.Dns]::GetHostByAddress($Computer)
}
+ else {
+ $IPAddress = @(Get-IPAddress -ComputerName $Computer)[0].IPAddress
+ }
+
+ $PtrInfo = [IntPtr]::Zero
+
+ $Result = $Netapi32::DsGetSiteName($Computer, [ref]$PtrInfo)
+ Write-Debug "Get-SiteName result for $Computer : $Result"
+
+ $ComputerSite = New-Object PSObject
+ $ComputerSite | Add-Member Noteproperty 'ComputerName' $Computer
+ $ComputerSite | Add-Member Noteproperty 'IPAddress' $IPAddress
+
+ if ($Result -eq 0) {
+ $Sitename = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($PtrInfo)
+ $ComputerSite | Add-Member Noteproperty 'SiteName' $Sitename
+ }
+ elseif($Result -eq 1210) {
+ Write-Verbose "Computername '$Computer' is not in a valid form."
+ $ComputerSite | Add-Member Noteproperty 'SiteName' 'ERROR'
+ }
+ elseif($Result -eq 1919) {
+ Write-Verbose "Computer '$Computer' is not in a site"
+
+ $ComputerSite | Add-Member Noteproperty 'SiteName' $Null
+ }
+ else {
+ Write-Verbose "Error"
+ $ComputerSite | Add-Member Noteproperty 'SiteName' 'ERROR'
+ }
+
+ $Null = $Netapi32::NetApiBufferFree($PtrInfo)
+ $ComputerSite
}
-function Get-LastLoggedOn {
+filter Get-LastLoggedOn {
<#
.SYNOPSIS
@@ -6953,6 +7964,10 @@ function Get-LastLoggedOn {
The hostname to query for the last logged on user.
Defaults to the localhost.
+ .PARAMETER Credential
+
+ A [Management.Automation.PSCredential] object for the remote connection.
+
.EXAMPLE
PS C:\> Get-LastLoggedOn
@@ -6964,38 +7979,58 @@ function Get-LastLoggedOn {
PS C:\> Get-LastLoggedOn -ComputerName WINDOWS1
Returns the last user logged onto WINDOWS1
+
+ .EXAMPLE
+
+ PS C:\> Get-NetComputer | Get-LastLoggedOn
+
+ Returns the last user logged onto all machines in the domain.
#>
[CmdletBinding()]
param(
[Parameter(ValueFromPipeline=$True)]
- [String]
- [Alias('HostName')]
- $ComputerName = "."
+ [Alias('HostName')]
+ [Object[]]
+ [ValidateNotNullOrEmpty()]
+ $ComputerName = 'localhost',
+
+ [Management.Automation.PSCredential]
+ $Credential
)
- process {
+ # extract the computer name from whatever object was passed on the pipeline
+ $Computer = $ComputerName | Get-NameField
- # process multiple host object types from the pipeline
- $ComputerName = Get-NameField -Object $ComputerName
+ # HKEY_LOCAL_MACHINE
+ $HKLM = 2147483650
- # try to open up the remote registry key to grab the last logged on user
- try {
- $Reg = [WMIClass]"\\$ComputerName\root\default:stdRegProv"
- $HKLM = 2147483650
- $Key = "SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\LogonUI"
- $Value = "LastLoggedOnUser"
- $Reg.GetStringValue($HKLM, $Key, $Value).sValue
+ # try to open up the remote registry key to grab the last logged on user
+ try {
+
+ if($Credential) {
+ $Reg = Get-WmiObject -List 'StdRegProv' -Namespace root\default -Computername $Computer -Credential $Credential -ErrorAction SilentlyContinue
}
- catch {
- Write-Warning "[!] Error opening remote registry on $ComputerName. Remote registry likely not enabled."
- $Null
+ else {
+ $Reg = Get-WmiObject -List 'StdRegProv' -Namespace root\default -Computername $Computer -ErrorAction SilentlyContinue
}
+
+ $Key = "SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\LogonUI"
+ $Value = "LastLoggedOnUser"
+ $LastUser = $Reg.GetStringValue($HKLM, $Key, $Value).sValue
+
+ $LastLoggedOn = New-Object PSObject
+ $LastLoggedOn | Add-Member Noteproperty 'ComputerName' $Computer
+ $LastLoggedOn | Add-Member Noteproperty 'LastLoggedOn' $LastUser
+ $LastLoggedOn
+ }
+ catch {
+ Write-Warning "[!] Error opening remote registry on $Computer. Remote registry likely not enabled."
}
}
-function Get-CachedRDPConnection {
+filter Get-CachedRDPConnection {
<#
.SYNOPSIS
@@ -7011,14 +8046,9 @@ function Get-CachedRDPConnection {
The hostname to query for RDP client information.
Defaults to localhost.
- .PARAMETER RemoteUserName
-
- The "domain\username" to use for the WMI call on the remote system.
- If supplied, 'RemotePassword' must be supplied as well.
-
- .PARAMETER RemotePassword
+ .PARAMETER Credential
- The password to use for the WMI call on a remote system.
+ A [Management.Automation.PSCredential] object for the remote connection.
.EXAMPLE
@@ -7034,105 +8064,99 @@ function Get-CachedRDPConnection {
.EXAMPLE
- PS C:\> Get-CachedRDPConnection -ComputerName WINDOWS2.testlab.local -RemoteUserName DOMAIN\user -RemotePassword Password123!
+ PS C:\> Get-CachedRDPConnection -ComputerName WINDOWS2.testlab.local -Credential $Cred
Returns the RDP connection client information for the WINDOWS2.testlab.local machine using alternate credentials.
+
+ .EXAMPLE
+
+ PS C:\> Get-NetComputer | Get-CachedRDPConnection
+
+ Get cached RDP information for all machines in the domain.
#>
[CmdletBinding()]
param(
[Parameter(ValueFromPipeline=$True)]
- [String]
- $ComputerName = "localhost",
-
- [String]
- $RemoteUserName,
+ [Alias('HostName')]
+ [Object[]]
+ [ValidateNotNullOrEmpty()]
+ $ComputerName = 'localhost',
- [String]
- $RemotePassword
+ [Management.Automation.PSCredential]
+ $Credential
)
- begin {
- if ($RemoteUserName -and $RemotePassword) {
- $Password = $RemotePassword | ConvertTo-SecureString -AsPlainText -Force
- $Credential = New-Object System.Management.Automation.PSCredential($RemoteUserName,$Password)
- }
+ # extract the computer name from whatever object was passed on the pipeline
+ $Computer = $ComputerName | Get-NameField
- # HKEY_USERS
- $HKU = 2147483651
- }
-
- process {
-
- try {
- if($Credential) {
- $Reg = Get-Wmiobject -List 'StdRegProv' -Namespace root\default -Computername $ComputerName -Credential $Credential -ErrorAction SilentlyContinue
- }
- else {
- $Reg = Get-Wmiobject -List 'StdRegProv' -Namespace root\default -Computername $ComputerName -ErrorAction SilentlyContinue
- }
- }
- catch {
- Write-Warning "Error accessing $ComputerName, likely insufficient permissions or firewall rules on host"
- }
+ # HKEY_USERS
+ $HKU = 2147483651
- if(!$Reg) {
- Write-Warning "Error accessing $ComputerName, likely insufficient permissions or firewall rules on host"
+ try {
+ if($Credential) {
+ $Reg = Get-WmiObject -List 'StdRegProv' -Namespace root\default -Computername $Computer -Credential $Credential -ErrorAction SilentlyContinue
}
else {
- # extract out the SIDs of domain users in this hive
- $UserSIDs = ($Reg.EnumKey($HKU, "")).sNames | ? { $_ -match 'S-1-5-21-[0-9]+-[0-9]+-[0-9]+-[0-9]+$' }
-
- foreach ($UserSID in $UserSIDs) {
-
- try {
- $UserName = Convert-SidToName $UserSID
+ $Reg = Get-WmiObject -List 'StdRegProv' -Namespace root\default -Computername $Computer -ErrorAction SilentlyContinue
+ }
- # pull out all the cached RDP connections
- $ConnectionKeys = $Reg.EnumValues($HKU,"$UserSID\Software\Microsoft\Terminal Server Client\Default").sNames
+ # extract out the SIDs of domain users in this hive
+ $UserSIDs = ($Reg.EnumKey($HKU, "")).sNames | ? { $_ -match 'S-1-5-21-[0-9]+-[0-9]+-[0-9]+-[0-9]+$' }
- foreach ($Connection in $ConnectionKeys) {
- # make sure this key is a cached connection
- if($Connection -match 'MRU.*') {
- $TargetServer = $Reg.GetStringValue($HKU, "$UserSID\Software\Microsoft\Terminal Server Client\Default", $Connection).sValue
-
- $FoundConnection = New-Object PSObject
- $FoundConnection | Add-Member Noteproperty 'ComputerName' $ComputerName
- $FoundConnection | Add-Member Noteproperty 'UserName' $UserName
- $FoundConnection | Add-Member Noteproperty 'UserSID' $UserSID
- $FoundConnection | Add-Member Noteproperty 'TargetServer' $TargetServer
- $FoundConnection | Add-Member Noteproperty 'UsernameHint' $Null
- $FoundConnection
- }
- }
+ foreach ($UserSID in $UserSIDs) {
- # pull out all the cached server info with username hints
- $ServerKeys = $Reg.EnumKey($HKU,"$UserSID\Software\Microsoft\Terminal Server Client\Servers").sNames
+ try {
+ $UserName = Convert-SidToName $UserSID
- foreach ($Server in $ServerKeys) {
+ # pull out all the cached RDP connections
+ $ConnectionKeys = $Reg.EnumValues($HKU,"$UserSID\Software\Microsoft\Terminal Server Client\Default").sNames
- $UsernameHint = $Reg.GetStringValue($HKU, "$UserSID\Software\Microsoft\Terminal Server Client\Servers\$Server", 'UsernameHint').sValue
+ foreach ($Connection in $ConnectionKeys) {
+ # make sure this key is a cached connection
+ if($Connection -match 'MRU.*') {
+ $TargetServer = $Reg.GetStringValue($HKU, "$UserSID\Software\Microsoft\Terminal Server Client\Default", $Connection).sValue
$FoundConnection = New-Object PSObject
- $FoundConnection | Add-Member Noteproperty 'ComputerName' $ComputerName
+ $FoundConnection | Add-Member Noteproperty 'ComputerName' $Computer
$FoundConnection | Add-Member Noteproperty 'UserName' $UserName
$FoundConnection | Add-Member Noteproperty 'UserSID' $UserSID
- $FoundConnection | Add-Member Noteproperty 'TargetServer' $Server
- $FoundConnection | Add-Member Noteproperty 'UsernameHint' $UsernameHint
- $FoundConnection
+ $FoundConnection | Add-Member Noteproperty 'TargetServer' $TargetServer
+ $FoundConnection | Add-Member Noteproperty 'UsernameHint' $Null
+ $FoundConnection
}
-
}
- catch {
- Write-Debug "Error: $_"
+
+ # pull out all the cached server info with username hints
+ $ServerKeys = $Reg.EnumKey($HKU,"$UserSID\Software\Microsoft\Terminal Server Client\Servers").sNames
+
+ foreach ($Server in $ServerKeys) {
+
+ $UsernameHint = $Reg.GetStringValue($HKU, "$UserSID\Software\Microsoft\Terminal Server Client\Servers\$Server", 'UsernameHint').sValue
+
+ $FoundConnection = New-Object PSObject
+ $FoundConnection | Add-Member Noteproperty 'ComputerName' $Computer
+ $FoundConnection | Add-Member Noteproperty 'UserName' $UserName
+ $FoundConnection | Add-Member Noteproperty 'UserSID' $UserSID
+ $FoundConnection | Add-Member Noteproperty 'TargetServer' $Server
+ $FoundConnection | Add-Member Noteproperty 'UsernameHint' $UsernameHint
+ $FoundConnection
}
+
+ }
+ catch {
+ Write-Debug "Error: $_"
}
}
+
+ }
+ catch {
+ Write-Warning "Error accessing $Computer, likely insufficient permissions or firewall rules on host: $_"
}
}
-function Get-NetProcess {
+filter Get-NetProcess {
<#
.SYNOPSIS
@@ -7142,14 +8166,9 @@ function Get-NetProcess {
The hostname to query processes. Defaults to the local host name.
- .PARAMETER RemoteUserName
-
- The "domain\username" to use for the WMI call on a remote system.
- If supplied, 'RemotePassword' must be supplied as well.
-
- .PARAMETER RemotePassword
+ .PARAMETER Credential
- The password to use for the WMI call on a remote system.
+ A [Management.Automation.PSCredential] object for the remote connection.
.EXAMPLE
@@ -7161,74 +8180,40 @@ function Get-NetProcess {
[CmdletBinding()]
param(
[Parameter(ValueFromPipeline=$True)]
- [String]
- $ComputerName,
-
- [String]
- $RemoteUserName,
+ [Alias('HostName')]
+ [Object[]]
+ [ValidateNotNullOrEmpty()]
+ $ComputerName = [System.Net.Dns]::GetHostName(),
- [String]
- $RemotePassword
+ [Management.Automation.PSCredential]
+ $Credential
)
- process {
-
- if($ComputerName) {
- # process multiple host object types from the pipeline
- $ComputerName = Get-NameField -Object $ComputerName
+ # extract the computer name from whatever object was passed on the pipeline
+ $Computer = $ComputerName | Get-NameField
+
+ try {
+ if($Credential) {
+ $Processes = Get-WMIobject -Class Win32_process -ComputerName $ComputerName -Credential $Credential
}
else {
- # default to the local hostname
- $ComputerName = [System.Net.Dns]::GetHostName()
+ $Processes = Get-WMIobject -Class Win32_process -ComputerName $ComputerName
}
- $Credential = $Null
-
- if($RemoteUserName) {
- if($RemotePassword) {
- $Password = $RemotePassword | ConvertTo-SecureString -AsPlainText -Force
- $Credential = New-Object System.Management.Automation.PSCredential($RemoteUserName,$Password)
-
- # try to enumerate the processes on the remote machine using the supplied credential
- try {
- Get-WMIobject -Class Win32_process -ComputerName $ComputerName -Credential $Credential | ForEach-Object {
- $Owner = $_.getowner();
- $Process = New-Object PSObject
- $Process | Add-Member Noteproperty 'ComputerName' $ComputerName
- $Process | Add-Member Noteproperty 'ProcessName' $_.ProcessName
- $Process | Add-Member Noteproperty 'ProcessID' $_.ProcessID
- $Process | Add-Member Noteproperty 'Domain' $Owner.Domain
- $Process | Add-Member Noteproperty 'User' $Owner.User
- $Process
- }
- }
- catch {
- Write-Verbose "[!] Error enumerating remote processes, access likely denied: $_"
- }
- }
- else {
- Write-Warning "[!] RemotePassword must also be supplied!"
- }
- }
- else {
- # try to enumerate the processes on the remote machine
- try {
- Get-WMIobject -Class Win32_process -ComputerName $ComputerName | ForEach-Object {
- $Owner = $_.getowner();
- $Process = New-Object PSObject
- $Process | Add-Member Noteproperty 'ComputerName' $ComputerName
- $Process | Add-Member Noteproperty 'ProcessName' $_.ProcessName
- $Process | Add-Member Noteproperty 'ProcessID' $_.ProcessID
- $Process | Add-Member Noteproperty 'Domain' $Owner.Domain
- $Process | Add-Member Noteproperty 'User' $Owner.User
- $Process
- }
- }
- catch {
- Write-Verbose "[!] Error enumerating remote processes, access likely denied: $_"
- }
+ $Processes | ForEach-Object {
+ $Owner = $_.getowner();
+ $Process = New-Object PSObject
+ $Process | Add-Member Noteproperty 'ComputerName' $Computer
+ $Process | Add-Member Noteproperty 'ProcessName' $_.ProcessName
+ $Process | Add-Member Noteproperty 'ProcessID' $_.ProcessID
+ $Process | Add-Member Noteproperty 'Domain' $Owner.Domain
+ $Process | Add-Member Noteproperty 'User' $Owner.User
+ $Process
}
}
+ catch {
+ Write-Verbose "[!] Error enumerating remote processes on $Computer, access likely denied: $_"
+ }
}
@@ -7289,10 +8274,6 @@ function Find-InterestingFile {
Switch. Mount target remote path with temporary PSDrives.
- .PARAMETER Credential
-
- Credential to use to mount the PSDrive for searching.
-
.OUTPUTS
The full path, owner, lastaccess time, lastwrite time, and size for each found file.
@@ -7323,15 +8304,15 @@ function Find-InterestingFile {
http://www.harmj0y.net/blog/redteaming/file-server-triage-on-red-team-engagements/
#>
-
- [CmdletBinding()]
+
param(
[Parameter(ValueFromPipeline=$True)]
[String]
$Path = '.\',
+ [Alias('Terms')]
[String[]]
- $Terms,
+ $SearchTerms = @('pass', 'sensitive', 'admin', 'login', 'secret', 'unattend*.xml', '.vmdk', 'creds', 'credential', '.config'),
[Switch]
$OfficeDocs,
@@ -7361,35 +8342,19 @@ function Find-InterestingFile {
$OutFile,
[Switch]
- $UsePSDrive,
-
- [System.Management.Automation.PSCredential]
- $Credential = [System.Management.Automation.PSCredential]::Empty
+ $UsePSDrive
)
begin {
- # default search terms
- $SearchTerms = @('pass', 'sensitive', 'admin', 'login', 'secret', 'unattend*.xml', '.vmdk', 'creds', 'credential', '.config')
- if(!$Path.EndsWith('\')) {
- $Path = $Path + '\'
- }
- if($Credential -ne [System.Management.Automation.PSCredential]::Empty) { $UsePSDrive = $True }
+ $Path += if(!$Path.EndsWith('\')) {"\"}
- # check if custom search terms were passed
- if ($Terms) {
- if($Terms -isnot [system.array]) {
- $Terms = @($Terms)
- }
- $SearchTerms = $Terms
+ if ($Credential) {
+ $UsePSDrive = $True
}
- if(-not $SearchTerms[0].startswith("*")) {
- # append wildcards to the front and back of all search terms
- for ($i = 0; $i -lt $SearchTerms.Count; $i++) {
- $SearchTerms[$i] = "*$($SearchTerms[$i])*"
- }
- }
+ # append wildcards to the front and back of all search terms
+ $SearchTerms = $SearchTerms | ForEach-Object { if($_ -notmatch '^\*.*\*$') {"*$($_)*"} else{$_} }
# search just for office documents if specified
if ($OfficeDocs) {
@@ -7399,29 +8364,31 @@ function Find-InterestingFile {
# find .exe's accessed within the last 7 days
if($FreshEXEs) {
# get an access time limit of 7 days ago
- $LastAccessTime = (get-date).AddDays(-7).ToString('MM/dd/yyyy')
+ $LastAccessTime = (Get-Date).AddDays(-7).ToString('MM/dd/yyyy')
$SearchTerms = '*.exe'
}
if($UsePSDrive) {
# if we're PSDrives, create a temporary mount point
+
$Parts = $Path.split('\')
$FolderPath = $Parts[0..($Parts.length-2)] -join '\'
$FilePath = $Parts[-1]
+
$RandDrive = ("abcdefghijklmnopqrstuvwxyz".ToCharArray() | Get-Random -Count 7) -join ''
- Write-Verbose "Mounting path $Path using a temp PSDrive at $RandDrive"
+ Write-Verbose "Mounting path '$Path' using a temp PSDrive at $RandDrive"
try {
- $Null = New-PSDrive -Name $RandDrive -Credential $Credential -PSProvider FileSystem -Root $FolderPath -ErrorAction Stop
+ $Null = New-PSDrive -Name $RandDrive -PSProvider FileSystem -Root $FolderPath -ErrorAction Stop
}
catch {
- Write-Debug "Error mounting path $Path : $_"
+ Write-Debug "Error mounting path '$Path' : $_"
return $Null
}
# so we can cd/dir the new drive
- $Path = $RandDrive + ":\" + $FilePath
+ $Path = "${RandDrive}:\${FilePath}"
}
}
@@ -7475,7 +8442,7 @@ function Find-InterestingFile {
end {
if($UsePSDrive -and $RandDrive) {
Write-Verbose "Removing temp PSDrive $RandDrive"
- Get-PSDrive -Name $RandDrive -ErrorAction SilentlyContinue | Remove-PSDrive
+ Get-PSDrive -Name $RandDrive -ErrorAction SilentlyContinue | Remove-PSDrive -Force
}
}
}
@@ -7504,6 +8471,7 @@ function Invoke-ThreadedFunction {
$ScriptParameters,
[Int]
+ [ValidateRange(1,100)]
$Threads = 20,
[Switch]
@@ -7898,8 +8866,8 @@ function Invoke-UserHunter {
[Switch]
$ForeignUsers,
- [ValidateRange(1,100)]
[Int]
+ [ValidateRange(1,100)]
$Threads
)
@@ -8003,7 +8971,7 @@ function Invoke-UserHunter {
if($ForeignUsers) {
# if we're searching for user results not in the primary domain
- $krbtgtName = Convert-CanonicaltoNT4 -ObjectName "krbtgt@$($Domain)"
+ $krbtgtName = Convert-ADName -ObjectName "krbtgt@$($Domain)" -InputType Simple -OutputType NT4
$DomainShortName = $krbtgtName.split("\")[0]
}
}
@@ -8064,7 +9032,7 @@ function Invoke-UserHunter {
$User
} | Where-Object {$_}
- }
+ }
}
else {
ForEach ($Domain in $TargetDomains) {
@@ -8103,12 +9071,12 @@ function Invoke-UserHunter {
$TargetUsers | Where-Object {$UserName -like $_.MemberName} | ForEach-Object {
- $IP = Get-IPAddress -ComputerName $ComputerName
+ $IPAddress = @(Get-IPAddress -ComputerName $ComputerName)[0].IPAddress
$FoundUser = New-Object PSObject
$FoundUser | Add-Member Noteproperty 'UserDomain' $_.MemberDomain
$FoundUser | Add-Member Noteproperty 'UserName' $UserName
$FoundUser | Add-Member Noteproperty 'ComputerName' $ComputerName
- $FoundUser | Add-Member Noteproperty 'IP' $IP
+ $FoundUser | Add-Member Noteproperty 'IPAddress' $IPAddress
$FoundUser | Add-Member Noteproperty 'SessionFrom' $CName
# see if we're checking to see if we have local admin access on this machine
@@ -8121,7 +9089,7 @@ function Invoke-UserHunter {
}
$FoundUser
}
- }
+ }
}
}
if(!$Stealth) {
@@ -8148,12 +9116,12 @@ function Invoke-UserHunter {
}
}
if($Proceed) {
- $IP = Get-IPAddress -ComputerName $ComputerName
+ $IPAddress = @(Get-IPAddress -ComputerName $ComputerName)[0].IPAddress
$FoundUser = New-Object PSObject
$FoundUser | Add-Member Noteproperty 'UserDomain' $UserDomain
$FoundUser | Add-Member Noteproperty 'UserName' $UserName
$FoundUser | Add-Member Noteproperty 'ComputerName' $ComputerName
- $FoundUser | Add-Member Noteproperty 'IP' $IP
+ $FoundUser | Add-Member Noteproperty 'IPAddress' $IPAddress
$FoundUser | Add-Member Noteproperty 'SessionFrom' $Null
# see if we're checking to see if we have local admin access on this machine
@@ -8190,7 +9158,7 @@ function Invoke-UserHunter {
}
# kick off the threaded script block + arguments
- Invoke-ThreadedFunction -ComputerName $ComputerName -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams
+ Invoke-ThreadedFunction -ComputerName $ComputerName -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams -Threads $Threads
}
else {
@@ -8353,15 +9321,6 @@ function Invoke-ProcessHunter {
File of usernames to search for.
- .PARAMETER RemoteUserName
-
- The "domain\username" to use for the WMI call on a remote system.
- If supplied, 'RemotePassword' must be supplied as well.
-
- .PARAMETER RemotePassword
-
- The password to use for the WMI call on a remote system.
-
.PARAMETER StopOnSuccess
Switch. Stop hunting after finding after finding a target user/process.
@@ -8399,6 +9358,11 @@ function Invoke-ProcessHunter {
The maximum concurrent threads to execute.
+ .PARAMETER Credential
+
+ A [Management.Automation.PSCredential] object of alternate credentials
+ for connection to the target machine/domain.
+
.EXAMPLE
PS C:\> Invoke-ProcessHunter -Domain 'testing'
@@ -8472,12 +9436,6 @@ function Invoke-ProcessHunter {
[String]
$UserFile,
- [String]
- $RemoteUserName,
-
- [String]
- $RemotePassword,
-
[Switch]
$StopOnSuccess,
@@ -8504,7 +9462,10 @@ function Invoke-ProcessHunter {
[ValidateRange(1,100)]
[Int]
- $Threads
+ $Threads,
+
+ [Management.Automation.PSCredential]
+ $Credential
)
begin {
@@ -8537,16 +9498,16 @@ function Invoke-ProcessHunter {
}
elseif($SearchForest) {
# get ALL the domains in the forest to search
- $TargetDomains = Get-NetForestDomain | ForEach-Object { $_.Name }
+ $TargetDomains = Get-NetForestDomain -DomainController $DomainController -Credential $Credential | ForEach-Object { $_.Name }
}
else {
# use the local domain
- $TargetDomains = @( (Get-NetDomain).name )
+ $TargetDomains = @( (Get-NetDomain -Domain $Domain -Credential $Credential).name )
}
ForEach ($Domain in $TargetDomains) {
Write-Verbose "[*] Querying domain $Domain for hosts"
- $ComputerName += Get-NetComputer -Domain $Domain -DomainController $DomainController -Filter $ComputerFilter -ADSpath $ComputerADSpath
+ $ComputerName += Get-NetComputer -Domain $Domain -DomainController $DomainController -Credential $Credential -Filter $ComputerFilter -ADSpath $ComputerADSpath
}
# remove any null target hosts, uniquify the list and shuffle it
@@ -8587,15 +9548,15 @@ function Invoke-ProcessHunter {
elseif($UserADSpath -or $UserFilter) {
ForEach ($Domain in $TargetDomains) {
Write-Verbose "[*] Querying domain $Domain for users"
- $TargetUsers += Get-NetUser -Domain $Domain -DomainController $DomainController -ADSpath $UserADSpath -Filter $UserFilter | ForEach-Object {
+ $TargetUsers += Get-NetUser -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $UserADSpath -Filter $UserFilter | ForEach-Object {
$_.samaccountname
} | Where-Object {$_}
- }
+ }
}
else {
ForEach ($Domain in $TargetDomains) {
Write-Verbose "[*] Querying domain $Domain for users of group '$GroupName'"
- $TargetUsers += Get-NetGroupMember -GroupName $GroupName -Domain $Domain -DomainController $DomainController| Foreach-Object {
+ $TargetUsers += Get-NetGroupMember -GroupName $GroupName -Domain $Domain -DomainController $DomainController -Credential $Credential| ForEach-Object {
$_.MemberName
}
}
@@ -8608,7 +9569,7 @@ function Invoke-ProcessHunter {
# script block that enumerates a server
$HostEnumBlock = {
- param($ComputerName, $Ping, $ProcessName, $TargetUsers, $RemoteUserName, $RemotePassword)
+ param($ComputerName, $Ping, $ProcessName, $TargetUsers, $Credential)
# optionally check if the server is up first
$Up = $True
@@ -8618,12 +9579,7 @@ function Invoke-ProcessHunter {
if($Up) {
# try to enumerate all active processes on the remote host
# and search for a specific process name
- if($RemoteUserName -and $RemotePassword) {
- $Processes = Get-NetProcess -RemoteUserName $RemoteUserName -RemotePassword $RemotePassword -ComputerName $ComputerName -ErrorAction SilentlyContinue
- }
- else {
- $Processes = Get-NetProcess -ComputerName $ComputerName -ErrorAction SilentlyContinue
- }
+ $Processes = Get-NetProcess -Credential $Credential -ComputerName $ComputerName -ErrorAction SilentlyContinue
ForEach ($Process in $Processes) {
# if we're hunting for a process name or comma-separated names
@@ -8654,12 +9610,11 @@ function Invoke-ProcessHunter {
'Ping' = $(-not $NoPing)
'ProcessName' = $ProcessName
'TargetUsers' = $TargetUsers
- 'RemoteUserName' = $RemoteUserName
- 'RemotePassword' = $RemotePassword
+ 'Credential' = $Credential
}
# kick off the threaded script block + arguments
- Invoke-ThreadedFunction -ComputerName $ComputerName -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams
+ Invoke-ThreadedFunction -ComputerName $ComputerName -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams -Threads $Threads
}
else {
@@ -8680,7 +9635,7 @@ function Invoke-ProcessHunter {
Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay)
Write-Verbose "[*] Enumerating server $Computer ($Counter of $($ComputerName.count))"
- $Result = Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $Computer, $False, $ProcessName, $TargetUsers, $RemoteUserName, $RemotePassword
+ $Result = Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $Computer, $False, $ProcessName, $TargetUsers, $Credential
$Result
if($Result -and $StopOnSuccess) {
@@ -8689,7 +9644,6 @@ function Invoke-ProcessHunter {
}
}
}
-
}
}
@@ -8775,6 +9729,11 @@ function Invoke-EventHunter {
The maximum concurrent threads to execute.
+ .PARAMETER Credential
+
+ A [Management.Automation.PSCredential] object of alternate credentials
+ for connection to the target domain.
+
.EXAMPLE
PS C:\> Invoke-EventHunter
@@ -8835,7 +9794,10 @@ function Invoke-EventHunter {
[ValidateRange(1,100)]
[Int]
- $Threads
+ $Threads,
+
+ [Management.Automation.PSCredential]
+ $Credential
)
begin {
@@ -8858,7 +9820,7 @@ function Invoke-EventHunter {
}
else {
# use the local domain
- $TargetDomains = @( (Get-NetDomain).name )
+ $TargetDomains = @( (Get-NetDomain -Credential $Credential).name )
}
#####################################################
@@ -8876,7 +9838,7 @@ function Invoke-EventHunter {
[array]$ComputerName = @()
ForEach ($Domain in $TargetDomains) {
Write-Verbose "[*] Querying domain $Domain for hosts"
- $ComputerName += Get-NetComputer -Domain $Domain -DomainController $DomainController -Filter $ComputerFilter -ADSpath $ComputerADSpath
+ $ComputerName += Get-NetComputer -Domain $Domain -DomainController $DomainController -Credential $Credential -Filter $ComputerFilter -ADSpath $ComputerADSpath
}
}
else {
@@ -8884,7 +9846,7 @@ function Invoke-EventHunter {
[array]$ComputerName = @()
ForEach ($Domain in $TargetDomains) {
Write-Verbose "[*] Querying domain $Domain for domain controllers"
- $ComputerName += Get-NetDomainController -LDAP -Domain $Domain -DomainController $DomainController | ForEach-Object { $_.dnshostname}
+ $ComputerName += Get-NetDomainController -LDAP -Domain $Domain -DomainController $DomainController -Credential $Credential | ForEach-Object { $_.dnshostname}
}
}
@@ -8923,15 +9885,15 @@ function Invoke-EventHunter {
elseif($UserADSpath -or $UserFilter) {
ForEach ($Domain in $TargetDomains) {
Write-Verbose "[*] Querying domain $Domain for users"
- $TargetUsers += Get-NetUser -Domain $Domain -DomainController $DomainController -ADSpath $UserADSpath -Filter $UserFilter | ForEach-Object {
+ $TargetUsers += Get-NetUser -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $UserADSpath -Filter $UserFilter | ForEach-Object {
$_.samaccountname
} | Where-Object {$_}
- }
+ }
}
else {
ForEach ($Domain in $TargetDomains) {
Write-Verbose "[*] Querying domain $Domain for users of group '$GroupName'"
- $TargetUsers += Get-NetGroupMember -GroupName $GroupName -Domain $Domain -DomainController $DomainController | Foreach-Object {
+ $TargetUsers += Get-NetGroupMember -GroupName $GroupName -Domain $Domain -DomainController $DomainController -Credential $Credential | ForEach-Object {
$_.MemberName
}
}
@@ -8943,7 +9905,7 @@ function Invoke-EventHunter {
# script block that enumerates a server
$HostEnumBlock = {
- param($ComputerName, $Ping, $TargetUsers, $SearchDays)
+ param($ComputerName, $Ping, $TargetUsers, $SearchDays, $Credential)
# optionally check if the server is up first
$Up = $True
@@ -8951,10 +9913,18 @@ function Invoke-EventHunter {
$Up = Test-Connection -Count 1 -Quiet -ComputerName $ComputerName
}
if($Up) {
- # try to enumerate
- Get-UserEvent -ComputerName $ComputerName -EventType 'all' -DateStart ([DateTime]::Today.AddDays(-$SearchDays)) | Where-Object {
- # filter for the target user set
- $TargetUsers -contains $_.UserName
+ # try to enumerate
+ if($Credential) {
+ Get-UserEvent -ComputerName $ComputerName -EventType 'all' -DateStart ([DateTime]::Today.AddDays(-$SearchDays)) | Where-Object {
+ # filter for the target user set
+ $TargetUsers -contains $_.UserName
+ }
+ }
+ else {
+ Get-UserEvent -ComputerName $ComputerName -Credential $Credential -EventType 'all' -DateStart ([DateTime]::Today.AddDays(-$SearchDays)) | Where-Object {
+ # filter for the target user set
+ $TargetUsers -contains $_.UserName
+ }
}
}
}
@@ -8971,10 +9941,11 @@ function Invoke-EventHunter {
'Ping' = $(-not $NoPing)
'TargetUsers' = $TargetUsers
'SearchDays' = $SearchDays
+ 'Credential' = $Credential
}
# kick off the threaded script block + arguments
- Invoke-ThreadedFunction -ComputerName $ComputerName -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams
+ Invoke-ThreadedFunction -ComputerName $ComputerName -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams -Threads $Threads
}
else {
@@ -8995,7 +9966,7 @@ function Invoke-EventHunter {
Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay)
Write-Verbose "[*] Enumerating server $Computer ($Counter of $($ComputerName.count))"
- Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $Computer, $(-not $NoPing), $TargetUsers, $SearchDays
+ Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $Computer, $(-not $NoPing), $TargetUsers, $SearchDays, $Credential
}
}
@@ -9291,7 +10262,7 @@ function Invoke-ShareFinder {
}
# kick off the threaded script block + arguments
- Invoke-ThreadedFunction -ComputerName $ComputerName -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams
+ Invoke-ThreadedFunction -ComputerName $ComputerName -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams -Threads $Threads
}
else {
@@ -9448,10 +10419,6 @@ function Invoke-FileFinder {
Switch. Mount target remote path with temporary PSDrives.
- .PARAMETER Credential
-
- Credential to use to mount the PSDrive for searching.
-
.EXAMPLE
PS C:\> Invoke-FileFinder
@@ -9514,8 +10481,9 @@ function Invoke-FileFinder {
[Switch]
$FreshEXEs,
+ [Alias('Terms')]
[String[]]
- $Terms,
+ $SearchTerms,
[ValidateScript({Test-Path -Path $_ })]
[String]
@@ -9577,10 +10545,7 @@ function Invoke-FileFinder {
$Threads,
[Switch]
- $UsePSDrive,
-
- [System.Management.Automation.PSCredential]
- $Credential = [System.Management.Automation.PSCredential]::Empty
+ $UsePSDrive
)
begin {
@@ -9626,7 +10591,7 @@ function Invoke-FileFinder {
if ($TermList) {
ForEach ($Term in Get-Content -Path $TermList) {
if (($Term -ne $Null) -and ($Term.trim() -ne '')) {
- $Terms += $Term
+ $SearchTerms += $Term
}
}
}
@@ -9667,9 +10632,9 @@ function Invoke-FileFinder {
Write-Verbose "[*] Adding share search path $DCSearchPath"
$Shares += $DCSearchPath
}
- if(!$Terms) {
+ if(!$SearchTerms) {
# search for interesting scripts on SYSVOL
- $Terms = @('.vbs', '.bat', '.ps1')
+ $SearchTerms = @('.vbs', '.bat', '.ps1')
}
}
else {
@@ -9691,7 +10656,7 @@ function Invoke-FileFinder {
# script block that enumerates shares and files on a server
$HostEnumBlock = {
- param($ComputerName, $Ping, $ExcludedShares, $Terms, $ExcludeFolders, $OfficeDocs, $ExcludeHidden, $FreshEXEs, $CheckWriteAccess, $OutFile, $UsePSDrive, $Credential)
+ param($ComputerName, $Ping, $ExcludedShares, $SearchTerms, $ExcludeFolders, $OfficeDocs, $ExcludeHidden, $FreshEXEs, $CheckWriteAccess, $OutFile, $UsePSDrive)
Write-Verbose "ComputerName: $ComputerName"
Write-Verbose "ExcludedShares: $ExcludedShares"
@@ -9737,7 +10702,7 @@ function Invoke-FileFinder {
ForEach($Share in $SearchShares) {
$SearchArgs = @{
'Path' = $Share
- 'Terms' = $Terms
+ 'SearchTerms' = $SearchTerms
'OfficeDocs' = $OfficeDocs
'FreshEXEs' = $FreshEXEs
'LastAccessTime' = $LastAccessTime
@@ -9748,7 +10713,6 @@ function Invoke-FileFinder {
'CheckWriteAccess' = $CheckWriteAccess
'OutFile' = $OutFile
'UsePSDrive' = $UsePSDrive
- 'Credential' = $Credential
}
Find-InterestingFile @SearchArgs
@@ -9765,7 +10729,7 @@ function Invoke-FileFinder {
$ScriptParams = @{
'Ping' = $(-not $NoPing)
'ExcludedShares' = $ExcludedShares
- 'Terms' = $Terms
+ 'SearchTerms' = $SearchTerms
'ExcludeFolders' = $ExcludeFolders
'OfficeDocs' = $OfficeDocs
'ExcludeHidden' = $ExcludeHidden
@@ -9773,17 +10737,16 @@ function Invoke-FileFinder {
'CheckWriteAccess' = $CheckWriteAccess
'OutFile' = $OutFile
'UsePSDrive' = $UsePSDrive
- 'Credential' = $Credential
}
# kick off the threaded script block + arguments
if($Shares) {
# pass the shares as the hosts so the threaded function code doesn't have to be hacked up
- Invoke-ThreadedFunction -ComputerName $Shares -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams
+ Invoke-ThreadedFunction -ComputerName $Shares -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams -Threads $Threads
}
else {
- Invoke-ThreadedFunction -ComputerName $ComputerName -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams
- }
+ Invoke-ThreadedFunction -ComputerName $ComputerName -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams -Threads $Threads
+ }
}
else {
@@ -9808,7 +10771,7 @@ function Invoke-FileFinder {
Write-Verbose "[*] Enumerating server $_ ($Counter of $($ComputerName.count))"
- Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $_, $False, $ExcludedShares, $Terms, $ExcludeFolders, $OfficeDocs, $ExcludeHidden, $FreshEXEs, $CheckWriteAccess, $OutFile, $UsePSDrive, $Credential
+ Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $_, $False, $ExcludedShares, $SearchTerms, $ExcludeFolders, $OfficeDocs, $ExcludeHidden, $FreshEXEs, $CheckWriteAccess, $OutFile, $UsePSDrive
}
}
}
@@ -10031,7 +10994,7 @@ function Find-LocalAdminAccess {
}
# kick off the threaded script block + arguments
- Invoke-ThreadedFunction -ComputerName $ComputerName -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams
+ Invoke-ThreadedFunction -ComputerName $ComputerName -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams -Threads $Threads
}
else {
@@ -10052,7 +11015,7 @@ function Find-LocalAdminAccess {
Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay)
Write-Verbose "[*] Enumerating server $Computer ($Counter of $($ComputerName.count))"
- Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $Computer, $False, $OutFile, $DomainSID, $TrustGroupsSIDs
+ Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $Computer, $False
}
}
}
@@ -10121,6 +11084,11 @@ function Get-ExploitableSystem {
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
The example below shows the standard command usage. Disabled system are excluded by default, but
@@ -10210,7 +11178,10 @@ function Get-ExploitableSystem {
[ValidateRange(1,10000)]
[Int]
- $PageSize = 200
+ $PageSize = 200,
+
+ [Management.Automation.PSCredential]
+ $Credential
)
Write-Verbose "[*] Grabbing computer accounts from Active Directory..."
@@ -10371,11 +11342,12 @@ function Get-ExploitableSystem {
$Null = $TableVulnComputers.Rows.Add($AdsHostname,$AdsOS,$AdsSP,$AdsLast,$ExploitMsf,$ExploitCVE)
}
}
- }
+ }
# Display results
$VulnComputer = $TableVulnComputers | Select-Object ComputerName -Unique | Measure-Object
$VulnComputerCount = $VulnComputer.Count
+
if ($VulnComputer.Count -gt 0) {
# Return vulnerable server list order with some hack date casting
Write-Verbose "[+] Found $VulnComputerCount potentially vulnerable systems!"
@@ -10440,6 +11412,10 @@ function Invoke-EnumerateLocalAdmin {
Switch. Only return results that are not part of the local machine
or the machine's domain. Old Invoke-EnumerateLocalTrustGroup
functionality.
+
+ .PARAMETER DomainOnly
+
+ Switch. Only return domain (non-local) results
.PARAMETER Domain
@@ -10454,6 +11430,11 @@ function Invoke-EnumerateLocalAdmin {
Switch. Search all domains in the forest for target users instead of just
a single domain.
+ .PARAMETER API
+
+ Switch. Use API calls instead of the WinNT service provider. Less information,
+ but the results are faster.
+
.PARAMETER Threads
The maximum concurrent threads to execute.
@@ -10512,6 +11493,9 @@ function Invoke-EnumerateLocalAdmin {
[Switch]
$TrustGroups,
+ [Switch]
+ $DomainOnly,
+
[String]
$Domain,
@@ -10523,7 +11507,10 @@ function Invoke-EnumerateLocalAdmin {
[ValidateRange(1,100)]
[Int]
- $Threads
+ $Threads,
+
+ [Switch]
+ $API
)
begin {
@@ -10592,7 +11579,7 @@ function Invoke-EnumerateLocalAdmin {
# script block that enumerates a server
$HostEnumBlock = {
- param($ComputerName, $Ping, $OutFile, $DomainSID, $TrustGroupsSIDs)
+ param($ComputerName, $Ping, $OutFile, $DomainSID, $TrustGroupsSIDs, $API, $DomainOnly)
# optionally check if the server is up first
$Up = $True
@@ -10601,18 +11588,27 @@ function Invoke-EnumerateLocalAdmin {
}
if($Up) {
# grab the users for the local admins on this server
- $LocalAdmins = Get-NetLocalGroup -ComputerName $ComputerName
+ if($API) {
+ $LocalAdmins = Get-NetLocalGroup -ComputerName $ComputerName -API
+ }
+ else {
+ $LocalAdmins = Get-NetLocalGroup -ComputerName $ComputerName
+ }
# if we just want to return cross-trust users
- if($DomainSID -and $TrustGroupSIDS) {
+ if($DomainSID) {
# get the local machine SID
$LocalSID = ($LocalAdmins | Where-Object { $_.SID -match '.*-500$' }).SID -replace "-500$"
-
+ Write-Verbose "LocalSid for $ComputerName : $LocalSID"
# filter out accounts that begin with the machine SID and domain SID
# but preserve any groups that have users across a trust ($TrustGroupSIDS)
$LocalAdmins = $LocalAdmins | Where-Object { ($TrustGroupsSIDs -contains $_.SID) -or ((-not $_.SID.startsWith($LocalSID)) -and (-not $_.SID.startsWith($DomainSID))) }
}
+ if($DomainOnly) {
+ $LocalAdmins = $LocalAdmins | Where-Object {$_.IsDomain}
+ }
+
if($LocalAdmins -and ($LocalAdmins.Length -ne 0)) {
# output the results to a csv if specified
if($OutFile) {
@@ -10624,11 +11620,10 @@ function Invoke-EnumerateLocalAdmin {
}
}
else {
- Write-Verbose "[!] No users returned from $Server"
+ Write-Verbose "[!] No users returned from $ComputerName"
}
}
}
-
}
process {
@@ -10644,8 +11639,16 @@ function Invoke-EnumerateLocalAdmin {
'TrustGroupsSIDs' = $TrustGroupsSIDs
}
- # kick off the threaded script block + arguments
- Invoke-ThreadedFunction -ComputerName $ComputerName -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams
+ # kick off the threaded script block + arguments
+ if($API) {
+ $ScriptParams['API'] = $True
+ }
+
+ if($DomainOnly) {
+ $ScriptParams['DomainOnly'] = $True
+ }
+
+ Invoke-ThreadedFunction -ComputerName $ComputerName -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams -Threads $Threads
}
else {
@@ -10664,9 +11667,11 @@ function Invoke-EnumerateLocalAdmin {
# sleep for our semi-randomized interval
Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay)
-
Write-Verbose "[*] Enumerating server $Computer ($Counter of $($ComputerName.count))"
- Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $Computer, $False, $OutFile, $DomainSID, $TrustGroupsSIDs
+
+ $ScriptArgs = @($Computer, $False, $OutFile, $DomainSID, $TrustGroupsSIDs, $API, $DomainOnly)
+
+ Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $ScriptArgs
}
}
}
@@ -10727,7 +11732,7 @@ function Get-NetDomainTrust {
param(
[Parameter(Position=0,ValueFromPipeline=$True)]
[String]
- $Domain = (Get-NetDomain).Name,
+ $Domain,
[String]
$DomainController,
@@ -10737,13 +11742,21 @@ function Get-NetDomainTrust {
[ValidateRange(1,10000)]
[Int]
- $PageSize = 200
+ $PageSize = 200,
+
+ [Management.Automation.PSCredential]
+ $Credential
)
process {
+
+ if(!$Domain) {
+ $Domain = (Get-NetDomain -Credential $Credential).Name
+ }
+
if($LDAP -or $DomainController) {
- $TrustSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -PageSize $PageSize
+ $TrustSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -Credential $Credential -PageSize $PageSize
if($TrustSearcher) {
@@ -10787,11 +11800,11 @@ function Get-NetDomainTrust {
else {
# if we're using direct domain connections
- $FoundDomain = Get-NetDomain -Domain $Domain
+ $FoundDomain = Get-NetDomain -Domain $Domain -Credential $Credential
if($FoundDomain) {
- (Get-NetDomain -Domain $Domain).GetAllTrustRelationships()
- }
+ $FoundDomain.GetAllTrustRelationships()
+ }
}
}
}
@@ -10807,6 +11820,11 @@ function Get-NetForestTrust {
Return trusts for the specified forest.
+ .PARAMETER Credential
+
+ A [Management.Automation.PSCredential] object of alternate credentials
+ for connection to the target domain.
+
.EXAMPLE
PS C:\> Get-NetForestTrust
@@ -10824,11 +11842,15 @@ function Get-NetForestTrust {
param(
[Parameter(Position=0,ValueFromPipeline=$True)]
[String]
- $Forest
+ $Forest,
+
+ [Management.Automation.PSCredential]
+ $Credential
)
process {
- $FoundForest = Get-NetForest -Forest $Forest
+ $FoundForest = Get-NetForest -Forest $Forest -Credential $Credential
+
if($FoundForest) {
$FoundForest.GetAllTrustRelationships()
}
@@ -11102,6 +12124,76 @@ function Find-ForeignGroup {
}
+function Find-ManagedSecurityGroups {
+<#
+ .SYNOPSIS
+
+ This function retrieves all security groups in the domain and identifies ones that
+ have a manager set. It also determines whether the manager has the ability to add
+ or remove members from the group.
+
+ Author: Stuart Morgan (@ukstufus) <stuart.morgan@mwrinfosecurity.com>
+ License: BSD 3-Clause
+
+ .EXAMPLE
+
+ PS C:\> Find-ManagedSecurityGroups | Export-PowerViewCSV -NoTypeInformation group-managers.csv
+
+ Store a list of all security groups with managers in group-managers.csv
+
+ .DESCRIPTION
+
+ Authority to manipulate the group membership of AD security groups and distribution groups
+ can be delegated to non-administrators by setting the 'managedBy' attribute. This is typically
+ used to delegate management authority to distribution groups, but Windows supports security groups
+ being managed in the same way.
+
+ This function searches for AD groups which have a group manager set, and determines whether that
+ user can manipulate group membership. This could be a useful method of horizontal privilege
+ escalation, especially if the manager can manipulate the membership of a privileged group.
+
+ .LINK
+
+ https://github.com/PowerShellEmpire/Empire/pull/119
+
+#>
+
+ # Go through the list of security groups on the domain and identify those who have a manager
+ Get-NetGroup -FullData -Filter '(&(managedBy=*)(groupType:1.2.840.113556.1.4.803:=2147483648))' | Select-Object -Unique distinguishedName,managedBy,cn | ForEach-Object {
+
+ # Retrieve the object that the managedBy DN refers to
+ $group_manager = Get-ADObject -ADSPath $_.managedBy | Select-Object cn,distinguishedname,name,samaccounttype,samaccountname
+
+ # Create a results object to store our findings
+ $results_object = New-Object -TypeName PSObject -Property @{
+ 'GroupCN' = $_.cn
+ 'GroupDN' = $_.distinguishedname
+ 'ManagerCN' = $group_manager.cn
+ 'ManagerDN' = $group_manager.distinguishedName
+ 'ManagerSAN' = $group_manager.samaccountname
+ 'ManagerType' = ''
+ 'CanManagerWrite' = $FALSE
+ }
+
+ # Determine whether the manager is a user or a group
+ if ($group_manager.samaccounttype -eq 0x10000000) {
+ $results_object.ManagerType = 'Group'
+ } elseif ($group_manager.samaccounttype -eq 0x30000000) {
+ $results_object.ManagerType = 'User'
+ }
+
+ # Find the ACLs that relate to the ability to write to the group
+ $xacl = Get-ObjectAcl -ADSPath $_.distinguishedname -Rights WriteMembers
+
+ # Double-check that the manager
+ if ($xacl.ObjectType -eq 'bf9679c0-0de6-11d0-a285-00aa003049e2' -and $xacl.AccessControlType -eq 'Allow' -and $xacl.IdentityReference.Value.Contains($group_manager.samaccountname)) {
+ $results_object.CanManagerWrite = $TRUE
+ }
+ $results_object
+ }
+}
+
+
function Invoke-MapDomainTrust {
<#
.SYNOPSIS
@@ -11122,6 +12214,11 @@ function Invoke-MapDomainTrust {
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:\> Invoke-MapDomainTrust | Export-CSV -NoTypeInformation trusts.csv
@@ -11142,7 +12239,11 @@ function Invoke-MapDomainTrust {
[ValidateRange(1,10000)]
[Int]
- $PageSize = 200
+ $PageSize = 200,
+
+ [Management.Automation.PSCredential]
+ $Credential
+
)
# keep track of domains seen so we don't hit infinite recursion
@@ -11152,7 +12253,7 @@ function Invoke-MapDomainTrust {
$Domains = New-Object System.Collections.Stack
# get the current domain and push it onto the stack
- $CurrentDomain = (Get-NetDomain).Name
+ $CurrentDomain = (Get-NetDomain -Credential $Credential).Name
$Domains.push($CurrentDomain)
while($Domains.Count -ne 0) {
@@ -11160,7 +12261,7 @@ function Invoke-MapDomainTrust {
$Domain = $Domains.Pop()
# if we haven't seen this domain before
- if (-not $SeenDomains.ContainsKey($Domain)) {
+ if ($Domain -and ($Domain.Trim() -ne "") -and (-not $SeenDomains.ContainsKey($Domain))) {
Write-Verbose "Enumerating trusts for domain '$Domain'"
@@ -11170,10 +12271,10 @@ function Invoke-MapDomainTrust {
try {
# get all the trusts for this domain
if($LDAP -or $DomainController) {
- $Trusts = Get-NetDomainTrust -Domain $Domain -LDAP -DomainController $DomainController -PageSize $PageSize
+ $Trusts = Get-NetDomainTrust -Domain $Domain -LDAP -DomainController $DomainController -PageSize $PageSize -Credential $Credential
}
else {
- $Trusts = Get-NetDomainTrust -Domain $Domain -PageSize $PageSize
+ $Trusts = Get-NetDomainTrust -Domain $Domain -PageSize $PageSize -Credential $Credential
}
if($Trusts -isnot [system.array]) {
@@ -11181,7 +12282,7 @@ function Invoke-MapDomainTrust {
}
# get any forest trusts, if they exist
- $Trusts += Get-NetForestTrust -Forest $Domain
+ $Trusts += Get-NetForestTrust -Forest $Domain -Credential $Credential
if ($Trusts) {
@@ -11229,11 +12330,14 @@ $FunctionDefinitions = @(
(func netapi32 NetShareEnum ([Int]) @([String], [Int], [IntPtr].MakeByRefType(), [Int], [Int32].MakeByRefType(), [Int32].MakeByRefType(), [Int32].MakeByRefType())),
(func netapi32 NetWkstaUserEnum ([Int]) @([String], [Int], [IntPtr].MakeByRefType(), [Int], [Int32].MakeByRefType(), [Int32].MakeByRefType(), [Int32].MakeByRefType())),
(func netapi32 NetSessionEnum ([Int]) @([String], [String], [String], [Int], [IntPtr].MakeByRefType(), [Int], [Int32].MakeByRefType(), [Int32].MakeByRefType(), [Int32].MakeByRefType())),
+ (func netapi32 NetLocalGroupGetMembers ([Int]) @([String], [String], [Int], [IntPtr].MakeByRefType(), [Int], [Int32].MakeByRefType(), [Int32].MakeByRefType(), [Int32].MakeByRefType())),
+ (func netapi32 DsGetSiteName ([Int]) @([String], [IntPtr].MakeByRefType())),
(func netapi32 NetApiBufferFree ([Int]) @([IntPtr])),
+ (func advapi32 ConvertSidToStringSid ([Int]) @([IntPtr], [String].MakeByRefType())),
(func advapi32 OpenSCManagerW ([IntPtr]) @([String], [String], [Int])),
(func advapi32 CloseServiceHandle ([Int]) @([IntPtr])),
(func wtsapi32 WTSOpenServerEx ([IntPtr]) @([String])),
- (func wtsapi32 WTSEnumerateSessionsEx ([Int]) @([IntPtr], [Int32].MakeByRefType(), [Int], [IntPtr].MakeByRefType(), [Int32].MakeByRefType())),
+ (func wtsapi32 WTSEnumerateSessionsEx ([Int]) @([IntPtr], [Int32].MakeByRefType(), [Int], [IntPtr].MakeByRefType(), [Int32].MakeByRefType())),
(func wtsapi32 WTSQuerySessionInformation ([Int]) @([IntPtr], [Int], [Int], [IntPtr].MakeByRefType(), [Int32].MakeByRefType())),
(func wtsapi32 WTSFreeMemoryEx ([Int]) @([Int32], [IntPtr], [Int32])),
(func wtsapi32 WTSFreeMemory ([Int]) @([IntPtr])),
@@ -11296,6 +12400,26 @@ $SESSION_INFO_10 = struct $Mod SESSION_INFO_10 @{
sesi10_idle_time = field 3 UInt32
}
+# enum used by $LOCALGROUP_MEMBERS_INFO_2 below
+$SID_NAME_USE = psenum $Mod SID_NAME_USE UInt16 @{
+ SidTypeUser = 1
+ SidTypeGroup = 2
+ SidTypeDomain = 3
+ SidTypeAlias = 4
+ SidTypeWellKnownGroup = 5
+ SidTypeDeletedAccount = 6
+ SidTypeInvalid = 7
+ SidTypeUnknown = 8
+ SidTypeComputer = 9
+}
+
+# the NetLocalGroupGetMembers result structure
+$LOCALGROUP_MEMBERS_INFO_2 = struct $Mod LOCALGROUP_MEMBERS_INFO_2 @{
+ lgrmi2_sid = field 0 IntPtr
+ lgrmi2_sidusage = field 1 $SID_NAME_USE
+ lgrmi2_domainandname = field 2 String -MarshalAs @('LPWStr')
+}
+
$Types = $FunctionDefinitions | Add-Win32Type -Module $Mod -Namespace 'Win32'
$Netapi32 = $Types['netapi32']