首页 > 解决方案 > Powersell - 远程查询用户是否跨域存在 [最快]

问题描述

抽象的

因此,我为一家在我的域中拥有大约 10k 计算机资产的公司工作。我的问题是查询用户是否存在于计算机上以查看他们是否曾经登录过所述计算机所需的时间。我们需要这个功能进行审计,以防他们做了他们不应该做的事情。

我已经研究了两种方法来完成这项任务,以及我没有想到的第三种替代解决方案;

-方法 A:查询每台计算机的“C:\Users<USER>”以查看 LocalPath 是否存在

-方法 B:检查每个计算机注册表中的“HKU:<SID>”以查看 SID 是否存在

-方法C:你们都比我聪明,有更好的方法吗?XD

方法 A 函数

$AllCompFound = @()
$AllADComputer = Get-ADComputer -Properties Name -SearchBase "WhatsItToYa" -filter 'Name -like "*"' | Select-Object Name
ForEach($Computer in $AllADComputers) {
 $CName = $Computer.Name
 if (Get-CimInstance -ComputerName "$CName" -ClassName Win32_Profile | ? {"C:\Users\'$EDIPI'" -contains $_.LocalPath}) {
  $AllCompFound += $CName
 } else {
  #DOOTHERSTUFF
 }
}

注意:我有另一个功能,提示我输入用户名进行检查。在我工作的地方,它们是数字,所以区分大小写不是问题。我对这个函数的问题是我相信'if'语句每次都返回true,因为它运行而不是因为它与用户名匹配。

方法 B 功能

$AllCompFound = @()
$AllADComputer = Get-ADComputer -Properties Name -SearchBase "WhatsItToYa" -filter 'Name -like "*"' | Select-Object Name
$hive = [Microsoft:Win32.RegistryHive]::Users
ForEach($Computer in $AllADComputers) {
 try {
 $base = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey($hive, $Computer.Name)
 $key = &base.OpenSubKey($strSID)
 if ($!key) {
  #DOSTUFF
 } else {
  $AllCompFound += $Computer.Name
  #DOOTHERSTUFF
 }
} catch {
 #IDONTTHROWBECAUSEIWANTITTOCONTINUE
} finally {
 if($key) {
  $key.Close()
 }
 if ($base) {
  $base.Close()
 }
}
}

注意:我有另一个函数可以在此函数之前将用户名转换为 SID。有用。

我开始发呆的地方是使用 Invoke-Command 并实际返回一个值,以及是否将所有这些查询作为它们自己的 PS-Session 运行。我的方法 A 返回误报,我的方法 B 似乎在某些计算机上挂断了。

这些方法都没有真正快到足以获得 10k 结果,我一直在使用较小的计算机池以便在请求时测试这些结果。我绝不是专家,但我认为我有很好的理解,所以任何帮助表示赞赏!

标签: powershellactive-directoryregistrysidcim

解决方案


首先,使用 WMI Win32_UserProfile,而不是 C:\Users 或注册表。

其次,使用从电脑到某个数据库的报告,而不是从服务器到电脑。这通常要好得多。

关于 GPO:如果您获得访问权限,您可以不时通过 GPP(而非 GPO)为此类报告添加/删除计划任务。

第三:使用PoshRSJob进行并行查询。

Get-WmiObject -Class 'Win32_USerProfile' | 
    Select @(
        'SID', 
        @{ 
            Name = 'LastUseTime'; 
            Expression = {$_.ConvertToDateTime($_.LastUseTime)}}
        @{ 
            Name = 'NTAccount'; 
            Expression = { [System.Security.Principal.SecurityIdentifier]::new($_.SID).Translate([System.Security.Principal.NTAccount])}}
        )

转换为 NTAccount 时要小心:如果 SID 不转换,则会导致错误,因此,也许最好不要从用户空间收集 NTAccount。

如果您没有其他变体,则使用PoshRSJob并行作业

并行示例(可能有一些错别字)


$ToDo = [System.Collections.Concurrent.ConcurrentQueue[string]]::new() # This is Queue (list) of computers that SHOULD be processed
<# Some loop through your computers #>
    <#...#> $ToDo.Enqueue($computerName) 
<#LoopEnd#>
$result = [System.Collections.Concurrent.ConcurrentBag[Object]]::new() # This is Bag (list) of processing results


# This function has ComputerName on input, and outputs some single value (object) as a result of processing this computer
Function Get-MySpecialComputerStats
{
    Param(
        [String]$ComputerName
    )
    <#Some magic#>
    # Here we make KSCustomObject form Hashtable. This is result object
    return [PSCustomObject]@{
        ComputerName = $ComputerName;
        Result = 'OK'
        SomeAdditionalInfo1 = 'whateverYouWant'
        SomeAdditionalInfo2 = 42 # Because 42
    }
}


# This is script that runs on background. It can not output anything.
# It takes 2 args: 1st is Input queue, 2nd is output queue

$JobScript = [scriptblock]{
    $inQueue = [System.Collections.Concurrent.ConcurrentQueue[string]]$args[0]
    $outBag = [System.Collections.Concurrent.ConcurrentBag[Object]]$args[1]
    $compName = $null
    
    # Logging inside, if you need it
    $log = [System.Text.StringBuilder]::new()
    
    # we work until inQueue is empty ( then TryDequeue will return false )
    while($inQueue.TryDequeue([ref] $compName) -eq $true)
    {
        $r= $null
        try 
        {
            $r = Get-MySpecialComputerStats -ComputerName $compName -EA Stop
            [void]$log.AppendLine("[_]: $($compName) : OK!")
            [void]$outBag.Add($r) # We append result to outBag
        }
        catch
        {
            [void]$log.AppendLine("[E]: $($compName) : $($_.Exception.Message)")
        }
    }

    # we return log.
    return $log.ToString()
}

# Some progress counters
$i_max = $ToDo.Count
$i_cur = $i_max

# We start 20 jobs. Dont forget to say about our functions be available inside job
$jobs = @(1..20) <# Run 20 threads #> | % { Start-RSJob -ScriptBlock $JobScript -ArgumentList @($ToDo, $result) -FunctionsToImport 'Get-MySpecialComputerStats'  }

# And once per 3 seconds we check, how much entries left in Queue ($todo)
while ($i_cur -gt 0)
{
    Write-Progress -Activity 'Working' -Status "$($i_cur) left of $($i_max) computers" -PercentComplete (100 - ($i_cur / $i_max * 100)) 
    Start-Sleep -Seconds 3
    $i_cur = $ToDo.Count
}

# When there is zero, we shall wait for jobs to complete last items and return logs, and we collect logs
$logs = $jobs | % { Wait-RSJob -Job $_ } | % { Receive-RSJob -Job $_ } 
# Logs is LOGS, not result

# Result is in the result variable.
$result | Export-Clixml -Path 'P:/ath/to/file.clixml' # Exporting result to CliXML file, or whatever you want

请注意:$JobScript 内部没有输出 done,因此必须完美完成,并且Get-MySpecialComputerStats必须以不寻常的方式测试函数以返回可以解释的值。


推荐阅读