Эй, автор сценариев!Кто ты?

Сотрудники группы Microsoft Scripting Guys

Загрузить исходный код для этой статьи: HeyScriptingGuy2007-082007_08.exe (152KB)

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

"Стой, кто идет?" – рявкает часовой.

"Это я, сержант Смит», – отвечают ему.

«Сержант Смит? У нас нет сержанта Смита».

«Я новенький, только что перевели сюда из роты А».

«Правда? Из роты А? Ладно, Смит. Кто выиграл чемпионат по бейсболу 1934 года?»

«Нью-Йорк Янкиз».

«Неправильно, "Смит"»! (Дальше, как и следовало ожидать, его немедленно арестовывают и сажают на гауптвахту.) Любой американец, настоящий американец, знает, что чемпионат по бейсболу в 1934 году выиграли Сент-Луис Кардиналз — ну, знаете, Диззи Дин и Газ Хаус. Тот, кто этого не знает, может быть только шпионом.

Если тот, кто читает эту статью, не знает, что чемпионат по бейсболу в 1934 году выиграли Сент-Луис Кардиналз, мы можем сказать только одно: игры закончились, мы знаем, что вы шпион. Надеемся, у вас хватит благоразумия сдаться в ближайшее отделение ФБР. Или, по крайней мере, позвонить им. Когда речь заходит о шпионах, агенты ФБР могут и сами приехать.

По мере того как автор сценариев, пишущий эту статью, смотрел кино дальше, он понимал, что у него были бы все шансы ответить на второй вопрос часового: «Кто выиграл чемпионат по бейсболу 1966 года?» Балтимор Ориолз.Чемпионат по бейсболу 1960? Питтсбург Пайратс. Чемпионат по бейсболу 1994? Ха! Вопрос с подвохом: в 1994 году не было чемпионата по бейсболу.

Тем не менее, в наше время ему сложнее всего было бы ответить на самый первый вопрос: «Кто ты?» Сейчас этот вопрос так прост, как в 1940-х годах. Только в Active Directory® пользователи могут иметь любое количество различных учетных данных, включая:

  • Имя (givenName).
  • Фамилия (sn).
  • Отображаемое имя (displayName).
  • Имя пользователя (userPrincipalName).
  • Имя для входа в сеть (samAccountName).
  • Различающееся имя (distinguishedName).

Все эти имена обозначают одного и того же человека, и все эти имена, в зависимости обязательств, нужно знать. А это уже проблема. Большинство пользователей знают свое имя и фамилию. Но если спросить пользователя, какое у него различающееся имя, лишь несколько смогут ответить: «А, это очень просто! Я CN=Кен.Майер, OU=Финансы, DC=fabrikam, DC=com. Но все друзья зовут меня CN=Кен.Майер."

Если вы системный администратор или сотрудник службы поддержки, вам необходимо также знать эти имена. Но как вам получить эту информацию? Одним из решений будет приостановить доступ пользователей, пока они не сообщат свои различающиеся имена. Это может сработать, но большинство отделов кадров относятся к этому неодобрительно. Поэтому, возможно, придется воспользоваться планом Б: сценарием. Каким сценарием? Как, например, для начала, насчет сценария на рис. 1?

Figure 1 Получение атрибутов пользователя с ADSystemInfo

On Error Resume Next

Set objSysInfo = CreateObject("ADSystemInfo")
strUser = objSysInfo.UserName

Set objUser = GetObject("LDAP://" & strUser)
WScript.Echo "First Name: " & objUser.givenName
WScript.Echo "Last Name: " & objUser.sn
WScript.Echo "Display Name: " & objUser.displayName
WScript.Echo "User Principal Name: " & objUser.userPrincipalName
WScript.Echo "SAM Account Name: " & objUser.sAMAccountName
WScript.Echo "Distinguished Name: " & objUser.distinguishedName

Эх, ну почему сержант Смит не изучал VBScript? Этот сценарий использует в своих целях малоизвестный (но исключительно полезный) объект ADSI под названием ADSystemInfo. Это небольшой удобный объект, который собирает всю информацию о пользователе, вошедшем в сеть на локальном компьютере, а также о самом локальном компьютере и домене, к которому он принадлежит. Например, посмотрите на рис. 2.

Figure 2 Отображение всех типов информации из домена

On Error Resume Next

Set objSysInfo = CreateObject("ADSystemInfo")

