ADG Оставить заявку
Блог Автоматизация 5 мин чтения

PowerShell 2.0 + WinRM: собираем отчёт по дискам со 200 серверов и чистим AD от уволенных

Как PowerShell Remoting с WinRM переводит рутинные Windows-задачи в скрипты: дисковый отчёт с сотен серверов за полминуты и автоматическая деактивация в AD.

Контекст момента

PowerShell 2.0 с поддержкой Remoting через WinRM вошёл в стандартную поставку Windows Server 2008 R2 и стал основным инструментом массовой автоматизации Windows-инфраструктуры

Несколько месяцев назад мы начали системно переводить рутинные Windows-задачи с «зайти на сервер руками» на PowerShell с Remoting. Теперь есть что рассказать - не в духе «смотрите как круто», а конкретно: что работает, где споткнулись и что пока вызывает сомнения.

Почему именно сейчас

PowerShell 2.0 вышел ещё с Windows 7 и Server 2008 R2, WinRM (Windows Remote Management) шёл в комплекте. Но реально использовать Remoting в парке из сотен серверов мы начали только в этом году - после того как убедились, что WinRM включается через GPO без плясок и что Invoke-Command с параметром -ComputerName и списком хостов работает надёжно, а не через раз.

До этого у нас был зоопарк: VBScript через WMI, PSExec для разовых задач, кое-где самописные bat-файлы с net user и dsquery. Всё это работало, но плохо масштабировалось и не давало нормального вывода для дальнейшей обработки.

Задача 1: дисковое пространство с 200 серверов

Типичная ситуация у нескольких наших клиентов - мониторинг есть (Zabbix), но бывают серверы, которые в него не попали или попали без агента. Нужен быстрый adhoc-отчёт: где диски заполнены больше порога, отсортировать по свободному месту.

Раньше это делалось через веб-интерфейс vCenter или руками - кто-то шёл по списку и смотрел. Теперь:

$servers = Get-Content .\servers.txt

$result = Invoke-Command -ComputerName $servers -ThrottleLimit 50 -ScriptBlock {
    Get-WmiObject Win32_LogicalDisk -Filter "DriveType=3" | 
    Select-Object DeviceID,
        @{N='SizeGB';  E={[math]::Round($_.Size/1GB,1)}},
        @{N='FreeGB';  E={[math]::Round($_.FreeSpace/1GB,1)}},
        @{N='FreePct'; E={[math]::Round($_.FreeSpace/$_.Size*100,0)}},
        PSComputerName
} -ErrorAction SilentlyContinue

$result | Sort-Object FreePct | 
    Where-Object FreePct -lt 20 |
    Export-Csv .\disk-report.csv -NoTypeInformation -Encoding UTF8

-ThrottleLimit 50 - это ключевой параметр. По умолчанию PowerShell пытается открыть соединения со всеми хостами разом; при 200 серверах это даёт перегрузку и таймауты. С лимитом 50 параллельных сессий весь прогон занимает около 30 секунд, что принципиально лучше последовательного обхода.

-ErrorAction SilentlyContinue в данном случае осознанный выбор: если сервер недоступен, он просто не попадёт в отчёт, и мы увидим это по отсутствию строк. Для инвентаризации нам важны результаты с тех, кто откликнулся - а не остановка на первом же недоступном хосте.

WinRM на Windows Server 2008 R2 включается через GPO, что важно: не нужно обходить серверы руками. Настроили один раз политику Allow remote server management through WinRM, применили к OU серверов - и Remoting заработал на всём парке.

Задача 2: деактивация уволенных в AD

У нескольких клиентов процесс увольнения выглядел примерно одинаково: HR сообщает в IT, кто-то вспоминает отключить учётку, иногда через день, иногда через неделю. Иногда не вспоминает. После разговора про 152-ФЗ решили это автоматизировать.

Схема простая: в Active Directory есть атрибут extensionAttribute1, который HR-система заполняет датой увольнения при проведении приказа. Скрипт раз в ночь забирает всех пользователей, у кого эта дата прошла, и деактивирует учётку.

Import-Module ActiveDirectory

$today = Get-Date

$fired = Get-ADUser -Filter {
    extensionAttribute1 -like "*" -and Enabled -eq $true
} -Properties extensionAttribute1, DisplayName, Department |
Where-Object {
    $_.extensionAttribute1 -and 
    ([datetime]::TryParse($_.extensionAttribute1, [ref]$null)) -and
    ([datetime]$_.extensionAttribute1 -le $today)
}

foreach ($user in $fired) {
    Disable-ADAccount -Identity $user
    Move-ADObject -Identity $user.DistinguishedName `
        -TargetPath "OU=Disabled,DC=corp,DC=local"

    Add-ADGroupMember -Identity "BlockedUsers" -Members $user

    Write-Output "$(Get-Date -f 'yyyy-MM-dd HH:mm') | DISABLED | $($user.SamAccountName) | $($user.Department)"
}

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

Парсинг даты через TryParse - потому что HR-система пишет атрибут в своём формате, и иногда туда попадает мусор. Лучше пропустить сомнительную запись, чем упасть с ошибкой в середине ночного прогона.

Что выяснилось в процессе

Несколько наблюдений, которые в документации не написаны.

Первое - WinRM и двойной хоп. Если со скрипт-сервера подключаешься к хосту A, а оттуда нужно подключиться к ресурсу B (файлопомойка, SQL), - не работает. Kerberos не умеет делегировать учётные данные через второй прыжок без явной настройки CredSSP или Kerberos constrained delegation. Мы на это наткнулись, когда попробовали через Remoting подключиться к серверу и оттуда смонтировать сетевую шару. Решение - либо CredSSP (с пониманием рисков), либо переписать скрипт так, чтобы работа с сетевым ресурсом шла с управляющего сервера, а не с удалённого хоста.

Второе - кодировки в выводе. Invoke-Command возвращает объекты PSObject, и кириллица в свойствах живёт нормально, пока не делаешь Export-Csv. Без -Encoding UTF8 получаешь кашу в Excel. Добавили в шаблон скриптов как стандарт.

Третье - $ErrorActionPreference на удалённой стороне. Переменная не передаётся в ScriptBlock автоматически. Если нужно гасить ошибки внутри блока - нужно явно выставлять там. Неочевидно, но один раз поймали на этом.

Где мы сейчас

Для клиентов на сопровождении инфраструктуры PowerShell + WinRM закрывает добрую половину задач, которые раньше требовали либо rdp-сессии, либо отдельного агента. Инвентаризация, дисковые отчёты, проверка сервисов, работа с AD - всё это теперь скрипты с расписанием.

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

Контакт

Нужна такая же инженерная работа?

Опишите задачу и контекст. Ответим в течение рабочего дня, при необходимости подпишем NDA.