🏠: it

Праздничные дни в Powershell

Сегодня у меня сработало оповещение о том, что базы Консультанта старые — скрипт смотрит на дату в файле [Путь к каталогу Консультанта]\RECEIVE\LAST_REC.TXT, и если эта дата раньше вчерашней и сегодня не понедельник, отправляет уведомление по почте.

Сегодня вторник, но вчера был выходной в счёт праздника, попавшего на воскресенье, поэтому то, что последнее обновление было 6-го числа, совершенно нормально. Тем не менее, я подумал — а можно ли учитывать и праздничные дни тоже? Как сделать так, чтобы компьютер «знал» про праздничные дни? Ведь это может много где пригодиться, начиная от более гибкого расписания выполнения автоматических заданий, каких-нибудь оповещений, да мало ли где ещё?

Можно! В данном случае я нашёл сайт xmlcalendar.ru, где выкладывают производственный календарь в формате XML. Чтобы выгрузка с него была понятнее, я сделал некоторые преобразования. Вот скрипт:

# Скачивание XML
curl http://xmlcalendar.ru/data/ru/2020/calendar.xml -OutFile d:\temp\cal.xml
# Импорт
[xml]$cal = gc d:\temp\cal.xml -Encoding utf8
# Обработка
foreach ($day in $cal.calendar.days.day) {
$day.d = (($cal.calendar.year + '.' + $day.d) -as [datetime]).tostring("dd.MM.yyyy")

if ($day.t -eq 1) {$day.t = "Вых"}
elseif ($day.t -eq 2) {$day.t = "Сокр"}
elseif ($day.t -eq 3) {$day.t = "Раб"}

if ($day.h -match "\d") {$day.h = ($cal.calendar.holidays.holiday |? id -eq $day.h).title}
}
# Экспорт в CSV
$cal.calendar.days.day |Export-Csv "d:\temp\cal$($cal.calendar.year).csv" -Encoding utf8 -Delimiter ';' -NoTypeInformation

Результат:

d          t    h                                                                     
-          -    -                                                                     
01.01.2020 Вых  Новогодние каникулы (в ред. Федерального закона от 23.04.2012 № 35-ФЗ)
02.01.2020 Вых  Новогодние каникулы (в ред. Федерального закона от 23.04.2012 № 35-ФЗ)
03.01.2020 Вых  Новогодние каникулы (в ред. Федерального закона от 23.04.2012 № 35-ФЗ)
04.01.2020 Вых  Новогодние каникулы (в ред. Федерального закона от 23.04.2012 № 35-ФЗ)
05.01.2020 Вых  Новогодние каникулы (в ред. Федерального закона от 23.04.2012 № 35-ФЗ)
06.01.2020 Вых  Новогодние каникулы (в ред. Федерального закона от 23.04.2012 № 35-ФЗ)
07.01.2020 Вых  Рождество Христово                                                    
08.01.2020 Вых  Новогодние каникулы (в ред. Федерального закона от 23.04.2012 № 35-ФЗ)
23.02.2020 Вых  День защитника Отечества                                              
24.02.2020 Вых                                                                        
08.03.2020 Вых  Международный женский день                                            
09.03.2020 Вых                                                                        
30.04.2020 Сокр                                                                       
01.05.2020 Вых  Праздник Весны и Труда                                                
04.05.2020 Вых                                                                        
05.05.2020 Вых                                                                        
08.05.2020 Сокр                                                                       
09.05.2020 Вых  День Победы                                                           
11.05.2020 Вых                                                                        
11.06.2020 Сокр                                                                       
12.06.2020 Вых  День России                                                           
03.11.2020 Сокр                                                                       
04.11.2020 Вых  День народного единства                                               
31.12.2020 Сокр

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

Удобная альтернатива — https://www.isdayoff.ru

Подкручиваем HTTPS

Больше трёх лет назад я делал подобную заметку, настало время дальнейшего пересмотра настроек. Я мало смыслю в безопасности, но зато люблю, когда онлайн-проверки светятся зелёным. За прошедшее время появился протокол HTTP/2, который я включил в конце 2017-го, и TLS 1.3, а версии TLS 1.0 и 1.1 уже признаются устаревшими с марта этого года, и отправляются на заслуженную пенсию составлять компанию уже находящемуся там SSL.

