Выкачивание драйверов с сайта HP

usbVacuum Не так давно мне понадобилось скачать драйверы и утилиты для множества моделей компьютеров HP. Учитывая что обычно для каждой модели приводится несколько десятков драйверов, скачивать их вручную и раскладывать по папкам мне показалось слишком долго и нудно. Кроме того я не хотел устанавливать какие либо менеджеры закачек которые помогли бы просто выдрать все ссылки со страницы, да они и не смогли бы правильно разложить файлы по категориям… Поэтому я решил написать простенький сценарий, который бы разбирал html страницы с драйверами для модели, понимал бы к какой категории относится драйвер или утилита, скачивал бы их, и раскладывал бы в соответствующие папки (при необходимости создавая эти папки самостоятельно).

На вход сценарий принимает адрес страницы с которой необходимо скачать драйверы, например такой http://h20000.www2.hp.com/bizsupport/TechSupport/SoftwareIndex.jsp?lang=ru&cc=ru&prodNameId=462858&prodTypeId=321957&prodSeriesId=462857&swLang=33&taskId=135&swEnvOID=1093

Если вы приглядитесь к кнопкам “Загрузить” на этой странице, то вы увидите что в них содержится ftp ссылка на файл, так что для закачки нам не подойдет простой System.Net.WebClient или BitsTransfer а понадобится System.Net.FtpWebRequest. Он несколько сложнее в использовании, но я нашел хороший пример функции для скачивания файлов по FTP здесь, и включил его в тело сценария. Разумеется можно просто поместить его в свой профиль, но я посчитал что лучше сделать сценарий самодостаточным и независимым от окружения.

Кроме страницы для скачивания, сценарию можно указать еще параметры Destination (папка куда скачивать файлы, по умолчанию текущий каталог) и ключ Plain (если он указан, то файлы сваливаются в одну кучу, без раскладывания по папкам).

Ну и наконец Get-HPDrivers.ps1:

param ($Url, $Destination=$Pwd, [switch]$Plain)            
            
#Функция Get-FTPFile взята отсюда - http://powershell.com/cs/media/p/804.aspx            
function Get-FTPFile ($Source,$Target,$UserName,$Password)              
{              
              
 # Create a FTPWebRequest object to handle the connection to the ftp server              
 $ftprequest = [System.Net.FtpWebRequest]::create($Source)              
               
 # set the request's network credentials for an authenticated connection              
 $ftprequest.Credentials =              
     New-Object System.Net.NetworkCredential($username,$password)              
               
 $ftprequest.Method = [System.Net.WebRequestMethods+Ftp]::DownloadFile              
 $ftprequest.UseBinary = $true              
 $ftprequest.KeepAlive = $false              
               
 # send the ftp request to the server              
 $ftpresponse = $ftprequest.GetResponse()              
               
 # get a download stream from the server response              
 $responsestream = $ftpresponse.GetResponseStream()              
            
 # create the target file on the local system and the download buffer              
 $targetfile = New-Object IO.FileStream ($Target,[IO.FileMode]::Create)              
 [byte[]]$readbuffer = New-Object byte[] 1024              
               
 # loop through the download stream and send the data to the target file              
 do{              
     $readlength = $responsestream.Read($readbuffer,0,1024)              
     $targetfile.Write($readbuffer,0,$readlength)              
 }              
 while ($readlength -ne 0)              
               
 $targetfile.close()              
}              
            
#Выводим на экран текущее выполняемое действие:            
Write-Progress "Receiving metadata" "Downloading"            
#Создаем объект WebClient для выкачивания текста страницы            
$wc = new-object System.Net.WebClient            
#Устанавливаем для него кодировку            
$wc.Encoding = [System.Text.Encoding]::GetEncoding("UTF-8")            
#Указываем что надо использовать учетные данные той учетной записи             
#под которой запущен сценарий            
$wc.UseDefaultCredentials = $true            
#Загружаем страницу в переменную $Page            
$Page = $wc.DownloadString($url)            
            
