При разработке и администрировании баз 1С часто возникает задача однозначной идентификации рабочего места пользователя, подключенного к серверу терминалов по протоколу RDP. Стандартные сетевые функции платформы 1С или системные сокеты при выполнении внутри терминальной сессии возвращают сетевые реквизиты самого сервера, а не удаленного клиента. Разберем основные причины такого поведения и рассмотрим надежные способы получения реального IP-адреса и имени локальной рабочей станции.
Проанализируем ключевые факторы, которые вызывают ошибки при попытке получить данные о клиенте:
127.0.0.1 либо внутренний IP-адрес сетевой карты сервера.&НаСервере), он выполняется в контексте системной службы сервера 1С, где отсутствует информация об интерактивной пользовательской сессии RDP. Опрос параметров клиента необходимо выполнять на стороне тонкого или толстого клиента (директива &НаКлиенте).Класс не зарегистрирован или Недопустимая строка с указанием класса из-за несовпадения разрядности 32-битного клиента 1С и 64-битной операционной системы сервера, либо отсутствия регистрации сборки через утилиту RegAsm.exe.Для прямого взаимодействия со службой удаленных рабочих столов Windows Terminal Services можно использовать специализированные библиотеки-обертки над системным API. Рассмотрим пример подключения и считывания параметров текущей сессии через COM-объект:
Попытка
WTS = Новый COMОбъект("WTSManager.Shell");
ИмяКлиента = WTS.MyClientName();
IPАдресКлиента = WTS.MyIPAddress();
Сообщить("Имя компьютера клиента: " + ИмяКлиента);
Сообщить("IP-адрес клиента: " + IPАдресКлиента);
Исключение
Сообщить("Не удалось получить данные через WTSManager: " + ОписаниеОшибки());
КонецПопытки;
Если требуется получить список всех активных терминальных сессий на сервере, переберем коллекцию подключений в цикле:
Попытка
WTS = Новый COMОбъект("WTSManager.Shell");
КоличествоСессий = WTS.ClientCount;
Для Индекс = 1 По КоличествоСессий Цикл
Клиент = WTS.Clients(Индекс);
Сообщить("Сессия: " + Клиент.WinStationName +
" | Пользователь: " + Клиент.UserName +
" | IP: " + Клиент.IPAddress);
КонецЦикла;
Исключение
Сообщить("Ошибка при переборе сессий: " + ОписаниеОшибки());
КонецПопытки;
Чтобы избежать необходимости регистрировать внешние COM-компоненты на терминальном сервере, используем динамический вызов функции WTSQuerySessionInformationW из системной библиотеки wtsapi32.dll с помощью PowerShell. Системный API Windows содержит класс информации WTSClientAddress (значение 14), позволяющий извлечь IP-адрес текущей сессии.
Разберем реализацию запуска PowerShell-скрипта из 1С с передачей результата выполнения:
Функция ПолучитьIPАдресКлиентаRDP() Экспорт
WshShell = Новый COMОбъект("WScript.Shell");
КомандаPS = "powershell -NoProfile -ExecutionPolicy Bypass -Command """ +
"$signature = @'" + Символы.ПС +
"[DllImport(\""wtsapi32.dll\"", SetLastError = true)]" + Символы.ПС +
"public static extern bool WTSQuerySessionInformation(IntPtr hServer, int sessionId, int wtsInfoClass, out IntPtr ppBuffer, out int pBytesReturned);" + Символы.ПС +
"[DllImport(\""wtsapi32.dll\")]" + Символы.ПС +
"public static extern void WTSFreeMemory(IntPtr pMemory);" + Символы.ПС +
"'@;" + Символы.ПС +
"$wts = Add-Type -MemberDefinition $signature -Name 'WtsApi' -Namespace 'Wts' -PassThru;" + Символы.ПС +
"$buffer = [IntPtr]::Zero; $bytes = 0;" + Символы.ПС +
"if ($wts::WTSQuerySessionInformation([IntPtr]::Zero, -1, 14, [ref]$buffer, [ref]$bytes)) {" + Символы.ПС +
" $family = [System.Runtime.InteropServices.Marshal]::ReadInt16($buffer, 0);" + Символы.ПС +
" if ($family -eq 2) {" + Символы.ПС +
" $ip = (2..5 | ForEach-Object { [System.Runtime.InteropServices.Marshal]::ReadByte($buffer, $_) }) -join '.';" + Символы.ПС +
" Write-Output $ip;" + Символы.ПС +
" }" + Символы.ПС +
" $wts::WTSFreeMemory($buffer);" + Символы.ПС +
"}""";
Запуск = WshShell.Exec(КомандаPS);
Результат = Запуск.StdOut.ReadAll();
Возврат СокрЛП(Результат);
КонецФункции
Если для бизнес-логики достаточно имени удаленного компьютера, воспользуемся стандартной переменной окружения %CLIENTNAME%, которую служба RDP автоматически инициализирует внутри сессии пользователя.
Посмотрим на пример кода:
Попытка
WshShell = Новый COMОбъект("WScript.Shell");
ИмяКлиента = WshShell.ExpandEnvironmentStrings("%CLIENTNAME%");
Если ИмяКлиента = "%CLIENTNAME%" Или ПустаяСтрока(ИмяКлиента) Тогда
// Сессия запущена локально на консоли сервера
ИмяКлиента = ИмяКомпьютера();
КонецЕсли;
Сообщить("Сетевое имя клиентской машины: " + ИмяКлиента);
Исключение
Сообщить("Не удалось прочитать переменную среды: " + ОписаниеОшибки());
КонецПопытки;
При каждом успешном входе пользователя по RDP служба удаленных рабочих столов регистрирует событие с кодом Event ID 1149 в журнале Microsoft-Windows-TerminalServices-RemoteConnectionManager/Operational. В теле этого события содержится как логин пользователя, так и исходный IP-адрес клиента.
Считаем последнее событие для текущего пользователя через командлет PowerShell:
Функция ПолучитьIPИзЖурналаСобытий(ИмяПользователяОС)
WshShell = Новый COMОбъект("WScript.Shell");
Команда = "powershell -NoProfile -Command """ +
"$log = Get-WinEvent -FilterHashtable @{LogName='Microsoft-Windows-TerminalServices-RemoteConnectionManager/Operational'; Id=1149} -MaxEvents 20 | " +
"Where-Object { $_.Properties[0].Value -like '*" + ИмяПользователяОС + "*' } | Select-Object -First 1; " +
"if ($log) { Write-Output $log.Properties[2].Value }""";
Запуск = WshShell.Exec(Команда);
Возврат СокрЛП(Запуск.StdOut.ReadAll());
КонецФункции
Если вы используете сторонние .NET-библиотеки (например, SendToLocalHost.dll), важно соблюдать правила регистрации сборки в реестре Windows:
regsvr32.exe не подходит. Регистрация должна выполняться утилитой RegAsm.exe с параметром /codebase.RegAsm.exe, расположенную по пути: C:\Windows\Microsoft.NET\Framework\v4.0.30319\RegAsm.exe.