Как получить список пользователей из Active Directory (LDAP) в 1С и исправить ошибки подключения?

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

Синхронизация учетных записей сотрудников между Active Directory (AD) и информационной базой 1С — это классическая задача, с которой рано или поздно сталкивается практически каждый разработчик. Автоматическое заполнение справочника пользователей, сопоставление контактной информации (Email, телефоны, должности) существенно сокращают рутинную работу администраторов. Однако при попытке связать эти системы напрямую через протокол LDAP мы часто сталкиваемся со специфическими ошибками ОС Windows, правами доступа и сетевыми ограничениями.

В этой статье мы подробно разберем все этапы настройки подключения, рассмотрим типичные ошибки (такие как отсутствие домена 0x8007054B или сбои при выполнении метода Execute) и напишем оптимальный, отказоустойчивый код для платформы 1С:Предприятие.

Базовый механизм подключения через COM-объект

Для взаимодействия с Active Directory в среде Windows традиционно используется технология ADO (ActiveX Data Objects). Мы обращаемся к провайдеру данных Active Directory ADsDSOObject, создаем SQL-подобный запрос и получаем выборку данных.

Давайте посмотрим на классический пример инициализации подключения:


Соединение = Новый COMОбъект("ADODB.Connection");
Соединение.Provider = "ADsDSOObject";
Соединение.Open("Active Directory Provider");

После успешного открытия соединения мы можем сформировать LDAP-запрос. В простейшем виде он выглядит следующим образом:


ИмяДомена = "DC=company,DC=local"; // пример структуры вашего домена
ТекстЗапроса = "SELECT ADsPath FROM 'LDAP://" + ИмяДомена + "'";
ВыборкаДанных = Соединение.Execute(ТекстЗапроса);

Казалось бы, всё просто. Однако именно на этих этапах чаще всего и возникают первые системные исключения.

Разбор ошибки: "Указанный домен не существует или к нему невозможно подключиться"

Эта проблема, также известная под кодом ошибки 0x8007054B или исключением Active Directory: Произошла ошибка операции, очень часто сбивает с толку начинающих разработчиков. Программа отлично работает под учетной записью администратора в режиме толстого клиента, но при попытке запустить её на сервере или под обычным пользователем выдает критический сбой на строчке инициализации корня домена:

КореньDSE = ПолучитьCOMОбъект("LDAP://rootDSE");

Выясним причину этого поведения. Проблема кроется в контексте безопасности выполнения кода. Если код выполняется на стороне сервера 1С, то по умолчанию служба сервера "Агент сервера 1С:Предприятие" работает под локальной учетной записью (например, USR1CV8). Эта учетная запись не является доменной и не имеет никаких прав на чтение данных из контроллера домена Active Directory.

Чтобы решить эту проблему раз и навсегда, нам нужно принудительно указать авторизационные данные пользователя, у которого есть права на чтение каталога (обычно достаточно прав любого доменного пользователя). Для этого используются специальные свойства COM-соединения:


Соединение = Новый COMОбъект("ADODB.Connection");
Соединение.Provider = "ADsDSOObject";

// Передаем логин доменного пользователя и его пароль
Соединение.Properties("User ID").Value = "DomainUser@company.local"; 
Соединение.Properties("Password").Value = "VerySecurePassword123";

Соединение.Open("Active Directory Provider");

Использование явной авторизации гарантирует стабильную работу интеграции вне зависимости от того, под каким системным пользователем запущен сервер 1С.

Как обойти лимит в 1000 записей (Постраничная выборка)

Если в вашей компании работает крупный штат сотрудников (более 1000 человек), вы столкнетесь с тем, что стандартный метод Соединение.Execute() возвращает ровно 1000 записей и прекращает работу. Это жесткое системное ограничение безопасности контроллеров домена Active Directory (параметр MaxPageSize), призванное защитить сервер от перегрузок при выполнении тяжелых запросов.

Чтобы обойти это ограничение, нам придется отказаться от прямого метода Execute() у объекта соединения и использовать промежуточный объект команды ADODB.Command, настроив свойство постраничного вывода (Page Size).

Проанализируем пример реализации постраничной выборки:


Соединение = Новый COMОбъект("ADODB.Connection");
Соединение.Provider = "ADsDSOObject";
Соединение.Properties("User ID").Value = "DomainUser@company.local";
Соединение.Properties("Password").Value = "Password";
Соединение.Open("Active Directory Provider");

// Создаем объект команды для настройки параметров выполнения
Команда = Новый COMОбъект("ADODB.Command");
Команда.ActiveConnection = Соединение;

// Указываем размер страницы данных (например, порциями по 500 записей)
Команда.Properties("Page Size").Value = 500;

