Что сделал - 10

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

Авторесет виртуальной машины при недоступности HTTP или если она не отвечает на пинг

Есть виртуальная машина на Ubuntu 8.04, перевезённая мной на Hyper-V с железки несколько месяцев назад, где крутится веб-сервис, управляемый CMS отечественного производства RBC Contents 4.0. В каталоге веб-сервера находится более миллиона файлов. Операционная система старая, несовместимая с виртуальной средой, поэтому сетевые адаптеры там пришлось ставить не современные синтетические, а старые эмулируемые (100 Мбит). Мало того, что версия системы управления контентом античных времён — она уже больше 10 лет вообще не существует в природе, как и компания, её выпускавшая. Инструкций тоже нет, в интернете нашлись два документа по три странички, писанных на коленке и совершенно бесполезных. В общем, классический «чёрный ящик», который невозможно тронуть, чтобы он, чего доброго, не развалился, но, вместе с тем, необходимо поддерживать его работоспособность.

Ящик время от времени по неизвестным причинам либо перестаёт отвечать по сети, либо вешается намертво; особенно радует, когда он это делает в три часа ночи. В журналах пусто. Так как всегда решением проблемы является нажатие кнопки сброса, я написал скрипт, который в нерабочее время каждые 10 минут проверяет доступность ящика по сети и порта HTTP. Если что-то не отвечает, скрипт ищет машину в кластере и жмёт ей на ресет, после чего пишет мне письмо, почему он это сделал.

$name = "webserver"
$ip = "192.168.1.10"

if (!(Test-Connection $ip -Count 4 -Quiet -OutVariable ping) -or `
!(Test-NetConnection $ip -Port 80 -OutVariable http).TcpTestSucceeded) {
$vm = Get-Clustergroup |? {$_.grouptype -eq 'VirtualMachine' -and $_.name -eq "$name"} |get-vm

  if ($vm -and $vm.State -eq "Running") {

  $vm |Restart-VM -force

  $body = "<p>Причины:</p>"
  $body += "<ul>"
    if (!$ping) {$body += "<li>Нет сетевого соединения</li>"}
    if (!$http.TcpTestSucceeded) {$body += "<li>Нет ответа по HTTP</li>"}
  $body += "</ul>"

  Send-MailMessage -SmtpServer mail.domain.ru -From support@domain.ru -Subject "Hyper-V - сервер $name был принудительно перезагружен" `
  -To admin@domain.ru -Body $body -BodyAsHtml -Encoding utf8
  }
}

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

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

Группы рассылки на базе групп подразделений компании

Обратились в просьбой сделать группу рассылки на один из отделов компании. Чтобы не возвращаться больше к этой теме в будущем, решили сделать такие группы сразу для всех отделов (кроме руководства, конечно).

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

Пришлось создать обычные группы рассылки, а членство в группах спрашивать у домена.

function Get-ADGroupMemberEnabledAndMail {
param(
[parameter(mandatory=$true)]
$group
)
Get-ADGroupMember "$group" -Recursive |? objectclass -eq 'user' |% {
    Get-ADUser -filter "name -eq '$($_.name)' -and enabled -eq 'True'" -Properties mail
    }
}

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

По результатам работы скрипт шлёт отчёт, причём, я распробовал всякие html-символы, чтобы сделать его более читаемым и лаконичным. Примерный вид:

Отдел по развитию (группа рассылки)
Управление по повышению (группа рассылки)
Бумажкин Иван Ильич Дирекция по улучшению (группа рассылки)
Бюрократов Тихон Тарасович Группа по превозмоганию (группа рассылки)

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

Обнаружение лишних объектов в корне DFS

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

Удалить этот скапливающийся хлам вручную бывает непросто — нужно искать, на каком конкретно сервере он лежит и удалять уже оттуда, потому что если админ подключен к DFS на одном сервере, а хлам лежит на другом, то через DFS админ удалить хлам не сможет, даже имея все полномочия — система пишет, что файлы не найдены. Ещё нюанс — из команд Powershell, относящихся к DFS, я не смог получить путей к его корням на локальных серверах, т. е., элегантно извлечь данные из одного источника не вышло. Пришлось просто перечислить все локальные пути в явном виде в скрипте, благо, их немного.

А как вообще отделить хлам от легитимных папок? Оказывается, папки в DFS имеют атрибут d----l (reparse point), то есть, это даже и не папки, а ссылки. Поэтому, вычислить хлам можно так:

dir `
"\\server1\c$\DFS_roots\*\*",
"\\server2\c$\корни_DFS\*\*",
"\\server3\c$\DFS_roots\*\*" |
? mode -ne 'd----l'

Скрипт, конечно, данные сам не удаляет (мало ли что может туда попасть?), а шлёт письмо, где перечислены потенциально лишние объекты, обёрнутые в команды для их удаления.

Автодобавление разрешения на почтовые ящики для группы

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

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

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

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

$accessNotGranted = $allUserMbxes |? {!((Get-MailboxPermission $_).user -match "$fullControlGroup")}
if ($accessNotGranted) {
    $accessNotGranted |
    Add-MailboxPermission -User "$fullControlGroup" -AccessRights FullAccess -InheritanceType All
}