🏠: работа

Что сделал - 7

Сжатие файлов PDF

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

Оказалось, что практически все программы, которые работают с PDF и не принадлежат компании Adobe, «под капотом» используют инструмент под названием Ghostscript. У него невероятная куча непонятных опций и жутковатый синтаксис, но для простоты в интернете рекомендуют использование предустановленных значений: ebook и screen. Первый вариант даёт разрешение конечного документа 150 точек/дюйм, что даёт порой недостаточно компактный размер, а второй — 72, что даёт ужасное качество. Нужно было найти некий промежуточный вариант, и я остановился на таком:

& "$ghostScript" -q -o "$outputFile" `
-sDEVICE=pdfwrite -dPDFSETTINGS=/screen `
-dColorImageDownsampleType=/Bicubic -dColorImageResolution=110 `
-dGrayImageDownsampleType=/Bicubic -dGrayImageResolution=110 `
-dMonoImageDownsampleType=/Bicubic -dMonoImageResolution=200 `
-f "$inputFile"

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

Пример выше хорошо подходит для файлов с изображениями и цветной графикой, вроде детских учебников, но нужен ещё один компактный вариант для полностью чёрно-белых документов. Изучение вопроса показало, что в современных версиях Ghostscript нет простого способа переделывать документы в ч/б. Вкратце — раньше можно было сначала конвертировать исходный PDF в монохромный PS (PostScript) через опцию -sDEVICE=psmono, а потом обратно в PDF, добавляя ещё одну хитрую команду; потом psmono убрали, и если сейчас и есть способ такого преобразования, то какой-то совсем нетривиальный, а привязываться к старой версии не хочется. Можно делать документ в оттенках серого, но в этом нет смысла, потому что выяснилось, что разница в размерах с цветным вариантом ничтожна.

Чтобы решить задачу преобразования в ч/б, я задействовал ImageMagick, получилось так:

& "$imageMagick" -density 200 "$inputFile" `
-monochrome -compress Group4 `
"$bwFile"

Density — это указание рассматривать исходный документ с таким-то разрешением, а Group4 — это великолепный алгоритм сжатия ч/б изображений, используемый в факсах. ImageMagick при работе с PDF использует тот же Ghostscript, поэтому на сервере они должны стоять оба.

В результате система выглядит так: есть каталог, куда люди кладут PDF-файлы, каждые 3 минуты запускается задание в планировщике, скрипт читает каталог, берёт подходящие по критериям файлы в обработку, перенося их во временный каталог с текущим временем, а потом результат кладёт обратно в одноимённый подкаталог с добавлением к имени файла «(сжатый)» и «(сжатый чб)». Оригинал после обработки удаляется сразу, результаты — через час. В свойствах задания в планировщике нужно разрешить запуск нескольких экземпляров — ведь обработка может идти больше 3 минут.

Примеры обработки:

  1. Нотный сборник, 49 страниц:
Оригинал (22,8 МБ) Сжатый (5,5 МБ) Сжатый ч/б (1,8 МБ)
  1. Какой-то договор из интернета, 27 страниц:
Оригинал (15,7 МБ) Сжатый (4,5 МБ) Сжатый ч/б (2,4 МБ)

Позже обнаружился способ задания порога для устранения зернистости букв и уменьшения шума для ч/б режима. Нужно добавить -level 10%,90% +dither, но это существенно замедляет обработку.

Дополнительно я нашёл способ прописывания метаинформации в свойства PDF-документа, проще всего для этого использовать ExifTool:

gci "$temp\*.pdf" |% {
& "$exifTool" -charset filename=Cyrillic -charset Cyrillic `
-Author="Обработано в Институте сжатия ПДФ им. Красноглазова" "$($_.FullName)" > $null
}

Результат:

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

Включение Wake-on-LAN на Windows 8 и новее

