Передісторія
В одній медичній організації впроваджували рішення на базі PACS-серверів Orthanc та DICOM-клієнта Radiant. У ході налаштування з'ясували, що кожен DICOM-клієнт має бути описаний у PACS-серверах таким чином:
- Ім'я клієнта
- AE-ім'я (має бути унікально)
- TCP-порт, який автоматично відкривається на стороні клієнта та приймає DICOM-обстеження від PACS-сервера (тобто сервер хіба що штовхає їх у бік клієнта – ініціюючи з'єднання першим)
- IP-адреса
Після налаштування Radiant клієнтів отримали наступну інформацію для роздумів – у кожного клієнта налаштування ПЗ із зазначеними вище параметрами призводило до заповнення файлу pacs.xml, який розташовувався у профілі користувача (шлях: %APPDATA%RadiantViewerpacs.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%RadiantViewerpacs.xml copy %APPDATA%RadiantViewerpacs.xml srv.test.localpconfigs$pacs-%COMPUTERNAME%-%USERNAME%.xml
Таким чином, на сервері в прихованому ресурсі будуть накопичуватися файли pacs.xml, в імені яких є інформація з якого комп'ютера і з якого користувача був скопійований цей конфіг.
Найскладніше було – дочекатися, коли у всіх користувачів відпрацює ця політика.
Зміна конфігурацій за допомогою Perl скрипту
нам буде потрібно під Windows от компании ActiveState, а также модуль XML::Writer, который можно установить с помощью команды ppm install XML-Writer.
А сам скрипт вийшов досить простий:
use XML::Writer;
# Открываем папку с отчетами, обрабатываем ссписок (удаляем лишнее):
$report_dir = "C:Perl64WORKPACS-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:Perl64WORKPACS-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%RadiantViewerpacs.xml copy /Y srv.test.localpconfigsnew$pacs-%COMPUTERNAME%-%USERNAME%.xml %APPDATA%RadiantViewerpacs.xml
Підсумковий .bat файл виглядає так:
@echo off
If exist %APPDATA%RadiantViewerpacs.xml copy %APPDATA%RadiantViewerpacs.xml srv.test.localpconfigs$pacs-%COMPUTERNAME%-%USERNAME%.xml
If exist %APPDATA%RadiantViewerpacs.xml copy /Y srv.test.localpconfigsnew$pacs-%COMPUTERNAME%-%USERNAME%.xml %APPDATA%RadiantViewerpacs.xml
Висновок
Таке ось «наколінний" Рішення. Випробували його вже двічі (у вересні 2018 та у лютому 2019), поки політ нормальний. Звичайно оновлює не 100% клієнтів, але близько до цього значення - решту доробляємо віддалено. Скрипт по .
Джерело: habr.com
