В одной большой организации было много-много серверов, на которых выполнялись различные задачи, и информационная безопасность периодически требовала сменить на них пароль локального администратора. Каждый раз безопасность уходила ни с чем, потому что на этих серверах под этим пользователем работали какие-то системные службы, задачи в Task Scheduler, и при внезапной смене пароля работа во всей компании была бы слегка парализована.
В связи с этим, а также учитывая количество серверов, был разработан план по смене пароля локального администратора, и написаны скрипты, помогающие это сделать.
Чтобы сменить пароль на всех серверах, и ничего при этом не сломать, нужно было:
1. Получить список серверов (или ограничиться выборкой компьютеров из одного OU в домене). Таким образом, сервера, не входящие в домен, выпадали из выборки, и с ними нужно было разбираться вручную.
2. Написать скрипт, который находит службы, работающие под локальным пользователем.
3. Написать скрипт, который находит задачи в Task Scheduler, работающие под локальным пользователем.
4. Определить ответственного за каждый сервер, чтобы согласовать смену пользователя для запуска службы. Для этого скрипт пытался определить роль сервера, чтобы понять, к каким администраторам оьращаться для его перенастройки.
5. Изменить пользователя в данных задачах на соответствующую системе доменную технологическую учётную запись, и протестировать работу каждой системы под ней.
6. Если система не работала под другой учётной записью, к этому серверу нужно было вернуться потом, после смены пароля, чтобы заново его прописать в настройках службы или задачи.
7. Написать скрипт смены пароля локальных администраторов на серверах.
8. Выполнить скрипт, и ждать звонка о том, что где-то что-то отвалилось. Работать по звонку.
Для инвентаризации служб и задач на серверах был написан такой страшноватый скрипт:
# Здесь указываем папку в Active Directory, внутри которой будем искать.
#$searchOU = [ADSI]"LDAP://dc=test,dc=local"
$searchOU = [ADSI]"LDAP://ou=Servers,dc=test,dc=local"
# Фильтр поиска в AD:
$SearchFilter = "(&(&(&(&(&(samAccountType=805306369)(!(primaryGroupId=516)))(objectCategory=computer)(operatingSystem=Windows Server*)))))"
# Получаем список серверов (LDAP-объектов) из AD и сохраняем в переменную $ADServerList
$ADServerList = (New-Object System.DirectoryServices.DirectorySearcher($searchOU,$searchFilter)).FindAll()
# Переменные для определения сроков давности запуска задач
$never = Get-Date "30.12.1899 0:00:00"
$davno = Get-Date "01.03.2011 0:00:00"
# Запрашиваем имя и пароль для подключения к серверам по WMI.
if (!($cred))
{
$cred = $host.ui.PromptForCredential("Запрос учетных данных", "Введите имя пользователя и пароль", "", "NetBiosUserName")
}
# Очистка переменных, используемых в скрипте.
$Servers = @() # тут будет список всех серверов
$ServersInaccessible = @() # тут будут сервера, до которых не удалось достучаться
$ServersDenied = @() # тут будут сервера, на которые меня не пускают
$ServersConnected = @() # тут будут сервера, к которым удалось подключиться
$ServersWMIerror = @() # тут будут сервера, на которых не работает WMI
$WMIerror = @()
# Запуск Excel
$Excel = New-Object -Com Excel.Application
$Excel.Visible = $True
# Создаем новый файл
$WorkBook = $Excel.WorkBooks.Add()
$WorkSheet = $WorkBook.WorkSheets.Item(1)
$WorkSheet.Cells.Item(1,1) = "OU"
$WorkSheet.Cells.Item(1,2) = "Имя сервера"
$WorkSheet.Cells.Item(1,3) = "Описание"
$WorkSheet.Cells.Item(1,4) = "Роль сервера"
$WorkSheet.Cells.Item(1,5) = "Служба/задача"
$WorkSheet.Cells.Item(1,6) = "Название службы/задачи"
$WorkSheet.Cells.Item(1,7) = "Имя пользователя"
# Выделяем заголовок
$CellRange = $WorkSheet.UsedRange
$CellRange.Interior.ColorIndex = 19
$CellRange.Font.ColorIndex = 11
$intRow = 2
# Обработка списка серверов, полученного из AD
foreach ($objComputer in $ADServerlist)
{
# Сначала нужно получить из LDAP-объекта объект компьютера, и оттуда извлечь
# LDAP-атрибуты Name и operatingSystem
$computer=$objComputer.GetDirectoryEntry()
[string] $servername = $computer.Name
[string] $ServerDescr = $computer.description
[string] $OSversion = $computer.operatingSystem
[string] $AccountControl = $computer.userAccountControl
[String[]]$ou = @()
# Получаем из DN компьютера OU, отсекаем лишнее, делим на составляющие строки
$ou = $computer.distinguishedName -replace ",dc=test,dc=local", "" -split "CN=" -split "OU=" -replace ",", ""
# определяем категорию сервера по имени
$ServerRole = ""
switch -wildcard ($servername)
{
"*wsus*" { $ServerRole = "Infrastructure" }
"*avp*" { $ServerRole = "Infrastructure" }
"*msg*" { $ServerRole = "Infrastructure" }
"*exch*" { $ServerRole = "Infrastructure" }
"*fs*" { $ServerRole = "Infrastructure" }
"*dhcp*" { $ServerRole = "Infrastructure" }
"*service*" { $ServerRole = "Infrastructure" }
"*-bak*" { $ServerRole = "Infrastructure" }
"*ctx*" { $ServerRole = "Citrix" }
"*citrix*" { $ServerRole = "Citrix" }
"*sql*" { $ServerRole = "SQL" }
"*-ora*" { $ServerRole = "Oracle" }
}
# если роль пустая - задаём роль [Нет]
if ($ServerRole -eq "") { $ServerRole = "[Нет]" }
# переформатируем строчку OU
$a = $ou.Count-1
$ServerOU = ""
do
{
$ServerOU += $ou[$a] + ""
$a = $a-1
}
while ($a -gt 1)
if ($AccountControl -eq "4098")
# если учетная запись отключена, то не пингуем и не пытаемся подключиться к серверу
{
$Worksheet.Cells.Item($intRow, 1) = $ServerOU
$Worksheet.Cells.Item($intRow, 2) = $servername
$Worksheet.Cells.Item($intRow, 3) = $ServerDescr
$Worksheet.Cells.Item($intRow, 4) = "[Disabled]"
$Worksheet.Cells.Item($intRow, 5) = "Ошибка"
$Worksheet.Cells.Item($intRow, 6) = "Учетная запись компьютера отключена"
($WorkSheet.Rows.Item($intRow)).Interior.ColorIndex = 15
# ($WorkSheet.Rows.Item($intRow)).Font.ColorIndex = 11
$intRow += 1
}
else
{
# пингуем сервер
$pingoutput = ping -n 1 $servername
if($LASTEXITCODE -eq "0")
{
# сбрасываем флаг наличия службы или задачи на сервере
$ServiceTask = 0
# получаем данные о службах
Write-Host "Подключаемся к WMI компьютера", $servername # отладка
$ComputerObject = Get-WMIObject Win32_service -Computername $servername -Credential $cred
if($?) # если удалось подключиться
{
$WMIerror = ""
#Заполняем таблицу
ForEach($objItem in $computerObject)
{
if (($objItem.StartName -like "*admin*") -and ( ! ($objItem.StartName -like "TEST\*")))
{
if ($objItem.StartMode -like "Auto*")
{
$Worksheet.Cells.Item($intRow, 1) = $ServerOU
$Worksheet.Cells.Item($intRow, 2) = $servername
$Worksheet.Cells.Item($intRow, 3) = $ServerDescr
$Worksheet.Cells.Item($intRow, 4) = $ServerRole
$Worksheet.Cells.Item($intRow, 5) = "Служба"
$Worksheet.Cells.Item($intRow, 6) = $objItem.DisplayName
$Worksheet.Cells.Item($intRow, 7) = $objItem.startname
$intRow = $intRow + 1
$ServiceTask = 1
}
}
}
}
else # если не удалось подключиться по WMI
{
$WMIerror = $Error[0].Exception.Message.ToString()
if ($WMIerror -like "*ACCESSDENIED*") # если доступ запрещён
{
$ServersDenied += $servername
$WMIerror = "Ошибка WMI: доступ запрещён"
}
else { if ($WMIerror -like "*0x800706BA*") # если не удалось подключиться
{
$ServersInaccessible += $servername
$WMIerror = "Ошибка WMI: сервер недоступен"
}
else { if ($WMIerror -like "*Initialization failure*") # если WMI не работает
{
$ServersWMIerror += $servername
$WMIerror = "Ошибка инициализации WMI"
}}}
$Worksheet.Cells.Item($intRow, 1) = $ServerOU
$Worksheet.Cells.Item($intRow, 2) = $servername
$Worksheet.Cells.Item($intRow, 3) = $ServerDescr
$Worksheet.Cells.Item($intRow, 4) = $ServerRole
$Worksheet.Cells.Item($intRow, 5) = "Ошибка"
$Worksheet.Cells.Item($intRow, 6) = $WMIerror
($WorkSheet.Rows.Item($intRow)).Interior.ColorIndex = 3
Write-Host $intRow, $ServerOU, $servername, $ServerDescr, "Ошибка", $WMIerror -Separator `t
$intRow = $intRow + 1
$ServiceTask = 1
}
# получаем данные о задачах Task Scheduler
Write-Host "Подключаемся к Task Scheduler компьютера", $servername # отладка
$ST = new-object -ComObject "Schedule.Service"
$ST.connect($servername)
if ($ST.Connected)
{
$RootFolder = $ST.getfolder("")
$ScheduledTasks = $RootFolder.GetTasks(0)
foreach ($Task in $ScheduledTasks)
{
if (($Task.Enabled) -and ($Task.LastRunTime -gt $davno))
{
if (($Task.Definition.Principal.UserId -like "*admin*") -and ( ! ($Task.Definition.Principal.UserId -like "*TEST\*")) -and ( ! ($Task.Definition.Principal.UserId -like "TEST\admin")))
{
if (($Task.NextRunTime -ne $never)<# -and ($Task.LastRunTime -gt $davno)#>)
{
$Worksheet.Cells.Item($intRow, 1) = $ServerOU
$Worksheet.Cells.Item($intRow, 2) = $servername
$Worksheet.Cells.Item($intRow, 3) = $ServerDescr
$Worksheet.Cells.Item($intRow, 4) = $ServerRole
$Worksheet.Cells.Item($intRow, 5) = "Задача"
$Worksheet.Cells.Item($intRow, 6) = $Task.name
$Worksheet.Cells.Item($intRow, 7) = $Task.Definition.Principal.UserId -replace $servername, "."
Write-Host $intRow, $ServerOU, $servername, $ServerDescr, $ServerRole, "Задача", $Task.name, $Task.Definition.Principal.UserId -Separator `t
$intRow = $intRow + 1
$ServiceTask = 1
}
}
}
}
}
else
{
$Worksheet.Cells.Item($intRow, 1) = $ServerOU
$Worksheet.Cells.Item($intRow, 2) = $servername
$Worksheet.Cells.Item($intRow, 3) = $ServerDescr
$Worksheet.Cells.Item($intRow, 4) = $ServerRole
$Worksheet.Cells.Item($intRow, 5) = "Ошибка"
$Worksheet.Cells.Item($intRow, 6) = "Ошибка подключения к службе Task Scheduler"
($WorkSheet.Rows.Item($intRow)).Interior.ColorIndex = 3
Write-Host $intRow, $ServerOU, $servername, $ServerDescr, $ServerRole, "Ошибка", "Ошибка подключения к службе Task Scheduler" -Separator `t
$intRow = $intRow + 1
$ServiceTask = 1
}
if ($ServiceTask -eq 0)
{
$Worksheet.Cells.Item($intRow, 1) = $ServerOU
$Worksheet.Cells.Item($intRow, 2) = $servername
$Worksheet.Cells.Item($intRow, 3) = $ServerDescr
$Worksheet.Cells.Item($intRow, 4) = $ServerRole
$Worksheet.Cells.Item($intRow, 5) = "[Нет]"
$Worksheet.Cells.Item($intRow, 6) = "На сервере нет служб и задач, выполняемых под Admin"
Write-Host $intRow, $ServerOU, $servername, $ServerDescr, $ServerRole, "[Нет]", "На сервере нет служб и задач, выполняемых под Admin" -Separator `t
$intRow = $intRow + 1
}
}
else
{
$Worksheet.Cells.Item($intRow, 1) = $ServerOU
$Worksheet.Cells.Item($intRow, 2) = $servername
$Worksheet.Cells.Item($intRow, 3) = $ServerDescr
$Worksheet.Cells.Item($intRow, 4) = "[Inaccessible]"
$Worksheet.Cells.Item($intRow, 5) = "Ошибка"
$Worksheet.Cells.Item($intRow, 6) = "Сервер недоступен по сети"
($WorkSheet.Rows.Item($intRow)).Interior.ColorIndex = 3
Write-Host $intRow, $ServerOU, $servername, $ServerDescr, "[Inaccessible]", "Ошибка", "Сервер недоступен по сети" -Separator `t
$ServersInaccessible += $servername
$intRow = $intRow + 1
$ServiceTask = 1
}
}
}
# Форматируем Excel-таблицу
$Cellrange.Font.Bold = $True
$output = $Cellrange.EntireColumn.AutoFit()
Для смены паролей был написан такой скрипт:
$Date = Get-Date -Format d
# Список серверов для исключения из обработки:
$Servers_exclude = "SRV1", "SRV2", "SRV-WAS01"
# Фильтр поиска в AD:
$searchOU = [ADSI]"LDAP://ou=Servers,dc=test,dc=local"
$SearchFilter = "(&(&(&(&(&(samAccountType=805306369)(!(primaryGroupId=516)))(objectCategory=computer)(operatingSystem=Windows Server*)))))"
# Получаем список серверов (LDAP-объектов) из AD и сохраняем в переменную $ADServerList
$ADServerList = (New-Object System.DirectoryServices.DirectorySearcher($searchOU,$searchFilter)).FindAll()
# Записываем результаты работы в файл:
$pwSuccess = New-Item "C:\Scripting\Change-admin-Password-Success.log" -Type file -Force
$pwFailed = New-Item "C:\Scripting\Change-admin-Password-Errors.log" -Type file -Force
$pwOffline = New-Item "C:\Scripting\Change-admin-Password-Offline.log" -Type file -Force
# Новый пароль:
$passwordSec = Read-Host "Введите новый пароль локального администратора:" -AsSecureString
$passwordConfirmSec = Read-Host "Подтвердите новый пароль локального администратора:" -AsSecureString
$password = [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($passwordSec))
$passwordConfirm = [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($passwordConfirmSec))
if ($password -ne $passwordConfirm)
{
Write-Host "Пароли не совпадают!"
break
}
else
{
# Запрашиваем имя и пароль администратора для подключения к компьютерам
$cred = $host.ui.PromptForCredential("Need to run as Domain Admin", "Please enter your Domain Admin user name and password.", "", "NetBiosUserName")
If($cred -eq $null)
{
Write-Host "Нужно ввести имя и пароль администратора серверов."
break
}
else
{
$DomainAdmin = $cred.UserName
# Обработка списка серверов, полученного из AD
foreach ($objComputer in $ADServerlist)
{
# Сначала нужно получить из LDAP-объекта объект компьютера, и оттуда извлечь
# LDAP-атрибуты Name и operatingSystem
$computerObj=$objComputer.GetDirectoryEntry()
[string] $computer = $computerObj.Name
[string] $AccountControl = $computer.userAccountControl
if (($AccountControl -ne "4098") -and (!($servers_exclude -contains $computer)))
# если учетная запись отключена, то не пингуем и не пытаемся подключиться к серверу
# то же самое, если имя сервера - в списке исключённых из обработки
{
# пытаемся подключиться к серверу
if (Test-Connection $computer -Count 2 -Quiet)
{
Write-Host $computer, ": Смена пароля administrator на сервере"
$administrator = Get-WmiObject -Credential $cred -ComputerName $Computer -query `
"SELECT * FROM Win32_UserAccount where Domain = '$Computer'" `
| where-object {$_.SID -match '-500$'} | Select-Object -ExpandProperty Name
$Admin=[adsi]("WinNT://" + $Computer + "/$administrator, user")
$Admin.PSBase.Invoke("SetPassword",$password)
if($?)
{
$PasswordAge = $Admin.PasswordAge
If($PasswordAge -ne $null)
{
Write-Host "$Computer password change succeeded $administrator, but password age remains", $PasswordAge
Add-Content $pwsuccess -Value "$Date, $Computer, $administrator, Password Changed - password age not updated."
}
Else
{
Write-Host "$Computer password change SUCCEEDED $administrator"
Add-Content $pwsuccess -Value "$Date, $Computer, $administrator"
}
}
else
{
Write-Host "$Computer password change FAILED"
Add-Content $pwfailed -Value "$Date, $Computer, Password Change Failed."
}
}
else
{
Write-Host "$Computer is not online - skipping"
Add-Content $pwoffline -Value "$Date, $Computer, offline."
}
}
}
}
}
Операция прошла незаметно, ни одного звонка о проблемах не было. Наиболее сложно было согласовать время проведения работы, и решиться на запуск скрипта.