Wscript.Echo "User name: " & objSysInfo.UserName
Wscript.Echo "Computer name: " & objSysInfo.ComputerName
Wscript.Echo "Site name: " & objSysInfo.SiteName
Wscript.Echo "Domain short name: " & objSysInfo.DomainShortName
Wscript.Echo "Domain DNS name: " & objSysInfo.DomainDNSName
Wscript.Echo "Forest DNS name: " & objSysInfo.ForestDNSName
Wscript.Echo "PDC role owner: " & objSysInfo.PDCRoleOwner
Wscript.Echo "Schema role owner: " & objSysInfo.SchemaRoleOwner
Wscript.Echo "Domain is in native mode: " & objSysInfo.IsNativeMode

Сегодня, тем не менее, нас интересует только одно свойство – это UserName. Что такого необычного в UserName? Ничего, просто оно соответствует свойству distinguishedName. А что такого в distinguishedName? Дело в том, что distinguishedName аналогично пути формата UNC к файлу: так же, как путь формата UNC позволяет нам идентифицировать файл где-то в нашей сети, distinguishedName (например, CN=Ken.Myer, OU=Finance, DC=fabrikam, DC=com) позволяет идентифицировать учетную запись пользователя в Active Directory. В свою очередь, это позволяет нам сделать привязку к этой учетной записи пользователя. После того как мы подключились, мы можем получать любую информацию о пользователе, включая информацию о том, кто он.

Это именно то, что мы делаем в первом сценарии, который мы вам показали. Для начала мы создаем экземпляр объекта ADSystemInfo, а затем приписываем значение свойства UserName переменной под названием strUser:

Set objSysInfo = CreateObject("ADSystemInfo")
strUser = objSysInfo.UserName

Больше ничего особенного нам здесь делать не нужно. Например, нам не нужно конкретизировать имя пользователя, домен пользователя или подразделение, в котором находится учетная запись пользователя; ADSystemInfo сделает это всё за нас. Как только мы определили distinguishedName пользователя, мы можем создать привязку к его или ее учетной записи пользователя, используя следующую строку кода:

Set objUser = GetObject("LDAP://" & strUser)

И снова сразу после соединения мы можем вывести значения любого из параметров Active Directory этой учетной записи. В нашем примере сценария мы просто выводим некоторые свойства имени пользователя, но мы с такой же легкостью могли получить номер телефона, местоположение офиса, адрес электронной почты и т.д.

Здорово. Все, что вам нужно, — это передать этот сценарий всем вашим пользователям, и им больше никогда не придется спрашивать себя «Кто я?» (А если спросят, то у них появится быстрый и простой способ выяснить это.) А как насчет другого вопроса: «Кто ты?» Одно дело — иметь возможность идентифицировать пользователя, вошедшего в сеть на локальном компьютере. Но каким способом определить, кто вошел в сеть на удаленном компьютере? Как вы понимаете, этот орешек расколоть значительно труднее.

Мы об этом тоже думали и приносим извинения — к сожалению, нет никакого способа передать сценарий ADSystemInfo на удаленный компьютер. Причина в том, что объект ADSystemInfo может создаваться только локально. Таким образом, у нас остается три варианта действий:

  • Создать сценарий входа в сеть, который записывает имя вошедшего в сеть пользователя в каком-нибудь легкодоступном месте.
  • Использовать класс WMI с названием Win32_ComputerSystem и свойство UserName.
  • Сделать что-нибудь другое.

Первый вариант выглядит довольно привлекательно. Как вы могли заметить, объект ADSystemInfo может вернуть различающееся имя компьютера (свойство ComputerName), а также различающееся имя пользователя. Если хотите, взгляните на пример сценария на рис. 3.

Figure 3 Сценарий входа в сеть

On Error Resume Next

Set objSysInfo = CreateObject("ADSystemInfo")

strUser = objSysInfo.UserName
strComputer = objSysInfo.ComputerName

Set objUser = GetObject("LDAP://" & _
    strUser)
strUserName = objUser.displayName

Set objComputer = GetObject("LDAP://" & _
    strComputer)
objComputer.Description = strUserName
objComputer.SetInfo

Что мы здесь делаем? Начнем с того, что мы берем значения свойств UserName и ComputerName и сохраняем их в переменных (strUser и strComputer). Затем мы делаем привязку к учетной записи пользователя в Active Directory (как и раньше), получаем значение параметра displayName (как и раньше) и сохраняем это значение в переменной под названием strUserName. Довольно незамысловато, не правда ли?

Теперь мы можем использовать эту строку кода для подключения к учетной записи компьютера в Active Directory:

Set objComputer = GetObject("LDAP://" & _
    strComputer)

После установки связи мы присваиваем displayName пользователя свойству пользователя Description и затем вызываем метод SetInfo для записи изменения в Active Directory:

objComputer.Description = strUserName
objComputer.SetInfo

