Автоматизация установки обновлений на клиентскую машину с отсевом ошибочных обновлений

в 16:53, , рубрики: powershell, windows update, WSUS, системное администрирование

Раз в год выходит обновление ломающее привычную работу. Windows пытается поставить его 3 раза, и если 3 раза был откат то она загружается без установки. Пользователи с утра начинают звонить. Если ничего не предпринять то на следующее утро ситуация повторится.

  • На WSUS сервере нет смысла перемещать его в Unapproved т.к. патч уже скачан на клиенскую машину и находится в папке SoftwareDistribution, надо удалять вручную
  • глючных патчей может быть много
  • через пол годика админ может по запарке его снова одобрить
  • если одобряем раз в полгода-год, отсев плохих от хороших займет до 1 дня работы на 1 машину (класс машин)
  • обновления одной конфигурации могут не подходить для другой конфигурации даже если ОС одна
  • состояние обновления скрытое будет сброшено если удалить SoftwareDistribution
  • SoftwareDistribution надо скидывать если глюкнула база, слишком долго идет поиск, а лучше делать это периодически

Это все неудобно, поэтому админ почти всегда сделает одно и то же — он отключит обновления на проблемной машине. А через годик или два он начнет накатывать обновления и столконется с этой же проблемой.

Т.к. глюк обновлений вызывает аварийный режим, жрет неимоверное количество времени персонала на абсолютно бессмысленную фигню, и с целью преехода к умной инфраструктуре было решено попробовать автоматизировать этот процесс. Попутно был получен скрипт который может установить все обновления на свежеустановленую машину в автоматическом режиме.

Сразу оговорюсь — данный скрипт не умеет ремонтировать систему которая не загружается вообще, он умеет сам ставить обновления, в автоматическом режиме выкидывать обновы вызывающих циклическую перезагрузку, имеет возможность вручную добавить плохие обноления в список и сигнализировать админу о возникших неполадках с установкой.

Как это все запустить, быстрый старт

исходники к статье одним файлом
0. Разрешите исполнение скриптов в вашей системе. Set-ExecitionPolicy Unrestricted или подпишите скрипты вашим сертификатом.
1. скопируйте скрипт WUErrorreporting на диск контроллируемой машины и добавьте его в планировщик задач например 12:30 дня. Если кто не знает как создавать задания по расписанию жмите сюда

WUErrorreporting.ps1

<#
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 "KBd+"
    $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. скопировать скрипт сброса конфигурации на нужную машину и запустить его. Он создаст каталог с начальными настройками, если каталог есть сбросит их в начальное состояние.

Rearm_Install-ClientUpdate.ps1
<#
сбрасывает переменные автоматов для первого использования
#>

# путь до директории где лежат рабочие файлы модуля
$Path = Split-Path ($MyInvocation.MyCommand.Path) -Parent

if ((Test-Path -Path "$PathBadUpdates") -eq $false)
{
    New-Item -Path "$PathBadUpdates" -ItemType Directory -Force
}

# последние установленные обновления
$LastInstalledUpdatesFile = "$PathBadUpdatesLastInstalledUpdates.txt"
# список точно плохих обновлений
$BadUpdatesFile = "$PathBadUpdatesBadUpdates.txt"
# FastInstaller
$KAFUFile = "$PathBadUpdatesKAFU.txt"
# Скорость установки обновлений за раз
$KAFUDeltaFile = "$PathBadUpdatesKAFUDelta.txt"
# Fastinstaller watchdog
$KAFUWatchDogFile = "$PathBadUpdatesKAFUWatchDog.txt"
# Fastinstaller update installer watchdog
$KAFUWatchDog2File = "$PathBadUpdatesKAFUWatchDog2.txt"
# SlowInstaller
$KASUFile = "$PathBadUpdatesKASU.txt"
# файл лог
$WorkLogFile = "$PathBadUpdateslog.txt"
# общий автомат
$KAFile = "$PathBadUpdatesKA.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кб), чтобы не копипастить скачайте его отсюда
Автоматизация установки обновлений на клиентскую машину с отсевом ошибочных обновлений - 1

Install-ClientUpdate.ps1

<#
2016.06.14
ver 2

устанавливает обновления на клиентских машинах пытаясь определить обновления вызывающие
тормоза при загрузке. Делалось для снижения стресса на отдел системных администраторов.

может
    устнававливать быстро много обновлений пачками (FastUpdate) при быстром проходе ставит сначала по 30%
        за раз, если не получилось то по 25%, если не получилось то по 5 за раз.
    ставить по одному обновленияю, и после прохода по всем те что не встали добавит в список плохих,
        если добавит в список плохих то при следующей работе эти обновления будут игнорироваться не зависимо
        от того одобрены они на вышестоящем сервере или нет
    сбрасывает настройки агента всус на машине на каждом запуске, позволяет сбросить глюки, или кривые апдейты