Итак, чтобы выключить все протоколы, кроме самых новых и надёжных, нужно добавить в файл конфигурации Апача, например, /etc/apache2/sites-available/default-ssl.conf (у меня другой файл, который создаёт Let’s Encrypt), следующее:

# Set Forward Secrecy
SSLProtocol -all +TLSv1.2 +TLSv1.3
SSLHonorCipherOrder on
SSLCipherSuite HIGH:!aNULL:!MD5:!3DES

# Strict transport security
<IfModule mod_headers.c>
 Header always set Strict-Transport-Security "max-age=15768000; includeSubDomains"
 Header always set Referrer-Policy "no-referrer-when-downgrade"
</IfModule>

С TLS 1.3, по ощущениям, действительно работает быстрее — ведь для установки защищённого соединения ему нужно меньше согласований. SSLHonorCipherOrder on — это включение расстановки приоритета алгоритмов шифрования самим сервером, что рекомендуется.

Также, я включил в Апаче поддержку OCSP Stapling — вроде бы полезная вещь — и, забавы ради, HSTS preloading, но это уже совсем необязательно.

Результат обстоятельного теста на Ssllabs после настройки:

Список других полезных тестов:

Что сделал - 5

Скрипт поиска несмонтированных виртуальных дисков Hyper-V

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

Алгоритм работы: берутся все виртуальные машины всех хостов, для каждой выводится список дисков (.vhd и .vhdx), если там вместо диска снапшот, то ищется родительский диск (там другое свойство), и результаты заносятся в один массив. Затем ищутся файлы с теми же расширениями на локальных дисках серверов, результаты заносятся в другой массив. Потом массивы сравниваются — отбрасываются значения первого массива, т. е., смонтированные диски, а потом сортируются по уникальным значениям — дело в том, что если хосты принадлежат кластеру и подключены к одной дисковой полке, то и результаты у них будут практически одинаковые. Ну, а дальше из этого делается красивая табличка, подсчитывается общий итог, генерируются команды на удаление и всё высылается на почту. Выглядит это примерно так:

# Команды на удаление
del "D:\VM\disk_c.vhdx" -Force -Confirm:$false
del "L:\vm\vmws-sccm\sccm2.vhdx" -Force -Confirm:$false
del "L:\vm\vmws-dns\Virtual Hard Disks\vmws-dns.vhdx" -Force -Confirm:$false
del "L:\back\vmls-web\vmls-web_Disk0.vhdx" -Force -Confirm:$false
del "L:\vm\vmws-sccm\sccm_c.vhdx" -Force -Confirm:$false

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

