В процессе разработки и администрирования систем на базе 1С:Предприятие часто возникает задача идентификации конкретного рабочего места. Особенно остро эта проблема стоит в клиент-серверных вариантах работы на платформе 8.2 или ранних версиях 8.3, где встроенные механизмы получения IP-адреса клиента еще отсутствовали. Ситуация осложняется, если в локальной сети используются компьютеры с неуникальными именами (например, клонированные образы ОС без смены ComputerName) или когда несколько пользователей работают под одной учетной записью 1С.
В данной статье мы подробно разберем, как программно получить IP-адрес клиента, как уникально идентифицировать «железо» и каким образом организовать контроль количества запущенных сессий, чтобы избежать дублирования и нецелевого расхода лицензий.
Начиная с версии платформы 8.3.17, разработчикам стал доступен стандартный метод получения IP. Мы можем использовать свойство IPАдресКлиента объекта СеансИнформационнойБазы. Однако, если ваша информационная база работает на версии 8.2 или ранних 8.3, это свойство будет недоступно. Выясним причину: до определенного момента сервер 1С не «пробрасывал» информацию о сетевом уровне клиента в контекст встроенного языка. Поэтому нам приходится обращаться к средствам операционной системы через механизмы COM и WMI.
Инструментарий управления Windows (WMI) — это мощный механизм, позволяющий получить практически любую информацию о системе. Разберем по шагам, как вытащить IP-адрес активного сетевого адаптера на стороне клиента. Этот код должен выполняться в контексте «НаКлиенте».
Проанализируем пример кода, который перебирает сетевые адаптеры и ищет тот, у которого включен IP:
Функция ПолучитьIPАдресКомпьютера() Экспорт
Попытка
Locator = Новый COMОбъект("WbemScripting.SWbemLocator");
Service = Locator.ConnectServer(".", "root\cimv2");
// Запрос только активных адаптеров с настроенным IP
Query = "SELECT IPAddress FROM Win32_NetworkAdapterConfiguration WHERE IPEnabled = True";
РезультатЗапроса = Service.ExecQuery(Query);
Для Каждого Объект Из РезультатЗапроса Цикл
Если Объект.IPAddress <> Неопределено Тогда
// IPAddress возвращается в виде массива строк (IPv4, IPv6)
Для Каждого Адрес Из Объект.IPAddress Цикл
Если Найти(Адрес, ".") > 0 Тогда // Простейшая проверка на IPv4
Возврат Адрес;
КонецЕсли;
КонецЦикла;
КонецЕсли;
КонецЦикла;
Исключение
Возврат "0.0.0.0";
КонецПопытки;
Возврат "";
КонецФункции
Рассмотрим подробнее, что здесь происходит. Мы подключаемся к локальному пространству имен root\cimv2 и выполняем SQL-подобный запрос к классу Win32_NetworkAdapterConfiguration. Важно помнить, что у компьютера может быть несколько IP-адресов (Wi-Fi, Ethernet, VPN, виртуальные адаптеры), поэтому мы фильтруем результат, выбирая первую попавшуюся строку, похожую на стандартный IPv4-адрес.
Если IP-адрес может меняться (динамический DHCP), а имена компьютеров в вашей сети не уникальны, нам нужны дополнительные признаки. Рассмотрим использование объекта WScript.Network для получения системной информации:
Процедура ЗаполнитьСведенияОСистеме(Параметры) Экспорт
Попытка
СетевойОбъект = Новый COMОбъект("WScript.Network");
Параметры.Вставить("ComputerName", СокрЛП(СетевойОбъект.ComputerName));
Параметры.Вставить("UserDomain", СокрЛП(СетевойОбъект.UserDomain));
Параметры.Вставить("UserName", СокрЛП(СетевойОбъект.UserName));
Исключение
ЗаписьЖурналаРегистрации("Ошибка получения сетевых данных",,, ОписаниеОшибки());
КонецПопытки;
КонецПроцедуры
Комбинация ComputerName + UserName + UserDomain дает более высокую точность, чем просто имя ПК. Однако в условиях, описанных автором (неуникальные имена и общие учетки), этого все равно может быть недостаточно.
В случае работы через терминальный сервер, обычные методы определения IP могут вернуть адрес самого сервера. Чтобы узнать адрес физического устройства, с которого зашел пользователь, проанализируем переменные окружения сессии. В ОС Windows при RDP-подключении создаются специфические переменные.
Shell = Новый COMОбъект("WScript.Shell");
ИмяКлиентаRDP = Shell.ExpandEnvironmentStrings("%CLIENTNAME%");
Если ИмяКлиентаRDP содержит значение, отличное от "%CLIENTNAME%", значит, пользователь работает в терминале, и мы получили имя его локальной машины, даже если сессия запущена на сервере.
Чтобы гарантированно отличить один компьютер от другого, даже если у них одинаковые имена и IP, посмотрим на серийные номера «железа». Самым надежным является UUID материнской платы или серийный номер BIOS. Выясним, как их получить через WMI:
Функция ПолучитьHardwareID()
Локатор = Новый COMОбъект("WbemScripting.SWbemLocator");
Сервис = Локатор.ConnectServer(".", "root\cimv2");
// Получаем UUID системы
Объекты = Сервис.ExecQuery("SELECT UUID FROM Win32_ComputerSystemProduct");
Для Каждого Элемент Из Объекты Цикл
Возврат Элемент.UUID;
КонецЦикла;
Возврат "";
КонецФункции
Этот идентификатор уникален для каждого физического устройства и не меняется при переименовании компьютера или смене сетевых настроек.
Теперь, когда мы научились получать уникальные признаки (IP или UUID), разберем, как ограничить запуск нескольких сеансов под одним пользователем с одного ПК. Поскольку сеансы в 1С изолированы, нам нужно «общее место» для хранения данных.
Рекомендуемый алгоритм:
АктивныеСеансыПользователей с измерениями Пользователь, ИдентификаторПК и ресурсом НомерСеанса.ПриНачалеРаботыСистемы вызываем серверный метод.ПолучитьСоединенияИнформационнойБазы(), выдаем предупреждение и завершаем работу (Отказ = Истина).Важный нюанс: борьба с «зависшими» записями. Если 1С завершится аварийно, запись в регистре останется. Чтобы этого избежать, используем проверку реальности сеанса:
МассивСоединений = ПолучитьСоединенияИнформационнойБазы();
// Проверяем, жив ли еще сеанс, номер которого записан у нас в регистре
СеансЖив = Ложь;
Для Каждого Соединение Из МассивСоединений Цикл
Если Соединение.НомерСеанса = НомерИзРегистра Тогда
СеансЖив = Истина;
Прервать;
КонецЕсли;
КонецЦикла;
Если СеансЖив Тогда
Сообщить("С данного компьютера уже запущен сеанс под этим пользователем!");
ЗавершитьРаботуСистемы(Ложь);
КонецЕсли;
Мы проанализировали ситуацию и выяснили, что для надежного определения клиента в старых версиях 1С необходимо использовать внешние механизмы. Подведем итоги наших решений:
Win32_NetworkAdapterConfiguration.UUID из Win32_ComputerSystemProduct.WScript.Shell.Эти методы позволяют гибко настроить безопасность и лицензионный контроль в любой конфигурации, независимо от версии платформы и дисциплинированности системных администраторов в вопросах именования рабочих станций.