ShS's Blog

Just another sysadmin's weblog

Скрипт для удаленного сброса клиента службы Автоматического обновления.

Posted by shs на 2010/02/18

Как-то раз, шатаясь по Интернету,  я наткнулся на очень интересную статью в блоге Ильи Сазонова, в которой речь идет о том, как при помощи простого скрипта на PowerShell выполнить сверку списка компьютеров из AD и WSUS. Запустил этот скрипт на своей системе и с удивлением обнаружил, что компьютеров, которые присутствуют в AD и отсутствуют в WSUS, у меня чуть более чем до хрена. Как же так? Ведь я никогда не отдавал компьютеры пользователям, не убедившись в том, что он способен получать обновления с WSUS, плюс ко всему в вялотекущем режиме мониторил состояние клиентов автоматического обновления и был уверен, что все члены домена исправно получают обновления.   Бросился, конечно же, проверять %windir%\WindowsUpdate.log на тех компьютерах, которые почему-то не отображались в оснастке WSUS и с еще большим удивлением обнаружил … отсутствие проблем с получением обновлений с сервера WSUS. Обмозговав имеющуюся у меня информацию, я понял, что проблемных клиентов роднит то, что все они (в отличие от беспроблемных), «разливались» из образа. «Погуглив», я нашел статью kb903262, подтвердившую мои предположения: A Windows 2000-based, Windows Server 2003-based, or Windows XP-based computer that was set up by using a Windows 2000, Windows Server 2003, or Windows XP image does not appear in the WSUS console. Как выяснилось из статьи sysprep в win2k, winXP и win2k3 (в отличие от Vista и выше) не умеет без дополнительных усилий со стороны администратора сбрасывать идентификатор клиента сервера WSUS, что и приводило к таким странным последствиям. Что надо делать, чтобы не допускать повторения ситуации, указано  в kb903262, в ней же указан способ «лечения» проблемных клиентов. Т.к. работать «ногами», бегая от клиента к клиенту, выполняя требуемые в kb действия, не хотелось, решено было написать скрипт.

 И так, сначала я взял скрипт Ильи и чуть-чуть его изменил: так, добавив строку

$NoWSUSCompNames|Out-File .\NoWSUS.txt -Encoding default

, мы добились того, что скрипт стал сохранять в файл информацию о компьютерах, которые есть в AD, но отсутствуют в WSUS. Выложу этот скрипт здесь (надеюсь, Илья не будет возражать):

#######################################################################################
#
# WSUSAD-Compare.ps1 PowerShell (c) sie
# WSUS - сверка списка компьютеров с AD
# http://sazonov.spaces.live.com/blog/cns!C80884C5BEC6A15D!170.entry
#######################################################################################
# Текущая версия WSUS имеет API, который позволяет удаленное управление сервером. 
# Чтобы его задействовать, необходимо установить на компьютер клиентскую часть сервера. 
# После чего запускаем оболочку Powershell 2.0 и загружаем WSUS API: 

[reflection.assembly]::LoadWithPartialName("Microsoft.UpdateServices.Administration") 

# Теперь надо подключаемся к удаленному серверу по имени «WSUS»: 

$wsus = [Microsoft.UpdateServices.Administration.AdminProxy]::GetUpdateServer("rta-srv-05", $false) 

# Второй параметр $false говорит о том, что будет использоваться HTTP протокол, а не HTTPS, т.е. не будет шифрования. 
# Теперь получаем список всех компьютеров зарегистрированных на WSUS-сервере: 

$WSUScomps = $wsus.GetComputerTargets() 

# Каждый элемент массива $WSUScomps это объект, а нам нужны только имена компьютеров. Получаем FQDN имена компьютеров: 

$WSUSCompNames = $WSUScomps | ForEach { $_.FullDomainName.ToUpper() } 

# Перевод имени в верхний регистр не критичен (по умолчанию Powershell выполняет сравнение строк без учета регистра), но формально все же это надо сделать. 
# Следующий шаг – получение списка учетных записей компьютеров из Active Directory: 

$ADcomps = (new-object System.DirectoryServices.DirectorySearcher([ADSI]"LDAP://OU=Workstations,OU=RTA,DC=dom,DC=local","(&(objectCategory=computer)(!userAccountControl:1.2.840.113556.1.4.803:=2))")).findAll() 

# Тут конструкция !userAccountControl:1.2.840.113556.1.4.803:=2 исключает запрещенные (disabled) учетные записи компьютеров. LDAP://ou=DEPS,dc=DOMAIN,dc=RU задает корень поиска в дереве AD. objectCategory=computer – выбираем только учетные записи компьютеров. 
# Из объектов учетных записей компьютеров извлекаем имена компьютеров (также формально переводим их в верхний регистр): 

$ADCompNames = $ADcomps | ForEach {$_.GetDirectoryEntry().dNSHostName.ToString().ToUpper()} 