$hosts = "hv01","hv02","hv03","hv04","hv05"
$allvhds = Invoke-Command -computername $hosts -command {
dir ((gcim Win32_LogicalDisk -filter "drivetype=3").DeviceID -replace "$","\") -Include *.vhd,*.vhdx -Recurse -ErrorAction SilentlyContinue
}

Здесь инициатор просто даёт команду всем удалённым хостам, а потом просто ждёт, когда они завершат работу, и результаты падают в массив по мере их поступления. Работает во столько раз быстрее, сколько хостов участвует. Там есть нюансы — стандартно может работать до 32 потоков одновременно, но это настраивается, да и редко нужно.

Результат работы скрипта — нашлось 49 бесхозных дисков общим объёмом немногим менее 4 терабайт.

Скрипт создания папки на файловом ресурсе

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

Конвертация виртуальных дисков VMWare (.vmdk) в Hyper-V (.vhdx)

Последний месяц плотно занимался перетаскиванием виртуальных машин со старой VMWare ESXi 5.5 на модный молодёжный Hyper-V 2016. Есть прекрасная программа под названием Microsoft Virtual Machine Converter, которая хорошо выполняет эту функцию. Но иногда виртуальные машины были уже выгружены в виде набора файлов, и нужно было просто переконвертировать диск. Оказалось, что этот конвертер имеет в своём составе модуль для Powershell, и всё заверте…

# импортировать модуль
Import-Module 'C:\Program Files\Microsoft Virtual Machine Converter\MvmcCmdlet.psd1'
# конвертировать
ConvertTo-MvmcVirtualHardDisk -SourceLiteralPath C:\temp\disk.vmdk -DestinationLiteralPath 'C:\temp' -VhdType DynamicHardDisk -VhdFormat Vhdx

И последняя на сегодня забавная история.

Прошивка дисковой полки HP P2000 G3 FC

Великая и ужасная компания Хьюлет-Паккард выпускает (или выпускала) широко известные в узких кругах дисковые полки P2000. После того, как я перенёс все виртуальные машины со старой VMWare, эта полка освободилась, и там нужно было в одном контроллере заменить сбойную флеш-карту и всячески её обновить. С обновлением возникли проблемы, потому что без наличия сервисного контракта Хьюлет-Паккард скачать свежую прошивку со своего сайта не даёт.

Начал поиск в интернетах — и обнаружилось, что эта полка — перемаркированная DotHill AssuredSAN 3000 series, и компания Дотхилл, её выпускавшая, впоследствии была поглощена компанией Сигейт, у которой эти прошивки свободно доступны на сайте. Я скачал прошивку с сайта Сигейта, и она без проблем установилась и работает.

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

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

Обучение компьютера русскому

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

Уважаемый пользователь Информационных Систем!

Проблема хорошо описана в статье «Здравствуйте, с вами говорит робот», и мне хотелось очеловечить эти формальные уведомления, например, чтобы первой строкой было обращение по имени и отчеству с соответствующим изменением окончания слова «уважаемый» в зависимости от пола адресата.

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

Первым делом нужно понять, как отделять мужские ФИО от женских. Просматривая списки, я обнаружил, что ключом к разгадке являются отчества — все мужские отчества заканчиваются на «ич», а все женские — на «на». Сначала я думал, что одинаковы три последних буквы, но, например, у мужчин есть Ильич и Кузьмич, а у женщин Ильинична и Кузьминична, так что остановился на двух.

Так-то лучше:

# Выборка
if ($user.givenname -match "на$") {$body1 = "Уважаемая $($user.givenname)!"}
elseif ($user.givenname -match "ич$") {$body1 = "Уважаемый $($user.givenname)!"}
# А случаи бывают разные
elseif (!($user.givenname)) {$body1 = "Уважаемый $($user.name)!"}
else {$body1 = "Уважаемый пользователь информационных систем АО «Мир шерстяных носков»!"}
# Результат
$body1

Уважаемый Игорь Валерианович!
Уважаемый Михаил Сергеевич!
Уважаемый Игорь Алексеевич!
Уважаемая Олеся Андреевна!
Уважаемая Анна Васильевна!
Уважаемая Светлана Николаевна!
Уважаемый Алексей Владимирович!
Уважаемый Сергей Владимирович!
Уважаемая Любовь Владимировна!
Уважаемая Полина Евгеньевна!

Следующий этап — зависимость окончания слова «день» от их количества. Надо составить фразу «Действие вашего пароля истекает через N дней». Путём нехитрых размышлений получается, что вроде бы все числа, заканчивающиеся на 1 — это «день», на 2,3 и 4 — «дня», а все остальные — «дней». Проверяем:

if ($user.daystoexp -match "1$") {$in = "через $($user.daystoexp) день"}
elseif ($user.daystoexp -match "[234]$") {$in = "через $($user.daystoexp) дня"}
else {$in = "через $($user.daystoexp) дней"}
# Результат
"Действие вашего пароля истекает $in."

Действие вашего пароля истекает через 24 дня.
Действие вашего пароля истекает через 17 дней.
Действие вашего пароля истекает через 14 дня. # !
Действие вашего пароля истекает через 2 дня.
Действие вашего пароля истекает через 49 дней.
Действие вашего пароля истекает через 23 дня.
Действие вашего пароля истекает через 55 дней.
Действие вашего пароля истекает через 12 дня. # !
Действие вашего пароля истекает через 1 день.
Действие вашего пароля истекает через 11 день. # !

Русский язык велик и могуч, его с кондачка не возьмёшь! Исключение — числа с одиннадцати до четырнадцати: они все должны заканчиваться словом «дней», причём, в любой сотне — «через 11, 112, 1513, 614 дней». Исправляем:

if ($user.daystoexp -match "(?<!1)1$") {$in = "через $($user.daystoexp) день"}
elseif ($user.daystoexp -match "(?<!1)[234]$") {$in = "через $($user.daystoexp) дня"}
else {$in = "через $($user.daystoexp) дней"}
# Результат
"Действие вашего пароля истекает $in."

Действие вашего пароля истекает через 24 дня.
Действие вашего пароля истекает через 17 дней.
Действие вашего пароля истекает через 14 дней.
Действие вашего пароля истекает через 2 дня.
Действие вашего пароля истекает через 49 дней.
Действие вашего пароля истекает через 23 дня.
Действие вашего пароля истекает через 55 дней.
Действие вашего пароля истекает через 12 дней.
Действие вашего пароля истекает через 1 день.
Действие вашего пароля истекает через 11 дней.

Так-то лучше! Теперь бездушный компьютер будет обращаться к людям по имени-отчеству, да ещё и правильно склоняя слова, что, по-моему, отлично.

Напоследок расскажу, что, оказывается, дата истечения срока действия пароля вычисляется самим контроллером домена и хранится в атрибуте msDS-UserPasswordExpiryTimeComputed, причём, в нечитаемом формате FileTime (количество 100-наносекундных интервалов с 1 января 1601 г.). Чтобы преобразовать это значение в «нормальный» формат при запросе, нужно написать что-то вроде

Get-ADUser username -Properties msDS-UserPasswordExpiryTimeComputed |
select name,samaccountname,@{n='passexpdate';e={[datetime]::FromFileTime($_.'msDS-UserPasswordExpiryTimeComputed')}}

У нас в домене нет Fine-Grained Password Policies (FGPP), так что при их наличии значение этого параметра надо проверять.

Реконструкция сетевого хранилища

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

Логично было предположить, что не хватает питания, и для проверки я приволок с работы стандартный блок (AeroCool KCAS-700W), подключил его и убедился, что хранилище прекрасно включается и работает. Вернув блок обратно, я решил пересобрать систему в новом корпусе и с новым БП.

Итак, корпус нужен как можно более компактный и совместимый с материнскими платами Mini-ITX, но чтобы туда влезали 2 диска 3,5″ и обычный блок питания ATX — со вcякой экзотикой связываться неохота. На рынке есть предложения компактных корпусов такого рода, например, компании Термалтейк, но стоят они как-то дороговато. К тому же, эти корпуса довольно широкие, а мне нужен был не шире 20 см, чтобы влезть на полку в коридоре. Что касается блока питания, то от него требуется бесшумность и наличие активного модуля коррекции коэффициента мощности (Active PFC). Самой мощности особой не нужно, 300 ватт вполне хватит.

В итоге взял супердешёвый и достаточно компактный корпус Exegate BA-110 и блок питания Zalman ZM400-LX (400 ватт, меньше они в этой линейке не делают), скрутил их вместе, пересадил туда всё остальное железо:

Стенки нового корпуса можно натурально гнуть руками, так как металл тонкий и мягкий, а винты в него нужно закручивать очень нежно, до малейшего сопротивления, иначе резьба тут же сорвётся и затянуть их потом будет невозможно. Но ничего другого от корпуса за 830 рублей и не ожидалось, а на самом деле за свои деньги это очень даже достойное изделие. Единственное, к чему может быть небольшое нарекание — что из четырёх верхних посадочных мест под 3,5-дюймовые диски реально можно использовать только два средних: сверху мешают внутренние разъёмы передней панели, а снизу проходит ребро жёсткости, соединяющее стойки корпуса — это не позволяет разнести диски дальше друг от друга. Но это не проблема, диски при работе всё равно холодные.

Светодиод питания (Power LED) оказался синим и очень ярким, я отключил его, вентилятор 80×80 из старой системы задействовать не стал. В результате хранилище получилось тихим и несветящимся — ровно то, что нужно.