[Из песочницы] Внутренний сервер обновления Adobe Flash Player

Предыстория


Начальство поставило задачу: нужно поддерживать в актуальном состоянии Flash Player. Масштабы: ~15000 компов, на половине из которых FP действительно нужен.

Казалось бы, в чём проблема — делаем доменную политику, запихиваем MSI пакет и радуемся… Но не тут-то было! Структура компании сильно распределена, т.е. в удалённые точки с каналом ~1мбит на 20 компов пропихнуть политиками даже 15мб уже проблема — утром сотрудники включают компьютер и по полчаса ждут загрузки, пока всё скачается и поставится (или просто отвалится по таймауту). Не говоря уже о том, что компы в подобных офисах имеют неприятное свойство периодически из домена выпадать.

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

Выручил, как обычно, Гугл: оказалось, что можно поднять внутренний сервер обновления и настроить на него встроенное средство обновления FP. При этом на клиенты нужно будет только распространить файл настроек. Подробности под катом.

Настройка сервера обновлений


1. Получаем лицензионное соглашение

Для распространения своего ПО Adobe требует получить лицензионное соглашение. Не будем нарушать условия использования и получим лицензию (благо, это совсем не сложно): FlashPlayer: Adobe Runtimes / Reader Distribution License Agreement. Лицензия выдаётся сроком на год. По истечении можно отправить запрос ещё раз.

2. Поднимаем веб-сервер

Платформа роли не играет, в моём случае вертится N-ным сайтом на IIS под Win2012. Ресурсов оно практически не жрёт даже при том, что уже ~3000 компьютеров настроены на этот сервер.

Настройки сервера:

  • Доступ по портам 80, 443 (http, https соответственно).
    Первый нужен, собственно, для скачивания, по второму FP будет ходить за XML-кой актуальной версии.
  • Валидный сертификат https.
    Я выписывал сертификат на основе корневого корпоративного, который по умолчанию есть на всех машинах.
  • Листинг директорий.
    Не проверял работу без него — в документации просят, я решил сделать как написано.


Подробно останавливаться на настройке сервера не буду.

Для наглядности назовём сервер FlashPlayerUpdate.domain.local.

3. Скачиваем ресурсы и выкладываем на сервер

В корне веб-сервера создаём дерево директорий: /pub/flashplayer/update/current/sau/.

Дерево директорий на моём сервере:

a077f79843ef45188a9a90fb75990240.png

Если вы запросили лицензию на первом шаге, то в ответ должно прийти письмо со ссылкой, откуда скачивать FlashPlayer — проходим по этой самой ссылке. Если не пришло, или не запрашивали, то идём сюда: https://www.adobe.com/products/flashplayer/distribution3.html и скачиваем архив по ссылке "Download Background Update Resources":

8f6aaeacea814764924193afecb9c084.png

Extended Support Release или Public Release
Тут нужно сделать ремарку. На страничке 2 варианта загрузок: стандартный (Public) и Extended Support Release. В моём случае важна стабильность работы и не нужны новые фичи, поэтому был выбран вариант ESR. При этом я добавил себе некоторое количество геморроя: паблик версию можно напрямую выкачивать скриптом с сайта Macromedia. Как выкачивать ESR, я так и не нагуглил, поэтому в моём случае обновление контента на внутреннем сервере происходит в ручном режиме.

В конце статьи приложил 2 скрипта PowerShell: для автоматического обновления (только для стандартной версии; легко портируется на bash), для проверки обновлений и оповещении по e-mail (для любой версии, в т.ч. ESR).


Скачанный архив распаковать в папку /pub/flashplayer/update/current/sau/ на сервере.

4. Распространяем на клиенты файл конфигурации

В зависимости от разрядности системы:

  • 32-bit: C:\Windows\System32\Macromed\Flash\mms.cfg
  • 64-bit: C:\Windows\SysWOW64\Macromed\Flash\mms.cfg


Распространять можно любыми способами. Я использовал сочетание доменной политики и сервера администрирования антивируса (для компов, которые вылетели из домена).

В файле включаем тихое автообновление, прописываем интервал обновлений (в днях), путь к нашему серверу, и на всякий случай логирование, чтобы проще было диагностировать проблемы, если они возникнут:

AutoUpdateDisable=0
SilentAutoUpdateEnable=1
AutoUpdateInterval=2
SilentAutoUpdateServerDomain=FlashPlayerUpdate.domain.local
SilentAutoUpdateVerboseLogging=1


Если всё было сделано верно, то Flash Player на клиентских машинах должен начать обновляться по расписанию (согласно приведённому файлу выше — раз в 2 дня). Обычно сервис обновления Adobe запускается раз в час для проверки условий обновления — в это время Updater должен увидеть файл конфигурации, перенастроить обновления согласно прописанным настройкам и сходить на новый сервер проверить версию.