В связи с тем, что куча народу сейчас работает из дома, пользовательские компьютеры стали работать в режиме 24/7, прямо как сервера. Тем временем, периодически проводятся какие-то работы по электрике, моргает свет и т. п., и компьютеры остаются выключенными. Чтобы включить их, сотрудник техподдержки идёт открывает кабинеты (под подпись актов, с охранником), и жмёт кнопочки включения. Есть разные способы решения этой проблемы, но, например, идея настраивать пользовательские тачки в BIOS так, чтобы они автоматически включались при обрыве питания — идея не очень; всё же есть надежда, что жизнь вернётся в нормальное русло, а в обычной ситуации такую настройку включать не нужно, да и чтобы включить её, опять же надо открыть все кабинеты, перезапустить все компьютеры, зайти им в BIOS и настроить руками, что наводит тоску, когда у тебя в хозяйстве больше полутысячи машин. Мне рассказали, что техподдержка уже несколько лет как включает в BIOS опцию пробуждения компьютера при запросе из сети (Wake-on-LAN), но проблема заключалась в том, что старые системы, типа Windows 7, нормально пробуждаются, а вот новые, вроде Windows 10 — нет. Соответственно, возникла необходимость разобраться, в чём дело.

Полез читать интернеты. Выяснилось, что поведение WoL на системах Windows 8 и новее отличается от более старых. Грубо говоря, в новых системах статус S5 (т. е., «компьютер выключен») стал более строгим — если компьютер выключен, значит, он должен быть реально выключен, т. е., энергопотребление должно быть равным нулю. В связи с этим, чтобы можно было будить комп с новой системой, он при выборе пользователем пункта «выключить компьютер» должен переходить в некое промежуточное состояние S4 (гибридное выключение или «быстрый запуск»). Этот быстрый запуск, или Fast boot, включён в новых системах изначально, но если он выключен, его нужно включить либо через групповую политику, либо через реестр.

Второй момент — в системе должен стоять свежий драйвер сетевой карты, чтобы в его настройках была вкладка «Управление электропитанием». В варианте Windows 10 1809, широко распространённом у меня на работе, стандартный драйвер не имеет этой вкладки, соответственно, комп не просыпается и настроить его невозможно. Пришлось писать скрипт, обновляющий драйверы на машинах в зависимости от модели адаптера. В варианте 1909 уже всё в порядке изначально.

Третий момент — на этой вкладке должны быть включены как минимум две верхних галки из трёх, а лучше все три, чтобы не будить комп зазря (как их включить удалённо, см. статью Information about power management setting on a network adapter). Причём, обнаружилось, что если снять верхнюю галку, компьютер перестаёт просыпаться (а отключать этот пункт — широко распространённая практика).

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

Четвёртый момент — в тех же настройках драйвера на вкладке «Дополнительно» чаще всего нужно отключить настройки типа «зелёный ethernet» и «энергосберегающий ethernet», чтобы адаптер не засыпал. Например, это указано в инструкции по настройке WoL для компьютеров Dell, которые встречаются в моей сети, в общем, надо смотреть инструкции на сайтах производителей по поводу тонкостей. А вот настройки со словами «wake» и «magic packet», наверное, стоит включить все, надо просмотреть их и разбираться в каждом отдельном случае. Для управления электропитанием сетевого адаптера в Powershell существуют удобные команды

Get-NetAdapterPowerManagement
Enable-NetAdapterPowerManagement
Disable-NetAdapterPowerManagement
# Для доп. опций
Get-NetAdapterAdvancedProperty
Set-NetAdapterAdvancedProperty

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

Так как, в числе прочего, у меня в AD пишется и MAC-адрес активных сетевых адаптеров компьютеров, то не проблема будить их, используя Wake-on-LAN, прямо из Powershell просто по имени машины, и это отлично.

Что сделал - 6

Скрипт синхронизации данных из базы Microsoft SQL в Active Directory

Во многих компаниях существуют сервисы, дублирующие друг друга — это происходит отчасти из-за незнания о существовании уже имеющихся инструментов или неумения их настраивать, отчасти из-за сложных взаимоотношений или плохой координации между подразделениями, а отчасти из-за конъюнктурных стремлений различных начальников. К примеру, не раз я встречал разные реализации внутренних телефонных справочников, которые не связаны с Active Directory, хотя логично было бы всю первичную информацию держать именно там, и вся информация была бы доступна через адресную книгу Outlook, или, в крайнем случае, веб-версию справочника формировать из запросов в AD. Нет, так просто нельзя — в моей нынешней конторе телефонный справочник представляет собой отдельный сервис, причём разделённый на два сервера — древняя писанная на коленке веб-морда, которая крутится на Ubuntu 14.04, и база данных, которая работает — это радует отдельно в данной ситуации — на Microsoft SQL.

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