Зачем мы это делаем? Это просто. Предположим, Кен Майер входит в сеть с компьютера atl-ws-01. Догадайтесь, каким будет значение свойства Description компьютера atl-ws-01? Верно, Кен Майер, человек, который недавно входил в сеть с этого компьютера. Хотите узнать, кто входит в сеть с atl-ws-01? Просто посмотрите свойство Description.

В общем, этот сценарий работает довольно хорошо, и даже лучше, если у вас есть сценарий выхода из сети, очищающий свойство Description каждый раз после выхода пользователя из сети. Однако это не стопроцентно надежное решение. Почему? Сценарии входа в сеть не всегда выполняются. Например, они обычно не выполняются, когда люди входят в сеть, используя RAS. Кроме того, сценарий входа в сеть не выполняется, если пользовать отключает свое сетевое подключение, входит в систему, используя кэшированные учетные данные, а затем вновь подключает компьютер к сети. Также предположим, что пользователь выключает компьютер, не выходя из сети. Это означает, что сценарий выхода из сети не будет запущен. В этом случае Кен Майер будет по-прежнему находиться в сети, используя компьютер atl-ws-01, хотя машина будет выключена. Другими словами, это полезный прием, но...

А что насчет варианта 2 с использованием класса Win32_ComputerSystem? Опять же, это может сработать, но проблема с классом Win32_ComputerSystem заключается в том, что он не всегда возвращает имя вошедшего в сеть пользователя, особенно в случае пользователей без администраторских прав (и в особенности с компьютеров, на которых установлена Windows® 2000). Сценарий на рис. 4 может показать, кто вошел в сеть с компьютера, но опять же без гарантий.

Figure 4 Поиск того, кто вошел в сеть, при помощи WMI

strComputer = "."

Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")
Set colItems = objWMIService.ExecQuery("Select * From Win32_ComputerSystem")

For Each objItem in colItems
  Wscript.Echo objItem.UserName
Next

Между прочим, в этом случае свойство UserName возвращается в формате домен\имя пользователя. Другими словами: FABRIKAM\kenmyer.

Да, чуть не забыл: даже если имя возвращается, это не означает, что пользователь на самом деле вошел в сеть с компьютера. Когда Кен Майер входит в сеть с atl-ws-01, его имя сохраняется как значение свойства UserName и не заменяется до тех пор, пока в сеть не зайдет кто-то другой.

Ужас.

Но погодите – это не значит, что мы шпионы. Мы можем воспользоваться другим способом. Если кто-то входит в сеть с компьютера, должен запускаться процесс Explorer.exe. Другими словами, если Explorer.exe не запущен, с этой машины никто не входит в сеть, а поскольку Explorer.exe работает по учетным данным вошедшего в сеть пользователя, мы почти всегда можем определить, кто вошел в сеть с компьютера, используя сценарий, аналогичный показанному на рис. 5.

Figure 5 Определение владельца Explorer.exe

strComputer = "atl-ws-01"

Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")

Set colItems = objWMIService.ExecQuery _
  ("Select * from Win32_Process Where Name = 'explorer.exe'")

If colItems.Count = 0 Then
  Wscript.Echo "No one is logged on to the computer."
Else
  For Each objProcess in colItems
    objProcess.GetOwner strUser, strDomain
    Wscript.Echo strDomain & "\" & strUser
  Next
End If

Как видите, в этом случае мы соединяемся со службой WMI на удаленной машине (если точнее, atl-ws-01). Затем мы используем эту строку кода для получения коллекции всех экземпляров класса Win32_Process, в которых есть Explorer.exe:

Set colItems = objWMIService.ExecQuery _
  ("Select * from Win32_Process Where " & _
  "Name = 'explorer.exe'")

Что дальше? Как мы только что заметили, если Explorer.exe не запущен, существует большая вероятность, что с этого компьютера никто не входит в сеть. Как узнать, запущен ли Explorer.exe? Простой способ сделать это — проверить значение свойства коллекции Count. Если Count равен 0, у нас пустая коллекция, а единственным объяснением пустой коллекции является отсутствие запущенных экземпляров Explorer.exe на atl-ws-01. В этом случае мы можем получить сообщение о том, что с компьютера никто не входит в сеть:

Wscript.Echo "No one is logged on " & _
"to the computer."

Если Count не равен 0, мы настраиваем цикл For Each для просмотра коллекции процессов, в которых есть Explorer.exe (и мы предполагаем, что в нашей коллекции есть хотя бы один экземпляр). Для каждого экземпляра Explorer.exe мы затем вызываем метод GetOwner для определения учетных данных, с которыми запускается Explorer.exe:

objProcess.GetOwner strUser, strDomain