То есть примерно через час после распространения файла конфигурации можно смотреть логи на сервере на предмет запросов на проверку версии.

Автоматизация


Как классический представитель айтишного братства, я терпеть не могу рутинную ручную работу и просто ну никак не мог не автоматизировать процесс проверки и выкачивания новой версии. Однако, как отмечено выше, пока я не нашёл способа выкачивать версии ESR с сайта Macromedia, потому скриптом только проверяю обновления. Предложения приветствуются.Скрипт для автоматического скачивания обновлений
Только для публичной версии!

Логика работы: скрипт в тупую скачивает файлы обновления напрямую с Macromedia для версий 11,15,16,17,18,19 (если какую-то версию уже убрали с сайта — скрипт просто ругнётся, что не смог скачать и пропустит) и кладёт с заменой на сервер обновления. Никаких проверок версий. На этапе тестирования я использовал этот скрипт: запускал через шедулер на сервере по ночам.

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

Параметры скрипта:

  • *FPRoot — путь к папке sau. Локальный, или сетевой. Естественно, у пользователя, от которого будет запущен скрипт должны быть права на запись в эту папку. Обязательный параметр!
  • FPDownloadRoot — путь на сайте Macromedia. Задан по умолчанию, но можно изменить при необходимости.
  • DownloadProxy — прокси сервер, если используется в компании. Писать полностью: http://proxy.domain.local.
  • ProxyCreds — имя пользователя для авторизации на прокси.
  • UserAgent — для изменения юзерагента, с которым PowerShell пойдёт качать. Например, у нас на прокси ограничение по UserAgent-ам, я хожу с агентом Internet Explorer.


* — обязательный параметр

Пример использования:

powershell.exe -command "& '.\FPUpdater.ps1' -FPRoot '\\FlashPlayerUpdate\pub\flashplayer\update\current\sau' -DownloadProxy 'http://proxy.domain.local' -ProxyCreds 'DOMAIN\UserName' -UserAgent 'Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko'"


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

Код скрипта
#requires -version 3.0
#https://www.microsoft.com/en-us/download/details.aspx?id=48729
param(
    [parameter(mandatory=$true)]
    [string]$FPRoot,
    [string]$FPDownloadRoot = 'fpdownload2.macromedia.com/pub/flashplayer/update/current/sau',
    [string]$DownloadProxy,
    [string]$ProxyCreds,
    [String]$UserAgent
)

$WebrequestParams=@{}

if ($DownloadProxy) {
    $WebrequestParams['Proxy']=$DownloadProxy
}

if ($ProxyCreds) {
    $cred = get-credential $ProxyCreds # this prompts for credentials
    $WebrequestParams['ProxyCredential']=$cred
}
elseif ($DownloadProxy -and !$ProxyCreds) {
    $WebrequestParams['ProxyUseDefaultCredentials']=$true
}

if ($UserAgent) {
    $WebrequestParams['UserAgent']=$UserAgent
}

# Make invoke-webrequest to ignore cert check
add-type @"
    using System.Net;
    using System.Security.Cryptography.X509Certificates;
    public class TrustAllCertsPolicy : ICertificatePolicy {
        public bool CheckValidationResult(
            ServicePoint srvPoint, X509Certificate certificate,
            WebRequest request, int certificateProblem) {
            return true;
        }
    }
