Привет, Хабр! Меня зовут Иван Мороз, я системный администратор в BPMSoft. В нашей компании существовала проблема с контролем прав локального администратора на сотнях корпоративных ноутбуков. Ручной учет через Excel или стандартные GPO оказалось неэффективным, а ошибки могли приводить к проблемам с безопасностью и операционным рискам.

В этой статье я расскажу, как автоматизировал выдачу и изъятие прав локального админа с помощью PowerShell и шедулера, как строилась концепция решения, какие трудности возникли и как их удалось обойти. Я покажу конкретные блоки кода и дам практические советы для внедрения подобных процессов в крупных корпоративных средах.

Задача

В какой-то момент перед отделами ИТ \ ИБ встала комплексная задача — систематизировать процесс выдачи, изъятия и контроля прав локального администратора на корпоративных ноутбуках.

  1. Понять, кто из сотрудников имеет права сейчас.

  2. Забрать права у тех, кому они не нужны.

  3. Автоматизировать процесс так, чтобы выдача и изъятие происходили быстро, контролируемо и без подключения к ноутбуку. Желательно в пару кликов.

  4. Иметь возможность убедиться, что наши действия привели к ожидаемому результату: автоматика отработала корректно, сотрудник, получив права, не выдал их кому-то ещё; если права были изъяты, они действительно изъяты.

Известные решения, и почему они не подошли

  1. Самый простой и неэффективный способ решения задачи — выдавать права “руками” и вести реестр в Excel. Это плохо примерно всем: сплошной “человеческий фактор” и никакого контроля. Вы один раз выдали права и не имеете представления, что происходит с ноутбуком дальше.

  2. Делегирование прав через Group Policy Management (GPO): Computer Configuration → Preferences → Control Panel Settings → Local Users and Groups → Item-level Targeting.
    Этот способ уже лучше — групповая политика при каждом применении будет выдавать права, проверять, что они уже есть, и вычищать из группы Administrators всех лишних. 

Из минусов

  1. На каждого сотрудника надо делать отдельную политику, а когда у вас сотня-другая разработчиков, это становится проблемой. 

  2. Если на конкретном ноутбуке что-то пошло не так (по любой причине), вы об этом не узнаете. Оснастка GPO не дает отчета об успешном применении политики. 

  3. Нельзя сделать оперативно выгрузку и посмотреть, кто на каком ноутбуке имеет повышенные привилегии, а этот вопрос рано или поздно будет задан.

Решение

Концепция — нужно обеспечить приведение каждого ноутбука к следующему состоянию:

Права администратора имеют:

  1. Встроенная учетная запись администратора

  2. Группа Domain Admins

  3. “Легитимный админ” — сотрудник, которому согласованы права локального администратора (если такой есть)

  4. У всех остальных их нужно отобрать.

    Выдача и изъятия прав происходит посредством PowerShell — скрипта, который запускается на каждом ноутбуке через мгновенный шедулер (Immediate Task) от имени системы (NT AUTHORITY\System) при каждом обновлении GPO в фоновом режиме. Дальше разберем подробнее составляющие процесса.

Планировщик задач:

  • Вне зависимости от регистрации пользователя

  • От имени системной учетной записи

  • С повышенными привилегиями

  • При выполнении скрипта — игнорировать ExecutionPolicy

Может возникнуть вопрос: почему процесс запускается через Планировщик задач, а не через Startup \ Logon скрипт? Кажется, что второе проще и интуитивно понятнее.
Ответ: потому что это работает только в “тепличных условиях”, когда машина имеет стабильное соединение с контроллером домена (желательно по кабелю), в том числе при включении компьютера \ входе пользователя в сессию. Если у вас половина компании — удаленщики, которые запускают VPN как придется, это не будет работать стабильно.

В качестве альтернативы, для запуска скрипта, можно использовать не мгновенные шедулеры, а создать постоянный, который будет запускаться по расписанию или по заданным триггерам.

Сетевая папка для хранения скрипта и логов

Я решил хранить скрипт в сетевой шаре и, поскольку он должен выполняться от имени SYSTEM, компьютеры в AD должны иметь к ней доступ. Я выдавал доступ сразу на группу Domain Computers. В шару, где лежит скрипт — только на чтение. Но в ней ещё есть папка для хранения логов — и уже туда запись + чтение.