#Обновляем статус            
Write-Progress "Receiving metadata" "Parsing HTML"            
$Meta = $(            
    #С помощью конструкции switch разбираем текст страницы используя регулярные выражения            
    #Так как разные элементы (категория, имя, ссылка) находятся на разных строках, я использовал            
    #переменные $Category и $Name для сохранения их значений            
 switch -regex ($page.split("`n"))            
 {            
        #Это категория, устанавливаем переменную $Category            
  '<a name="\d+"></a><b>([^<]+)</b>' {$Category = $matches[1]}            
        #Это имя пакета, помещаем его в $Name            
  'SoftwareDescription.jsp[^"]+">([^<]+)' {$Name = $matches[1]}            
        #Это уже ссылка            
  'targetPage=([^&]+exe)' {            
            #Размаскировываем ссылку и помещаем в переменную $Link            
   $Link = [system.uri]::UnescapeDataString($matches[1])            
            #Создаем новый объект со свойствами Name, Category и Link.            
   New-Object PSObject -Property @{Name=$Name; Category=$Category; Link = $Link}            
  }            
 }            
)            
#Страница разобрана, полученные объекты помещены в массив $Meta            
            
#Получаем имя для файла со списком распарсенного,             
#он будет лежать для справки в корне целевой папки            
$FileName = Join-Path $Destination "Index.csv"            
#Отображаем состояние            
Write-Progress "Exporting Metadata" $FileName            
#Экспортируем $Meta в файл            
$Meta | Export-Csv $FileName -Encoding UTF8 -NoTypeInformation -Delimiter ","            
            
#Для каждого объекта в массиве $Meta...            
foreach ($Driver in $Meta)            
{            
    #Выводим статус и имя текущего драйвера/программы            
 Write-Progress "Downloading files" $Driver.Name            
    #Если не указан ключ $Plain то...            
 if (!$Plain)            
 {            
        #Определяем папки для категории и файла, и при необходимости создаем их            
  $CategoryFolder = Join-Path $Destination $Driver.Category            
  if (-not (test-path $CategoryFolder)) {md $CategoryFolder | out-null}            
  $DriverFolder = Join-Path $CategoryFolder $Driver.Name.replace("/","")            
  if (-not (test-path $DriverFolder)) {md $DriverFolder | out-null}            
        #Устанавливаем имя под которым будет сохранен файл в соответствующей папке            
  $FileName = Join-Path $DriverFolder ($Driver.Link -replace '^.+/')            
 }            
 Else            
 {            
        #Устанавливаем имя под которым будет сохранен файл в корне папки            
  $FileName = Join-Path $Destination ($Driver.Link -replace '^.+/')            
 }            
    #Если файл еще не существует...            
 if (-not (test-path $FileName)) {            
        #Вызываем функцию Get-FTPFile для его скачивания            
  Get-FTPFile -Source $Driver.Link -Target $Filename -User "Anonymous" -Pass "anon@hp.com"            
 } else {            
        #Иначе выдаем предупреждение, и продолжаем работу            
  Write-Warning "File $FileName already exists, skipping..."            
 }            
}

Так как моделей у меня было много, я не стал для каждой ссылки вызывать сценарий, а просто сохранил ссылки в текстовый файл urls.txt и запустил следующую команду:

Get-Content .\urls.txt | foreach { C:\Root\Get-HPDrivers.ps1 -Url $_ }

Ну и вот как то так оно выглядит в работе🙂

hpdownload

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

