Как получить IP-адрес локального компьютера, подключенного к 1С через RDP?

Программист 1С v8.3 (Обычные формы) IT и автоматизация бизнеса
← На главную

При разработке и администрировании баз 1С часто возникает задача однозначной идентификации рабочего места пользователя, подключенного к серверу терминалов по протоколу RDP. Стандартные сетевые функции платформы 1С или системные сокеты при выполнении внутри терминальной сессии возвращают сетевые реквизиты самого сервера, а не удаленного клиента. Разберем основные причины такого поведения и рассмотрим надежные способы получения реального IP-адреса и имени локальной рабочей станции.

Причины сложностей при определении IP-адреса клиента RDP

Проанализируем ключевые факторы, которые вызывают ошибки при попытке получить данные о клиенте:

  1. Изоляция терминальной сессии: стандартные методы определения локального адреса через сокеты или WMI в контексте терминальной сессии опрашивают локальные сетевые интерфейсы сервера, поэтому возвращают loopback-адрес 127.0.0.1 либо внутренний IP-адрес сетевой карты сервера.
  2. Контекст исполнения кода в 1С: если код запускается на стороне сервера базы данных (директива &НаСервере), он выполняется в контексте системной службы сервера 1С, где отсутствует информация об интерактивной пользовательской сессии RDP. Опрос параметров клиента необходимо выполнять на стороне тонкого или толстого клиента (директива &НаКлиенте).
  3. Конфликты разрядности и регистрации COM-объектов: сторонние библиотеки часто вызывают ошибки Класс не зарегистрирован или Недопустимая строка с указанием класса из-за несовпадения разрядности 32-битного клиента 1С и 64-битной операционной системы сервера, либо отсутствия регистрации сборки через утилиту RegAsm.exe.

Способ 1. Получение данных сессии через COM-объект WTSManager

Для прямого взаимодействия со службой удаленных рабочих столов 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);
    КонецЦикла;
Исключение
    Сообщить("Ошибка при переборе сессий: " + ОписаниеОшибки());
КонецПопытки;

Способ 2. Вызов WTS API через PowerShell без сторонних библиотек

Чтобы избежать необходимости регистрировать внешние 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();
    
    Возврат СокрЛП(Результат);
КонецФункции

Способ 3. Определение сетевого имени клиента через переменные окружения

Если для бизнес-логики достаточно имени удаленного компьютера, воспользуемся стандартной переменной окружения %CLIENTNAME%, которую служба RDP автоматически инициализирует внутри сессии пользователя.

Посмотрим на пример кода:


Попытка
    WshShell = Новый COMОбъект("WScript.Shell");
    ИмяКлиента = WshShell.ExpandEnvironmentStrings("%CLIENTNAME%");
    
    Если ИмяКлиента = "%CLIENTNAME%" Или ПустаяСтрока(ИмяКлиента) Тогда
        // Сессия запущена локально на консоли сервера
        ИмяКлиента = ИмяКомпьютера();
    КонецЕсли;
    
    Сообщить("Сетевое имя клиентской машины: " + ИмяКлиента);
Исключение
    Сообщить("Не удалось прочитать переменную среды: " + ОписаниеОшибки());
КонецПопытки;

Способ 4. Получение IP-адреса из журнала событий Windows (Event Log)

При каждом успешном входе пользователя по 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());
КонецФункции

Правила регистрации COM-библиотек в 64-битных операционных системах

Если вы используете сторонние .NET-библиотеки (например, SendToLocalHost.dll), важно соблюдать правила регистрации сборки в реестре Windows:

  1. Для библиотек, разработанных на платформе .NET Framework, стандартная утилита regsvr32.exe не подходит. Регистрация должна выполняться утилитой RegAsm.exe с параметром /codebase.
  2. Учитывайте разрядность: если клиент 1С работает в 32-битном режиме, регистрацию выполняют через 32-битную версию RegAsm.exe, расположенную по пути: C:\Windows\Microsoft.NET\Framework\v4.0.30319\RegAsm.exe.
  3. Командную строку необходимо запускать с правами локального администратора сервера.
← На главную