# Получаем имена компьютеров, которые есть в Active Directory, но отсутствуют в WSUS: 
"`n`nИмена компьютеров, которые есть в Active Directory, но отсутствуют в WSUS:`n"
$NoWSUSCompNames = $ADCompNames | Where { $WSUSCompNames -notcontains $_ } |Sort-Object
$NoWSUSCompNames|Out-File .\NoWSUS.txt -Encoding default
$NoWSUSCompNames

# И последний шаг – получаем имена компьютеров, которые есть в WSUS, но отсутствуют в AD: 
"`n`nИмена компьютеров, которые есть в WSUS, но отсутствуют в Active Directory:`n"
$NoADCompNames = $WSUSCompNames | Where { $ADCompNames  -notcontains $_ } | Where {$_ -like "*.domain.local"} |Sort-Object
$NoADCompNames

Оставалось написать скрипт, который бы выполнял все действия, описанные в kb903262

Вот, что у меня получилось:

#########################################################################################################################
#  WSUSClientRemoteReset.ps1 PowerShell shs 20100218
#
#  Скрипт, дистаннционно выполняющий действия, описанные в kb903262, над  компьютерами, согласно заданного списка
#  Список компьютеров, который скрипт загружает из файла .\NoWSUS.txt, может быть сформирован при помощи скрипта WSUSAD-Compare.ps1,
#  и представляет из себя список FQDN-имен компьютеров (одна строка - одно имя)
#########################################################################################################################
cls
#########################################################################################################################
### Объявляем вспомогательные функции
#########################################################################################################################
#
#Функция возращает true, если заданный хост пингуется и false  - в противном случае (спасибо Xaerg'у)
function Test-Host ($Name)
{
    $ping = new-object System.Net.NetworkInformation.Ping
    trap {Write-Verbose "Ошибка пинга"; $False; continue}
    if ($ping.send($Name).Status -eq "Success" ) { $True }
    else { $False }
}
#########################################################################################################################
#
# Начало скрипта
#
#########################################################################################################################
### Загружаем в переменную список компьютеров из файла .\NoWSUS.txt,
### попутно получая короткое имя компьютера из его FQDN### 
$noInWSUS = gc .\NoWSUS.txt| foreach{($_ -replace " ") -replace "\..+$"}
#Обработаем полученный список
$noInWSUS| where{Test-Host $_}|foreach{`
	$CompName = $_
	Write-Host "`n`n$CompName"
	#Подлкючаемся к службе Автоматического обновления (wuauserv) (далее по тексту - служба) на удаленной машине
	###см. http://thepowershellguy.com/blogs/posh/archive/2007/01/03/powershell-using-net-to-manage-remote-services.aspx
	[System.Reflection.Assembly]::LoadWithPartialName('system.serviceprocess')
	$wuauserv=new-Object System.ServiceProcess.ServiceController('wuauserv',$CompName)
	#Инициализация флага успешной остановки службы
	$Stopped=$true
	#Если служба не остановлена, то остановливаем ее
	if ($wuauserv.Status -ne "Stopped") {
		try {
			#Останавливаем службу
			$wuauserv.Stop()
			#Ожидаем остановки службы в течении заданного таймаута
			$wuauserv.WaitForStatus('Stopped',(new-timespan -seconds 10))
		}
		catch {
			# если в течение отведенного таймаута служба не остановилась, то сообщим об этом...
			"На $CompName службу wuauserv остановить не удалось...`n"
			# ...и установим флаг успешной остановки службы в состояние $false
			$Stopped=$false
		}
	}
	# если служба была успешно остановлена, то
	# выполняем действия, указанные в kb903262
	if ($Stopped) {
		#
		#Удаляем ключи реестра, согласно http://support.microsoft.com/kb/903262
		###О том, как работать с реестром на удаленной машине, читаем здесь:
		###http://thepowershellguy.com/blogs/posh/archive/2007/06/20/remote-registry-access-and-creating-new-registry-values-with-powershell.aspx	
		$reg = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('LocalMachine', $CompName)
		$regKey= $reg.OpenSubKey("SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate",$true)
		$regKey.DeleteValue("SusClientId")
		$regKey.DeleteValue("SusClientIdValidation")
		$regKey.DeleteValue("PingID")
		$regKey.DeleteValue("AccountDomainSid")
		#Инициализация флага успешного запуска службы Автоматического обновления (wuauserv)
		$Started=$true
		#
		try {
			#Запускаем службу Автообновления
			$wuauserv.Start()
			#Ожидаем запуска службы в течении заданного таймаута
			$wuauserv.WaitForStatus('Running',(new-timespan -seconds 10))
		}
		catch {
			# если в течение отведенного таймаута служба не стартовала, то сообщим об этом...
			"На $CompName службу wuauserv запустить не удалось...`n"		
			# ...и установим флаг успешного запуска службы в состояние $false
			$Started=$false
		}
		# если служба Автоматического обновления была успешно запущенна, то
		# выполняем действия, указанные в kb903262
		if ($Started) {
			#Ждем 5 секунд
			Start-Sleep -Seconds 5
			#принудительное применение политики (на всякий случай, лучше перебдеть)
			"Запускаем принудительное обновление политики..."
			$RemoteProcess=([wmiclass]"\\$CompName\root\cimv2:Win32_Process").create("cmd /c gpupdate /force")
			"...код возврата запуска - $($RemoteProcess.ReturnValue), ID запущеного процесса - $($RemoteProcess.ProcessId)`n"
			#Ждем 30 секунд
			Start-Sleep -Seconds 30
			"Выполняем wuauclt /resetauthorization /detectnow"
			#выполняем wuauclt /resetauthorization /detectnow, согласно http://support.microsoft.com/kb/903262
			$RemoteProcess=([wmiclass]"\\$CompName\root\cimv2:Win32_Process").create("cmd /c wuauclt /resetauthorization /detectnow")
			"...код возврата запуска - $($RemoteProcess.ReturnValue), ID запущеного процесса - $($RemoteProcess.ProcessId)`n"
		}
	}
}

