- На WSUS сервере нет смысла перемещать его в Unapproved т.к. патч уже скачан на клиенскую машину и находится в папке SoftwareDistribution, надо удалять вручную
- глючных патчей может быть много
- через пол годика админ может по запарке его снова одобрить
- если одобряем раз в полгода-год, отсев плохих от хороших займет до 1 дня работы на 1 машину (класс машин)
- обновления одной конфигурации могут не подходить для другой конфигурации даже если ОС одна
- состояние обновления скрытое будет сброшено если удалить SoftwareDistribution
- SoftwareDistribution надо скидывать если глюкнула база, слишком долго идет поиск, а лучше делать это периодически
Это все неудобно, поэтому админ почти всегда сделает одно и то же — он отключит обновления на проблемной машине. А через годик или два он начнет накатывать обновления и столконется с этой же проблемой.
Т.к. глюк обновлений вызывает аварийный режим, жрет неимоверное количество времени персонала на абсолютно бессмысленную фигню, и с целью преехода к умной инфраструктуре было решено попробовать автоматизировать этот процесс. Попутно был получен скрипт который может установить все обновления на свежеустановленую машину в автоматическом режиме.
Сразу оговорюсь — данный скрипт не умеет ремонтировать систему которая не загружается вообще, он умеет сам ставить обновления, в автоматическом режиме выкидывать обновы вызывающих циклическую перезагрузку, имеет возможность вручную добавить плохие обноления в список и сигнализировать админу о возникших неполадках с установкой.
Как это все запустить, быстрый старт
исходники к статье одним файлом
0. Разрешите исполнение скриптов в вашей системе. Set-ExecitionPolicy Unrestricted или подпишите скрипты вашим сертификатом.
1. скопируйте скрипт WUErrorreporting на диск контроллируемой машины и добавьте его в планировщик задач например 12:30 дня. Если кто не знает как создавать задания по расписанию жмите сюда
<#
2016.05.23
Windows Update Error reporter
симафорит о возных проблемах на клиенте. Осматривает журнал установки обновлений и в случае если
есть событие 20 формирует административное письмо.
Pak N.V.
#>
#############
# Variables #
#############
# количество дней за который будет просматриваться журнал
[int]$DaysBefore = 5
# сохранять ли список не установившихся обновлений и куда
$SaveLog = $false
$LogPath = 'c:\WUError.txt'
# сохранять отчет HTML
[boolean]$SaveReport = $False
[string]$ReportPath = 'C:\WUErrorReport.html'
# отправлять HTML отчет
[boolean]$SendReport = $true
[string]$ReportMail1 = 'admin@test.local'
[string]$from = 'bot_abormot@test.local'
[string]$SMTPServer = 'mail.test.local'
#############################################################################
# получаем события за последние сутки
$Events = Get-EventLog -LogName System -EntryType Error -After (Get-Date).AddDays(-$DaysBefore) -InstanceId 20 -ErrorAction SilentlyContinue
# если были обшики установки то продолажем обработку иначе выход
if ($Events.Count -eq 0)
{
Write-Host 'ошибок установки обновлений нет, завершаем сценарий' -ForegroundColor Green
Exit
}
# контейнер для событий
$Log = @()
# переберием события и вытаскиваем номера обновлений с ошибками установки
foreach ($Event in $Events)
{
$regex = $Event.Message -match "KB\d+"
$KB = $matches[0]
$params = [ordered]@{ 'KB'=$KB
#'EntryType'=$Event.EntryType
'Index'=$Event.Index
'MachineName'=$Event.MachineName
'Message'=$Event.Message
'Source'=$Event.Source
'TimeGenerated'=$Event.TimeGenerated
'TimeWritten'=$Event.TimeWritten
'UserName'=$Event.UserName
}
$obj = New-Object -TypeName PSObject -Property $params
$Log += $obj
}
if ($SaveLog -eq $true)
{
# сохраняем список обновлений с ошибками
Add-Content -Path $LogPath -Value ''
$Log | select -ExpandProperty KB | Add-Content -Path $LogPath
}
#отработали, ниже отправка письма
################################# красивый отчет #####################################
Write-Verbose 'HTML fragment producing'
$ClientName = $env:COMPUTERNAME
$TotalErrors = $log.Count
$frag1 = $Log | ConvertTo-Html -As table -Fragment -PreContent "<h2>Windows Update error report. $ClientName </h2><br><h3>Total $TotalErrors errors.</h3>" | Out-String
Write-Verbose 'definiting CSS'
$head = @'
<style>
body { background-color:#ffffff;
font-family:Tahoma;
font-size:12pt; }
td, th { border:1px solid black;
border-collapse:collapse; }
th { color:white;
background-color:black; }
table, tr, td, th { padding: 2px; margin: 0px }
table {
font-family: "Lucida Sans Unicode", "Lucida Grande", Sans-Serif;
font-size: 14px;
border-radius: 10px;
border-spacing: 0;
text-align: center;
}
th {
background: #BCEBDD;
color: white;
text-shadow: 0 1px 1px #2D2020;
padding: 10px 20px;
}
th, td {
border-style: solid;
border-width: 0 1px 1px 0;
border-color: white;
}
th:first-child, td:first-child {
text-align: left;
}
th:first-child {
border-top-left-radius: 10px;
}
th:last-child {
border-top-right-radius: 10px;
border-right: none;
}
td {
padding: 10px 20px;
background: #F8E391;
}
tr:last-child td:first-child {
border-radius: 0 0 0 10px;
}
tr:last-child td:last-child {
border-radius: 0 0 10px 0;
}
tr td:last-child {
border-right: none;
}
</style>
'@
$Date = Get-Date
if ($SendReport -eq $true)
{
Write-Verbose 'SendEmail'
$encoding = [System.Text.Encoding]::UTF8
$body = ConvertTo-HTML -head $head -PostContent $frag1 -PreContent "<h1>Windows Update error report. Client $ClientName. Date:$Date</h1>" | Out-String
$params = @{'To'=$ReportMail1
'From'=$from
'Subject'="$ClientName. Windows Update error $Date"
'Body'=$Body
'BodyAsHTML'=$True
'SMTPServer'=$SMTPServer}
Send-MailMessage @params -Encoding $encoding
}
2. Выставье параметры оповещения о ошибках обновлений, для этого измените параметры
$ReportMail1 = 'admin@test.local' на вашу почту
$SMTPServer = 'mail.test.local' на ваш почтовый сервер.
3. скопировать скрипт сброса конфигурации на нужную машину и запустить его. Он создаст каталог с начальными настройками, если каталог есть сбросит их в начальное состояние.
<#
сбрасывает переменные автоматов для первого использования
#>
# путь до директории где лежат рабочие файлы модуля
$Path = Split-Path ($MyInvocation.MyCommand.Path) -Parent
if ((Test-Path -Path "$Path\BadUpdates") -eq $false)
{
New-Item -Path "$Path\BadUpdates" -ItemType Directory -Force
}
# последние установленные обновления
$LastInstalledUpdatesFile = "$Path\BadUpdates\LastInstalledUpdates.txt"
# список точно плохих обновлений
$BadUpdatesFile = "$Path\BadUpdates\BadUpdates.txt"
# FastInstaller
$KAFUFile = "$Path\BadUpdates\KAFU.txt"
# Скорость установки обновлений за раз
$KAFUDeltaFile = "$Path\BadUpdates\KAFUDelta.txt"
# Fastinstaller watchdog
$KAFUWatchDogFile = "$Path\BadUpdates\KAFUWatchDog.txt"
# Fastinstaller update installer watchdog
$KAFUWatchDog2File = "$Path\BadUpdates\KAFUWatchDog2.txt"
# SlowInstaller
$KASUFile = "$Path\BadUpdates\KASU.txt"
# файл лог
$WorkLogFile = "$Path\BadUpdates\log.txt"
# общий автомат
$KAFile = "$Path\BadUpdates\KA.txt"
Set-Content $LastInstalledUpdatesFile -Value ''
Set-Content $KAFUFile -Value 0
Set-Content $KAFUDeltaFile -Value 5
Set-Content $KAFUWatchDogFile -Value 0
Set-Content $KAFUWatchDog2File -Value 0
Set-Content $KASUFile -Value 0
Set-Content $KAFile -Value 99
Set-Content $BadUpdatesFile -Value ''
4. в каталог с предыдущим скриптом скопировать скрипт обновлений (внимание, ниже большой скрипт, 85кб), чтобы не копипастить скачайте его отсюда
<#
2016.06.14
ver 2
устанавливает обновления на клиентских машинах пытаясь определить обновления вызывающие
тормоза при загрузке. Делалось для снижения стресса на отдел системных администраторов.
может
устнававливать быстро много обновлений пачками (FastUpdate) при быстром проходе ставит сначала по 30%
за раз, если не получилось то по 25%, если не получилось то по 5 за раз.
ставить по одному обновленияю, и после прохода по всем те что не встали добавит в список плохих,
если добавит в список плохих то при следующей работе эти обновления будут игнорироваться не зависимо
от того одобрены они на вышестоящем сервере или нет
сбрасывает настройки агента всус на машине на каждом запуске, позволяет сбросить глюки, или кривые апдейты
для использования как робота обновлений
1. выберите каталог куда запишете сценарий
2. запишите в файл "\BadUpdates\KA.txt" число больше 9, например 11
3. создайте в планировщике заданий от админа задание с правами система
триггер - запуск системы, подождать 6 часов
действие - запуск программы powershell.exe -file "ваш путь до сценария\Install-ClientUpdate.ps1"
4. разрешите выполнение сценариев в вашей среде
5. пропишите в сценарии ваши параметры
установите $RebootEnabled = $False
на какие адреса посылать оповещение о плохих обновлениях и нужно ли вообще отправлять
для автоматической установки большого количества обновлений
1. выберите каталог куда запишете сценарий
2. запишите в файл "\BadUpdates\KA.txt" число больше 9, например 11
3. создайте в планировщике заданий от админа задание с правами система
триггер - при запуск (при включении компьютера), отложить на 10 минут
действие - запуск программы powershell.exe -file "ваш путь до сценария\Install-ClientUpdate.ps1"
4. разрешите выполнение сценариев в вашей среде
5. пропишите в сценарии ваши параметры
установите $RebootEnabled = $true
на какие адреса посылать оповещение о плохих обновлениях и нужно ли вообще отправлять
глюки:
в некоторых случая не удаляется папка SoftwareDistribution. происходит это из за того что какойнибудь процесс держит папку
в основном он создает каталог в подпапке Plugins. в таком случае нужно перезагрузится и один раз очистить папку вручную.
в коде сделана поправка чтобы сценарий не останавливался в таком случае, просто очистит ее по максимум и перезапустит службы
без завершения.
Юзер иногда жмакает при завершении работы на "Установить обновления и завершить работу" что вызывает установку вообще всех
обновлений. Нужно объяснить что достаоточно только завершить работу и показать как. Иначе нужно дописать часть
устанавливающую IsHidden=1 для всех обновлений кроме тех что выбраны на установку в этой сессии.
возможно зависание поиска обновлений если стоит настройка "4. автоматически устанавливать обновления и перезагружать компьютер".
нужно поставить другую настройку.
возможен глюк на win 7 если в качестве источника обновлений стоит мир. Скрипт никогда не может закончить получение обновлений
стабильно работает с локального всус
требует:
powershell 2.0
Pak Nikolay
GEOM, Aqtobe
проверен на win 8.1/2012R2; win 7; win 2008R2
#>
#############
# Variables #
#############
# Allow reboot. установка этого флага вызывает перезагрузку после каждого срабатывания. нужно если вам надо поставить
# много обновлений на машину. установите в планировщике время после запуска 10 минут, и разрешите ребуты. сценарий будет сам
# устанавливать обновления для вас.
#$RebootEnabled = $true
$RebootEnabled = $False
# Fast install если больше этого порога то переходим к быстрой установке
$FastInstallLimit = 45
# путь до директории где лежат рабочие файлы модуля
$Path = Split-Path ($MyInvocation.MyCommand.Path) -Parent
# последние установленные обновления
$LastInstalledUpdatesFile = "$Path\BadUpdates\LastInstalledUpdates.txt"
# список точно плохих обновлений
$BadUpdatesFile = "$Path\BadUpdates\BadUpdates.txt"
# FastInstaller
$KAFUFile = "$Path\BadUpdates\KAFU.txt"
# Скорость установки обновлений за раз
$KAFUDeltaFile = "$Path\BadUpdates\KAFUDelta.txt"
# Fastinstaller watchdog
$KAFUWatchDogFile = "$Path\BadUpdates\KAFUWatchDog.txt"
# Fastinstaller update installer watchdog
$KAFUWatchDog2File = "$Path\BadUpdates\KAFUWatchDog2.txt"
# SlowInstaller
$KASUFile = "$Path\BadUpdates\KASU.txt"
# файл лог
$WorkLogFile = "$Path\BadUpdates\log.txt"
# общий автомат
$KAFile = "$Path\BadUpdates\KA.txt"
########################## Report params ###################################
[boolean]$SaveReport = $true
[string]$ReportPath = 'C:\Report-InstallUpdates.html'
[boolean]$SendReport = $true
[string]$From = 'bot_abormot@test.local'
[string]$SMTP = 'mail.test.local'
[string]$ReportMail1 = 'admin1@test.local'
[string]$ReportMail2 = 'admin2@test.local'
[string]$ReportMail3 = 'admin3@test.local'
function report
{
Param ( [string]$Text = 'test' )
$Date = Get-Date
if ($SaveReport = $true)
{
$Text | Out-File $ReportPath
}
if ($SendReport = $true)
{
$encoding = [System.Text.Encoding]::UTF8
$body = $Text
$Subj = "install-updates script report $Date"
$params = @{'To'=$ReportMail1
'From'=$From
'Subject'=$Subj
'Body'=$Body
'BodyAsHTML'=$True
'SMTPServer'=$SMTP}
Send-MailMessage @params -Encoding $encoding
$params = @{'To'=$ReportMail2
'From'=$From
'Subject'=$Subj
'Body'=$Body
'BodyAsHTML'=$True
'SMTPServer'=$SMTP}
Send-MailMessage @params -Encoding $encoding
$params = @{'To'=$ReportMail3
'From'=$From
'Subject'=$Subj
'Body'=$Body
'BodyAsHTML'=$True
'SMTPServer'=$SMTP}
Send-MailMessage @params -Encoding $encoding
}
}
########################## Report params ###################################
function Log
{
PARAM ( [parameter(Mandatory = $true)]
[string]$Message
)
$Date = Get-Date -Format "yyyy.MM.dd HH:mm:ss"
[string]$Msg = $Date + "`t" + $Message
Out-File -FilePath $WorkLogFile -InputObject $Msg -Append # -encoding unicode
}
function Clear-Log
{
$Date = Get-Date -Format "yyyy.MM.dd HH:mm:ss"
$Msg = $Date + "`t" + 'лог очищен вызовом Clear-Log'
Set-Content -Path $WorkLogFile -Value $Msg
}
function Trim-Log
{
# если лог больше 5 мегабайт кильнуть лог
if ( (Get-Item -Path $WorkLogFile).Length -gt 5mb )
{
Clear-Log
}
}
<#
сбрасываем папку SoftwareDistribution
часто служба обновлений не может обновится, выдает ошибку, или криво работает
помогает сброс службы WUAUSRV в частности стереть папку с ее настройками.
при старте она ее создаст снова
#>
function ResetSoftwareDistribution
{
$WUServices = 'BITS','wuauserv'
$windows = 0x24
Log 'call ResetSoftwareDistribution'
Log 'stopping WU services'
Write-Host "сбрасываем SoftwareDistribution" -ForegroundColor Green
Write-Host "-------------------------------" -ForegroundColor Green
Write-Host "останавливаем сервисы" -ForegroundColor Green
$WUServices | Stop-Service -Verbose
Start-Sleep -Seconds 20
# если службы остановились то продолжаем
if ( ((Get-Service 'BITS').Status -eq 'stopped' ) -and ((Get-Service 'wuauserv').Status -eq 'stopped') )
{
Log 'services stopped sucsessful'
Write-Host 'службы остановлены продолжаем' -ForegroundColor Green
$Folders = Get-ChildItem ( (New-Object -ComObject Shell.Application).Namespace( $windows ).Self.Path) -Directory | where { $_.Name -like '*SoftwareDistribution*' }
Write-Host "получены папки службы WU" -ForegroundColor Green
$Folders
Write-Host "удаляем ..."
$Folders | Remove-Item -Recurse -Force -Verbose -Confirm:$false
# проверка удаления
$Folders = Get-ChildItem ( (New-Object -ComObject Shell.Application).Namespace( $windows ).Self.Path) -Directory | where { $_.Name -like '*SoftwareDistribution*' }
if ($Folders -eq $null)
{
Log 'SoftwareDistribution folder deleted successful'
Write-Host "удаление SoftwareDistribution прошло успешно" -ForegroundColor Green
}
else
{
Log 'SoftwareDistribution folder deleted NOT successful'
Write-Host "SoftwareDistribution не была удалена!" -ForegroundColor Red
# если вы раскомментируете оператор ниже то возможен завис если какаянибудь фигня блокирует удаление Softwaredistrib
#Exit
}
}
Log 'starting WU services'
Write-Host "запускаем сервисы" -ForegroundColor Green
$WUServices | Start-Service -Verbose
}
<#
скачивает и устанавливает указанные обновления
обновления на установку передаются списоком номеров KB
ищет только обновления имеющие флаг IsHidden равный 0, если вы сбросите Softwaredistribution то
сбросятся все Hidden обновления.
Теоретически для красоты надо докрутить чтобы он выставлять IsHidden = 1 для всех обновлений
кроме тех что ставтся на данный момент
пример вызова
'2976978', '3156418' | install-updates -Verbose
#>
function install-updates
{
[CmdletBinding()]
PARAM (
[Parameter( Position=0,
Mandatory=$true,
ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true)
]
[string[]]$KB
)
BEGIN
{
Log 'call install-updates'
$session = New-Object -ComObject Microsoft.Update.Session
$searcher = $session.CreateUpdateSearcher()
$Updates = $searcher.Search("IsInstalled=0 and Type='Software' and ISHidden=0")
# взводим флаг наличия обновления
if ($Updates.Updates.Count -eq 0) { $NoUpdates = $true } else { $NoUpdates = $false }
# диагностический вывод
Write-Verbose '---------------------------------------------------------------------'
Write-Verbose "install-updates: состояние переменной NoUpdates = $NoUpdates"
if ($NoUpdates -eq $false)
{
Write-Verbose "найдены обновления ожидающие установки"
$temp1 = $Updates.updates | select Title
$temp1 | Write-Verbose
$Count = $Updates.Updates.Count
Write-Verbose "$Count обновлений ожидают установки"
Log "$Count обновлений ожидают установки"
foreach ( $temp in $temp1 ) { Log " $temp" }
}
else
{
Log "install-updates: обновления на установку не найдены "
Write-Verbose "install-updates: обновления на установку не найдены "
}
Write-Verbose '---------------------------------------------------------------------'
}
PROCESS
{
Write-Verbose "install-updates: передан параметр в блок process $KB"
if ($NoUpdates -eq $true) { break }
if ($KB -match ' ' ) { break }
$HotFix = $Updates.Updates | where { $_.Title -like "*$KB*" }
if ($HotFix -eq $null)
{
Write-Verbose "переданное на установку обновление не найдено"
Log "переданное на установку обновление не найдено"
break
}
else
{
$Title = $HotFix.Title
Write-Verbose "найдено обновление на установку $Title"
Log "найдено обновление на установку $Title"
}
# скачиваем обновления
$downloads = New-Object -ComObject Microsoft.Update.UpdateColl
$downloads.Add( $HotFix )
# в этом месте известная ошибка 80070005. Эту операцию можно запустить только с правами SYSTEM
# если вы видите эту ошибку то создайте планировщике задач задание на запуск этого скрипта с правами систем
# и вызывайте само само задание
Write-Verbose 'скачиваем обновление'
Log 'download update'
$downloader = $session.CreateUpdateDownLoader()
$downloader.Updates = $downloads
$downloader.Download()
if ($HotFix.IsDownloaded)
{
Log 'download successfull'
Write-Verbose 'обновление скачано. (находится в папке downloads)'
}
$installs = New-Object -ComObject Microsoft.Update.UpdateColl
if ($HotFix.IsDownloaded)
{
$installs.Add( $HotFix ) | Out-Null
}
log 'start instal'
# в этом месте известная ошибка 80070005. Эту операцию можно запустить только с правами SYSTEM
# если вы видите эту ошибку то создайте планировщике задач задание на запуск этого скрипта с правами систем
# и вызывайте само само задание
$installer = $session.CreateUpdateInstaller()
$installer.Updates = $installs
$installresult = $installer.Install()
$installresult
log 'installing successfull'
}
END
{
Write-Verbose 'install-updates: отработали установку обновлений'
log 'install-updates: отработали установку обновлений'
log '--------------------------------------------------------------'
}
}
<#
применяйте эту функцию если вам нужно поставить больше 20-30 обновлений
быстро ставит кучу обновлений. не делает контроль обновлений.
предназначена для быстрого накатывания обновлений когда нужно установить много обновлений
и отсеить хорошие от плохих. имеет свои переменные
Пользователь не должен долго находится под работой этой функции, т.к. накатывание 10-20 обновлений
и откат в случае ошибки не даст ему работать час или полтора после каждого срабатывания.
ее применять можно только один раз, и желательно объяснить пользователю почему по утрам ему придется
долго ждать отката обновлений.
перед входом в процедуру нужно установить $KAFUWatchDog в ноль
если будет 0 то значит при проходе с шагом 1/3 в каждой порции было по ошибочному обновелению
если будет 1 то значит при проходе с шагом 1/4 в каждой порции было по ошибочному обновелению
если будет 2 то значит при проходе с шагом 5 штук в каждой порции было по ошибочному обновелению
#>
# Состояния
[int]$KAFU = Get-Content $KAFUFile
# Скорость установки обновлений за раз
[int]$KAFUDelta = Get-Content $KAFUDeltaFile
function Fast-Update
{
function Write-KAFU
{
PARAM ( $State )
Set-Content $KAFUFile -Value $State
}
Log "--- Fast-Update ---------------------------------------------------------"
Log "Fast-Update запуск функции быстрой установки"
switch ($KAFU)
{
0 {
Log "вошли в состояние 0"
# вычисляем с какой скоростью будем делать проход
$session = New-Object -ComObject Microsoft.Update.Session
$searcher = $session.CreateUpdateSearcher()
$temp = $searcher.Search("IsInstalled=0 and Type='Software'" )
$Count = $temp.Updates.Count
# смотрим ватч дог, если проход не первый значит был фейл и надо понизить скорость установки обновлений
$KAFUWatchDog = Get-Content $KAFUWatchDogFile
log "KAFUWatchDog = $KAFUWatchDog"
switch ($KAFUWatchDog)
{
0 { # дефолтный параметр, делим количество обновлений на 2 и берем число как шаг
$KAFUDelta = [Math]::Truncate( $Count / 3 )
Log "первый вход, устанавливаем шаг 1/3 от количества и равно $KAFUDelta"
}
1 { # делим количество обновлений на 4 и берем число как шаг
$KAFUDelta = [Math]::Truncate( $Count / 4 )
Log "первый вход, устанавливаем шаг 1/4 от количества и равно $KAFUDelta"
}
2 { # у нас конкретные глюки - в каждой из четырех пачек было по ошибке обновлений
# ставим по 5 обновлений за раз
$KAFUDelta = 5
log 'у нас конкретные глюки - в каждой из четырех пачек было по ошибке обновлений ставим по 5'
}
default { Log 'неизвестное состояние ватч дога, сбрасываем в ноль'
$KAFUWatchDog = 0
$KAFUDelta = [Math]::Truncate( $Count / 3 )
Set-Content $KAFUWatchDogFile -Value $KAFUWatchDog
}
}
# сбрасываем списки перед работой
Set-Content $LastInstalledUpdatesFile -Value ''
Set-Content $KAFUDeltaFile -Value $KAFUDelta
Set-Content $KAFUWatchDog2File -Value 0
Write-KAFU 1
break
}
1 {
Log "enter in state 1"
$LastInstalled = Get-Content $LastInstalledUpdatesFile
$BadUpdates = Get-Content $BadUpdatesFile
log 'получаем список обновлений с вышестоящего сервера'
$session = New-Object -ComObject Microsoft.Update.Session
$searcher = $session.CreateUpdateSearcher()
$temp = $searcher.Search("IsInstalled=0 and Type='Software'" )
$Updates = $temp.Updates | select title
# делаем список обновлений на установку по номерам
[string[]]$HotFixs = ''
foreach ($temp in $Updates)
{
$regex = $temp.Title -match "KB\d+"
$KB = $matches[0]
$HotFixs += $KB
Log "найдено обновление на установку: $KB"
}
$Count = $HotFixs.Count
Log "всего обновлений на установку $Count"
log 'чистим список'
$Upd = @()
foreach ($temp in $HotFixs) { if ($temp -ne '') { $Upd += $temp } }
$Upd
# выбросить те что пробовали ставить и заведомо плохие
if ($BadUpdates.Count -ne 0)
{
$temp = Compare-Object -ReferenceObject $HotFixs -DifferenceObject $BadUpdates
$temp = $temp | where { $_.Sideindicator -like '<=' } | select -ExpandProperty InputObject
$HotFixs = $temp
$Upd = @()
foreach ($temp in $HotFixs) { if ($temp -ne '') { $Upd += $temp } }
$HotFixs = $Upd
$temp = $HotFixs
Log "после исключения плохих обновлений остались:"
foreach ( $temp1 in $temp ) { Log "после исключения плохих обновлений остались: $temp1" }
$Count = $temp.Count
Log "всего: $Count штук"
}
else
{
Log "заведомо плохих обновлений нет"
}
if ($LastInstalled.Count -ne 0)
{
$temp = Compare-Object -ReferenceObject $HotFixs -DifferenceObject $LastInstalled
$temp = $temp | where { $_.Sideindicator -like '<=' } | select -ExpandProperty InputObject
$HotFixs = $temp
Log "после исключения тех что пробовали остались:"
$Upd = @()
foreach ($temp in $HotFixs) { if ($temp -ne '') { $Upd += $temp } }
$HotFixs = $Upd
$temp = $HotFixs
foreach ( $temp1 in $temp ) { Log "после исключения тех что пробовали остались: $temp1" }
$Count = $temp.Count
Log "всего: $Count штук"
}
else
{
Log "еще никаких не устанавливали до этого"
}
# проверяем ватч дог инсталлятора
[int]$KAFUWatchDog2 = Get-Content $KAFUWatchDog2File
if ($KAFUWatchDog2 -gt 20)
{
Log "сработал ватч дог инсталлятора, похоже мы зациклились в состоянии 1, принудительный сброс: $KAFUWatchDog2"
Write-KAFU 2
Set-Content $KAFUWatchDog2File -Value 0
break
}
if ($HotFixs.Count -eq 0)
{
# проход закончен
log 'проход закончен, обновлений не осталось. переходим в состояние 2'
Write-KAFU 2
break
}
else
{
# выбираем обновления на установку
$temp = $HotFixs | select -First $KAFUDelta
$Count = $temp.Count
if ($Count -eq 0)
{
log 'не осталось обновлений. переходим в состояние 2'
Write-KAFU 2
break
}
foreach ( $temp1 in $temp ) { Log "выбраны на установку обновления: $temp1" }
Log "всего: $Count штук"
$HotFixs = $temp
# сохраняем список
Add-Content -Path $LastInstalledUpdatesFile -Value ''
Add-Content -Path $LastInstalledUpdatesFile -Value $HotFixs
@(Get-Content $LastInstalledUpdatesFile) -match '\S' | out-file $LastInstalledUpdatesFile
LOG "сохранили список обновлений для установки"
# отправляем на установку
log 'начинаем устанавливать обновления'
$HotFixs | install-updates
}
Set-Content $KAFUWatchDog2File -Value ($KAFUWatchDog2 + 1)
break
}
2 { # анализируем проход если постоянная ошибка снижаем скорость и пробуем снова
Log 'вошли в состояние 2.'
$KAFUWatchDog = Get-Content $KAFUWatchDogFile
$KAFUDelta = Get-Content $KAFUDeltaFile
$LastInstalled = Get-Content $LastInstalledUpdatesFile
log "KAFUWatchDog = $KAFUWatchDog"
switch ($KAFUWatchDog)
{
0 {
# первое из состояний. Проход был успешным? Получаем список не установленых обновлений
$session = New-Object -ComObject Microsoft.Update.Session
$searcher = $session.CreateUpdateSearcher()
$temp = $searcher.Search("IsInstalled=0 and Type='Software'" )
$Updates = $temp.Updates
# количество оставшихся обновлений меньше чем дельта * 3 - 2
$Count1 = $Updates.Count
if ($KAFUDelta -eq 0) { $KAFUDelta = 5 }
$Count2 = $KAFUDelta * 3 - 2
if ($Count2 -lt $Count1)
{
# не установилось ниодно из обновлений. Снижаем скорость установки обновлений
$KAFUWatchDog = 1
Set-Content $KAFUWatchDogFile -Value 1
Write-KAFU 0
Log 'не установилось ниодно из обновлений. вач дог = 1. переходим в состояние 0'
break
}
else
{
log 'быстрая установка прошла успешно WatchDog = 0'
log 'переходим в состояние 3'
Write-KAFU 3
break
}
}
1 {
# проверяем сколько чего наставили
$session = New-Object -ComObject Microsoft.Update.Session
$searcher = $session.CreateUpdateSearcher()
$temp = $searcher.Search("IsInstalled=0 and Type='Software'" )
$Updates = $temp.Updates | select title
# количество оставшихся обновлений меньше чем дельта * 3 - 2
$Count1 = $Updates.Count
$Count2 = $KAFUDelta * 4 - 2
if ($Count2 -gt $Count1)
{
# у нас эпичная лажа - 4 плохих хотфикса равномерно размазаных в каждом из 25% обновлений
# снижаем скорость установки обновлений до 5
$KAFUWatchDog = 2
Set-Content $KAFUWatchDogFile -Value 2
Write-KAFU 0
Log 'не установилось ниодно из обновлений. вачдог = 2. переходим в состояние 0'
break
}
else
{
log 'быстрая установка прошла успешно WatchDog = 1'
log 'переходим в состояние 3'
Write-KAFU 3
break
}
}
2 { # мы отработали установку по 5 обновлений за раз
log 'быстрая установка прошла успешно WatchDog = 2'
log 'переходим в состояние 3'
Write-KAFU 3
break
}
default { Log 'неизвестное состояние сбрасываем вачдог на 0'
Set-Content $KAFUWatchDogFile -Value 0
Write-KAFU 0
break
}
}
}
3 { # закончили, циклимся
Write-KAFU 3
break
}
default {
Set-Content $KAFUWatchDogFile -Value 0
Write-KAFU 0
Log "warning!!! неизвестное состояние КА, сброшен на 0. KAFU = $KAFU"
break
}
}
Log "--- Fast-Update -- отработала-------------------------------------------------------"
}
<################################################################################################
можно дописать два обновления за проход.
наиболее щадящяя функция по отношению к пользователю.
Применяется для ускорения установки обновлений, ставит по паре за один проход.
если обновлений будет 48 то на установку всех уйдет 24 дня по 2 в день, что займет
1 месяц. При небольших обновлениях для пользователя займет 3-6 минут в день, если обновы
большие то до 30-40 минут в случае сбоя.
################################################################################################>
<#
устанавливает обновления по одному с выкидываем в лист плохих обновлений
центровая фишка вокруг которой все крутится
именно благодаря этой функции мы можем сделать работу почти автоматической
#>
# Состояния
[int]$KASU = Get-Content $KASUFile
function Slow-update
{
function Write-KASU
{
PARAM ( $State )
Set-Content $KASUFile -Value $State
}
Log "--- Slow Update ---------------------------------------------------------"
Log "Slow Update запуск"
switch ($KASU)
{
0 {
# устанавливаем одно обновление
log "Вошли в состояние 0, делаем первоначальные настройки"
# сбрасываем списки перед работой
Set-Content $LastInstalledUpdatesFile -Value ''
Set-Content $KAFUWatchDog2File -Value 0
Set-Content $KAFUWatchDog2File -Value 0
Write-KASU 1
break
}
1 {
log "Вошли в состояие 1"
$LastInstalled = Get-Content $LastInstalledUpdatesFile
$BadUpdates = Get-Content $BadUpdatesFile
log 'получаем список обновлений с вышестоящего сервера'
$session = New-Object -ComObject Microsoft.Update.Session
$searcher = $session.CreateUpdateSearcher()
$temp = $searcher.Search("IsInstalled=0 and Type='Software'" )
$Updates = $temp.Updates | select title
# делаем список обновлений на установку по номерам
[string[]]$HotFixs = ''
foreach ($temp in $Updates)
{
$regex = $temp.Title -match "KB\d+"
$KB = $matches[0]
$HotFixs += $KB
Log "найдено обновление на установку: $KB"
}
$Count = $HotFixs.Count
Log "всего обновлений на установку $Count"
$Upd = @()
foreach ($temp in $HotFixs) { if ($temp -ne '') { $Upd += $temp } }
$PotentialBad = $Upd
# выбросить те что пробовали ставить и заведомо плохие
if ($BadUpdates.Count -ne 0)
{
$temp = Compare-Object -ReferenceObject $HotFixs -DifferenceObject $BadUpdates
$temp = $temp | where { $_.Sideindicator -like '<=' } | select -ExpandProperty InputObject
$HotFixs = $temp
Log "после исключения плохих обновлений остались:"
$Upd = @()
foreach ($temp in $HotFixs) { if ($temp -ne '') { $Upd += $temp } }
$HotFixs = $Upd
$temp = $HotFixs
foreach ( $temp1 in $temp ) { Log "после исключения плохих обновлений остались: $temp1" }
$Count = $temp.Count
Log "всего: $Count штук"
}
else
{
Log "заведомо плохих обновлений нет"
}
$Upd = @()
foreach ($temp in $HotFixs) { if ($temp -ne '') { $Upd += $temp } }
$HotFixs = $Upd
if ($LastInstalled.Count -ne 0)
{
$temp = Compare-Object -ReferenceObject $HotFixs -DifferenceObject $LastInstalled
$temp = $temp | where { $_.Sideindicator -like '<=' } | select -ExpandProperty InputObject
$HotFixs = $temp
Log "после исключения тех что пробовали остались:"
$Upd = @()
foreach ($temp in $HotFixs) { if ($temp -ne '') { $Upd += $temp } }
$HotFixs = $Upd
$temp = $HotFixs
foreach ( $temp1 in $temp ) { Log "после исключения тех что пробовали остались: $temp1" }
$Count = $temp.Count
Log "всего: $Count штук"
}
else
{
Log "еще никаких не устанавливали до этого"
}
$Upd = @()
foreach ($temp in $HotFixs) { if ($temp -ne '') { $Upd += $temp } }
$HotFixs = $Upd
if ($HotFixs.Count -eq 0)
{
# проход закончен
log 'проход закончен, обновлений не осталось. все не установленные потенциально плохие'
if ($PotentialBad -ne 0 )
{
foreach ( $Bad in $PotentialBad )
{
log " вероятно плохое обновление: $Bad"
}
# выходим отсюда т.к. функция не стандалон, репорт и добавление в плохие должен сделать вышестоящий
#Add-Content -Path $BadUpdatesFile -Value ''
#Add-Content -Path $BadUpdatesFile -Value $LastInstalled
}
Write-KASU 2
break
}
else
{
# выбираем обновления на установку
$temp = $HotFixs | select -First 1
$Count = $temp.Count
if ( $Count -eq 0 )
{
log 'переходим в состояние 2'
Write-KASU 2
break
}
foreach ( $temp1 in $temp ) { Log "выбраны на установку обновления: $temp1" }
Log "всего: $Count штук"
$HotFixs = $temp
# сохраняем список
Add-Content -Path $LastInstalledUpdatesFile -Value ''
Add-Content -Path $LastInstalledUpdatesFile -Value $HotFixs
@(Get-Content $LastInstalledUpdatesFile) -match '\S' | out-file $LastInstalledUpdatesFile
LOG "сохранили список обновлений для установки"
# отправляем на установку
log 'начинаем устанавливать обновления'
Write-Host $HotFixs -ForegroundColor Green
$HotFixs | install-updates
}
Set-Content $KAFUWatchDog2File -Value ($KAFUWatchDog2 + 1)
break
}
2 {
# завершили работу, циклимся
Write-KASU 2
break
}
default {
# неизвестное состояние
log "неизвестное состояние"
Write-KASU 0
}
}
Log "--- Slow-Update -- отработала-------------------------------------------------------"
}
[int]$KA = Get-Content $KAFile
function Write-KA
{
PARAM ( $State )
Set-Content $KAFile -Value $State
}
Log ' '
Log " "
Log ' '
Log ' '
Log ' '
Log '##############################'
Log "### --- Мы запустились --- ###"
Log '##############################'
switch ($KA)
{
0 { # мы запустились
log( 'КА = 0. Сбрасываем все переменные и переходим в состояние ожидания обновлений' )
Set-Content $LastInstalledUpdatesFile -Value ''
Set-Content $KAFUWatchDogFile -Value 0
Set-Content $KASUFile -Value 0
Set-Content $KAFUFile -Value 0
Write-KA 9
break
}
1 { # ставим обновления
Log 'Состояние КА 1. Переходим быстрой установке обновлений'
[int]$KAFU = Get-Content $KAFUFile
if ($KAFU -eq 3)
{
# быстрая установка закончилась
log 'быстрая установка отработала, переходим к медленной установке'
Write-KA 2
Set-Content $KASUFile -Value 0
break
}
else
{
log 'вызываем быстрый устновщик'
ResetSoftwareDistribution
Fast-Update
Trim-Log
break
}
}
2 { # отработали
log 'состояние 2, медленная установка'
[int]$KASU = Get-Content $KASUFile
if ($KASU -eq 2)
{
# отработали медленную установку
# делаем репорт админу что отработка закончилась и нужно обновления проверить и возможно перенсти в группу плохих
Trim-Log
ResetSoftwareDistribution
log( 'КА = 2. Отработали медленную установку обновлений' )
$BadUpdates = Get-Content $BadUpdatesFile
$session = New-Object -ComObject Microsoft.Update.Session
$searcher = $session.CreateUpdateSearcher()
$temp = $searcher.Search("IsInstalled=0 and Type='Software'" )
$Updates = $temp.Updates | select title
# делаем список обновлений на установку по номерам
[string[]]$HotFixs = ''
foreach ($temp in $Updates)
{
$regex = $temp.Title -match "KB\d+"
$KB = $matches[0]
$HotFixs += $KB
Log "найдено обновление на установку: $KB"
}
$Count = $HotFixs.Count
Log "всего обновлений на установку $Count"
$PotentialBad = $HotFixs
# выбросить заведомо плохие
if ($BadUpdates.Count -ne 0)
{
$temp = Compare-Object -ReferenceObject $HotFixs -DifferenceObject $BadUpdates
$temp = $temp | where { $_.Sideindicator -like '<=' } | select -ExpandProperty InputObject
$HotFixs = $temp
Log "после исключения плохих обновлений остались:"
foreach ( $temp1 in $temp ) { Log "после исключения плохих обновлений остались: $temp1" }
$Count = $temp.Count
Log "всего: $Count штук"
}
else
{
Log "заведомо плохих обновлений нет"
}
$Updates = @()
foreach ($temp in $HotFixs)
{
if ($temp -ne '') { $Updates += $temp }
}
if ($Updates.Count -ne 0)
{
# отправляем административное оповещение
log 'отправляем административное оповещение и добавляем обновления с писок плохих'
Add-Content -Path $BadUpdatesFile -Value ''
Add-Content -Path $BadUpdatesFile -Value $Updates
$Date = Get-Date
$CompName = $env:COMPUTERNAME
$body = "<H1>завершена установка обновлений на компьютер $CompName,<br><br> вероятно проблемные обновления</H1><h3>"
foreach ($upd in $updates)
{ $body += "<br> $upd<br>" }
$body += "<br>они добавлены в список плохих и больше устанавливаться не будут. проверьте их предварительно, возможно среди них обновление одобренное вчера или сегодня"
report -text $body
}
Write-KA 9
break
}
else
{
log 'обновления еще не доставились ставим одно обновление и засыпаем'
ResetSoftwareDistribution
Trim-Log
Slow-update
break
}
}
9 { # ждем когда появятся обновления
log( 'КА = 9. Ждем появления обновлений на установку' )
Trim-Log
ResetSoftwareDistribution
$BadUpdates = Get-Content $BadUpdatesFile
log 'получаем обновления с вышестоящего сервера'
$session = New-Object -ComObject Microsoft.Update.Session
$searcher = $session.CreateUpdateSearcher()
$temp = $searcher.Search("IsInstalled=0 and Type='Software'" )
$Updates = $temp.Updates | select title
# делаем список обновлений на установку по номерам
[string[]]$HotFixs = ''
foreach ($temp in $Updates)
{
$regex = $temp.Title -match "KB\d+"
$KB = $matches[0]
$HotFixs += $KB
Log "найдено обновление на установку: $KB"
}
$Count = $HotFixs.Count
Log "всего обновлений на установку $Count"
$PotentialBad = $HotFixs
# выбросить заведомо плохие
if ($BadUpdates.Count -ne 0)
{
$temp = Compare-Object -ReferenceObject $HotFixs -DifferenceObject $BadUpdates
$temp = $temp | where { $_.Sideindicator -like '<=' } | select -ExpandProperty InputObject
$HotFixs = $temp
Log "после исключения плохих обновлений остались:"
$Upd = @()
foreach ($temp in $HotFixs) { if ($temp -ne '') { $Upd += $temp } }
$HotFixs = $Upd
$temp = $HotFixs
foreach ( $temp1 in $temp ) { Log "после исключения плохих обновлений остались: $temp1" }
$Count = $temp.Count
Log "всего: $Count штук"
}
else
{
Log "заведомо плохих обновлений нет"
}
log 'чистим список'
$Updates = @()
foreach ($temp in $HotFixs)
{
if ($temp -ne '')
{ $Updates += $temp }
}
$Count = $Updates.Count
Log "Всего обновлений $Count переключаем установщик"
# если обновлений больше чем $FastInstallLimit запускаем быструю установку
if ($Updates.Count -gt $FastInstallLimit)
{
# ставим обновления быстро
log "updates more than $FastInstallLimit. Go to FastInstall"
Write-KA 1
break
}
if ($Updates.Count -gt 0)
{
log "Go to Slow Install"
Write-KA 2
Set-Content $KASUFile -Value 0
break
}
if ($Updates.Count -eq 0)
{
log 'новых обновлений нет ждем обновлений'
Write-KA 9
break
}
}
default { # неизвестное состояние
log( 'неизвестное состояние главного автомата. переходим в состояние 0' )
Write-KA 0
}
}
#ResetSoftwareDistribution
#Fast-Update
#Trim-Log
#Slow-update
Log '##############################'
Log "### --- Мы отработали --- ###"
Log '##############################'
if ( $RebootEnabled -eq $true )
{
Restart-Computer -Force
Log 'перезагрузка >>>'
}
<#
schtasks /run /tn "\My_Tasks\PSWindowsUpdate"
[Console]::OutputEncoding = [System.Text.Encoding]::GetEncoding("cp866")
#>
5. создать задание в планировщике с правами SYSTEM на запуск 1 раз в день. Известная проблема — установка обновлений может быть инициирована только под правами SYSTEM. (кстати можете этот способ применять для запуска активаторов и всего что должно работать с наивысшей привелегией)
хорошо себя показала задержка после старта 6 часов
Все, сценарий должен работать.
Первые 3 раза его можно запустить вручную для проверки. Запустите задание из планировщика и проверьте содержимое файла \BadUpdates\log.txt он должен содержать лог работы сценария. Если он пуст проверьте разрешена ли политика исполнения скриптов, доступен ли сервер обновлений, попробуйте вручную поискать обновления.
Если обновлений будет больше чем 45 (можете менять это значение) сценарий будет пытаться устанавливать обновления пачками. При этом первый раз он попытается поставить по 1\3 за раз, если ни одна из 3х пачек обновлений не установится, шаг будет изменен на 1/4, если ниодна из пачек не встанет то алгоритм перейдет на установку по 5. Данный алгоритм родился из жизни, когда встретилось на одной машине 6 неподходящих обновлений равномерно размазаных, при этом устанавливать по одному почти 90 обновлений не вариант (90 дней по одной обновке).
Происходить будет следующее:
1. каждый день WUErrorreporting будет проверять журнал на ошибку 20, если ошибка есть то будет отправлять оповещение на почту с выборкой какое обновление и когда не установилось.
2. каждый день будет происходить сброс папки SoftwareDistribution
3. каждый день скрипт установки обновлений будет проверять наличие обновлений. Если обновления есть то:
4. если их больше чем 45 автомат переключится в состояние быстрых обновлений и «уснет» до перезагрузки
- на следующий день сбросит автомат быстрых обновлений, вычислено сколько обновлений ставить за раз, при первом проходе 1/3, при втором быстром проходе 1/4, при третьем по 5
- каждый день пока работает быстрая установка будет получатся список обновлений из него будут удалены обновления которые пробовали ставить ранее и обновления из списка точно плохих обновлений
- из оставшихся будут выбраны некоторое количество обновлений, они будут отправлены на установку
- обновления отправленные на установку будут записаны в файл BadUpdates\LastInstalledUpdates.txt и будут исключены из списка при следующем проходе
- После завершения быстрого прохода произойдет автоматический переход к установке по одному обновлению за раз
5. если их меньше 45 автомат переключится в режим установки обновлений по одному
6. в режиме установки по одному
- сначала произойдет сброс переменных для установки по одному обновлению
- будет получен список обновлений без тех что пробовали и точно плохих, если обновлений не осталось то не установленные обновления будут добавлены в список точно плохих и потправлены оповещением на почту. Если еще есть то начнется установка
7. После отработки автомат перейдет в состояние ожидания обновлений
Если в логе видно что не удаляется папка SoftwareDistribution проверьте папку SoftwareDistribution\Plugins, какаято программа держит ее и не дает очистить.
Если вам нужно обновить свежеустановленную систему сделайте следующее:
- установите параметр $RebootEnabled = $true, это разрешит сделать перезагрузку сразу же после работы скрипта
- установите триггер срабатывания на 10 минут после запуска системы
- сохраните сценарий и запустите его
- дальше он сам будет пытаться устанавливать обновления и будет сам перезагружать компьютер, создавать отдельного пользователя, или логинится специально для его работы не нужно.
- через определенное время контролируйте его заглядывая в лог файл
- когда он все поставит и перейдет в состояние 9 (ожидание обновлений) установите $RebootEnabled = $false
- отключите триггер если вам больше не нужно обновляться
разберем работу
WUErrorreporting
1. Установите Ваши переменные
Наиболее значимым явлется переменная $DaysBefore — за сколько дней будет сканироваться журнал, по умолчанию 5 дней. Если произошла ошибка установки обновления то вы будете получать административное оповещение 5 дней подряд об одной и тойже ошибке. В принципе нормально, ставить 1 или 2 дня, не имеет смысла, можно пропустить репорт если ктото выходил работать на выходные.
Переменная $SaveLog отвечает за сохранение лога текущего прогона на диск. Полезно, вы можете придти или подключится к клиентской машине и во время работы заглянуть какое обновление не поставилось.
Последние 4 переменные регулируют кому отправлять отчет. Нужно оставить $SendReport = $true если нужно чтобы приходило оповещение на почту, указать email админа $ReportMail1 = 'admin@test.local' и на какой сервер слать $SMTPServer = 'mail.test.local'. Если нужна аутентификация то добавьте ее в 156 строке
#############
# Variables #
#############
# количество дней за который будет просматриваться журнал
[int]$DaysBefore = 5
# сохранять ли список не установившихся обновлений и куда
$SaveLog = $false
$LogPath = 'c:\WUError.txt'
# сохранять отчет HTML
[boolean]$SaveReport = $False
[string]$ReportPath = 'C:\WUErrorReport.html'
# отправлять HTML отчет
[boolean]$SendReport = $true
[string]$ReportMail1 = 'admin@test.local'
$from = 'bot_abormot@test.local'
[string]$SMTPServer = 'mail.test.local'
2. Принцип работы очень прост
сгружаем только ошибки с ID20 из журнала System
$Events = Get-EventLog -LogName System -EntryType Error -After (Get-Date).AddDays(-$DaysBefore) -InstanceId 20 -ErrorAction SilentlyContinue
Если ошибки есть то формируем объекты отчета
# контейнер для событий
$Log = @()
# переберием события и вытаскиваем номера обновлений с ошибками установки
foreach ($Event in $Events)
{
$regex = $Event.Message -match "KB\d+"
$KB = $matches[0]
$params = [ordered]@{ 'KB'=$KB
#'EntryType'=$Event.EntryType
'Index'=$Event.Index
'MachineName'=$Event.MachineName
'Message'=$Event.Message
'Source'=$Event.Source
'TimeGenerated'=$Event.TimeGenerated
'TimeWritten'=$Event.TimeWritten
'UserName'=$Event.UserName
}
$obj = New-Object -TypeName PSObject -Property $params
$Log += $obj
}
и отправляем их «правильным» способом
$encoding = [System.Text.Encoding]::UTF8
$body = ConvertTo-HTML -head $head -PostContent $frag1 -PreContent "<h1>Windows Update error report. Client $ClientName. Date:$Date</h1>" | Out-String
$params = @{'To'=$ReportMail1
'From'=$from
'Subject'="$ClientName. Windows Update error $Date"
'Body'=$Body
'BodyAsHTML'=$True
'SMTPServer'=$SMTPServer}
Send-MailMessage @params -Encoding $encoding
применяемый CSS стиль заточен под outlook, а он в качестве HTML движка использует Word, что ведет к неполной поддержке стандарта. Если ктонибудь подберет другой красивый стиль который работал бы в Outlook скиньте и мне.
$head = @'
<style>
body { background-color:#ffffff;
font-family:Tahoma;
font-size:12pt; }
td, th { border:1px solid black;
border-collapse:collapse; }
th { color:white;
background-color:black; }
table, tr, td, th { padding: 2px; margin: 0px }
table {
font-family: "Lucida Sans Unicode", "Lucida Grande", Sans-Serif;
font-size: 14px;
border-radius: 10px;
border-spacing: 0;
text-align: center;
}
th {
background: #BCEBDD;
color: white;
text-shadow: 0 1px 1px #2D2020;
padding: 10px 20px;
}
th, td {
border-style: solid;
border-width: 0 1px 1px 0;
border-color: white;
}
th:first-child, td:first-child {
text-align: left;
}
th:first-child {
border-top-left-radius: 10px;
}
th:last-child {
border-top-right-radius: 10px;
border-right: none;
}
td {
padding: 10px 20px;
background: #F8E391;
}
tr:last-child td:first-child {
border-radius: 0 0 0 10px;
}
tr:last-child td:last-child {
border-radius: 0 0 10px 0;
}
tr td:last-child {
border-right: none;
}
</style>
'@
Install-ClientUpdate.ps1
в скрипте реализованы 3 конечных автомата, для простого администратора сценарий будет достаточно сложным для понимания.
выставляем нужные перменные:
$FastInstallLimit выше этого порога будет включатся быстрая установка, меньше этого порога будем ставить обновления по одному.
$RebootEnabled разрешает перезагрузку после каждого прохода скрипта. Применяется если вам нужно обновить машину. Устанавливаете этот параметр в $true, выставляете время запуска 10 минут и оставляете до утра.
Чуть ниже идут настройки почты
########################## Report params ###################################
[boolean]$SaveReport = $true
[string]$ReportPath = 'C:\Report-InstallUpdates.html'
[boolean]$SendReport = $true
[string]$From = 'bot_abormot@test.local'
[string]$SMTP = 'mail.test.local'
[string]$ReportMail1 = 'admin1@test.local'
[string]$ReportMail2 = 'admin2@test.local'
[string]$ReportMail3 = 'admin3@test.local'
Если вам нужно посмотреть есть ли обновления на удаленной или локальной машине используйте следующий код. Можно просто скопипастить в консоль
$session = New-Object -ComObject Microsoft.Update.Session
$searcher = $session.CreateUpdateSearcher()
$temp = $searcher.Search("IsInstalled=0 and Type='Software'" )
$Count = $temp.Updates.Count
$Count
''
Общая рекомендация: при первом запуске лучше быть на связи с пользователем, т.к. он пугается долгой установки обновлений и последующего отката.
Также, лучше ему объяснить чтобы он просто отключал компьютер, а не нажимал «обновить и завершить работу» иначе установятся вообще все обновления и логика работы будет нарушена.
Данный сценарий прошел обкатку на win 8.1; 2012R2; win 7 x64
дополнение 2016.08.03:
если у вас есть известное точно плохое обновление запишите его вручную в файлик \BadUpdates\BadUpdates.txt в виде KB121212 при работе скрипт будет автоматический выбрасывать это обновление из вариантов на установку
Комментарии (16)
kvaps
02.08.2016 09:22Интересный получился у вас скрипт. Надо будет обязательно попробовать на досуге, спасибо. Правильно ли я понимаю, что перед его установкой нужно отключить автоматическое обновление?
И было бы замечательно, если бы вы выложили исходники на GitHub
pak-nikolai
02.08.2016 09:31спасибо. Это не скрипт даже, это скорее программа написаная на скриптовом языке.
в ней 3 конечных автомата, 2 ватч дога, и простенький граф переходов. Я осознаю что для среднестатистического админа это довольно крышесносно, но для прогера ничего сложного
автоматическое обновление отключать не нужно. можете оставить настройку 2 или 3. если поставите 4 (автоматическая установка) то не будет от него смысла.
При тесте на win7 был получен странный глюк, когда search обновлений зависал если сервер обновлений был microsoft. с локальным wsus работает точно проверено многократно.
можете развернуть виртуальную машину, закинуть его туда и поставить разрешение на ребут
у нее не хватает функции когда она скрывает обновления которые не будет ставить в текущей сессии работы, тогда будет гарантированно правильный ребут и выключение и в отработку отправятся только эти обновления. Данное дописывать не стал просто в виду того что мне в продакшене не пригодилось, както само заработало в таком виде.
kvaps
02.08.2016 09:46Проблема с обновлениями в Windows действительно существует, никогда не понимал почему Microsoft не может сделать уже нормальную систему обновлений?! И есть ведь на кого равняться: Почему в Linux и других unix like системах при использовании пакетных менеджеров такой проблемы просто никогда не было и не наблюдается?
Система обновлений Windows, та что существует на данный момент, тихий ужас: она отжирает огромное количество ресурсов просто ради установки новых обновлений. Из-за этого во время установки обновлений, на большинстве систем наблюдаются адские тормоза, идет большая нагрузка на ЦП и Жесткий диск. Пользователи сидят, иногда больше получаса наблюдая экран установки обновлений. Почти всегда это сильно тормозит рабочий процесс. Я уж не говорю что папка Windows некоторых систем после обновления может спокойно достигать аж 100 гигов, или про эти неотключаемые обновления до Windows 10...
Забавно, но иногда действительно создается такое впечатление, что Microsoft через свои обновления бесплатно использует вычислительные мощности вашей машины, для создания огромного и децентрализованного пула ресурсов для своего Azure (шутка конечно)
pak-nikolai
02.08.2016 10:05Согласен. Ну это что критиковать, думаю логика есть какаято. У МС TrustedInstaller запускается при старте, чем больше обновлений тем дольше идет перебор вариантов, фактически чем больше времени тем тормознутее работают старые версии windows т.к. у них экспоненциально растет количество переборов.
Полный набор обновлений по сегодня на офис 2013 около 6,43Гб
Полный набор обновлений по сегодня на офис 2010 около 6,43Гб
Полный набор обновлений по сегодня на вин7\2008R2 около 2,0 Гб
Полный набор обновлений по сегодня на вин8.1\2012R2 около 2,64 Гб (плюс еще пак с 8 до 8.1 еще гига 1,5)
тоесть если вы устанавливаете офис 2010 размером 2 Гб, на него сверху ляжет патчей еще 6,5Гб
Но есть и плюсы:
- обновления выкатывают регулярно
- из тысяч обновлений только 1-2 в год может вызвать проблемы
- при всей сложности системы она досточно стабильна, юзабельна, и стандартизирована
- офисные продукты очень быстрые (либер не может сравнится по скорости с эксель даже близко)
- огромное количество софта
zedalert
02.08.2016 10:07Мне на домашнем ноуте так и не удалось побороть пожирание ресурсов при установке/проверке обновлений, пришлось полностью их отключить, иначе почти всё время svhosts -netsvc что-то делает, или TrustedInstaller лопатит весь диск. В общем: чем дальше в дебри администрирования Windows залезаешь, тем меньше он нравится.
pak-nikolai
02.08.2016 10:13вроде на вин 8.1 систему обновлений переделали. она поумнее и пошустрее стала работать. можно выставить задержку на запуск TrustedInstaller.
если у вас есть выход в инет жить совсем без обнов плохо, хотя бы секурити патчи надо ставить
AlexeevEugene
02.08.2016 10:08+1Вроде как Linux, в обновлениях целое ядро подсовывает вместо заплаток безопасности. А Windows, приходится пересчитывать все зависимости.
pak-nikolai
02.08.2016 10:11+1Да и именно поэтому так долго. Чем больше апдейтов тем больше вариантов.
на win7 х64 есть 231 апдейт, установка на машине с 1 процессором и 3 Гб памяти займет 2 (!) дня. А если он еще не может поставить какойто 1 и делает потом циклический откат 3 раза, то его поиск правращается в маленький ад.ErshoFF
02.08.2016 10:49Для свежеустановленных ОС можно попробовать что-то типа этого (не ради рекламы, никак с ресурсом не связан)
_http://forum.oszone.net/thread-257198-75.html
Времени занимает меньше чем два дня.
navion
02.08.2016 14:33На виртуалке полное обновление занимает 6-8 часов, а с KB3125574 устанавливает ~70 патчей за 2-4 часа.
Чтобы избежать этого Windows 10 периодически обновляется установкой новой сборки ОС вместо отдельных патчей (особенно заметно на Insider Preview), а Office 2016 идёт в виде App-V для большнства клиентов.
JTProg
08.08.2016 01:12ИМХО довольно интересный вариант для небольшого парка в 80-100 машин. Прямо взял на заметку! Автор, спасибо тебе!
tsklab
Всё выше приведённое совершенно не нужно. Обновления, распространяемые через WSUS, нужно проверять до одобрения.
pak-nikolai
Принцип красив и правилен, но по разному бывает.
штука достаточно сложная, родилась из жизни. В этом году когда 3 раза подряд с начала года вышли обновы на битлокер, BCD и на системные либы которые вызвали в нашей инфраструктуре сбой. Результат — звонят с отделов ругаются, мы бегаем ножками и отключаем обновления.
Итог — воткнул на такую машину скрипт и забыл о ней, подключаюсь иногда проверяю что происходит, когда он отработает все скидывает список того что не может встать на эту машину. По моему эффект есть, т.к. это уже навсегда.
При командировке в другой филиал, если есть такая же проблема не нужно сидеть и целый день тыцкать 100-150 обнов, также поставил на автоматическую установку и пошел делать чтонибудь другое.
kvaps
WSUS позволяет контролировать установку обновлений на клиентскую Windows не корпоративного выпуска?
pak-nikolai
да. На клиенте выглядит так как будто этого обновления вообще не существует.
Но при администрировании сервера всус это обновление будет лезть при фильтрации обнов во время апрува, и можно случайно его апрувнуть