Бывает так — что в крупной организации, подчиненной сложным корпоративным правилам — возникает несколько точек проникновения информации из реального мира в мир production! Например — мелкая торговая фирма вела список своих сотрудников в 1С — с помощью нанятого со стороны сотрудника отдела кадров — а потом — раз, и выросла в большую фирму — с разветвленной сетью филиалов и большой IT — инфрастуктурой! Нанято большое количество сотрудников — на скорую руку (с привлечением интеграторов, разумеется) построена AD инфраструктура — заведены пользователи, настроены сайты, групповые политики работают, etc… и вдруг — оказывается, что информации в AD о пользователях — ноль! ну то есть имя — фамилия, OU, членство в группах, пароль, logon-скрипт — и все! ни тебе имени, ни комнаты, ни телефона — где он и кто он — пустые поля. Что делать???
Или вот взять мою ситуацию — пришел работать маленьким винтиком большой корпоративной системы! Она распределена по всей стране — и была не организована — но за время моей работы — подтянулась к реалиям современного мира, и вместо отдельно взятого домена в каждом филиале, не связанным с остальными — построила большое AD на всю страну! И поехали мои пользователи в новое прогрессивное будущее через ADMT, и их рабочие станции поехали, и они сами! И быстро организовались и OU, и групповые политики с логон — скриптами, действующими WMI — фильтрами и прочее — прочее! Да только вот старый их домен не хранил никаких знаний о них — ни о кабинете, где сидят, ни о том, как их зовут толком, ни об отделе, где трудятся! И так они поехали, простите, голыми в новый домен, что уже само по себе неприлично! А отсюда выросла задача первая:
1) Заполняем данные о пользователе в AD с помощью powershell
Надо сказать — что, несмотря на то, что филиал у нас был отдаленный от центра как России, так и организации — но есть и у нас увлеченные люди! Ну, а куда без них! И поэтому в свое время — одним из хороших товарищей была написана утилита к местному кадровому 1С: Предприятие, — которая позволяла выгрузить все, что ведет отдел кадров по сотруднику — в обычную Excel-таблицу. Есть Excel — есть powershell — значит можно запихать данные в AD! Поехали:
Здесь я немного отступлю, и расскажу, что вообще надо для того, что бы с помощью powershell начать общаться с AD.
Есть первый путь для начала общения с AD из Powershell — это установка на рабочую станцию пакета Microsoft Remote Server Administration Tolls (RSAT). Кроме того, что бы все заработало — на вашем контроллере домена должны быть установлены web-службы active directory, что не всегда возможно. Например, в своем филиале я могу администрировать свою OU – но не могу, что — либо ставить на контроллер домена – прав не хватает.
Но есть выход и из этой ситуации. Хорошая фирма Quest Software разработала свое бесплатное решение для обращения к AD из PowerShell – Active Roles Management Shell for Active Directory (ссылка). Пакет так же ставиться на рабочую станцию c операционной системой не ниже Windows XP. На контроллер домена в данном случае ставить ничего не нужно – все работает из коробки.
Итак, вернемся к задаче получения информации из таблицы excel. Сама таблица выглядит следующим образом:
Все данные являются выдуманными – нам же не надо проблем с разглашением персональных данных.
При этом в нашем AD есть только данные по фамилии, имени и отчеству – следовательно, это и будет ключевым полем. Начинаем писать.
Сначала необходимо подгрузить библиотеку Active Roles Management Shell for Active Directory. В открытой консоле powershell это можно сделать следующей командой:
Add-pssnapin Quest.ActiveRoles.ADManagement
Тоже самое надо написать в файл скрипта, что бы не подключать модуль руками.
Дальше приведу весь код с комментариями:
# очищаем экран
cls
#Задаем путь к справочнику сотудников
$TelSPR="C:info2ADтелефонный.xls"
#Имя листа (WorkSheet) рабочей книги Excel
$SheetName="Лист1"
#"Запускаем" Excel (создаем COM-объект Excel.Application)
$objExcel=New-Object -comobject Excel.Application
#выполняем открытие файла ("Рабочей книги") в Excel
$objWorkbook=$objExcel.Workbooks.Open($TelSPR)
#Номер колонки, содержащей ФИО
$ColumnName=1
#Номер колонки, содержащей должность
$ColumnTitle=2
#номера телефонов
$ColumnHomePhone=3
$ColumnPhone=4
$ColumnMobPhone=5
#Комната
$ColumnOffice=6
$ColumnMail=7
#Департамент
$ColumnDep=8
#
#Константа для использования с методом SpecialCells
$xlCellTypeLastCell = 11
#
#Получаем номер последней используемой строки на листе
#$TotalsRow=$objWorkbook.Worksheets.Item($SheetName).UsedRange.SpecialCells($xlCellTypeLastCell).CurrentRegion.Row
#Выполняем перебор строк в открытом файле Excel
for ($Row=1;$Row -le $TotalsRow; $Row++) {
#Сохраняем в переменных значения соответствующих ячеек
$UserName=$objWorkbook.ActiveSheet.Cells.Item($Row, $ColumnName).Value()
$Title=$objWorkbook.ActiveSheet.Cells.Item($Row, $ColumnTitle).Value()
$HomePhone=$objWorkbook.ActiveSheet.Cells.Item($Row, $ColumnHomePhone).Value()
$Phone=$objWorkbook.ActiveSheet.Cells.Item($Row, $ColumnPhone).Value()
$MobPhone=$objWorkbook.ActiveSheet.Cells.Item($Row, $ColumnMobPhone).Value()
$Office=$objWorkbook.ActiveSheet.Cells.Item($Row, $ColumnOffice).Value()
$Mail=$objWorkbook.ActiveSheet.Cells.Item($Row, $ColumnMail).Value()
$Department=$objWorkbook.ActiveSheet.Cells.Item($Row, $ColumnDep).Value()
#разбиваем ФИО, так как в AD это разные поля
$arrfio =-split $UserName
$arrfio[2]=$arrfio[2].Substring(0,1)
# Добавляем префикс к номеру телефона
if ($Phone -ne $null)
{ $Phone="(888) "+$Phone }
if ($MobPhone -ne $null)
{ $MobPhone="(888) "+$MobPhone }
#Пишем данные в AD, если пользователь включен (enabled), включаем обработку ошибок
try {
Get-QADUser -DisplayName $UserName -enabled | Set-QADUser -FirstName $arrfio[1] -Initials $arrfio[2] -LastName $arrfio[0] -Department $Department -HomePhone $HomePhone -Phone $Phone -MobilePhone $MobPhone -Office $Office -Title $Title -Company "ООО Комета" -Mail $Mail
}
catch {
$ReportString=("{0,-50} <-> {1,50}" -f $UserName, "Ошибка записи")
}
write-Host $reportString
$reportString=" "
}
#Закрываем книгу Excel
$objExcel.Workbooks.Close()
#Выходим из Excel (вернее даем команду на выход из Excel)
$objExcel.Quit()
#обнуляем объект
$objExcel = $null
#запускаем принудительную сборку мусора для освобождения памяти и окончательного завершения процесса
[gc]::collect()
[gc]::WaitForPendingFinalizers()
Вот такой скрипт. Работает достаточно быстро. Скажем, список из 300 человек, обрабатывается не дольше минуты. Скрипт также можно повесить в запланированные задачи и попросить отдел кадров выгружать файл с данными сотрудников куда-нибудь в сетевую папку при изменении штатного расписания. Тогда в AD вы будете иметь соответствующую реалиям структуру.
2) Забираем данные из AD в список Sharepoint
После внедрения в нашей конторе Microsoft Sharepoint возникла необходимость использовать списки сотрудников внутри этого монстрообразного детища Microsoft. И опять нам на помощь приходит Powershell.
Для начала заберем данные из AD в текстовый файл формата csv – делается это в две строки:
Add-pssnapin Quest.ActiveRoles.ADManagement
$user=get-qaduser -SearchRoot «OU=Accounts,OU=comenta,DC=domen,DC=local»|Select-Object GivenName, DN, DisplayName, mail, LogonName, telephoneNumber, Office, Department, Title, HomePhone, MobilePhone, sid |Export-CSV -delimiter ";" -path «C:PowershellScriptsADUsers.csv» -Encoding UTF8
Можно было бы отгружать в csv и все данные по пользователям – но мы обратили внимание –что время работы скрипта тогда существенно увеличивается.
А уже следующим скриптом грузим данный файл в список Sharepoint. Приведу текст целиком, но под катом – я думаю, будет полезно:
##################################################################################
param([string]$path, [string]$url,[string]$ou,[switch]$help)
[void][System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint")
#функция помощи
function GetHelp() {
$HelpText = @"
Описание:
Данный скрипт используется для актуализации данных списка пользователей.
Предварительно данные из ActiveDirectory должны быть экспортированы в *.csv файл.
Из csv файла в список SharePoint'а забираются следующие столбцы:
* sn - Фамилия
* givenName - Имя
* DisplayName - Полное имя (Ф.И.О.)
* mail - E-mail адрес
* LogonName - Логин
* telephoneNumber - Телефонный номер ВТС
* Office - Кабинет
* Department - Отдел
* Title - Должность
* HomePhone - Домашний телефон (в нашем случае Городсокй телефон )
* MobilePhone - Телефонный номер мобильный
--------------------------------------------------------------------------------
Параметры:
-path Путь расположения csv файла, откуда будут импортироваться данные
-url http путь расположения конечного списка, куда будут импортироваться данные
-ou Указать Organization unit в AD, откуда будут импортироваться пользователи,
например: OU=accaunts- загрузяться все пользователи.
OU=cometa - загрузяться только пользователи филиала
--------------------------------------------------------------------------------
Синтаксис:
Запуск скрипта из PowerShell:
.ImportADUsersToSPList.ps1 -path "Your path" -url "List URL" -ou "OU=cometa"
Вызов справки:
.ImportADUsersToSPList.ps1 -help
--------------------------------------------------------------------------------
Синтаксис:
Запуск скрипта из командной строки Windows:
powershell .ImportADUsersToSPList.ps1 -path "Your path" -url "List URL" -ou "OU=Magadan"
Вызов справки:
powershell .ImportADUsersToSPList.ps1 -help
--------------------------------------------------------------------------------
"@
$HelpText
}
function nz($value)
{ if ($value -eq $null) {
$value=""
}
return $value
}
function UpdateList([string]$path,[string]$url,[string]$ou)
{
#Загружаем необхоимые данные и вводим переменные:
$site=New-Object Microsoft.SharePoint.SPSite($url)
$web=$site.OpenWeb()
$list=$web.GetList($url)
$csv_file=Import-Csv -Delimiter ";" $path
$listItems=$list.Items
$spFiledType=[Microsoft.SharePoint.SPFieldType]::Text
#########################################################
#В цикле проверяем каждую строку csv файла и заносим данные в список:
#пишем лог:
"Script started at:" | Out-File -encoding default ".UpdateUsers.log" -Append
Get-Date | Out-File -encoding default ".UpdateUsers.log" -Append
foreach ($line in $csv_file) {
$update=$false
if (!($item=$list.Items | where {$_["Sid"] -eq $line.Sid})) {
#убираем служебные учетные записи
if (!(select-string -pattern "88" -inputobject $line.givenName) -and (!(select-string -pattern "Admin" -inputobject $line.givenName)) -and (!(select-string -pattern "Operator" -inputobject $line.givenName))-and ((select-string -pattern $ou -inputobject $line.DN)))
{ Write-Output $line
$item=$list.Items.Add()
$item["Sid"]=$line.Sid
$update=$true
} else {continue}
}
[array]$sids+=$line.Sid
$t=$line.DisplayName -split " ",3
if ((nz($item["Фамилия"])) -ne (nz($t[0])))
{
$item["Фамилия"]=$t[0]
$update=$true
}
if ((nz($item["Имя"])) -ne (nz($t[1])))
{ $item["Имя"]=$t[1]
$update=$true
}
if ((nz($item["Отчество"])) -ne (nz($t[2])))
{ $item["Отчество"]=$t[2]
$update=$true
}
$tfioname=$t[1] -split "",3
$tfioMidName=$t[2] -split "",3
$fio=$t[0]+" "+$tfioname[1]+"."+$tfioMidName[1]+"."
if ((nz($item["Ф.И.О."])) -ne (nz($fio)))
{ $item["Ф.И.О."]=$fio
$update=$true
}
$fio=$tfioname[1]+"."+$tfioMidName[1]+". "+$t[0]
if ((nz($item["И.О.Ф."])) -ne (nz($fio)))
{ $item["И.О.Ф."]=$fio
$update=$true
}
if ((nz($item["E-mail"])) -ne (nz($line.mail)))
{ $item["E-mail"]=$line.mail
$update=$true
}
if ((nz($item["Логин"])) -ne (nz($line.LogonName)))
{ $item["Логин"]=$line.LogonName
$update=$true
}
if ((nz($item["ВТС"])) -ne (nz($line.telephoneNumber)))
{ $item["ВТС"]=$line.telephoneNumber
$update=$true
}
if ((nz($item["Кабинет"])) -ne (nz($line.Office)))
{ $item["Кабинет"]=$line.Office
$update=$true
}
if ((nz($item["Отдел"])) -ne (nz($line.Department)))
{ $item["Отдел"]=$line.Department
$update=$true
}
if ((nz($item["Должность"])) -ne (nz($line.Title)))
{ $item["Должность"]=$line.Title
$update=$true
}
if ((nz($item["ГТС"])) -ne (nz($line.HomePhone)))
{ $item["ГТС"]=$line.HomePhone
$update=$true
}
if ((nz($item["DECT"])) -ne (nz($line.MobilePhone)))
{ $item["DECT"]=$line.MobilePhone
$update=$true
}
if ($update -eq $true)
{
$item.Update()
}
}
$listItems=$list.Items
for ($x=$listItems.Count-1; $x -ge 0; $x--)
{
if (($sids | where {$_ -eq $listItems[$x]["Sid"] }) -eq $null)
{
$notify="Удален пользователь: "+ $listItems[$x]["Фамилия"].ToString()
$notify | Out-File -encoding default ".UpdateGUUsers.log" -Append
$listItems[$x].Recycle()
}
}
"Script finished at:" | Out-File -encoding default ".UpdateGUUsers.log" -Append
Get-Date | Out-File -encoding default ".UpdateGUUsers.log" -Append
"______________________________" | Out-File -encoding default ".UpdateGUUsers.log" -Append
$site.Dispose()
}
if($help) { GetHelp; Continue }
if($path -AND $url -AND $ou) { UpdateList -path $path -url $url -ou $ou}
В результате работы последнего скрипта – мы получаем готовый список в sharepoint, при этом актуальный.
Если по теме статьи у вас возникли какие-то вопросы – готов ответить. Спасибо за внимание.
Автор: Goblinoid