@@ -9304,11 +9304,16 @@ function Invoke-ThreadedFunction {
93049304 $Pool = [runspacefactory ]::CreateRunspacePool(1 , $Threads , $SessionState , $Host )
93059305 $Pool.Open ()
93069306
9307- $Jobs = @ ()
9308- $PS = @ ()
9309- $Wait = @ ()
9307+ $method = $null
9308+ ForEach ($m in [PowerShell ].GetMethods() | Where-Object { $_.Name -eq " BeginInvoke" }) {
9309+ $methodParameters = $m.GetParameters ()
9310+ if (($methodParameters.Count -eq 2 ) -and $methodParameters [0 ].Name -eq " input" -and $methodParameters [1 ].Name -eq " output" ) {
9311+ $method = $m.MakeGenericMethod ([Object ], [Object ])
9312+ break
9313+ }
9314+ }
93109315
9311- $Counter = 0
9316+ $Jobs = @ ()
93129317 }
93139318
93149319 process {
@@ -9324,54 +9329,42 @@ function Invoke-ThreadedFunction {
93249329 }
93259330
93269331 # create a "powershell pipeline runner"
9327- $PS + = [powershell ]::create()
9332+ $p = [powershell ]::create()
93289333
9329- $PS [ $Counter ] .runspacepool = $Pool
9334+ $p .runspacepool = $Pool
93309335
93319336 # add the script block + arguments
9332- $Null = $PS [ $Counter ] .AddScript($ScriptBlock ).AddParameter(' ComputerName' , $Computer )
9337+ $Null = $p .AddScript ($ScriptBlock ).AddParameter(' ComputerName' , $Computer )
93339338 if ($ScriptParameters ) {
93349339 ForEach ($Param in $ScriptParameters.GetEnumerator ()) {
9335- $Null = $PS [ $Counter ] .AddParameter($Param.Name , $Param.Value )
9340+ $Null = $p .AddParameter ($Param.Name , $Param.Value )
93369341 }
93379342 }
93389343
9339- # start job
9340- $Jobs += $PS [$Counter ].BeginInvoke();
9344+ $o = New-Object Management.Automation.PSDataCollection[Object ]
93419345
9342- # store wait handles for WaitForAll call
9343- $Wait += $Jobs [$Counter ].AsyncWaitHandle
9346+ $Jobs += @ {
9347+ PS = $p
9348+ Output = $o
9349+ Result = $method.Invoke ($p , @ ($null , [Management.Automation.PSDataCollection [Object ]]$o ))
9350+ }
93449351 }
9345- $Counter = $Counter + 1
93469352 }
93479353 }
93489354
93499355 end {
9356+ Write-Verbose " Waiting for threads to finish..."
93509357
9351- Write-Verbose " Waiting for scanning threads to finish..."
9352-
9353- $WaitTimeout = Get-Date
9354-
9355- # set a 60 second timeout for the scanning threads
9356- while ($ ($Jobs | Where-Object {$_.IsCompleted -eq $False }).count -gt 0 -and $ ($ ($ (Get-Date ) - $WaitTimeout ).totalSeconds) -lt 60 ) {
9357- Start-Sleep - MilliSeconds 500
9358+ Do {
9359+ ForEach ($Job in $Jobs ) {
9360+ $Job.Output.ReadAll ()
93589361 }
9362+ } While (($Jobs | Where-Object { ! $_.Result.IsCompleted }).Count -gt 0 )
93599363
9360- # end async call
9361- for ($y = 0 ; $y -lt $Counter ; $y ++ ) {
9362-
9363- try {
9364- # complete async job
9365- $PS [$y ].EndInvoke($Jobs [$y ])
9366-
9367- } catch {
9368- Write-Warning " error: $_ "
9369- }
9370- finally {
9371- $PS [$y ].Dispose()
9372- }
9364+ ForEach ($Job in $Jobs ) {
9365+ $Job.PS.Dispose ()
93739366 }
9374-
9367+
93759368 $Pool.Dispose ()
93769369 Write-Verbose " All threads completed!"
93779370 }
@@ -9507,6 +9500,11 @@ function Invoke-UserHunter {
95079500
95089501 The maximum concurrent threads to execute.
95099502
9503+ . PARAMETER Poll
9504+
9505+ Continuously poll for sessions for the given duration. Automatically
9506+ sets Threads to the number of computers being polled.
9507+
95109508 . EXAMPLE
95119509
95129510 PS C:\> Invoke-UserHunter -CheckAccess
@@ -9561,6 +9559,13 @@ function Invoke-UserHunter {
95619559 Executes old Invoke-StealthUserHunter functionality, enumerating commonly
95629560 used servers and checking just sessions for each.
95639561
9562+ . EXAMPLE
9563+
9564+ PS C:\> Invoke-UserHunter -Stealth -StealthSource DC -Poll 3600 -Delay 5 -ShowAll | ? { ! $_.UserName.EndsWith('$') }
9565+
9566+ Poll Domain Controllers in parallel for sessions for an hour, waiting five
9567+ seconds before querying each DC again and filtering out computer accounts.
9568+
95649569 . LINK
95659570 http://blog.harmj0y.net
95669571#>
@@ -9650,7 +9655,10 @@ function Invoke-UserHunter {
96509655
96519656 [Int ]
96529657 [ValidateRange (1 , 100 )]
9653- $Threads
9658+ $Threads ,
9659+
9660+ [UInt32 ]
9661+ $Poll = 0
96549662 )
96559663
96569664 begin {
@@ -9659,9 +9667,6 @@ function Invoke-UserHunter {
96599667 $DebugPreference = ' Continue'
96609668 }
96619669
9662- # random object for delay
9663- $RandNo = New-Object System.Random
9664-
96659670 Write-Verbose " [*] Running Invoke-UserHunter with delay of $Delay "
96669671
96679672 # ####################################################
@@ -9732,6 +9737,14 @@ function Invoke-UserHunter {
97329737 }
97339738 }
97349739
9740+ if ($Poll -gt 0 ) {
9741+ Write-Verbose " [*] Polling for $Poll seconds. Automatically enabling threaded mode."
9742+ if ($ComputerName.Count -gt 100 ) {
9743+ throw " Too many hosts to poll! Try fewer than 100."
9744+ }
9745+ $Threads = $ComputerName.Count
9746+ }
9747+
97359748 # ####################################################
97369749 #
97379750 # Now we build the user target set
@@ -9829,97 +9842,54 @@ function Invoke-UserHunter {
98299842
98309843 # script block that enumerates a server
98319844 $HostEnumBlock = {
9832- param ($ComputerName , $Ping , $TargetUsers , $CurrentUser , $Stealth , $DomainShortName )
9845+ param ($ComputerName , $Ping , $TargetUsers , $CurrentUser , $Stealth , $DomainShortName , $Poll , $Delay , $Jitter )
98339846
98349847 # optionally check if the server is up first
98359848 $Up = $True
98369849 if ($Ping ) {
98379850 $Up = Test-Connection - Count 1 - Quiet - ComputerName $ComputerName
98389851 }
98399852 if ($Up ) {
9840- if (! $DomainShortName ) {
9841- # if we're not searching for foreign users, check session information
9842- $Sessions = Get-NetSession - ComputerName $ComputerName
9843- ForEach ($Session in $Sessions ) {
9844- $UserName = $Session.sesi10_username
9845- $CName = $Session.sesi10_cname
9846-
9847- if ($CName -and $CName.StartsWith (" \\" )) {
9848- $CName = $CName.TrimStart (" \" )
9849- }
9850-
9851- # make sure we have a result
9852- if (($UserName ) -and ($UserName.trim () -ne ' ' ) -and (! ($UserName -match $CurrentUser ))) {
9853-
9854- $TargetUsers | Where-Object {$UserName -like $_.MemberName } | ForEach-Object {
9855-
9856- $IPAddress = @ (Get-IPAddress - ComputerName $ComputerName )[0 ].IPAddress
9857- $FoundUser = New-Object PSObject
9858- $FoundUser | Add-Member Noteproperty ' UserDomain' $_.MemberDomain
9859- $FoundUser | Add-Member Noteproperty ' UserName' $UserName
9860- $FoundUser | Add-Member Noteproperty ' ComputerName' $ComputerName
9861- $FoundUser | Add-Member Noteproperty ' IPAddress' $IPAddress
9862- $FoundUser | Add-Member Noteproperty ' SessionFrom' $CName
9863-
9864- # Try to resolve the DNS hostname of $Cname
9865- try {
9866- $CNameDNSName = [System.Net.Dns ]::GetHostEntry($CName ) | Select-Object - ExpandProperty HostName
9867- $FoundUser | Add-Member NoteProperty ' SessionFromName' $CnameDNSName
9868- }
9869- catch {
9870- $FoundUser | Add-Member NoteProperty ' SessionFromName' $Null
9871- }
9872-
9873- # see if we're checking to see if we have local admin access on this machine
9874- if ($CheckAccess ) {
9875- $Admin = Invoke-CheckLocalAdminAccess - ComputerName $CName
9876- $FoundUser | Add-Member Noteproperty ' LocalAdmin' $Admin.IsAdmin
9877- }
9878- else {
9879- $FoundUser | Add-Member Noteproperty ' LocalAdmin' $Null
9880- }
9881- $FoundUser.PSObject.TypeNames.Add (' PowerView.UserSession' )
9882- $FoundUser
9853+ $Timer = [System.Diagnostics.Stopwatch ]::StartNew()
9854+ $RandNo = New-Object System.Random
9855+
9856+ Do {
9857+ if (! $DomainShortName ) {
9858+ # if we're not searching for foreign users, check session information
9859+ $Sessions = Get-NetSession - ComputerName $ComputerName
9860+ ForEach ($Session in $Sessions ) {
9861+ $UserName = $Session.sesi10_username
9862+ $CName = $Session.sesi10_cname
9863+
9864+ if ($CName -and $CName.StartsWith (" \\" )) {
9865+ $CName = $CName.TrimStart (" \" )
98839866 }
9884- }
9885- }
9886- }
9887- if (! $Stealth ) {
9888- # if we're not 'stealthy', enumerate loggedon users as well
9889- $LoggedOn = Get-NetLoggedon - ComputerName $ComputerName
9890- ForEach ($User in $LoggedOn ) {
9891- $UserName = $User.wkui1_username
9892- # TODO: translate domain to authoratative name
9893- # then match domain name ?
9894- $UserDomain = $User.wkui1_logon_domain
98959867
9896- # make sure wet have a result
9897- if (($UserName ) -and ($UserName.trim () -ne ' ' )) {
9868+ # make sure we have a result
9869+ if (($UserName ) -and ($UserName.trim () -ne ' ' ) -and ( ! ( $UserName -match $CurrentUser ) )) {
98989870
9899- $TargetUsers | Where-Object {$UserName -like $_.MemberName } | ForEach-Object {
9871+ $TargetUsers | Where-Object {$UserName -like $_.MemberName } | ForEach-Object {
99009872
9901- $Proceed = $True
9902- if ($DomainShortName ) {
9903- if ($DomainShortName.ToLower () -ne $UserDomain.ToLower ()) {
9904- $Proceed = $True
9905- }
9906- else {
9907- $Proceed = $False
9908- }
9909- }
9910- if ($Proceed ) {
99119873 $IPAddress = @ (Get-IPAddress - ComputerName $ComputerName )[0 ].IPAddress
99129874 $FoundUser = New-Object PSObject
9913- $FoundUser | Add-Member Noteproperty ' UserDomain' $UserDomain
9875+ $FoundUser | Add-Member Noteproperty ' UserDomain' $_ .MemberDomain
99149876 $FoundUser | Add-Member Noteproperty ' UserName' $UserName
99159877 $FoundUser | Add-Member Noteproperty ' ComputerName' $ComputerName
99169878 $FoundUser | Add-Member Noteproperty ' IPAddress' $IPAddress
9917- $FoundUser | Add-Member Noteproperty ' SessionFrom' $Null
9918- $FoundUser | Add-Member Noteproperty ' SessionFromName' $Null
9879+ $FoundUser | Add-Member Noteproperty ' SessionFrom' $CName
9880+
9881+ # Try to resolve the DNS hostname of $Cname
9882+ try {
9883+ $CNameDNSName = [System.Net.Dns ]::GetHostEntry($CName ) | Select-Object - ExpandProperty HostName
9884+ $FoundUser | Add-Member NoteProperty ' SessionFromName' $CnameDNSName
9885+ }
9886+ catch {
9887+ $FoundUser | Add-Member NoteProperty ' SessionFromName' $Null
9888+ }
99199889
99209890 # see if we're checking to see if we have local admin access on this machine
99219891 if ($CheckAccess ) {
9922- $Admin = Invoke-CheckLocalAdminAccess - ComputerName $ComputerName
9892+ $Admin = Invoke-CheckLocalAdminAccess - ComputerName $CName
99239893 $FoundUser | Add-Member Noteproperty ' LocalAdmin' $Admin.IsAdmin
99249894 }
99259895 else {
@@ -9931,10 +9901,61 @@ function Invoke-UserHunter {
99319901 }
99329902 }
99339903 }
9934- }
9904+ if (! $Stealth ) {
9905+ # if we're not 'stealthy', enumerate loggedon users as well
9906+ $LoggedOn = Get-NetLoggedon - ComputerName $ComputerName
9907+ ForEach ($User in $LoggedOn ) {
9908+ $UserName = $User.wkui1_username
9909+ # TODO: translate domain to authoratative name
9910+ # then match domain name ?
9911+ $UserDomain = $User.wkui1_logon_domain
9912+
9913+ # make sure wet have a result
9914+ if (($UserName ) -and ($UserName.trim () -ne ' ' )) {
9915+
9916+ $TargetUsers | Where-Object {$UserName -like $_.MemberName } | ForEach-Object {
9917+
9918+ $Proceed = $True
9919+ if ($DomainShortName ) {
9920+ if ($DomainShortName.ToLower () -ne $UserDomain.ToLower ()) {
9921+ $Proceed = $True
9922+ }
9923+ else {
9924+ $Proceed = $False
9925+ }
9926+ }
9927+ if ($Proceed ) {
9928+ $IPAddress = @ (Get-IPAddress - ComputerName $ComputerName )[0 ].IPAddress
9929+ $FoundUser = New-Object PSObject
9930+ $FoundUser | Add-Member Noteproperty ' UserDomain' $UserDomain
9931+ $FoundUser | Add-Member Noteproperty ' UserName' $UserName
9932+ $FoundUser | Add-Member Noteproperty ' ComputerName' $ComputerName
9933+ $FoundUser | Add-Member Noteproperty ' IPAddress' $IPAddress
9934+ $FoundUser | Add-Member Noteproperty ' SessionFrom' $Null
9935+ $FoundUser | Add-Member Noteproperty ' SessionFromName' $Null
9936+
9937+ # see if we're checking to see if we have local admin access on this machine
9938+ if ($CheckAccess ) {
9939+ $Admin = Invoke-CheckLocalAdminAccess - ComputerName $ComputerName
9940+ $FoundUser | Add-Member Noteproperty ' LocalAdmin' $Admin.IsAdmin
9941+ }
9942+ else {
9943+ $FoundUser | Add-Member Noteproperty ' LocalAdmin' $Null
9944+ }
9945+ $FoundUser.PSObject.TypeNames.Add (' PowerView.UserSession' )
9946+ $FoundUser
9947+ }
9948+ }
9949+ }
9950+ }
9951+ }
9952+
9953+ if ($Poll -gt 0 ) {
9954+ Start-Sleep - Seconds $RandNo.Next ((1 - $Jitter )* $Delay , (1 + $Jitter )* $Delay )
9955+ }
9956+ } While ($Poll -gt 0 -and $Timer.Elapsed.TotalSeconds -lt $Poll )
99359957 }
99369958 }
9937-
99389959 }
99399960
99409961 process {
@@ -9949,6 +9970,9 @@ function Invoke-UserHunter {
99499970 ' CurrentUser' = $CurrentUser
99509971 ' Stealth' = $Stealth
99519972 ' DomainShortName' = $DomainShortName
9973+ ' Poll' = $Poll
9974+ ' Delay' = $Delay
9975+ ' Jitter' = $Jitter
99529976 }
99539977
99549978 # kick off the threaded script block + arguments
@@ -9964,6 +9988,7 @@ function Invoke-UserHunter {
99649988
99659989 Write-Verbose " [*] Total number of active hosts: $ ( $ComputerName.count ) "
99669990 $Counter = 0
9991+ $RandNo = New-Object System.Random
99679992
99689993 ForEach ($Computer in $ComputerName ) {
99699994
@@ -9973,7 +9998,7 @@ function Invoke-UserHunter {
99739998 Start-Sleep - Seconds $RandNo.Next ((1 - $Jitter )* $Delay , (1 + $Jitter )* $Delay )
99749999
997510000 Write-Verbose " [*] Enumerating server $Computer ($Counter of $ ( $ComputerName.count ) )"
9976- $Result = Invoke-Command - ScriptBlock $HostEnumBlock - ArgumentList $Computer , $False , $TargetUsers , $CurrentUser , $Stealth , $DomainShortName
10001+ $Result = Invoke-Command - ScriptBlock $HostEnumBlock - ArgumentList $Computer , $False , $TargetUsers , $CurrentUser , $Stealth , $DomainShortName , 0 , 0 , 0
997710002 $Result
997810003
997910004 if ($Result -and $StopOnSuccess ) {
0 commit comments