Предыстория
В одной медицинской организации внедряли решения на базе PACS-серверов Orthanc и DICOM-клиента Radiant. В ходе настройки выяснили, что каждый DICOM-клиент должен быть описан в PACS-серверах следующим образом:
- Имя клиента
- AE-имя (должно быть уникально)
- TCP-порт, который автоматически открывается на стороне клиента и принимает DICOM-обследования от PACS-сервера (т.е. сервер как бы толкает их в сторону клиента – инициируя соединение первым)
- IP-адрес
После настройки Radiant клиентов получили следующую информацию к размышлению – у каждого клиента настройка ПО с указанными выше параметрами приводила к заполнению файла pacs.xml, который располагался в профиле пользователя (путь: %APPDATA%\RadiantViewer\pacs.xml). При этом конфиг одного клиента от другого отличался минимум двумя параметрами (AE-имя у всех разное, а порт в основном одинаковый, кроме терминальных клиентов, работающих на одном и том же сервере – там порты тоже приходилось назначать разными).
Пример файла pacs.xml по ссылке:
Примерно полгода все было хорошо, система заработала…и тут до нас дошли «подводные камни»:
- Нам нужно ввести в строй несколько новых PACS-серверов, которые подменят старые (где стало заканчиваться место на дисках). PACS сервера в виртуальных машинах, но речь не об этом;
- Нам нужно как-то централизованно изменить уникальные конфигурации (двумя отличающимися параметрами) на 200 машинах (их количество регулярно увеличивалось);
- Учитывая темпы роста объемов обследований, решение нужно не разовое, а тиражируемое и регулярное (например, 1 раз в 3-5 месяцев).
Решение ниже.
Выбор инструментария для решения задачи
Вначале были попытки найти какое-то решение, которое на стороне клиента изменяло файл pacs.xml, и вносило в него изменения в список PACS-серверов, не трогая настройки AE-имени и TCP-порта. Windows клиенты на тот момент были на базе как Windows XP, так и Windows 7 – поэтому были попытки написать что-то такое на базе VBScript. Но увы – осилить такую задачу не получилось, ввиду полного отсутствия опыта написания чего-либо сложного и комплексного на этом языке. Попытки же найти и переписать также не увенчались успехом (тут надо отметить, что в голове уже был другой план, поэтому я не долбился с VBScript больше 3-4 часов).
В итоге я остановился на следующем решении:
- Собрать групповой политикой все файлы pacs.xml в одном месте на каком ни будь сервере в сетевом ресурсе;
- Изменить файлы скопом (опыт решения таких задач уже был – с использованием Perl);
- Также с помощью групповых политик обновить настройки клиентов.
Сбор файлов с помощью групповой политики
Самая простая часть – при входе клиента в свой профиль он со своими правами выполняет некий .bat файл, в котором прописано:
echo off
If exist %APPDATA%\RadiantViewer\pacs.xml copy %APPDATA%\RadiantViewer\pacs.xml \\srv.test.local\pconfigs$\pacs-%COMPUTERNAME%-%USERNAME%.xml
Таким образом на сервере в скрытом ресурсе будут накапливаться файлы pacs.xml, в имени которых есть информация с какого компьютера и с какого пользователя был скопирован данный конфиг.
Самое сложное было – дождаться, когда у всех пользователей отработает данная политика.
Изменение конфигураций с помощью Perl скрипта
Нам потребуется Active Perl под Windows от компании ActiveState, а также модуль XML::Writer, который можно установить с помощью команды ppm install XML-Writer.
Сам же скрипт получился довольно простой:
use XML::Writer;
# Открываем папку с отчетами, обрабатываем ссписок (удаляем лишнее):
$report_dir = "C:\\Perl64\\WORK\\PACS-xml3\\";
opendir(DIR, "$report_dir") or die "Не могу открыть папку с отчетами!";
@report_files = readdir DIR;
shift (@report_files); # удаляем точку из элементов массива (.)
shift (@report_files); # удаляем две точки из элементов массива (..)
# print "@report_files";
closedir(DIR);
# Начинаем обрабатывать файлы - по одному за раз. Нужно считать параметр AET и номер порта в переменные.
foreach $analiz_file (@report_files)
{
$full_path_to_file="C:\\Perl64\\WORK\\PACS-xml3\\".$analiz_file;
open (INFO, $full_path_to_file);
while ($line = <INFO>)
{
# Переменные $aet и $port содержат уникальные данные для каждого XML файла:
my ($other1, $aet, $other2, $port, $other3) = split /\"/, $line, 5;
# Если встречается строка listener - то мы дошли до нужной строчки и можно формировать новый XML:
if ($other1 =~ 'listener')
{
# Формируем новый XML c нужными полями и данными:
my $writer = XML::Writer->new(OUTPUT => 'self', DATA_MODE => 1, DATA_INDENT => 2, );
$writer->xmlDecl('utf-8');
$writer->startTag('pacs');
$writer->startTag('listener', ae => $aet, port => $port);
$writer->endTag();
$writer->startTag('hosts');
$writer->startTag('host', name => 'MRT', ae => 'ORTHANC', ip => 'XX.YY.214.17', ts => '1.2.840.10008.1.2.1', port => '4242', maxassoc => '1', allpres => '0', search => '1', protocol => '1', searchcharset => '', wildcards => '3', carets => '0');
$writer->endTag();
$writer->startTag('host', name => 'KT', ae => 'ORTHANC2', ip => 'XX.YY.215.253', ts => '1.2.840.10008.1.2.1', port => '4242', maxassoc => '1', allpres => '0', search => '1', protocol => '1', searchcharset => '', wildcards => '3', carets => '0');
$writer->endTag();
$writer->startTag('host', name => 'R', ae => 'ORTHANC3', ip => 'XX.YY.215.252', ts => '1.2.840.10008.1.2.1', port => '4242', maxassoc => '1', allpres => '0', search => '1', protocol => '1', searchcharset => '', wildcards => '3', carets => '0');
$writer->endTag();
$writer->startTag('host', name => 'KT-20180501-20180831', ae => 'ORTHANC4', ip => 'XX.YY.215.251', ts => '1.2.840.10008.1.2.1', port => '4242', maxassoc => '1', allpres => '0', search => '1', protocol => '1', searchcharset => '', wildcards => '3', carets => '0');
$writer->endTag();
$writer->startTag('host', name => 'KT-20180901-20181130', ae => 'ORTHANC5', ip => 'XX.YY.215.250', ts => '1.2.840.10008.1.2.1', port => '4242', maxassoc => '1', allpres => '0', search => '1', protocol => '1', searchcharset => '', wildcards => '3', carets => '0');
$writer->endTag();
$writer->endTag('hosts');
$writer->startTag('presets');
$writer->endTag();
$writer->startTag('lastsearch', dt => '4', mfid => '1048592');
$writer->endTag();
$writer->endTag('pacs');
# Помещаем готовый XML в переменную:
my $xml = $writer->end();
# Подготавливаем файл для перезаписи:
$rewritexml = $full_path_to_file;
# Переписываем XML файлы новыми данными:
open (NEWXML, ">$rewritexml");
print NEWXML $xml;
close (NEWXML);
}
}
}
Принцип его работы:
- Открываем каталог, в котором у нас собраны конфигурации pacs.xml от клиентов и помещаем список файлов в массив скаляров (@report_files);
- В цикле обрабатываем по одному файлу и считываем его построчно;
- С помощью split дробим каждую строку на 5 частей, используя кавычки как разделитель;
- Находим строку с словом listener и помещаем в две переменные уникальные для каждого файла данные (AE-имя клиента и номер TCP-порта);
- После этого просто формируем новый XML-файл, вписываем в него уникальные параметры и далее вставляем нужное количество PACS-серверов с их параметрами – т.е. то, ради чего все затевалось)
- Переписываем новый XML-файл поверх старого.
Надо отметить, что на самом деле я использую данный скрипт не полностью автоматически – по сути я копирую собираемые конфиги в отдельный каталог и потом запуская скрипт изменяю их всем скопом. Далее выборочная проверка – и конфиги можно разливать обратно по машинам.
Распространение измененных pacs.xml файлов по клиентам
Самое простое, что пришло в голову – внести изменения в уже работающий .bat файл, который собирает конфигурации с клиентов и добавить строку:
If exist %APPDATA%\RadiantViewer\pacs.xml copy /Y \\srv.test.local\pconfigsnew$\pacs-%COMPUTERNAME%-%USERNAME%.xml %APPDATA%\RadiantViewer\pacs.xml
Итоговый .bat файл выглядит так:
@echo off
If exist %APPDATA%\RadiantViewer\pacs.xml copy %APPDATA%\RadiantViewer\pacs.xml \\srv.test.local\pconfigs$\pacs-%COMPUTERNAME%-%USERNAME%.xml
If exist %APPDATA%\RadiantViewer\pacs.xml copy /Y \\srv.test.local\pconfigsnew$\pacs-%COMPUTERNAME%-%USERNAME%.xml %APPDATA%\RadiantViewer\pacs.xml
Заключение
Такое вот «наколеночное» решение. Опробовали его уже два раза (в сентябре 2018 и в феврале 2019), пока полет нормальный. Конечно обновляет не 100% клиентов, но близко к этому значению — остальных доделываем удаленно. Скрипт по ссылке.
Комментарии (7)
mrobespierre
16.04.2019 11:14+1Сейчас все 2 перловика Хабра проснутся и закидают вас тапками за отсутствие strict, warnings, ООП подхода в работе с файлами и другие сомнительные практики…
А вообще здорово, что Perl продолжает get shit done вне зависимости от того жив он или нет.Dorlas Автор
16.04.2019 11:16Аж целых два!? ))))))
Ну да — у меня есть подборка Perl-скриптов, которые писал сам когда то для различных задач админских — работают и по сей день.
В принципе какие то вещи действительно лучше написать (или переписать на PowerShell) — но тут просто вспомнилось, что есть уже скриптец, который чуть чуть переписать ))))
Naves
16.04.2019 13:16Orthanc до сих пор не умеет настраивать несколько хранилищ, шел 2019 год. Печально. Либо самому писать storage plugin.
А вот Conquest server это мог еще с лохматых годов.
Inobitec DICOM Viewer хранит настройки в реестре пользователя, в такой ситуации проще.
Dorlas Автор
16.04.2019 16:50Чет не нашел на их сайте стоимость этого клиента. Только табличку сравнения Free, Lite и Pro версий.
Naves
16.04.2019 17:37Lite-версия примерно как Radiant стоила.
А зачем вам Radiant на 200 рабочих мест, там прям все рентгенологи? Обычным врачам же хватает или web-интерфейса, или бесплатного K-pacs. Причем некоторым K-Pacs нравится даже больше, чем фирменный софт от одной широкоизвестной в узких кругах питерской конторы рентген-оборудования.
Tzimie
А почему Perl а не естественный для win powershell?
Dorlas Автор
Потому что уже был готовый скрипт для решения другой задачи.
Идея переписать на PowerShell стоит в планах — как нибудь займусь.