Active Directory

Выдавая или забирая права админа, система должна понимать \ ориентироваться, откуда-то брать данные “Кому выдать — Где выдать”. Для этих целей я решил использовать Active Directory, записывая в атрибут ManagedBy каждого компьютера - учетную запись пользователя, который должен иметь права админа на этом компьютере. Поэтому, если нужно выдать права, это делается через оснастку ADUC в пару кликов:

PowerShell-скрипт

У скрипта есть несколько логических блоков:

  1. Проверка и установка модуля Active Directory для Powershell, если его нет

  2. Определение переменных\монолитов

  3. Проверка, должен ли кто-то иметь права локального админа на этом ПК

    • Если никто не должен — переходим к следующему блоку

    • Если должен — проверить, имеет ли он уже права

      • Если имеет — все хорошо, ничего делать не нужно

      • Если не имеет — выдаем, оповещаем пользователя, фиксируем это для лога

  4. Проверка, имеет ли права локального админа кто-то лишний 

    • Смотрим список всех пользователей в группе администраторов

    • Проверяем каждого

      • Если это легитимный сотрудник или дефолтная учетка админа (SID = *500) — оставляем

      • Если это кто-то иной — удаляем его из группы администраторов, оповещаем пользователя, фиксируем это для лога

    • Смотрим, есть ли группы безопасности, отличные от Domain Admins. Если есть — удаляем, оповещаем пользователя, фиксируем это для лога

  5. Логирование

    • Если в процессе отработки скрипта не было произведено значимых действий (кому-то выдали, у кого-то забрали) и не было ошибок, лог писать не нужно

    • Если какие-то сущностные изменения были или были ошибки, делаем запись лога

      Разберем подробнее эти блоки

Блок 1. Проверка и установка модуля Active Directory 

#Проверяем, что компонент RSAT .ActiveDirectory установлен. Если не установлен — устанавливаем
$folder = "\\share\folder"
$status_AD=(Get-WindowsCapability -Name Rsat.ActiveDirectory.DS-LDS.Tools~~~~0.0.1.0 -Online).state
if ($status_AD -eq "NotPresent") {Add-WindowsCapability -Online -Name Rsat.ActiveDirectory.DS-LDS.Tools~~~~0.0.1.0 -LimitAccess -Source $folder}

Без модуля ActiveDirectory не получится выполнить запрос к AD и проверить наличие “легитимного” админа. Поэтому ставим, если ещё не стоит. Я ставлю из локальной шары через опцию LimitAccess.

Блок 2. Определяем переменные

Я всегда пишу этот блок в начале скрипта. Когда все значения, которые могут потенциально изменится, находятся в одном месте, это упрощает дальнейшую эксплуатацию, а также делает ваш код более читаемым для тех, кто будет поддерживать его после вас. Об этом надо думать сразу.

#кто легитимный локальный админ
try { $check_local_admin_rights = (Get-ADComputer -Identity $(hostname) -Properties ManagedBy -ErrorAction Stop).ManagedBy} 

#остановка выполнения скрипта, если не удалось определить
catch {throw "Не удалось определить легитимного админа. Остановка скрипта"} 

#определяем название группы доменных администраторов
try { $domain_admins = (Get-ADGroup -filter * -ErrorAction Stop | Where-Object {$_.SID -like '*-512'}).name} 
#остановка выполнения скрипта, если не удалось определить
catch {throw "Не удалось определить группу Администраторов Домена. Остановка скрипта"} 

#выясняем название учетки локального админа (у неё идентификатор заканчивается на 500)
$build_admin = (Get-LocalUser | Where-Object {$_.SID -like '*500'}).name 

#определяем название группы локальных администраторов
$build_admin_group = (Get-LocalGroup | Where-Object {$_.SID -like '*544'}).name 

#куда пишутся логи
$error_logs = "\\share\folder\file.txt" 

#определяем доменный префикс
$domain = (Get-ADDomain -Current LocalComputer).name