// Формируем запрос
Команда.CommandText = "SELECT mail, sAMAccountName FROM 'LDAP://DC=company,DC=local'";

// Выполняем запрос через команду
Выборка = Команда.Execute();

Использование свойства Page Size заставляет ADODB-провайдер автоматически запрашивать данные порциями в фоновом режиме, что позволяет выгрузить абсолютно всех пользователей домена без сбоев и зависаний.

Оптимизация LDAP-запроса и фильтрация данных

Рассмотрим частую ошибку оптимизации, которую совершают разработчики при написании кода. Зачастую в запросе выбираются абсолютно все объекты домена:

ТекстЗапроса = "SELECT ADsPath FROM 'LDAP://DC=company,DC=local'";

А затем в коде 1С в цикле по полученному рекордсету проверяется класс каждого объекта:


Пока НЕ Выборка.EOF() Цикл    
    ОбъектAD = ПолучитьCOMОбъект(Выборка.Fields(0).Value);    
    Если ОбъектAD.Class = "user" Тогда      
        // Полезная логика
    КонецЕсли;    
    Выборка.MoveNext();    
КонецЦикла;

Такой подход является крайне неоптимальным. Метод ПолучитьCOMОбъект для каждого элемента выполняет отдельный сетевой запрос к контроллеру домена. Если у вас 2000 объектов, программа совершит 2000 синхронных сетевых вызовов, что приведет к выполнению обработки в течение десятков минут!

Правильный путь — фильтровать данные непосредственно на стороне Active Directory средствами самого LDAP-запроса, запрашивая только нужные поля (например, логин sAMAccountName, полное имя displayName, электронную почту mail). Также крайне важно исключать из выборки компьютеры, группы и заблокированные учетные записи.

Давайте разберем по шагам правильные фильтры LDAP:

  1. Выбор только людей: Для этого в блоке WHERE мы указываем objectCategory='person' AND objectClass='user'. Это сразу отсечет группы, контакты и компьютеры.
  2. Исключение заблокированных сотрудников: Заблокированные пользователи обычно имеют признак отключенной учетной записи в атрибуте userAccountControl. Нам нужно проверить побитовое состояние флага. Значение 2 отвечает за отключение записи. Чтобы убрать заблокированных пользователей из выборки, добавим условие: AND NOT userAccountControl & 2.

С учетом этих требований оптимальный поисковый запрос примет следующий вид:


ТекстЗапроса = "SELECT sAMAccountName, mail, displayName, department, title " + 
               "FROM 'LDAP://DC=company,DC=local' " + 
               "WHERE objectCategory='person' AND objectClass='user' AND NOT userAccountControl & 2";

При таком подходе мы моментально получаем готовую плоскую таблицу со всеми необходимыми атрибутами пользователей напрямую в один запрос, полностью исключая необходимость вызывать медленный метод ПолучитьCOMОбъект внутри цикла!

Кроссплатформенный подход: работа на сервере 1С под управлением Linux

Современные тенденции автоматизации бизнеса часто требуют переноса серверов 1С на операционные системы семейства Linux (Ubuntu, Astra Linux, Ред ОС). На сервере под управлением Linux технология COM-объектов (таких как ADODB.Connection) принципиально недоступна.

Если перед вами стоит задача реализовать синхронизацию пользователей с Active Directory кроссплатформенным способом, мы рекомендуем использовать консольную утилиту ldapsearch, которая входит в состав стандартного пакета ldap-utils на Linux.

Схема работы выглядит следующим образом:

  1. Устанавливаем пакет утилит на сервере Linux командой apt-get install ldap-utils.
  2. С помощью встроенного языка 1С вызываем выполнение консольной команды через ЗапуститьПриложение. Результат выполнения перенаправляем во временный файл.
  3. Считываем текстовый файл средствами 1С с помощью объекта ЧтениеТекста и разбираем его структуру (формат данных LDIF).

Пример вызова утилиты ldapsearch из кода 1С:


ПутьКФайлуРезультата = ПолучитьИмяВременногоФайла("txt");
КомандаЗапуска = "ldapsearch -x -h 192.168.1.100 -D ""DomainUser@company.local"" -w ""Password"" -b ""dc=company,dc=local"" ""(&(objectCategory=person)(objectClass=user)(!(userAccountControl:1.2.840.113556.1.4.803:=2)))"" sAMAccountName mail displayName > " + ПутьКФайлуРезультата;

// Выполняем команду операционной системы Linux
ЗапуститьПриложение(КомандаЗапуска, , Истина); // Истина - дождаться завершения