для использования как робота обновлений
1. выберите каталог куда запишете сценарий
2. запишите в файл "BadUpdatesKA.txt" число больше 9, например 11
3. создайте в планировщике заданий от админа задание с правами система
    триггер - запуск системы, подождать 6 часов
    действие - запуск программы powershell.exe -file "ваш путь до сценарияInstall-ClientUpdate.ps1"
4. разрешите выполнение сценариев в вашей среде
5. пропишите в сценарии ваши параметры
    установите $RebootEnabled = $False
    на какие адреса посылать оповещение о плохих обновлениях и нужно ли вообще отправлять


для автоматической установки большого количества обновлений
1. выберите каталог куда запишете сценарий
2. запишите в файл "BadUpdatesKA.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 = "$PathBadUpdatesLastInstalledUpdates.txt"
# список точно плохих обновлений
$BadUpdatesFile = "$PathBadUpdatesBadUpdates.txt"
# FastInstaller
$KAFUFile = "$PathBadUpdatesKAFU.txt"
# Скорость установки обновлений за раз
$KAFUDeltaFile = "$PathBadUpdatesKAFUDelta.txt"
# Fastinstaller watchdog
$KAFUWatchDogFile = "$PathBadUpdatesKAFUWatchDog.txt"
# Fastinstaller update installer watchdog
$KAFUWatchDog2File = "$PathBadUpdatesKAFUWatchDog2.txt"
# SlowInstaller
$KASUFile = "$PathBadUpdatesKASU.txt"
# файл лог
$WorkLogFile = "$PathBadUpdateslog.txt"
# общий автомат
$KAFile = "$PathBadUpdatesKA.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 "KBd+"
                    $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 "KBd+"
                    $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 "KBd+"
                    $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 "KBd+"
                $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_TasksPSWindowsUpdate"


[Console]::OutputEncoding = [System.Text.Encoding]::GetEncoding("cp866")

#>

5. создать задание в планировщике с правами SYSTEM на запуск 1 раз в день. Известная проблема — установка обновлений может быть инициирована только под правами SYSTEM. (кстати можете этот способ применять для запуска активаторов и всего что должно работать с наивысшей привелегией)
Автоматизация установки обновлений на клиентскую машину с отсевом ошибочных обновлений - 2

хорошо себя показала задержка после старта 6 часов
Автоматизация установки обновлений на клиентскую машину с отсевом ошибочных обновлений - 3

Все, сценарий должен работать.

Первые 3 раза его можно запустить вручную для проверки. Запустите задание из планировщика и проверьте содержимое файла BadUpdateslog.txt он должен содержать лог работы сценария. Если он пуст проверьте разрешена ли политика исполнения скриптов, доступен ли сервер обновлений, попробуйте вручную поискать обновления.

Если обновлений будет больше чем 45 (можете менять это значение) сценарий будет пытаться устанавливать обновления пачками. При этом первый раз он попытается поставить по 13 за раз, если ни одна из 3х пачек обновлений не установится, шаг будет изменен на 1/4, если ниодна из пачек не встанет то алгоритм перейдет на установку по 5. Данный алгоритм родился из жизни, когда встретилось на одной машине 6 неподходящих обновлений равномерно размазаных, при этом устанавливать по одному почти 90 обновлений не вариант (90 дней по одной обновке).

Происходить будет следующее:
1. каждый день WUErrorreporting будет проверять журнал на ошибку 20, если ошибка есть то будет отправлять оповещение на почту с выборкой какое обновление и когда не установилось.
2. каждый день будет происходить сброс папки SoftwareDistribution
3. каждый день скрипт установки обновлений будет проверять наличие обновлений. Если обновления есть то:
4. если их больше чем 45 автомат переключится в состояние быстрых обновлений и «уснет» до перезагрузки

  • на следующий день сбросит автомат быстрых обновлений, вычислено сколько обновлений ставить за раз, при первом проходе 1/3, при втором быстром проходе 1/4, при третьем по 5
  • каждый день пока работает быстрая установка будет получатся список обновлений из него будут удалены обновления которые пробовали ставить ранее и обновления из списка точно плохих обновлений
  • из оставшихся будут выбраны некоторое количество обновлений, они будут отправлены на установку
  • обновления отправленные на установку будут записаны в файл BadUpdatesLastInstalledUpdates.txt и будут исключены из списка при следующем проходе
  • После завершения быстрого прохода произойдет автоматический переход к установке по одному обновлению за раз

5. если их меньше 45 автомат переключится в режим установки обновлений по одному
6. в режиме установки по одному

  • сначала произойдет сброс переменных для установки по одному обновлению
  • будет получен список обновлений без тех что пробовали и точно плохих, если обновлений не осталось то не установленные обновления будут добавлены в список точно плохих и потправлены оповещением на почту. Если еще есть то начнется установка

7. После отработки автомат перейдет в состояние ожидания обновлений

Если в логе видно что не удаляется папка SoftwareDistribution проверьте папку SoftwareDistributionPlugins, какаято программа держит ее и не дает очистить.

Если вам нужно обновить свежеустановленную систему сделайте следующее:

  • установите параметр $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 "KBd+"
    $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 скиньте и мне.

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>
'@

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

Автор: pak-nikolai

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js