Важные детали: 

  • Запросы к AD нужно обрабатывать через try \ catch с обязательной остановкой скрипта в случае ошибки. Если в этом моменте произойдет ошибка по любой причине (DC был перегружен и не смог ответить, сбой в сети и т.д.), в переменную ничего не запишется. И скрипт продолжит выполнение работы, считая, что на этом компьютере никто не должен иметь права админа. Так можно забрать у того, кто должен их иметь. Потом придется краснеть.

  • Название учетной записи встроенного админа или группы надо определять по SID, поскольку оно может меняться в зависимости от локализации (Администратор \ Administrator). SID дефолтных учетных записей и групп известны — это т.н. “well-known” идентификаторы безопасности:

    https://learn.microsoft.com/en-us/windows/win32/secauthz/well-known-sids

Блок 3. Проверка, должен ли кто-то иметь права локального админа на этом пк

Проверяем, должен ли какой-то сотрудник (его доменная учетка) иметь права админа на данном ПК, если должен - имеет ли, если имеет - отлично, если нет - выдаем, оповещаем пользователя, логгирование

#проверяем должен ли кто-то иметь права локального админа на данном компе
if ($check_local_admin_rights -eq $null) {$exit_legal = $null}  #если никто не должен - значит можно завершать этот блок скрипта

else #если должен - продолжаем выполнение

    {
      #смотрим, кто именно должен иметь права админа
      try { $legal_local_admin = (Get-ADUser -Filter * -Properties DistinguishedName -ErrorAction Stop | Where-Object {$_.DistinguishedName -like $check_local_admin_rights}).SamAccountName} 
      
      #остановка выполнения скрипта, если не удалось определить
      catch {throw "Не удалось определить легитимного админа. Остановка скрипта."} 
      
      #получаем список доменных групп и пользователей, которые находятся в группе локальных админов
      $all_local_admins = Get-LocalGroupMember -Group $build_admin_group | Where-Object {$_.PrincipalSource -EQ 'ActiveDirectory'}      

      foreach($user in $all_local_admins)
        
        {
            #выбираем из массива только пользователей и проверяем каждого
            if (($user.ObjectClass -eq "User") -or ($user.ObjectClass -eq "Пользователь")) 
            
            {   
                #проверяем, есть ли среди членов группы локальных админов - доменная учетная запись сотрудника
                if ($user.Name -like "*$legal_local_admin") {$i+=1} 
            }        
        }

      #если не имеет - выдаем, логируем это и оповещаем пользователя
      if ($i -eq $null) {

        try {

            Add-LocalGroupMember -Group $build_admin_group -Member "$domain\$legal_local_admin" -ErrorAction Stop
            #текст для лога            
        	$exit_legal = "Grant rights legitimate admin: $legal_local_admin"
            msg * "Доменному пользователю [$legal_local_admin] делегированы права локального администратора на этом ПК. Перелогиньтесь, чтобы они вступили в действие."
        }

        catch {Write-Error "Ошибка при выдаче прав локального админа"}
        
#дефолтное запись этого блока, которая будет записана в лог, если кого-то не удалят
        } else {$exit_legal = $null} #текст для лога остается пустой = значимых изменений не было
    } 

Важные детали:

  • К сожалению, в зависимости от локализации, значение ObjectClass может быть и “User”, и “Пользователь”. Я не нашел способа точно определять это значение, поэтому “прибил гвоздями” оба варианта.

  • Не забываем делать запись в переменную $exit_legal, которая потом понадобится для логирования.

Блок 4. Проверка, имеет ли права локального админа кто-то лишний \ нелегитимный