комментариев 11 to “Выкачивание драйверов с сайта HP”

  1. Aleksei Says:

    Спасибо! Часто сталкиваюсь с аналогичной необходимостью скачки драйверов от HP.

    • Xaegr Says:

      You welcome!🙂 Приятно когда мой сценарий пригождается кому нибудь еще🙂

  2. artem Says:

    Пять несущественных недостатков этого скрипта.

    1. Не поддерживает номер версии.
    2. Не поддерживает закачки по HTTP (иногда у HP попадаются и такие).
    3. Не поддерживает «multi-part download» (например, см. «HP BladeSystem Online Firmware Bundle for Windows» тут: http://h20000.www2.hp.com/bizsupport/TechSupport/SoftwareIndex.jsp?lang=en&cc=us&prodNameId=3288156&prodTypeId=3709945&prodSeriesId=1842750&swLang=8&taskId=135&swEnvOID=4064
    4. Не поддерживает внешние ссылки «Obtain software» (например, см. «Firmware CD Supplemental Update / Online ROM Flash Component for Linux — HP Integrated Lights-Out 2» там же). В таком случае на диске оказывается файл нулевой длины с именем вроде «v57779&files=CP012024.scexe».
    5. Лично у меня с какого-то момента скрипт перестал качать и впал в бесконечный цикл (крутится уже всю ночь) с выдачей вот такого сообщения:

    At \Get-HPDrivers.ps1:30 char:40
    + $readlength = $responsestream.Read <<<< ($readbuffer,0,1024)
    + CategoryInfo : InvalidOperation: (Read:String) [], RuntimeException
    + FullyQualifiedErrorId : InvokeMethodOnNull

    • Xaegr Says:

      Артем, это всё же скрипт написанный под конкретную задачу (не уточнил в посте, но это была выкачка драйверов под десктопные модели), так что неудивительно что с серверными страницами возникли такие сложности. Впрочем это хороший повод для тебя улучшить скрипт и поправить под свои задачи😉
      Что касается 5. — было такое тоже разок, не отловил к сожалению… После прерывания Ctrl+C и перезапуска — нормально продолжил закачку. Подозреваю что в функции FTP не совсем надежно записано условие While…

  3. bass Says:

    А как, вобщем, реализовать скрипт — аналог curl в php?
    Интересен вариант автовхода на сайт (передача переменных методом POST) с последующим парсингом содержимого.
    И второй вопрос, с Вашего позволения,- риторический. Как я понял, использование .NET обьектов очень эффективно, но как понять — когда их уже можно использовать и какие объекты существуют, чтобы не плодить километры лишнего кода? Спасибо.

    • Xaegr Says:

      Я не знаком с PHP, но уверен что тамошний curl на самом деле аналог утилиты cURL, которая доступна для множества ОС, в т.ч. Windows — http://curl.haxx.se/download.html
      Для использования POST в WebClient служит метод UploadString — http://msdn.microsoft.com/ru-ru/library/ms144236(v=VS.90).aspx

      Второй ответ…🙂
      Использовать .NET объекты можно всегда. Более того вы их всегда и используете в PowerShell — всё с чем вы работаете объекты .NET (иногда являющиеся обертками для объектов WMI, COM и т.п.).
      Что касается как их можно узнать… Ну можно наверное почитать каких нибудь книжек по разработке под .NET, или справочников, но мне хватает поиска по интернету и онлайн базы MSDN (для получения справки по уже обнаруженному объекту .NET или его свойствам/методам). Иногда можно использовать например PowerTab или альтернативными оболочками для PowerShell которые предоставляют автодополнение .NET классов.

  4. Илья Сазонов Says:

    Я как самый ленивый пошел другим путем и стал использовать в последнее время HP Universal Print Driver (UPD). Раньше UPD был тормозной и глюковатый, но в последнее время вполне сносно работает. Много принтеров — один драйвер! Это очень удобно в случае сервера печати, к которому подключены десятки принтеров разных моделей: хорошо известно насколько сильно не любят драйверы HP разных моделей друг друга!

  5. iggy Says:

    Такой вопрос возник: а как «правильнее» получать результаты работы функции/фильтра?
    1. через перехват вывода в консоль
    2. через переменную, сделав её global

    • Xaegr Says:

      Ни то ни другое. Функция должна выдавать объекты, и их уже можно либо передавать в другую функцию/командлет по конвейеру, либо захватывать в переменную, либо выводить на консоль.
      function sum ($a, $b) {$result = $a + $b; write-output $result}
      $s = sum 1 2 #присваиваем результат переменной
      sum 1 2 | set-content file.txt #перенаправляем в файл
      sum 1 2 #выводим на экран

      Командлет write-output подразумевается по умолчанию, то есть если данные ничему не присваиваются и не передаются дальше, то они передаются на вывод. Так что можно написать функцию так:
      function sum ($a, $b) {$result = $a + $b; $result}
      или даже так:
      function sum ($a, $b) {$a + $b}
      эффект будет тем же.
      А использовать вывод в консоль (write-host) стоит только для сообщений человеку работающему со скриптом. Никакие данные таким образом выводить не следует. Ну и глобальных переменных тоже хорошо бы избегать по возможности.

      • iggy Says:

        Спасибо, а вот еще вопрос:
        if ($config.demomode.equals(«on»)) {$relocwhatif=»-WhatIf»} Else {$relocwhatif=»$null»}
        copy -Path $from -Destination $where -Recurse -Force $relocwhatif;
        есть вот такая команда, с переменными from и where проблем нет, а я еще хочу задать параметр команды whatif через переменную, но при выполнении он выдаёт
        A positional parameter cannot be found that accepts argument ».
        At :line:80 char:6
        + copy <<<< -Path $from -Destination $where -Recurse -Force $relocwhatif;
        если переменная будет пустой. есть ли возможность задать параметры команд через переменные? Не хочу городить еще один цикл.

  6. Xaegr Says:

    -whatif:$var


Обсуждение закрыто.

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