'Powershell - Find computers with a specific username logged in
I've been trying to figure out a powershell script that doing a search of all computers with a specific username logged in.
so far I've found a way looking via all computers the 'explorer.exe' process and owner
Also I found this script:
Function Get-Username {
Clear-Host
$Global:Username = Read-Host "Enter Username"
if ($Username -eq $null){
Write-Host "Username cannot be blank, please enter a Username"
Get-Username
}
$UserCheck = Get-ADUser $Username
if ($UserCheck -eq $null){
Write-Host "Invalid Username, please verify this is the logon id for the account"
Get-Username
}
}
Get-Username
Function Get-Computername {
Clear-Host
$Global:Prefix = Read-Host "Enter Computername"
Clear-Host
}
Get-Computername
$computers = Get-ADComputer -Filter {Enabled -eq 'true' -and SamAccountName -like $Prefix}
$CompCount = $Computers.Count
Write-Host "Searching for $Username on $Prefix on $CompCount Computers`n"
foreach ($comp in $computers){
$Computer = $comp.Name
$Reply = $null
$Reply = test-connection $Computer -count 1 -quiet
if($Reply -eq 'True'){
if($Computer -eq $env:COMPUTERNAME){
$proc = gwmi win32_process -ErrorAction SilentlyContinue -computer $Computer -Filter "Name = 'explorer.exe'"
}
else{
$proc = gwmi win32_process -ErrorAction SilentlyContinue -Credential $Credential -computer $Computer -Filter "Name = 'explorer.exe'"
}
$progress++
ForEach ($p in $proc) {
$temp = ($p.GetOwner()).User
Write-Progress -activity "Working..." -status "Status: $progress of $CompCount Computers checked" -PercentComplete (($progress/$Computers.Count)*100)
if ($temp -eq $Username){
write-host "$Username is logged on $Computer"
}
}
}
}
This is not my script, only trying to use it.
this script can take a very long long time, trying to run on about 300 machines, it can take for 20 minutes run.
now im not sure, which part is taking most of the time, im thinking either the gwmi win32 process, that checking on each machine if the process 'explorer.exe' is on or the part test-connection that trying to ping each machine first, and then check for the 'explorer.exe' process.
for your knowledge, is there any faster way with powershell script to do this kind of checkup? to search for a specific username on which machines logged on?
Thanks in advance for any help!
Solution 1:[1]
Do you have the ability to read the remote registry on these computers?
If so:
- Get a list for registry keys from \\{Computername}\HKU
- Filter out all that DO NOT end with '_Classes'
HKEY_USERS\.DEFAULT
HKEY_USERS\S-1-5-19
HKEY_USERS\S-1-5-20
HKEY_USERS\S-1-5-21-4155237567-380904213-1424831347-1001
HKEY_USERS\S-1-5-21-4155237567-380904213-1424831347-1005
HKEY_USERS\S-1-5-21-4155237567-380904213-1424831347-1005_Classes
HKEY_USERS\S-1-5-21-4155237567-380904213-1424831347-500
HKEY_USERS\S-1-5-21-515967899-1938372697-839532595-26824
HKEY_USERS\S-1-5-21-515967899-1938372697-839532595-3976
HKEY_USERS\S-1-5-21-515967899-1938372697-839532595-3976_Classes
HKEY_USERS\S-1-5-18
becomes
HKEY_USERS\S-1-5-21-4155237567-380904213-1424831347-1005_Classes
HKEY_USERS\S-1-5-21-515967899-1938372697-839532595-3976_Classes
- From the now filtered list remove '_Classes' from the end of the keys.
HKEY_USERS\S-1-5-21-4155237567-380904213-1424831347-1005_Classes
HKEY_USERS\S-1-5-21-515967899-1938372697-839532595-3976_Classes
becomes
HKEY_USERS\S-1-5-21-4155237567-380904213-1424831347-1005
HKEY_USERS\S-1-5-21-515967899-1938372697-839532595-3976
- If any remain, then try to read the "USERNAME" value from the "Volatile Environment" key from each.
HKEY_USERS\S-1-5-21-4155237567-380904213-1424831347-1005\Volatile Environment
{Throws error, was just a ghost of past login - not real}
HKEY_USERS\S-1-5-21-515967899-1938372697-839532595-3976\Volatile Environment
USERNAME REG_SZ {UserName}
- Been doing this from BATCH language with REG.EXE and don't believe I've ever seen 2 names come back from step 4 above. If no keys remains after step 3, or all attempts to read USERNAME fails in step 4, then no one is logged on. If one name comes back, I guess it could be a ghost left over from a system crash, but in theory, it should be the user logged on.
I've seen PSLoggedOn \{ComputerName} fail to give correct info, so I think this is simply a very hard thing to do since Windows will crash, or have other problems, and leaves ghost fragments from past users.
Sorry I don't PowerShell code, still fairly new to PowerShell and haven't nose dived into the registry that far yet.
If I really needed to know for a fact who is logged on or not, I would investigate seeing if there is a way to get a script to run every 2 minutes while the user is logged in, and have the script save a UserName/DateTime stamp at the end of a log file in \Users\Public every time it ran. But I would have to want it bad to do that much work.
Solution 2:[2]
Here's is an alternative method that does not iterarte all computers in the domain, but it relies on all users have their Home directories redirected to a network share.
If that is the case in your domain, try:
# enter the SamAccountName of the user you are looking for
$user = 'SamAccountName'
# the UNC (\\Server\Share) name of the network share where all user homedirectories are
$usersHomePath = '\\HomesServer\HomesShare$'
# split the UNC path to get the server name and share name in separate variables
$server, $share = $usersHomePath.TrimStart("\") -split '\\'
$result = Get-CimInstance -ClassName Win32_ServerConnection -ComputerName $server |
Where-Object { $_.ShareName -eq $share -and $user -eq $_.UserName } |
Select-Object @{Name = "SamAccountName"; Expression = { $_.UserName }},
@{Name = "ComputerName"; Expression = {(([System.Net.Dns]::GetHostEntry($_.ComputerName).HostName) -split "\.")[0]}}
#output in console
$result
Solution 3:[3]
In all honesty, I don't think you will ever get super fast access of who is logged on. The only real approach is to multi-thread it with something like Split-Pipeline. https://github.com/nightroman/SplitPipeline
Once you install SplitPipeline, your code will need a framework like this:
function MakePackage {
param (
[Parameter(Mandatory = $true, Position = 1)]
[string]$UserName,
[Parameter(ValueFromPipeline)]
[string]$ComputerName
)
process {
[PSCustomObject]@{
ComputerName = $ComputerName
UserName = $UserName
}
}
}
$UserName = Read-Host "Please enter the username to search for"
$ComputerNames = @( 'ComputerName1', 'ComputerName2', 'ComputerName3', 'ComputerName4')
$ComputerNames | MakePackage $UserName | Split-Pipeline -Count 10 {
begin {
# Your functions go here.
}
Process {
[string]$ComputerName = $_.ComputerName
[string]$UserName = $_.UserName
# Your main code goes here.
Write-Host "Looking for $UserName on computer $ComputerName"
}
}
Solution 4:[4]
this is a slightly different approach. [grin] it presumes that you may want to check on more than one user name, so it grabs ALL the users on ALL the systems. once you have that, you can query for the one you want ... and then query for the next without having to wait for the system list to be scanned again.
the code ...
$ComputerList = @"
LocalHost
10.0.0.1
127.0.0.1
BetterNotBeThere
$env:COMPUTERNAME
"@ -split [System.Environment]::NewLine
#endregion >>> fake reading in a list of computer names
$GCIMI_Params = @{
ClassName = 'CIM_ComputerSystem'
ComputerName = $ComputerList
ErrorAction = 'SilentlyContinue'
# this will hold the errors from the non-repsonding systems - if you want that info
ErrorVariable = 'NonResponderList'
}
$LoggedInUserList = Get-CimInstance @GCIMI_Params |
ForEach-Object {
[PSCustomObject]@{
# the ".Split()" gets rid of the domain/workgroup/system part of the UserName
UserName = $_.UserName.Split('\')[-1]
ComputerName = $_.DNSHostName
# this shows the name used by the G-CimI call - it can be different
QueryComputerName = $_.PSComputerName
}
}
#get your target user name
$TargetUserName = $env:USERNAME
#get the system that name is logged into
$LoggedInUserList.Where({$_.UserName -eq $TargetUserName})
output on my system today ...
UserName ComputerName QueryComputerName
-------- ------------ -----------------
MyUserName MySysName MySysName
MyUserName MySysName LocalHost
MyUserName MySysName 127.0.0.1
what the code does ...
- builds a list of systems to check
when you are ready to do this with real data, just remove the entire#region/#endregionblock & replace it with yourGet-ADComputercall. - builds a
splatof the parameters for theGet-CimInstancecall - sends the
G-CimIcall to the target systems - generates a
[PSCustomObject]containing the desired properties - sends that PSCO out to a results collection
- grabs a user name
- filters the results collection
- shows the system[s] the user name is logged into - if there are any
note that i only have one system, so the 3 responses are all from that same box. also note that the computer can be referred to by DNS name, IP address, or LocalHost.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | |
| Solution 2 | Theo |
| Solution 3 | |
| Solution 4 |
