Powersell — удаленный запрос, существует ли пользователь в домене [самый быстрый]

Аннотация

Итак, я работаю в компании, у которой около 10 000 компьютеров на моем домене. Моя проблема заключается в том, сколько времени требуется, чтобы запросить, существует ли пользователь на компьютере, чтобы узнать, входили ли они когда-либо на указанный компьютер. Нам нужна эта функциональность для аудитов, если они сделали что-то, чего не должны были делать.

У меня есть два метода, которые я исследовал для выполнения этой задачи, и третье альтернативное решение, о котором я не подумал;

-Метод A: запрос на каждый компьютер C:\Users‹USER›, чтобы узнать, существует ли LocalPath.

-Метод B: проверка реестра каждого компьютера на наличие HKU:‹SID›, чтобы узнать, существует ли SID.

-Метод С: Вы все умнее меня и есть способ лучше? XD

Метод Функция

$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 каждый раз, потому что он запускается, а не потому, что он соответствует имени пользователя.

Функция метода Б

$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 зависает на некоторых компьютерах.

Ни один из этих методов не достаточно быстр, чтобы получить результаты 10 000, я использовал меньшие пулы компьютеров, чтобы проверить эти результаты по запросу. Я ни в коем случае не эксперт, но я думаю, что хорошо понимаю, поэтому любая помощь приветствуется!


person Karmaxdw    schedule 21.08.2020    source источник
comment
Если ты не можешь прийти к горе, пусть гора придет к тебе :) - напиши скрипт, который перечисляет локальные профили, затем отправляет список SID или имен пользователей куда-то еще (например, на простой веб-сервис). Затем, используя объект групповой политики или любое другое программное обеспечение для управления рабочим столом, которое у вас может быть, разверните запланированное задание на каждой отдельной машине, на которой выполняется сценарий. Затем расслабьтесь и подождите, пока данные будут автоматически сообщены вам   -  person Mathias R. Jessen    schedule 21.08.2020
comment
Мне нравится эта идея, однако у нас нет доступа/разрешения на использование GPO или программного обеспечения для управления рабочим столом. Это также не обязательно должно происходить каждый день, поэтому я не хочу делать это запланированным элементом, если я запускаю эти запросы только по требованию.   -  person Karmaxdw    schedule 21.08.2020


Ответы (1)


Во-первых, используйте 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])}}
        )

Be careful with translating to NTAccount: if SID does not translates, it will cause error, so, maybe, it's better not to collect NTAccount from user space.

Если у вас нет других вариантов, параллельные задания с использованием 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 нет вывода, поэтому он должен быть сделан идеально, а функция Get-MySpecialComputerStats должна быть протестирована на необычных способах возврата значения, которое можно интерпретировать.

person filimonic    schedule 21.08.2020
comment
И имейте в виду: если вы остановите скрипт с помощью Ctrl+C в середине, задания все еще будут выполняться в фоновом режиме. Используйте Get-RSJob | Stop-RSJob , чтобы завершить их. - person filimonic; 22.08.2020