На всё вышеперечисленное безобразие я никак повлиять не могу, однако, хотелось бы иметь актуальные телефоны в Active Directory, которой я управляю. Для этого я написал скрипт синхронизации данных из базы Microsoft SQL в Active Directory. Делается выгрузка из базы (Invoke-Sqlcmd) и из AD, затем для каждого пользователя базы ищется аналог в выгрузке AD, и при несовпадении значений полей они либо меняются, либо удаляются, отчёт по результатам изменений высылается в почту. У дублирующихся пользователей в базе данных информация берётся только из самого свежесозданного по ID.

Конечно, телефоны и прочую информацию я по возможности пытаюсь причёсывать, вот, например, строка обработки телефонных номеров из базы:

@{n='tel';e={($_.tel -split ',').trim() -replace "^49","+7 49" -replace "\)",') ' `
-replace "\s+",' ' -replace '^-$' -replace "^\(|^8 \(","+7 " -replace "\)" `
-replace "123045","123-45" -join ', '}},

Переделал отчёт по несмонтированным дискам Hyper-V в полноценный отчёт по Hyper-V в целом

Отчёт включает в себя:

  1. Таблицу по виртуальным машинам — имя машины, выделенная память, кол-во процессоров, состояние, хост, кластеризована или нет, версия конфигурации. В конце статистика, типа «Всего виртуальных машин 55, общий объём памяти 363 ГБ, всего виртуальных процессоров 134, включено машин 40, выключено 15, в ином статусе 0.»
  2. Таблицу по смонтированным дискам, в конце статистика.
  3. Таблицу по несмонтированным дискам, в конце статистика.

Сбор информации о состоянии жёстких дисков с пользовательских машин

Минимальную информацию об этом может давать и Windows, но хотелось сделать красиво, для этого я привлёк программу DiskSmartView, которая умеет выгружать отчёты в форматах CSV и HTML.

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

Локальная часть делает следующее: скрипт запускается после загрузки компьютера, смотрит во временный каталог, и если последний отчёт не старше недели, прерывает работу. Если отчёта нет или он старше, запускает DiskSmartView и делает отчёт во временный каталог в формате CSV, затем проверяет, есть ли в колонке статуса значения, отличные от «OK», «неизвестно» или пустых. Если нет, то работа завершается, а если да, то отчёт делается ещё раз, но уже в общий каталог на сервере и в формате HTML. Потом этот HTML открывается, оттуда выкидывается всё ненужное и вставляется имя компьютера и время составления отчёта. На этом локальная часть исчерпывается.

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

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

Уже опубликовал этот текст, как вспомнил ещё про один

Cкрипт, связанный с массовым переходом сотрудников на удалённую работу

Суть его в том, что он запускается при загрузке компьютера, ищет в AD пользователя, ФИО которого прописано в описании компьютера, и если находит, прописывает его в локальную группу «Пользователи удалённого рабочего стола», включает RDP через реестр, через реестр же отключает «проверку подлинности сети», прописывает правило файрволла под названием «Allow RDP COVID-2019», а потом по результатам — что было сделано — пишет отчёт в общий каталог на сервере.

Время;ФИО;Логин;Компьютер;Добавлен в группу;RDP;NLA;Правило файрволла
2020.03.25 15:05:55;Королёва Ирина Витальевна;koroleva;PC1384;Да;Включено;Отключено;Создано

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

Праздничные дни в 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

Что сделал - 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, и компания Дотхилл, её выпускавшая, впоследствии была поглощена компанией Сигейт, у которой эти прошивки свободно доступны на сайте. Я скачал прошивку с сайта Сигейта, и она без проблем установилась и работает.

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

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

Что сделал - 4

Мониторинг датчиков в серверной

Сделал вот такую красоту:

Скрипт в виде опросника, создающий пользователя в домене

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

Скрипт, разбирающий список IP-адресов, перечисленных через пробел

Затем для каждого выясняется имя сопоставленного этому адресу компьютера и ФИО пользователя (благо, ФИО последнего вошедшего пользователя уже забито другим периодически выполняющимся скриптом в описание компьютера). Оказалось, что в Powershell есть прекрасная команда Resolve-DNSName, позволяющая больше не пользоваться старым nslookup.