#получаем список групп и пользователей, которые находятся в группе локальных админов
$all_local_admins = Get-LocalGroupMember -Group $build_admin_group 

    foreach($user in $all_local_admins)
        { 
            #выбираем из массива ЛОКАЛЬНЫХ пользователей
            if ((($user.ObjectClass -eq "User") -or ($user.ObjectClass -eq "Пользователь")) -and ($user.PrincipalSource -eq "Local"))  
                { $delete_local_user = $user.Name.split('\')[1] #обрезаем доменный префикс

                    #если ты учетка локального админа, тебя оставляем
                    if ($build_admin -eq $delete_local_user) {Write-Host "Оставляем: $build_admin"}  
                    
                    #если ты кто-то иной, тебя удаляем из группы, логируем это и оповещаем пользователя
                    else {

                        try {
                            Remove-LocalGroupMember -Group $build_admin_group -Member $delete_local_user -ErrorAction Stop 
                            
                            #текст для лога            
                        	$exit_no_legal += " Deleted no-legitimate admin (local): $delete_local_user" 
                            msg * "У локального пользователя [$delete_local_user] больше нет прав локального администратора на этом ПК."
                            }

                        catch {Write-Error "Ошибка при изъятии прав у локального пользователя"} 
                         } 
                }

            #выбираем из массива ДОМЕННЫХ пользователей
            if ((($user.ObjectClass -eq "User") -or ($user.ObjectClass -eq "Пользователь")) -and ($user.PrincipalSource -eq "ActiveDirectory")) 
                
                { $delete_user = $user.Name.split('\')[1] #обрезаем доменный префикс

                    #если ты легитимный сотрудник, у которого должны быть права локального админа, тебя оставляем
                    if ($legal_local_admin -eq $delete_user) {Write-Host "Оставляем: $legal_local_admin"} 
                    
                    #если ты кто-то иной, тебя удаляем из группы, логируем это и оповещаем пользователя
                    else { 
                        try {
                            Remove-LocalGroupMember -Group $build_admin_group -Member "$domain\$delete_user" -ErrorAction Stop
                            #текст для лога
                         	$exit_no_legal += " Deleted no-legitimate admin: $delete_user"
                            msg * "У доменного пользователя [$delete_user] больше нет прав локального администратора на этом ПК."
                            }

                        catch {Write-Error "Ошибка при изъятии прав у доменного пользователя"}
                         } 
                }

            #выбираем из массива ГРУППЫ БЕЗОПАСНОСТИ    
            if (($user.ObjectClass -eq "Group") -or ($user.ObjectClass -eq "Группа")) 
                { $delete_group = $user.Name.split('\')[1] #обрезаем доменный префикс

                    #если ты группа Domain Admins, тебя оставляем
                    if ($delete_group -eq $domain_admins) {Write-Host "Оставляем: $delete_group"} 
                    
                    #если ты кто-то иной, тебя удаляем из группы, логируем это и оповещаем пользователя
                    else {
                        try {
                            Remove-LocalGroupMember -Group $build_admin_group -Member "$domain\$delete_group" -ErrorAction Stop
                            #текст для лога
                    		$exit_no_legal += "Deleted no-legitimate group: $delete_group"
                            msg * "Доменная группа [$delete_group] больше не имеет права локального администратора на этом ПК."
                            }

                        catch {Write-Error "Ошибка при изъятии прав у группы безопасности"}
                        }      
                }
          }
#дефолтное запись этого блока, которая будет записана в лог, если кого-то не удалят = сущностных операций не было
if ($exit_no_legal -eq $null) {$exit_no_legal = $null} 

Важные детали:

Локальных и доменных пользователей нужно проверять отдельно и удалять с префиксом (доменные) \ без префикса (локальные). Может возникнуть вопрос, зачем, если можно удалять доменных пользователей без доменного префикса. 

Например: Remove-LocalGroupMember -Group $build_admin_group -Member ivan.ivanov

И это действительно работает. До тех пор, пока на ноутбуке не будет создана локальная учетная запись с именем, полностью идентичным доменной.
Когда это произойдет, командлет Remove-LocalGroupMember будет пытаться удалить именно локальную. Может быть кейс, что вам надо выбить из группы локальных администраторов доменную учетку, а командлет будет пытаться выбить локальную и получать ошибку при каждой отработке скрипта. Вероятность этого кейса на грани погрешности, но я с ним столкнулся :-) 

Блок 5. Логирование

#проверяем, были ли выполнены какие-то сущностные действия
if ( ($exit_legal -eq $null) -and ($exit_no_legal -eq $null) -and (!$error) ) {Write-Host "Писать лог не нужно"} 
else 
  {
    #собираем данные в одну переменную
    $export = "$(Get-Date) | $(hostname) | $exit_legal | $exit_no_legal | Error: $error "
    $export | Add-Content -Path $error_logs  #делаем запись в лог
  }

Важные детали:

Логирование — базовое свойство любой информационной системы. Какую бы вы не писали автоматизацию, она должна сообщать вам, что сделала задуманное вами или не смогла, выводя текст ошибки.

Я не хотел видеть в файле лога результат выполнения каждого скрипта на каждом ноутбуке, поэтому решил, что буду писать лог только если произошло что-то сущностное, например выдача прав, изъятие прав, любая ошибка, которая окажется в переменной $error

Структура лога следующая:
время и дата выполнения | хостнейм ПК | выдача прав | изъятие прав | ошибки 

Пример записей в файле лога:
03/04/2025 16:30:03 | WIN10-PC4 | Grant rights legitimate admin: Ivan.Ivanov |  | Error: 
04/22/2025 22:32:39 | WIN11-PC14 |  |  Deleted no-legitimate admin: Ivan.Ivanov Deleted no-legitimate group: ИТ-отдел | Error: 

Я решил выполнять логирование всех значимых событий в одной строке, которая пишется в самом конце скрипта. Но можно сразу выполнять запись в лог, в момент когда произошло “значимое действие”, не дожидаясь окончания скрипта.

Преимущество моего решения

  1. Простота для исполнителя. Выдача \ изъятие происходит без подключения к рабочей станции, через оснастку ADUC в несколько кликов на вкладке ManagedBy. В ActiveDirectory я указываю “нужное состояние”, а дальше групповые политики приводят реальность к этому состоянию при каждом обновлении.

  2. Отчетность. Путем выполнения простого скрипта я получаю выгрузку “Компьютер — Пользователь” и могу ответить на вопросы “Кто?”, “Где?”, “Сколько всего?”

    $Local_Admins = Get-ADComputer -Filter * -Properties managedBy | Where-Object {$_.managedBy -ne $null} | Select-Object @{n="Computer"; e={$_.name}}, @{n="Local Admin"; e={$_.managedBy.split(',')[0]}}
    $Local_Admins
    Write-Host "Всего: $($Local_Admins.Count)`r`n" -ForegroundColor Yellow
  3. Логирование. В файле лога я вижу все сущностные операции, которые выполняет политика и скрипт: кому выдала, у кого забрала. Вижу тех, у кого не должно было быть прав, но почему-то были. А когда права нужно выдать — могу отследить, что они действительно были выданы.

Траблшутинг

Насколько бы не была продумана система, иногда она дает сбой. Надо разбираться почему. В этой части не будет подробного разбора кейсов — я лишь покажу, каким должен быть вектор вашей мысли:

  1. Вы не просто так добавляли логирование в логику скрипта. Первым делом идем смотреть, что скрипт написал нам туда из переменной $error и интерпретируем увиденное

  2. Автоматизация работает через групповую политику. А к моему ноутбуку она применяется? Проверяем через командную строку с повышенными привилегиями и gpresult /r

  3. Политика точно применяется, но что она делает? И где логируется это “что”? Первое — создает задачу в планировщике, поэтому идем смотреть логи в Event Viewer - Application and Services Logs - Microsoft - Windows - Task Scheduler - Operational. Там вы должны увидеть, что система:

    • зарегистрировала задачу (Task registered)

    • задача затригерилась (Task triggered on scheduler)

    • создала процесс (Created Task Process)

    • запустила задачу (Task Started)

    • выполнила задачу с каким-то кодом (Action completed)

  4. Хорошо, планировщик запускал задачу, она что-то делала. Что?
    Запускала PowerShell-скрипт! А значит еще можно посмотреть логи в Event Viewer - Application and Services Logs - Windows PowerShell. Там можно увидеть, какой именно скрипт запускался и запускался ли.

  5. Видим, что запускался наш скрипт из сетевой шары — отлично! Но хорошо бы понять, что произошло в процессе выполнения. И чтобы понять это, нужно заблаговременно включить через GPO на ноутбуках расширенное логирование PowerShell, которое будет сохранять транскрипции, например в C:\Windows\Logs\Powershell

Существует официальный баг (https://github.com/PowerShell/PowerShell/issues/2996), из-за которого при выполнении скрипта вы будете получать ошибку Get-LocalGroupMember : Failed to compare two elements in the array на единичных машинах. Она вызвана пустыми SID в группе администраторов, которые остались после присоединения/выхода из домена.

Фиксится следующим образом:

#смотрим название баганной учетки
([ADSI]"WinNT://./Administrators").psbase.Invoke('Members') | % {([ADSI]$_).InvokeGet('AdsPath')}
#удаляем её
Remove-LocalGroupMember -group "administrators" -member ‘name’

Заключение

  1. Решение будет удобно для тех, кому нужно делегировать права локального админа сильно больше, чем на 10-20 рабочих станциях. 

  2. Запуск скрипта посредством Планировщика актуален для тех, у кого в компании много удаленщиков, с нестабильным подключением к контроллеру домена.

  3. Выдачу прав через оснастку ADUC можно делегировать коллегам из ИБ \ Техподдержке, выдав права редактировать атрибут managedBy на уровне AD в конкретной OU. Ровно как и просмотр \ мониторинг логов — выдав права на чтение сетевой шары.

  4. О выдаче \ изъятии прав лучше оповещать пользователя посредством утилиты msg, чтобы этот процесс проходил для него прозрачно и вызывал меньше вопросов:

  5. Отдавая выполнение процесса со сложной логикой PowerShell-скрипту, вы должны базово уметь работать с этим средством автоматизации, читать чужой код, уметь интерпретировать его ошибки.

Комментарии (13)


  1. rdp
    30.09.2025 10:27

    А чем Вам LAPS не приглянулся?

    И как вы проверяете не запускает ли пользователь свой процесс для повышения привилегий от имени системы через Планировщик задач, созданный будучи легитимным локальным админом? Просто, чтобы Вас лишний раз не беспокоить или когда связи нет.


    1. ivan_moroz Автор
      30.09.2025 10:27

      LAPS нам приглянулся и мы его используем - просто не в этом сценарии)

      Есть операции, которые должны выполнять разработчики на корпоративных ноутбуках, которые мы выдаем и которые требуют админских прав. Не буду вдаваться в детали зачем - просто примем это как факт.

      Наша развилка:
      1. Они будут делать под своей доменной учеткой, которой мы выдадим админские права
      2. Мы будем как-то делегировать им возможность смотреть пароль встроенной учетки админа через LAPS

      Оба сценария дают больше прав, чем мы бы хотели и создают какие-то риски (запустить что-то от имени SYSTEM через шедулер, вывести ноут из домена и т.д.), но вариант 1 - проще в эксплуатации.
      От рисков совсем не уйти и для их минимизации - нужны дополнительные инструменты.

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


      1. rdp
        30.09.2025 10:27

        Разработчики посторонние, а доменные администраторы логинились на ноутбуках? Стоит решить проблему с неназванными операциями, требующими админских прав.


        1. ivan_moroz Автор
          30.09.2025 10:27

          Разработчики штатные. Доменные админы - не логинились.

          Стоит решить проблему с неназванными операциями, требующими админских прав

          Возможно, в обозримом будущем) Пока что - как есть)


  1. mureevms
    30.09.2025 10:27

    Если пользователь имеет права админа, то он может отключить задачу шедулера, который запускает скрипт отбора прав, а при сотне пользователей можно этого не увидеть поскольку логировние тут не поможет, логов от такого хоста не будет, они просто исчезнут.
    Можно возразить, что включение применяется через доменную политику и рано или поздно задача включится снова. На это просто пишется скрипт, который отключает задачу по расписанию и в случае, если задача отъема прав запускается редко, то, ее всегда можно успеть отключить.
    Есть ли защита от такого кейса?


    1. ivan_moroz Автор
      30.09.2025 10:27

      Вы правы, что если используется "постоянный" шедулер, то пользователь с правами админа - может его отключить \ удалить.

      Поэтому у нас используется мгновенный шедулер (Immediate Task). Его отличие от постоянного в том, что он не висит в списке планировщика постоянно. В момент обновления GPO (фоновый режим), политика создает задачу, запускает её и сразу после выполнения - удаляет. Это занимает секунды, если не доли. Таким образом, у пользователя нет особо возможности что-то сделать с ней.


  1. aarap
    30.09.2025 10:27

    Я правильно понял, что учетки с правами domain admins у Вас администрируют всё, вплоть до рабочих станций пользователей? (Поскольку скрипт только про domain admins знает, а учетки попроще, попавшие в локальные администраторы через те же Restricted Groups - будут исключаться) Тут вот пишут, что не надо так делать: https://learn.microsoft.com/en-us/security/privileged-access-workstations/privileged-access-access-model Или это про совсем отдельное от остального решение, типа только для ноутов только разработчиков?

    А так да, вопрос с локальными администаторами больной, скриптики эту боль частично уменьшают, но проблемы где-нибудь да остаются. Хорошо бы иметь какую-то надежную инвентаризацию членства в локальных администраторах, но какой-нибудь KSC - вроде как не делает этого. Ваш метод(через AD) - ломается, как выше уже написали.


    1. ivan_moroz Автор
      30.09.2025 10:27

      Вы поняли правильно.

      Пробежался быстро по документу, на который вы дали ссылку. Честно говоря, не понял в каком месте написано, что администрировать рабочие станции через группу Domain Admins плохая практика. А как лучше?

      Насколько я знаю, бест практикс от Microsoft = группа Domain Admins остается, но операции, требующие повышенных привелегий, запускаются техподдержкой через LAPS.

      На счет отключения \ удаления шедулера ответил выше - решается в значительной степени не постоянным шедулером, а мгновенным. Хотя риски и уязвимости в этой схеме, безусловно - остаются. Но тут, кажется, ничего не поделать. Выдая пользователю права админа - можно лишь принять эти риски и смести их к минимуму)


      1. aarap
        30.09.2025 10:27

        По ссылке не конкретны howto, а общие подходы. И у ms, смотрю, какие-то "улучшения" в этой методичке, что было раньше - теперь там обозвано legacy и какие-то огрызки от картинок. Вот тут, например, есть прежние картинки: https://www.teal-consulting.de/en/2021/02/15/esae-deep-dive-serie-part-7-tiering-model/ И разжевано хорошо.


        1. ivan_moroz Автор
          30.09.2025 10:27

          Да, тут нагляднее - спасибо)

          Суть сводится к созданию отдельных групп администрирования для каждого тира \ яруса (DC \ Server \ Workstation).

          Надо сказать, никогда не сталкивался с подобным разделением даже в очень крупных enterprice - средах. Но в любом случае - задуматься стоит, да.


  1. a3or
    30.09.2025 10:27

    А почему не сделали скрипт в шедулер DC, который для каждого ПК создаёт группу PCname-admins, gpo закидывает её в локальные администраторы ПК PCname?

    Также в aduc добавляете/убавляйте УЗ в группе для конкретного компа.

    Шедурел/gpo из локальных администраторов удаляет всех, кто: не локальный админ, доменный админ, группа админов техподдержки и группа PCname-admins.

    Но так поломавшийся в какой то момент шедулер/политики и т.д. не будут доставлять неприятности, т.к. группа уже будет на компе.


    1. ivan_moroz Автор
      30.09.2025 10:27

      Наиболее точный ответ на вопрос, почему я не реализовал концепцию, которую вы описали: потому что она не пришла мне в голову :)

      Не уверен, что до конца её понял, но меня смущает как минимум следующее: для каждого компа нужно создавать отдельную политику, которая будет закидывать группу PCname-admins на конкретный комп через таргетинг. Даже если это делается не руками, то открывая оснастку gpmc.msc - я не хочу видеть сотню однотипных политик.

      В остальном, не готов вникать в детали вашей концепции и обсуждать её достоинства и недостатки. Допускаю, что она вполне рабочая.


      1. RoHaS
        30.09.2025 10:27

        Поддержу предыдущего оратора на тему скрипта с группами по имени ПК. Например типа такого(кусок рабочего скрипта, тут основгная суть, остальное думаю допишите где переменных не хватает ;)):

        $oupcs = Get-ADComputer -Filter * -SearchBase "OU=Users_computers,DC=domain,DC=ru"
        foreach ($child in $oupcs) { 
         if ($child.ObjectClass -like '*computer*') { 
          $name = $child.Name
          $groupname = $name + "_LocAdm"
          $groupdescription = "Local Administrator Group for " + $name
           
            If (!($ougrp.name -contains $groupname)) {
                $groupname
                New-ADGroup -Name $groupname -SamAccountName $groupname -GroupCategory Security -GroupScope Global -DisplayName $groupname -Path "OU=LocAdm_PC_Groups,OU=Groups,DC=domain,DC=ru" -Description $groupdescription
                $groupadded++
                }
            else {$groupexists++}
            }
        }

        А отдельные ГПО создавать не нужно. Политика одна, а добавляете группу в локальные администраторы используя переменную имени компа в имени группы Например " DOMAIN\%Computername%_LocAdm"