Обратите внимание, что мы передаем GetOwner пару выходных параметров: strUser и strDomain. Выходные параметры — это переменные, которые мы называем и предоставляем методу; затем метод присвоит этим параметрам значения. В данном случае strUser получит имя для входа вошедшего в сеть пользователя (kenmyer), а strDomain получит имя домена для вошедшего в сеть пользователя (FABRIKAM). Все, что нам остается сделать, — это получить значения этих двух выходных параметров:

Wscript.Echo strDomain & "\" & strUser

И знаете что? Это очень хорошо. Но мы можем пойти еще дальше. При использовании метода GetOwner, мы получаем имя для входа (samAccountName) для пользователя, вошедшего в сеть с компьютера. Это хорошо, но, как мы отмечали ранее, у пользователей есть множество других имен, помимо samAccountName. Чтобы действительно ответить на вопрос «Кто ты?», было бы неплохо знать, например, displayName пользователя. Но мы не можем определить эти другие имена, используя GetOwner, не так ли?

Нет, не можем. Однако мы можем взять samAccountName, вставить его в сценарий поиска Active Directory и затем определить его расположение и сделать привязку к учетной записи пользователя с этим именем для входа (задача облегчается тем, что внутри домена samAccountNames должно быть уникальным). Сделав привязку к учетной записи пользователя, мы можем получить значения любого свойства Active Directory, включая displayName.

У нас нет времени давать подробные объяснения по сценарию на рис. 6; для получения дополнительных сведений о поиске в Active Directory ознакомьтесь со статьей "Где мой принтер, чувак?". Этого уже достаточно, чтобы сказать, что этот сценарий определяет имя для входа вошедшего в сеть пользователя, проводит поиск в Active Directory пользователя с этим именем для входа (samAccountName), делает привязку к учетной записи этого пользователя, а затем выдает displayName пользователя. И это все одной левой!

Figure 6 Привязка к вошедшему в сеть пользователю

strComputer = "atl-ws-01"

Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")

Set colItems = objWMIService.ExecQuery _
  ("Select * from Win32_Process Where Name = 'explorer.exe'")

If colItems.Count = 0 Then
  Wscript.Echo "No one is logged on to the computer."
Else
  For Each objProcess in colItems
    objProcess.GetOwner strUser,strDomain
  Next
End If

Const ADS_SCOPE_SUBTREE = 2

Set objConnection = CreateObject("ADODB.Connection")
Set objCommand = CreateObject("ADODB.Command")
objConnection.Provider = "ADsDSOObject"
objConnection.Open "Active Directory Provider"
Set objCommand.ActiveConnection = objConnection

objCommand.Properties("Page Size") = 1000
objCommand.Properties("Searchscope") = ADS_SCOPE_SUBTREE 

objCommand.CommandText = "SELECT displayName FROM " & _
  "'LDAP://DC=wingroup,DC=fabrikam,DC=com' WHERE " & _
    "objectCategory='user' " & _
    "AND samAccountName = '" & strUser & "'"
Set objRecordSet = objCommand.Execute

objRecordSet.MoveFirst

Do Until objRecordSet.EOF
  Wscript.Echo objRecordSet.Fields("displayName").Value
  objRecordSet.MoveNext
Loop

Так какие ключевые моменты мы сегодня рассмотрели? (Кстати, работники Майкрософт говорят именно так. Если вы хотите, чтобы автор сценариев вышел из себя, просто подойдите к нему и скажите что-нибудь вроде: «Нам нужно произвести сортировку ключевых моментов, описаний операций и второстепенных целей для наших акционеров.») Так вот, во-первых, мы теперь знаем, как получить информацию о пользователе, вошедшем в сеть — либо с локальной машины, либо с удаленного компьютера. Что более важно — мы знаем, что делать, если мы вдруг попадем во временную воронку и обнаружим, что участвуем во Второй мировой войне: Убедитесь, что у вас есть копия этой статьи (чтобы вы могли ответить на вопрос «Кто ты?»), и — что бы вы ни делали — всегда носите с собой список победителей чемпионата по бейсболу. В конце концов, вы никогда не знаете, когда вас спросят, кто выиграл чемпионат по бейсболу 1903 года.

Примечание: Бостон Ред Сокс. Кстати, это был первый чемпионат по бейсболу. А теперь, как вы думаете, кто выиграл чемпионат 1904 года? Что значит не знаете? Подождите минутку, нам надо позвонить...

Сотрудники группы Microsoft Scripting Guys работают на... ну, хорошо, получают зарплату в корпорации Майкрософт. Когда они не играют в бейсбол, не тренируют бейсбольную команду, не смотрят бейсбол и не занимаются другими делами, они ведут проект Script Center в журнале TechNet. Веб-узел проекта находится по адресу www.scriptingguys.com.

© 2008 Корпорация Майкрософт и компания CMP Media, LLC. Все права защищены; полное или частичное воспроизведение без разрешения запрещено.