// Считываем результат
ФайлРезультата = Новый ЧтениеТекста(ПутьКФайлуРезультата, КодировкаТекста.UTF8);
Содержимое = ФайлРезультата.Прочитать();
ФайлРезультата.Закрыть();

// Далее пишем алгоритм разбора полученного LDIF-текста

Этот метод гарантирует стабильную кроссплатформенную работу вашей конфигурации в любых инфраструктурных окружениях.

Готовое комплексное решение для Windows-инфраструктуры

Объединим все лучшие практики, рассмотренные нами ранее, в единую готовую функцию, которую вы можете использовать в своей работе. Данный алгоритм использует постраничную выборку, фильтрацию активных сотрудников и явную передачу авторизационных реквизитов, предотвращая все известные ошибки соединения.


Функция ПолучитьПользователейИзActiveDirectory() Экспорт

    ТаблицаПользователей = Новый ТаблицаЗначений;
    ТаблицаПользователей.Колонки.Добавить("Логин", Новый ОписаниеТипов("Строка"));
    ТаблицаПользователей.Колонки.Добавить("Почта", Новый ОписаниеТипов("Строка"));
    ТаблицаПользователей.Колонки.Добавить("ФИО", Новый ОписаниеТипов("Строка"));
    ТаблицаПользователей.Колонки.Добавить("Подразделение", Новый ОписаниеТипов("Строка"));

    // Задаем параметры подключения
    СерверAD = "192.168.1.100"; // IP или имя контроллера домена
    ИмяДомена = "DC=company,DC=local";
    ЛогинАдмин = "sync_user@company.local";
    ПарольАдмин = "StrongPassword789";

    Соединение = Новый COMОбъект("ADODB.Connection");
    Соединение.Provider = "ADsDSOObject";
    Соединение.Properties("User ID").Value = ЛогинАдмин;
    Соединение.Properties("Password").Value = ПарольАдмин;

    Попытка
        Соединение.Open("Active Directory Provider");
    Исключение
        ЗаписьЖурналаРегистрации("Синхронизация AD", УровеньЖурналаРегистрации.Ошибка, , , 
            "Ошибка открытия соединения с Active Directory: " + ОписаниеОшибки());
        Возврат Неопределено;
    КонецПопытки;

    Команда = Новый COMОбъект("ADODB.Command");
    Команда.ActiveConnection = Соединение;
    Команда.Properties("Page Size").Value = 500; // Обязательно для обхода лимита в 1000 строк

    // Пишем оптимизированный запрос с фильтрацией на стороне AD
    Команда.CommandText = "SELECT sAMAccountName, mail, displayName, department " +
                          "FROM 'LDAP://" + СерверAD + "/" + ИмяДомена + "' " +
                          "WHERE objectCategory='person' AND objectClass='user' AND NOT userAccountControl & 2";

    Попытка
        Выборка = Команда.Execute();
    Исключение
        ЗаписьЖурналаРегистрации("Синхронизация AD", УровеньЖурналаРегистрации.Ошибка, , , 
            "Ошибка выполнения запроса к AD: " + ОписаниеОшибки());
        Соединение.Close();
        Возврат Неопределено;
    КонецПопытки;

    Пока НЕ Выборка.EOF() Цикл
        
        НоваяСтрока = ТаблицаПользователей.Добавить();
        
        // Безопасно извлекаем значения полей выборки
        Если Выборка.Fields("sAMAccountName").Value <> Неопределено Тогда
            НоваяСтрока.Логин = СокрЛП(Выборка.Fields("sAMAccountName").Value);
        КонецЕсли;
        
        Если Выборка.Fields("mail").Value <> Неопределено Тогда
            НоваяСтрока.Почта = СокрЛП(Выборка.Fields("mail").Value);
        КонецЕсли;
        
        Если Выборка.Fields("displayName").Value <> Неопределено Тогда
            НоваяСтрока.ФИО = СокрЛП(Выборка.Fields("displayName").Value);
        КонецЕсли;
        
        Если Выборка.Fields("department").Value <> Неопределено Тогда
            НоваяСтрока.Подразделение = СокрЛП(Выборка.Fields("department").Value);
        КонецЕсли;
        
        Выборка.MoveNext();
    КонецЦикла;

    Соединение.Close();
    Возврат ТаблицаПользователей;

КонецФункции

Эта функция возвращает структурированную таблицу значений 1С с актуальными пользователями, готовыми к дальнейшей обработке: созданию новых элементов справочника "Пользователи", обновлению реквизитов или сопоставлению учетных записей.

Применяя данные подходы, вы сможете построить быструю, надежную и масштабируемую систему синхронизации между платформой 1С:Предприятие и службой каталогов Active Directory в вашей корпоративной сети.

← На главную