Powershell - 查找登录了特定用户名的计算机

Powershell - Find computers with a specific username logged in

我一直在尝试找出一个可以搜索所有计算机的 powershell 脚本 使用特定用户名登录。

到目前为止,我已经找到了一种通过所有计算机查看 'explorer.exe' 进程和所有者的方法

我还找到了这个脚本:

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"
                }
            }
        }   
    }

这不是我的脚本,只是尝试使用它。

此脚本可能需要很长时间,尝试 运行 在大约 300 台机器上,可能需要 20 分钟 运行。

现在我不确定,哪个部分占用了大部分时间,我想要么是 gwmi win32 进程,要么是在每台机器上检查进程 'explorer.exe' 是否开启 或首先尝试 ping 每台机器的部分测试连接,然后检查 'explorer.exe' 进程。

据您所知,是否有使用 powershell 脚本进行此类检查的更快方法? 搜索登录计算机的特定用户名?

在此先感谢您的帮助!

继续我的评论。 . .

这是您可以采用的一种快速而肮脏的方法:

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}

$check = Get-CimInstance -Query "SELECT Name,UserName from Win32_ComputerSystem WHERE UserName LIKE '%$userName%'" `
                -ComputerName $Computers.Name`
                -ErrorAction SilentlyContinue
if ($check) { 
    # $check.UserName/Name may return an array same name users are found. 
    Write-Output -InputObject ($check.UserName + " is logged into: " + $check.Name)
}
else {
    Write-Output -InputObject "Unable to find $userName."
}

鉴于您没有对无法 ping 的系统执行任何操作,您可以使输出静音,只返回在线系统的结果。针对您的场景和问题的最佳解决方案是利用 并行化 Get-CIMInstance 允许您在将一组计算机传递给 -ComputerName 时执行此操作。

  • Write-Progress 往往非常慢,如果您正在寻找一个快速的解决方案,这肯定会让您慢下来。
  • Get-WMIObject 是一个已弃用的 cmdlet,已被 Get-CIMInstance 取代,它提供相同的功能,但远程处理过程更安全。
  • 使用-Query,我们可以在查询时使用WQL进行搜索,进一步加快处理速度。对一些管理员来说唯一的缺点是它遵循自己的语法。

有一点要注意,如果你能ping通一台机器,并不代表你能连接上它。

这是一种略有不同的方法。 [grin] 它假定您可能想要检查多个用户名,因此它会抓取所有系统上的所有用户。一旦你有了它,你就可以查询你想要的那个......然后查询下一个,而不必等待系统列表被再次扫描。

代码 ...

$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})
 

今天我的系统输出...

UserName    ComputerName QueryComputerName
--------    ------------ -----------------
MyUserName  MySysName    MySysName            
MyUserName  MySysName    LocalHost        
MyUserName  MySysName    127.0.0.1

代码的作用...

  • 构建要检查的系统列表
    当您准备好使用真实数据执行此操作时,只需删除整个 #region/#endregion 块并将其替换为您的 Get-ADComputer 调用。
  • Get-CimInstance 调用构建 splat 参数
  • G-CimI 调用发送到目标系统
  • 生成包含所需属性的 [PSCustomObject]
  • 将该 PSCO 发送到结果集合
  • 获取用户名
  • 过滤结果集合
  • 显示用户名登录的系统[=] - 如果有的话

请注意,我只有一个系统,所以 3 个响应都来自同一个盒子。另请注意,可以通过 DNS 名称、IP 地址或 LocalHost.

引用计算机

你有能力读取这些计算机上的远程注册表吗?

如果是:

  1. \\{Computername}\HKU
  2. 获取注册表项列表
  3. 过滤掉所有不以“_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

变成

HKEY_USERS\S-1-5-21-4155237567-380904213-1424831347-1005_Classes
HKEY_USERS\S-1-5-21-515967899-1938372697-839532595-3976_Classes
  1. 从现在过滤的列表中删除键末尾的“_Classes”。
HKEY_USERS\S-1-5-21-4155237567-380904213-1424831347-1005_Classes
HKEY_USERS\S-1-5-21-515967899-1938372697-839532595-3976_Classes

变成

HKEY_USERS\S-1-5-21-4155237567-380904213-1424831347-1005
HKEY_USERS\S-1-5-21-515967899-1938372697-839532595-3976
  1. 如果还有剩余,则尝试从每个“易变环境”键中读取“USERNAME”值。
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}
  1. 使用 REG.EXE 从 BATCH 语言执行此操作并且不相信我见过 2 个名称从上面的第 4 步返回。如果在第 3 步之后没有密钥,或者在第 4 步中所有读取 USERNAME 的尝试都失败,则没有人登录。如果一个名字回来了,我猜可能是系统崩溃遗留下来的鬼,但理论上应该是登录的用户。

我看到 PSLoggedOn \{ComputerName} 无法提供正确的信息,所以我认为这是一件非常困难的事情,因为 Windows 会崩溃,或者有其他问题,并留下幽灵碎片来自过去的用户。

抱歉,我不会编写 PowerShell 代码,对 PowerShell 还是很陌生,还没有深入研究注册表。

如果我真的需要知道登录或未登录的事实,我会调查是否有办法在用户登录时每 2 分钟将脚本发送到 运行,并让脚本在每次 运行 时在 \Users\Public 中的日志文件末尾保存一个 UserName/DateTime 标记。但我不得不希望它做那么多工作。

这是一个替代方法,它不会迭代域中的所有计算机,但是它依赖于所有用户将他们的主目录重定向到网络共享。

如果您的域中出现这种情况,请尝试:

# 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

老实说,我认为您永远无法超快地访问谁已登录。唯一真正的方法是用 Split-Pipeline 之类的东西 multi-thread 它。 https://github.com/nightroman/SplitPipeline

安装 SplitPipeline 后,您的代码将需要这样的框架:

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"        
    }
}