Достаточно часто системному администратору Windows приходится делать различные выгрузки по информации о пользователях домена Active Directory. Представим, что у нас есть список учетных записей (имена пользователя в формате samAccountName), и нам, например, необходимо получить информацию о том, в какой организации эти пользователи работают и их Canonical Name (CN). Ранее для получения такой информации мне приходилось писать небольшой скрипт на vbs, который последовательно перебирает все записи в Excel и возвращает обратно требуемую информацию из Active Directory. Такой механизм полностью работоспособен, но не очень удобен, поэтому я решил воспользоваться мощью языка Visual Basic from Application и выполнять запросы к Active Directory прямо из Excel (из макроса), т.к. такая методика была бы достаточно универсальной и в принципе все эти скрипты можно со спокойной совестью передавать менее подкованным бухгалтерам и экономистам!
Я создал новый макрос в книге Excel, и создал функцию с именем GetADInfo, которая на входе получает имя поля, по которому осуществляется поиск (в моем случае это имя пользователя, которое хранится в атрибуте Active Directory – samAccountName), значение этого поля (значение ячейки с именем пользователя) и имя артибута AD, значение которого функция должна вернуть.
Как же все это работает? В моем примере в ячейке A2 содержится имя пользователя домена, и я хочу для этого пользователя узнать компанию, в которой он числится (поле AD “Company”) и его каноническое имя (поле AD «distinguishedName»), в этом случае формулы для ячеек соответственно будут выглядеть следующим образом:
Ячейка B2 (наименование организации):
=GetADInfo("samAccountName";A2; "Company")
Ячейка C2 (CN):
=GetADInfo("samAccountName";A2; "distinguishedName")
Код макроса на VBA для получения данных из AD следующий:
Function GetADInfo(ByVal SearchField, ByVal SearchString, ByVal ReturnField) ' Указываем имя домена ("dc=domain, dc=local") Dim adoCommand, strDomain, objConnection strDomain = "dc=winitpro,dc=ru"
Set objConnection = CreateObject("ADODB.Connection") objConnection.Open "Provider=ADsDSOObject;" ' Подключаемся Set adoCommand = CreateObject("ADODB.Command") adoCommand.ActiveConnection = objConnection ' Рекурсивный поиск по AD, начиная с корня домена adoCommand.CommandText = _ "<LDAP://" & strDomain & ">;(&(objectCategory=" & "User" & ")" & _ "(" & SearchField & "=" & SearchString & "));" & SearchField & "," & ReturnField & ";subtree" ' создаем набор записей RecordSet Dim objRecordSet Set objRecordSet = adoCommand.Execute If objRecordSet.RecordCount = 0 Then GetADInfo = "not found" ' ничего не найдено Else GetADInfo = objRecordSet.Fields(ReturnField) ' возвращаемое значение End If ' Закрываем подключение objConnection.Close ' Очищаем переменные Set objRecordSet = Nothing Set objCommand = Nothing Set objConnection = Nothing End Function
Чтобы данная функция заработала, необходимо подключить ряд библиотек в VBA. В редакторе VBA выбираем меню Tools->References и в появившемся окне отмечаем следующие библиотеки:
- Visual Basic For Application
- Microsoft Excel 14.0 Object Library
- OLE Automation
- Microsoft Office 14.0 Object Library
- Microsoft ActiveX Data Objects 2.8 Library (или около того)
- Microsoft Scripting Runtime
- Microsoft VBScript Regular Expressions 5.5
После того, как вы активируете следующие компоненты, сохраните макрос VBAи книгу Excel, и в результате в соответствующих полях Excel появится информация из Active Directory. Прелесть данного скрипта состоит в том, что он достаточно универсальный и с небольшими модификациями он поможет динамически получать практически любую информацию из Active Directory прямо в книге прямо в книге Excel!
Очень интересная статья, но не заработало, однако. Делал так: новая книга-новый макрос-занес скрипт с измененным доменом-отметил библиотеки-для теста в ячейке написал имя пользователя-в другой запрос =GetADInfo(«samAccountName»;A2; «distinguishedName») — ругается на GetADInfo
Валерий, действительно в коде была небольшая ошибка. Я обновил код vba модуля
Проверьте, заработает ли
спасибо, работает, но почему-то не полностью .. «lastLogon» и аналогичные не работают(
может подскажете, в чем может быть дело?
такое очучение, что не понимает большие числа — #ЗНАЧ!
Спасибо, скопировал, подправил настройки.
Работает.
Круть! Все работает на ура. С помощью этого скрипта можно просто и быстро получить массово любую информацию из Active Directory
Нюанс с которым пришлось покопаться — нужно создавать именно новый vba модуль в xls (с классом и текущей формой почему-то не заработало.)
А как узнать названия всех полей которые можно вытащить ?
типа Company ?
Все праметры обьектов в AD можно узнать с помощью любого LDAP браузера или с помощью консоли adsiedit.msc
Спасибо Вам большое за код! В своей работе, к сожалению, вынужден пользоваться поиском не по samAccountName, а по DisplayName, что приводит к выпадению полных тезок. Не подскажете как дополнить код, что бы можно было выбрать конкретного человека? Например добавить в excel поле какого по счету брать человека, а в код на каком человеке возращать значение.
Заранее спасибо
Не совсем понятно, что Вы хотите, но предполагаю, то что Вы хотите реализовать можно сделать с помощью дополнительного скрытого столбца с samAccountName и проверки значений ячеек функцией Excel IF (ЕСЛИ)
Добрый день! при работе с скриптом был обнаружен следующий нюанс: не возвращает значения атрибутов ни lastLogon, ни lastLogonTimestamp при этом если не находит объект или при работе с другими числовыми или строковыми атрибутами значения выдает корректно, самостоятельно решить данный вопрос не удается 🙁
К сожалению тоже не удалось победить проблему с возвратом значения lastLogonTimestam. Какая-то ерунда с обработкой timestamp в формате UTC…
Если получиться найди решение — поделитесь 🙂
VBA ошибок не выдает, запустив скрипт отдельно (не из Excel) получил ошибку «Несоответствие типа», выяснил, что lastLogon и ему подобные в формате UTC имеют размер в 64 бита. Проверить работоспособность в 64 разрядном «офисе» с поддержкой типа данных LongLong нет возможности.
lastLogon и ему подобные в формате UTC имеют размер в 64 бита. Проверить работоспособность скрипта в 64 битном «офисе» с поддержкой типа данных LongLong пока нет возможности.
Видимо проще эти переменные через vbs или dsquery вытягивать…
Для взятия полей типа LastLogon я где-то нашёл решение:
Set objDate = objRecordSet.Fields("lastLogon").Value
lngHigh = objDate.HighPart
lngLow = objDate.LowPart
If (lngLow < 0) Then
lngHigh = lngHigh + 1
End If
If (lngHigh = 0) And (lngLow = 0) Then
dtmDate = #1/1/1601#
Else
dtmDate = #1/1/1601# + (((lngHigh * (2 ^ 32)) + lngLow) / 600000000 - lngBias) / 1440
Cells(n, i + 1).Value = dtmDate + 1 / 24 * 3
dtmDate = ""
End If
Други, помогите! Я использую макрос в Excel для получения большого количества атрибутов по каждому из пользователей для каких-то массовых изменений. Потом делаю загрузку полученной таблицы обратно. Но есть нюанс, я не смог побороть ситуацию, когда значение какого-то атрибута надо отчистить. Т.е., например, у пользователя забрали мобильный телефон и надо это поле очистить. Не получается. Я вышел из ситуации пока глупо: пишу в атрибут пробел. Но это не дело… Пустое значение, типа objUser.Mobile = «» не прокатывает.
Что конкретно пишете в атрибут при очистке его значения? Возможно стоит записывать не просто «», а что-то типа
NULL
itpro, нет, так не прокатит. Почитав ихние мануалы, понял, что просто так писать пустое нельзя. Есть специальная конструкция очистки значения в атрибуте. И узнал немного полезного, например, как удалить одно из значений в многозначимом (можно так писать?) атрибуте. У меня, например, есть такая строчка в VBA: objUser.PutEx ADS_PROPERTY_CLEAR, «homePhone», 0 — использование метода не просто put, а putex, далее параметр, указывающий удаление значения, далее имя атрибута и еще какой-то параметр. Читал, как обычно, не внимательно, но в данный момент для меня важен результат.
подскажите, как скорректировать скрипт чтобы удалить из АД информацию lastlogon
Скрипт из статьи предназначен в первую очередь для построения различных отчетов и статистике непосредственно из Excel, т.е. для четния данных из AD В вашем случае было бы намного проще копать в сторону Powershell скрипта или WMI.
А если не секрет какова вообще задача? Почистить следы? 🙂
Подскажите, есть ли возможность с помощью данной функции извлекать все значения атрибута, который представлены в виде многозначных строк? Например, значение атрибута msSFU30PosixMemberOf ?
Добрый день!
А у меня почему-то никак не хочет работать…
В строке Set objRecordSet = adoCommand.Execute выдает ошибку:
Runtime error ‘-2147217865 (80040e37)’
Таблица не существует.
Даже не знаю, куда копать. Очень нужно.
Внимательно перепроверьте список подключаемых модулей и корректность указаная домена в строке strDomain=
ЗЫ. Скрипт точно работал в 2010 Excel
Спасибо. Поменял в strDomain «ru» на «local», все заработало
Добрый день!
Пытаюсь выбрать реквизиты пользователя по ФИО, немного доработав Вашу функцию, но получаю в Excel ошибку ЗНАЧ!
Уже всю голову сломал, помогите, пожалуйста!
Вот текст функции:
Public Function ADGetUserByFIO(ByVal SearchFIO, ByVal ReturnField)
Dim adoCommand, strDomain, objConnection, objRecordSet, Result, Response
strDomain = «OU=Users,»
Set objConnection = CreateObject(«ADODB.Connection»)
objConnection.Open «Provider = ADsDSOObject»
Set adoCommand = CreateObject(«ADODB.Command»)
adoCommand.ActiveConnection = objConnection
adoCommand.CommandText = _
«» & _
«;(&(ObjectClass=user)(objectCategory=Person)(cn=» & SearchFIO & «));» & ReturnField & _
«;onelevel»
Set objRecordSet = adoCommand.Execute
Result = objRecordSet.Fields(ReturnField).Value
ADGetUserByFIO = Result
objConnection.Close
Set objRecordSet = Nothing
Set objCommand = Nothing
Set objConnection = Nothing
objConnection.Close
End Function
Немного не понял задачи.
В каком атрибуте AD вы храните ФИО?
Часть почему-то не скопировалась:
Public Function ADGetUserByFIO(ByVal SearchFIO, ByVal ReturnField)
Dim adoCommand, strDomain, objConnection, objRecordSet, Result
strDomain = «OU=Users, »
Set objConnection = CreateObject(«ADODB.Connection»)
objConnection.Open «Provider = ADsDSOObject»
Set adoCommand = CreateObject(«ADODB.Command»)
adoCommand.ActiveConnection = objConnection
adoCommand.CommandTimeout = 0
adoCommand.CommandText = _
«» & _
«;(&(ObjectClass=user)(objectCategory=Person)(cn=» & SearchFIO & «));» & ReturnField & _
«;onelevel»
Set objRecordSet = adoCommand.Execute
Result = objRecordSet.Fields(«mail»)
ADGetUserByFIO = Result
objConnection.Close
Set objRecordSet = Nothing
Set objCommand = Nothing
Set objConnection = Nothing
objConnection.Close
End Function
Как тут было озвучено ранее, вынужден искать не по samAccountName, а по DisplayName, так же столкнулся с тезками. У ОК есть уникальные идентификаторы, которые прописываем в Description, но поиск только по нему занимает минут по 10 на каждую позицию. Решение было простым и банальным — искать по DisplayName и Description одновременно:
Function GetADInfo(ByVal SearchField, ByVal SearchField1, ByVal SearchString, ByVal SearchString1, ByVal ReturnField)
‘ Указываем имя домена («dc=domain, dc=local»)
Dim adoCommand, strDomain, objConnection
strDomain =
Set objConnection = CreateObject(«ADODB.Connection»)
objConnection.Open «Provider=ADsDSOObject;»
Set adoCommand = CreateObject(«ADODB.Command»)
adoCommand.ActiveConnection = objConnection
‘ Рекурсивный поиск по AD, начиная с корня домена
adoCommand.CommandText = _
«;(&(objectCategory=» & «User» & «)» & _
«(» & SearchField1 & «=» & SearchString1 & «)» & _
«(» & SearchField & «=» & SearchString & «));» & SearchField & «,» & ReturnField & «;subtree»
Соответственно в Excel функция выглядит:
=GetADInfo(«DisplayName»;»Description»;A1;B1;»samAccountName»)
В данном случае ищем по ФИО и идентификатору и возвращаю логин юзера.
Банально, но может кому-то пригодится.
2 crownn90, не тем путем идете, мне думается. Вышеприведенная функция ищет по чему угодно, что вы ей подсунете и возвращает что попросит. Менять надо не функцию, а объект запроса.
DisplayName — Отображаемое имя, чаще всего это именно ФИО
samAccountName — логин
Mail — почта
givenName — имя
SN — фамилия
title — должность
company — компания
department — отдел
description — описание
distinguishedName — локация юзера, тот же ответ возвращает, что и при запросе dsquery user -name c командной строки.
UserAccountControl — статус учетной записи, возвращает число. Можно сконвертировать его в удобный текст так: https://winitpro.ru/index.php/2018/05/14/convertaciya-atributa-useraccountcontrol-v-ad/
P.S. Огромное спасибо за код!!! Правда когда таблица на несколько тысяч, то значительно подвисает при каждом изменении. Пришлось перейти от функции к процедуре
как отображать все логины без введения? мне не поиск нужен а отображение в таблице все что есть по отдельным полям
как сделать чтобы не нужно было вводить логин а показывало все логи само?
itpro, задача в том, чтобы выбирать через Excel атрибуты пользователя из AD
Never, разобрался почему не работало. У меня в конце скрипта 2 раза идет objConnection.Close, что и приводит к ошибке при закрытии коннекта второй раз. Excel в этом случае не понимает, что произошло и возвращает ошибку !ЗНАЧ
Всё супер, указал домен и всё заработало.
Кто-нибудь знает как ускорить поиск, чтобы искал не по всему AD, а сразу в определенном юните зная путь или его distinguishedName?
OU=ИмяОргЮнита,DC=Domain name,DC=local
Например, в домене firma.local есть папка с сотрудниками «staff», то можно изменить переменную strDomain = «OU=staff,dc=firma,dc=local»
Скрипт точно не для админа.
Для админа скрипт должен быть wsf. Чтоб при его запуске сам открывался Excel с новой книгой, в которую вставлялись результаты работы скрипта.
Вопрос возник. Допустим структура в домене такая:
domain2.domain1.local
ou00
groups
pc
users
ou01
groups
pc
users
ou02
groups
pc
users
и т.д.
Прописываю домен strDomain = «OU=ou00,DC=domain2,DC=domain1,DC=local» — всё работает, но только вытаскивает данные в пределах OU=ou00
Прописываю домен strDomain = «DC=domain2,DC=domain1,DC=local» — всё работает, но только вытаскивает данные в пределах той же OU=ou00
Собственно вопрос: что в скрипте поправить, чтоб он по всем OU домена поиск делал?
И кстати, если прописываю конкретную OU, например strDomain = «OU=ou01,DC=domain2,DC=domain1,DC=local» — то тоже вытаскивает данные в пределах OU=ou00
Конструкция strDomain = ‘DC=domain2,DC=domain1,DC=local’ должна искать по всему домену. Вас случайно в правах не ограничили по остальным OU?
PS. По хорошему сейчас для выгрузки данных о пользователях лучше пользоваться PowerShell командлетов Get-ADUser, это намного гибче. Результат выгружаете в CSV и экспортируйте в excel (https://winitpro.ru/index.php/2015/05/21/powershell-get-aduser-poluchenie-dannih-o-polzovatelyax-active-directory/).
Нет, в правах не ограничен. Эксель запускал от локального администратора и от администратора домена.
Вариант с экселем для меня более удобный. Ещё раз. Структура:
dd1.domain.local
—ALL
—-OU00
———OU01
————Groups
————PCs
————Users
———OU02
————Groups
————PCs
————Users
———OU03
————Groups
————PCs
————Users
и т.д.
Пробовал разные варианты:
‘strDomain = «DC=dd1,DC=domain,DC=local»
‘strDomain = «OU=OU00,OU=ALL,DC=dd1,DC=domain,DC=local»
‘strDomain = «OU=OU01,OU=OU00,OU=ALL,DC=dd1,DC=domain,DC=local»
‘strDomain = «OU=OU02,OU=OU00,OU=ALL,DC=dd1,DC=domain,DC=local»
По пользователям, которые находятся в других OU выдает «not found».
Инфу получается по факту достаёт только из «OU=Users,OU=OU01,OU=OU00,OU=ALL,DC=dd1,DC=domain,DC=local»
Что ещё может быть?
Вообще говоря параметр subtree указывает, что выполняется рекурсивный поиск, начиная с указанного контенера или корня домена.
Поэтому достаточно указать:
strDomain = ‘DC=dd1,DC=domain,DC=local’
Попробуйте в скрите исследовать набор объектов, который возвращается из objRecordSet при поиске от корня.
Отличный скрипт! Рабочий. Вот только у меня не получается его доработать, чтобы он выдавал список групп в которых состоит пользователь. То есть юзер вводит логин произвольного пользователя и в итоге получает список групп домена, в которых состоит введённый пользователь. Подскажите, пжлст, как допилить скрипт под мои нужды?
Все возможно (хотя использовать powershell гораздо удобнее : — https://winitpro.ru/index.php/2015/05/21/powershell-get-aduser-poluchenie-dannih-o-polzovatelyax-active-directory/
Выводим список групп, в которых состоит пользователь:
Get-AdUser youruser1 -Properties memberof | Select memberof -expandproperty memberof
)Если нужен именно VBA/VBS, возьмите за основу такой VBS скрипт, его можно подправить для работы в Excel VBA:
https://gallery.technet.microsoft.com/scriptcenter/Get-All-Group-Membership-f637bece
вопрос именно в реализации на ексель. мне необходимо, чтобы любой пользователь со своего рабочего места мог открыть эксельку и получить список групп произвольного пользователя и мог скопировать результаты в др файл- заявку ексель. в повершелл я это бы для себя давно реализовал уже, но у пользователей на раб местах нет повершела. пока не получается подправить вышеуказанный вами скрипт для работы в эксель(
есть возможность хотя бы указать конкретную строчку которую нужно править и как? мне нужно, чтоб макрос считывал данные логина пользователя из ячейки екселя и рядом выдывал список групп пользователя.
Как я понимаю можно реализовать ещё такой способ: перебирать все группы домена и если в этой группе состоит введённый пользователь, то выводить в список эту группу. Как реализовать только все это- вот проблема…
что-то так и не получается у меня видоизменить скрипт. может сможете помочь?
Не готов за вас целиком написать скрипт. Тема уже не интересна, чтобы тратить свое время. Я использую PowerShell для таких задач 🙂 VBA/VBS уже прошлый век.
Вот нашел вам в помочь пример vba скрипта. Он попроще. Несложно будет его адаптировать:
_https://social.msdn.microsoft.com/Forums/office/en-US/1a643e89-7750-4c32-853c-398a84dc9145/getting-a-users-groups-from-activedirectory-using-access-vba?forum=accessdev
Спасибо большое за подсказку!!! немного видоизменил под себя, что-то добавил и убрал, но все заработало!
еще раз спасибо!
Подскажите, как можно этот vba код внедрить в word, к примеру в служебную записку чтоб сразу дергались данные из AD
Скорре всего да. Ищите данные по запросу «word vba get active directory user info»
Подскажите, как можно вывести сразу несколько значений в одну ячейку через точку с запятой или каждое требуемое значение в соседние ячейки от той в которую вводим =GetADInfo….?
GetADInfo это функция — она возвращает только одно значение.
Вам наверно проще сделать еще одну функцию, например GetADInfo2 и в строке справа использовать ее:
=GetADInfo2("samAccountName";A2; "description")
я так и делаю, НО, когда пяток пользователей , а вывести нужно 9 значений (enabled, SAMAccountName, Display, Name, sn, GivenName, employeeID, Title, mail, manager) то это уже 5*9=40 запросов к AD,
а когда файл с сотней пользователей, то соответственно и запросов соответственно под 1000… даже, если excel и делает эти запросы в 6 потоков, это зависун тот ещё. поэтому и думаю можно ли уменьшить кол-во запросов к AD. пробовал играться с настройками и выводом =GetADInfo(«samAccountName»;A2; «ReturnField «;»ReturnField2″…)
и в запросе к ад добавлять необходимые выводы…
adoCommand.CommandText = _
";(&(objectCategory=" & "User" & ")" & _
"(objectClass=" & "User" & ")" & _
"(" & SearchField & "=" & SearchString & "));" & SearchField & "," & ReturnField & "," & ReturnField2 &.....";subtree"
и даже если добавить
GetADInfo = objRecordSet.Fields(ReturnField) + "; " + objRecordSet.Fields(ReturnField)
он выведет несколько значений
но вот как его заставить вывести второе значение из полученного из AD по запросу выше…
напрмер так уже не работает
GetADInfo = objRecordSet.Fields(ReturnField) + "; " + objRecordSet.Fields(ReturnField2)
у меня знаний не хватает.
провожу ресертификацию пользователей приложений. когда вывожу данные по приложениям из MSSQL баз, там сразу добавляю данные из AD через openquery запрос.
а из приложений, где невозможно сразу обратиться к ad при выводе данных, то получается приходится работать с таким вот ёкслелем.
выход конечно выгружать данные из AD в отдельный ёксель и по нему ВПРить, но это постоянно обновлять данные нужно…
Вот и спросил у вас как у гуру по данномуц вопросу
потому что 100 запросов и 900 — разница огромная)
Понятна ваша проблема. Сейчас не готов вспоминать курс молодого бойца в vbs. Это уже устаревшая техника.
На самом деле я думаю, что будет проще формировать данные в esxel через PowerShell и командлеты рабаботы с AD. Как раз на след неделе буду писать про это статью, покажу как брать данные из AD и помещать их в документ Excel. Я думаю вам это будет проще.
Спасибо! Буду очень ждать, добавте пожалуйста ссылку на эту статью здесь, если вам не сложно будет.
Как обещал — статья по работе с Excel из PowerShell. В том числе с примером получения нескольких атрбиутов пользователя из AD и записи их в отдельные ячейки Excel.
https://winitpro.ru/index.php/2020/08/04/dostup-k-dannim-v-excel-iz-powershell/
Добрый день. А можно ли получить с помощью этого скрипта список всех групп из определённого оргюнита/домена?
Добрый день. А можно ли получить с помощью этого скрипта список всех групп из определённого оргюнита/домена?
Не подскажу. Именно такой задачи не стояло когда-то. А сейчас перешел на PowerShell. VBA уже не так интересен.
Цель у меня получить именно силами vba список всех групп из конкретного оргюнита (в нем будут вложены и другие оргюниты/каталоги). Повершеллом легче и быстрее, но нужно именно vba…
Доброго дня. Компилятор почему-то ругается на строчку %MINIFYHTML…
Ругается на самый первый символ: invalid character. Не совсем понятно откуда взялась эта последовательность шестнадцатеричная.