"@
[System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCertsPolicy

if ( !(Test-Path $FPRoot) ) {
    try{
        New-Item -ItemType directory -Path $FPRoot -Force -ErrorAction Stop | Out-Null
    }
    catch{
        Write-Host -f Red "Unable to create download path [$FPRoot]! Error: [$($_.Exception.Message)]"
        break
    }
}

try {
    Invoke-WebRequest -Uri "https://$FPDownloadRoot/currentmajor.xml" -OutFile "$FPRoot\currentmajor.xml" -ErrorAction Stop @WebrequestParams
}
catch {
    Write-Host -f Red "Failed to download FP current version XML! Error: [$($_.Exception.Message)]."
    break
}

11,15,16,17,18,19 | ForEach-Object {
    
    $DestXML = "$FPRoot\$_\xml"
    $DestInstall = "$FPRoot\$_\install"

    if (!(Test-Path $DestXML)) {
        New-Item -ItemType directory -Path $DestXML -Force | Out-Null
    }
    if (!(Test-Path $DestInstall)) {
        New-Item -ItemType directory -Path $DestInstall -Force | Out-Null
    }
    
    $sourceXML = "https://$FPDownloadRoot/$_/xml/version.xml"
    $sourceWinAX = "http://$FPDownloadRoot/$_/install/install_all_win_ax_sgn.z"
    $sourceWinPL = "http://$FPDownloadRoot/$_/install/install_all_win_pl_sgn.z"
    $sourceWin64AX = "http://$FPDownloadRoot/$_/install/install_all_win_64_ax_sgn.z"
    $sourceWin64PL = "http://$FPDownloadRoot/$_/install/install_all_win_64_pl_sgn.z"
    $SourceInstall = $sourceWinAX,$sourceWinPL,$sourceWin64AX,$sourceWin64PL
    
    Write-Host -f Yellow "`nDownloading files for FlashPlayer $_... " -NoNewline

    try {
    Invoke-WebRequest -Uri $sourceXML -OutFile "$DestXML\$($sourceXML.Split('/')[-1])" -ErrorAction Stop @WebrequestParams

        ForEach ($URI in $SourceInstall) {
            Invoke-WebRequest -Uri $URI -OutFile "$DestInstall\$($URI.split('/')[-1])" -ErrorAction Stop @WebrequestParams
        }
        Write-Host -f Green 'OK'
    }
    catch {
        Write-Host -f Red 'FAIL!'
        Write-Host -f Red $_.Exception.Message
        Remove-Item -Path "$((Get-Item -Path $DestXML).parent.FullName)"
    }
}



Примечание:

Если указан параметр DownloadProxy, но не указан ProxyCreds , то скрипт будет пытаться использовать сквозную авторизацию (параметр ProxyUseDefaultCredentials). Если это не нужно, удалите 20-22 строки из скрипта:

elseif ($DownloadProxy -and !$ProxyCreds) {
    $WebrequestParams['ProxyUseDefaultCredentials']=$true
}


Скрипт для проверки обновления и оповещения по e-mail
Проверка обновлений идёт по стандартной версии, но поскольку обновляются они одновременно (security-фиксы никто не отменял), то прокатит и для ESR. Для работы скрипта нужно создать в корне веб-сервера (рядом с папкой pub) файл CurrentPublic, в который вписать текущую публичную версию для ActiveX (для проверки используется именно версия ActiveX).

Логика работы: скрипт сравнивает версию, полученную из файла CurrentPublic с вашего сервера с версией на сервере Macromedia. Версию на сервере смотрит по логике автообновлялки: сначала ищет в XML текущий мажорный билд, идёт в папку с мажорным и там смотрит полный билд.

Параметры скрипта:

  • *FPIntServerRoot — Адрес нашего сервера. Например: FlashPlayerUpdate.domain.local
  • FPDownloadRoot — путь на сайте macromedia. Задан по умолчанию, но можно изменить при необходимости.
  • DownloadProxy — прокси сервер, если используется в компании. Писать полностью: http://proxy.domain.local.
  • ProxyCreds — имя пользователя для авторизации на прокси.
  • UserAgent — для изменения юзерагента, с которым PowerShell пойдёт качать. Например, у нас на прокси ограничение по UserAgent-ам, я хожу с агентом Internet Explorer.
  • *MailTo — e-mail адреса, на которые будут приходить уведомления.
  • *MailFrom — от кого будут приходить уведомления. Например: FPUpdater@company.com
  • SmtpServer — smtp-сервер, через который будет производиться отправка сообщения.


* — обязательный параметр

Пример использования:

powershell.exe -command "& '.\FPUpdater.ps1' -FPIntServerRoot 'FlashPlayerUpdate.domain.local' -DownloadProxy 'http://proxy.domain.local' -ProxyCreds 'DOMAIN\UserName' -UserAgent 'Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko' -MailTo 'admin@company.com' -MailFrom 'FPUpdater@company.com' -SmtpServer 'smtp2.company.com' "


Код скрипта
UPD (06/11/15 12:00): поправил код скрипта — исправлена логика сравнения версии. При ошибке получения версии будет отправлено сообщение об ошибке на указанный e-mail.
#requires -version 3.0
#https://www.microsoft.com/en-us/download/details.aspx?id=48729

param(
    #FPLAYER
    [parameter(mandatory=$true)]
    [string]$FPIntServerRoot,
    [string]$FPDownloadRoot = 'fpdownload2.macromedia.com/pub/flashplayer/update/current/sau',

    #WEBREQUEST
    [string]$DownloadProxy,
    [string]$ProxyCreds,
    [String]$UserAgent,

    #MAIL
    [parameter(mandatory=$true)]
    [string[]]$MailTo,
    [parameter(mandatory=$true)]
    [string]$MailFrom,
    [string]$SmtpServer
)

#region WEBREQUEST PARAMS
$WebrequestParams = @{}

if ($DownloadProxy) {
    $WebrequestParams['Proxy']=$DownloadProxy
}

if ($ProxyCreds) {
    $cred = Get-Credential $ProxyCreds
    $WebrequestParams['ProxyCredential']=$cred
}
elseif ($DownloadProxy -and !$ProxyCreds) {
    $WebrequestParams['ProxyUseDefaultCredentials']=$true
}

if ($UserAgent) {
    $WebrequestParams['UserAgent']=$UserAgent
}
#endregion

#region MAIL PARAMS
$MailParams = @{}
$MailParams['To'] = $MailTo
$MailParams['From'] = $MailFrom
if ($SmtpServer) {
    $MailParams['SmtpServer'] = $SmtpServer
}
#endregion

#region GET INTERNAL SERVER VERSION
Write-Host -f Gray "Getting INTERNAL server version..." -NoNewline
try{
    $FPServerVersion = (Invoke-WebRequest -Uri "http://$FPIntServerRoot/CurrentPublic").content

    Write-Host -f Green "$FPServerVersion"
}
catch {
    Write-Host -f Red 'FAIL!'
    Write-Host -f Red "Can't verify INTERNAL server version! Error: [$($_.Exception.Message)]."
    break
}
#endregion

#region GET MACROMEDIA VERSION
# Make invoke-webrequest to ignore cert check
add-type @"
    using System.Net;
    using System.Security.Cryptography.X509Certificates;
    public class TrustAllCertsPolicy : ICertificatePolicy {
        public bool CheckValidationResult(
            ServicePoint srvPoint, X509Certificate certificate,
            WebRequest request, int certificateProblem) {
            return true;
        }
    }
"@
[System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCertsPolicy

Write-Host -f Gray "Getting MACROMEDIA server version..." -NoNewline
try {
    ## MAJOR
    $VersionMajor = $TempData = $null
    $TempData = Invoke-WebRequest -Uri "https://$FPDownloadRoot/currentmajor.xml" -ErrorAction Stop @WebrequestParams
    $VersionMajor = ($TempData.Content).split('"')[1]

    ## FULL
    $XMLFull = "https://$FPDownloadRoot/$VersionMajor/xml/version.xml"
    $TempXML = [System.IO.Path]::GetTempFileName()
    Invoke-WebRequest -Uri $XMLFull -OutFile $TempXML -ErrorAction Stop @WebrequestParams
    [xml]$Full = Get-Content -Path $TempXML -ErrorAction Stop
    $FullAX = $Full.version.ActiveX
    $FullVersion = "$($FullAX.major).$($FullAX.minor).$($FullAX.buildMajor).$($FullAX.buildMinor)"

    Write-Host -f Green "$FullVersion"
    Remove-Item $TempXML -Force -ErrorAction SilentlyContinue
}
catch {
    Write-Host -f Red 'FAIL!'
    Write-Host -f Red "Can't verify MACROMEDIA server version! Error: [$($_.Exception.Message)]."
    break
}
#endregion

#region COMPARE VERSIONS
if ([version]::TryParse('1.2.3.4',[ref]$FPServerVersion)) {
    $FPServerVersionParsed = [version]::Parse($FPServerVersion)
}
else {
    $Message = "Can't parse INTERNAL server version: [$FPServerVersion]!"
    Write-Host -f Red $Message
    Send-MailMessage -Body $Message -Subject 'FLASH PLAYER UPDATE ERROR' -Encoding UTF8 -BodyAsHtml @MailParams
    break
}
if ([version]::TryParse('1.2.3.4',[ref]$FullVersion)) {
    $FPAdobeVersionParsed = [version]::Parse($FullVersion)
}
else {
    $Message = "Can't parse ADOBE server version: [$FullVersion]!"
    Write-Host -f Red $Message
    Send-MailMessage -Body $Message -Subject 'FLASH PLAYER UPDATE ERROR' -Encoding UTF8 -BodyAsHtml @MailParams
    break
}

if ($FPAdobeVersionParsed -gt $FPServerVersionParsed) {
    $Message = "Update is available!`n`tAdobe version:`t[$FullVersion]`n`tServer version:`t[$FPServerVersion]"
    $HTMLMessage = "<b>Update is available!</b><br /><br />
    Adobe version: <b>$FullVersion</b><br />
    Server version: <b>$FPServerVersion</b><br /><br />
    <a href='https://www.adobe.com/products/flashplayer/distribution3.html'>Download link</a>"
    
    Write-Host -f Green $Message
    Send-MailMessage -Body $HTMLMessage -Subject 'FLASH PLAYER UPDATE' -Encoding UTF8 -BodyAsHtml @MailParams
}
else {
    Write-Host -f Gray "Flash Player is Up-To-Date."
}
#endregion



Использованные ресурсы


© Habrahabr.ru