Смена пароля локального администратора на всех серверах домена? Легко!

В одной большой организации было много-много серверов, на которых выполнялись различные задачи, и информационная безопасность периодически требовала сменить на них пароль локального администратора. Каждый раз безопасность уходила ни с чем, потому что на этих серверах под этим пользователем работали какие-то системные службы, задачи в Task Scheduler, и при внезапной смене пароля работа во всей компании была бы слегка парализована.

В связи с этим, а также учитывая количество серверов, был разработан план по смене пароля локального администратора, и написаны скрипты, помогающие это сделать.

Чтобы сменить пароль на всех серверах, и ничего при этом не сломать, нужно было:

1. Получить список серверов (или ограничиться выборкой компьютеров из одного OU в домене). Таким образом, сервера, не входящие в домен, выпадали из выборки, и с ними нужно было разбираться вручную.
2. Написать скрипт, который находит службы, работающие под локальным пользователем.
3. Написать скрипт, который находит задачи в Task Scheduler, работающие под локальным пользователем.
4. Определить ответственного за каждый сервер, чтобы согласовать смену пользователя для запуска службы. Для этого скрипт пытался определить роль сервера, чтобы понять, к каким администраторам оьращаться для его перенастройки.
5. Изменить пользователя в данных задачах на соответствующую системе доменную технологическую учётную запись, и протестировать работу каждой системы под ней.
6. Если система не работала под другой учётной записью, к этому серверу нужно было вернуться потом, после смены пароля, чтобы заново его прописать в настройках службы или задачи.
7. Написать скрипт смены пароля локальных администраторов на серверах.
8. Выполнить скрипт, и ждать звонка о том, что где-то что-то отвалилось. Работать по звонку.

Для инвентаризации служб и задач на серверах был написан такой страшноватый скрипт:

cls

# Здесь указываем папку в 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()

Для смены паролей был написан такой скрипт:

cls

$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."
                    }
                }
            }
        }
    }

Операция прошла незаметно, ни одного звонка о проблемах не было. Наиболее сложно было согласовать время проведения работы, и решиться на запуск скрипта.

Добавить комментарий