Upd. Небольшие замечания по скрипту: помимо действий, указанных в kb903262, скрипт выполняет принудительное обновление политик. Этот блок команд необязателен и его можно закомментировать/удалить. С одной стороны, выполнение этого блока гарантирует применение политик на клиенте, если они по каким-то причинам еще не отработали, с другой – клиент отрабатывает их относительно медленно, поэтому при большом количестве клиентов скрипт будет работать очень долго.

Результат работы скрипта выглядит следующим образом:

ORG-20071101-1 DOMAIN\FedorovL
Исключение при вызове "DeleteValue" с "1" аргументами: "Значения для этого имени не существует."
C:\Scripts\PoSh.try\WSUS\WSUSClientRemoteReset.ps1:66 знак:22
+ $regKey.DeleteValue <<<< ("PingID")
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : DotNetMethodException

Исключение при вызове "DeleteValue" с "1" аргументами: "Значения для этого имени не существует."
C:\Scripts\PoSh.try\WSUS\WSUSClientRemoteReset.ps1:67 знак:22
+ $regKey.DeleteValue <<<< ("AccountDomainSid")
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : DotNetMethodException

Запускаем принудительное обновление политики...
...код возврата запуска - 0, ID запущеного процесса - 3516

Выполняем wuauclt /resetauthorization /detectnow
...код возврата запуска - 0, ID запущеного процесса - 2804

ORG-20071224-1 DOMAIN\KuritsynV
Исключение при вызове "DeleteValue" с "1" аргументами: "Значения для этого имени не существует."
C:\Scripts\PoSh.try\WSUS\WSUSClientRemoteReset.ps1:66 знак:22
+ $regKey.DeleteValue <<<< ("PingID")
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : DotNetMethodException

Исключение при вызове "DeleteValue" с "1" аргументами: "Значения для этого имени не существует."
C:\Scripts\PoSh.try\WSUS\WSUSClientRemoteReset.ps1:67 знак:22
+ $regKey.DeleteValue <<<< ("AccountDomainSid")
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : DotNetMethodException

Запускаем принудительное обновление политики...
...код возврата запуска - 0, ID запущеного процесса - 2180

Выполняем wuauclt /resetauthorization /detectnow
...код возврата запуска - 0, ID запущеного процесса - 2072

Некоторые ключи реестра, которые требует удалить kb903262, могут отсутствовать на рабочих станциях. В этом случае скрипт будет сообщать об ошибке удаления, как это видно в вышеприведенном примере. В этом нет ничего страшного, за исключением того, что информация, содержащаяся в стандартных сообщениях об ошибках избыточна для данного случая. Если вам не лень, можете дописать свой обработчик ошибок и «причесать» вывод результатов работы скрипта ;)

Реклама

Один ответ to “Скрипт для удаленного сброса клиента службы Автоматического обновления.”

  1. […] клиентов, наткнулся на интересную заметку — ShS’s Blog – Скрипт для удаленного сброса клиента слу…. Представленный в этой статье скрипт был взят за […]

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

Заполните поля или щелкните по значку, чтобы оставить свой комментарий:

Логотип WordPress.com

Для комментария используется ваша учётная запись WordPress.com. Выход / Изменить )

Фотография Twitter

Для комментария используется ваша учётная запись Twitter. Выход / Изменить )

Фотография Facebook

Для комментария используется ваша учётная запись Facebook. Выход / Изменить )

Google+ photo

Для комментария используется ваша учётная запись Google+. Выход / Изменить )

Connecting to %s

 
%d такие блоггеры, как: