Windows 10

Microsoft Update カタログから累積更新プログラムをダウンロードするVBScript

「累積更新プログラム」とは、その名の通りOSを最新の状態に保つための更新プログラムが累積されたものであり、毎月1回以上配信されます。

Windows Updateから更新を適用することもできますが、社内のPC管理をされている方であれば「Microsoft Update カタログ」から必要なファイルだけダウンロードされている方も多いのではないでしょうか。

しかし、このカタログからのダウンロード作業は

“地味に面倒くさい!”

です。
サイト(http://www.catalog.update.microsoft.com/)にアクセスして、KB番号や「累積 更新 Windows-10」といったキーワードで検索して、ヒットしたファイルをクリックしてダウンロードして・・・。
毎日行う作業ではないですが、ホント地味ーーーに手間が掛かる作業です。

そこで今回は、自分が楽するために、Microsoft Update カタログから自動的に累積更新プログラムをダウンロードするスクリプトを作成してみました。

Microsoft Updateカタログの仕様を調べる

ダウンロードする方法として、まずブラウザーの操作が考えられますが、動作が重くなるのであまり使いたくはありません。

できれば更新プログラム(msu)のURLを調べて、ブラウザーを介さずにファイルをダウンロードしたいものです。

そこで、まずは手動でダウンロードする際のMicrosoft Updateカタログの仕様を調べてみることにしました。

「ダウンロード」ボタンを押したときの挙動

Updateカタログの検索結果から「ダウンロード」ボタンをクリックすると、ファイルのダウンロードURLが記載された「DownloadDialog.aspx」ページが開きます。

このときの挙動を「Fiddler」で追ってみました(下記は必要なところだけ抜粋)。

POST http://www.catalog.update.microsoft.com/DownloadDialog.aspx HTTP/1.1
Content-Type: application/x-www-form-urlencoded

updateIDs=%5B%7B%22size%22%3A0%2C%22languages%22%3A%22%22%2C%22uidInfo%22%3A%22c51e9c8d-8757-49bd-a65f-c1fa9ebc2c6e%22%2C%22updateID%22%3A%22c51e9c8d-8757-49bd-a65f-c1fa9ebc2c6e%22%7D%5D&updateIDsBlockedForImport=&wsusApiPresent=&contentImport=&sku=&serverName=&ssl=&portNumber=&version=

POSTされたデータをデコードすると下記のようになります。

updateIDs=[{"size":0,"languages":"","uidInfo":"c51e9c8d-8757-49bd-a65f-c1fa9ebc2c6e","updateID":"c51e9c8d-8757-49bd-a65f-c1fa9ebc2c6e"}]&updateIDsBlockedForImport=&wsusApiPresent=&contentImport=&sku=&serverName=&ssl=&portNumber=&version=

これを見ると、重要なのは「updateIDs」パラメーターで、「updateID」という、それらしい値をJSON形式で渡しているようです。

[{
    "size": 0,
    "languages": "",
    "uidInfo": "c51e9c8d-8757-49bd-a65f-c1fa9ebc2c6e",
    "updateID": "c51e9c8d-8757-49bd-a65f-c1fa9ebc2c6e"
}]

この「updateID」さえ分かれば何とかなりそうな気がしてきました。

updateIDの取得

ソースを調べると、リンクのonclick属性の値の中にIDが記載されていました。

ただ、Webサイト = htmlから値を持ってくるのは面倒くさそうです。

そこで目を付けたのが検索結果の「RSSフィード」、XMLに値が記載されていれば、htmlの解析をするより楽に値を取得できます。

ところが、RSSフィードのリンクをクリックすると、見事にエラー発生!
世の中そう思い通りには動いてくれません・・・。

諦めきれないので、ブラウザーを替えInternet Explorerで開いてみると、

今度は問題なく表示できました。
updateIDもちゃんとguid要素の値として記載されています。

ここまでくれば、下記のような流れで処理できそうです。

  1. Microsoft Updateカタログで「累積 更新 Windows-10」キーワード検索した結果のRSSフィードを取得(Internet Explorer)
  2. 1.のRSSフィードから、対象となる更新プログラムのupdateIDを取得
  3. ダウンロードページに対してupdateIDを含めたパラメーターをPOST
  4. 結果として表示されるWebページからファイルのダウンロードURLを取得

Microsoft Update カタログから累積更新プログラムをダウンロードするVBScript

この流れを実装したのが下記のコードです。
すべての更新プログラムをダウンロードするわけにはいかないので、プラットフォームやOSのバージョン、日付で絞り込むようにしています。
日付はRSSフィードのpubDate要素でざっくりと年月を判断しているだけなので、指定月以外の更新プログラムがダウンロードされる場合がありますが、大まかには上手く動いていると思います。

'*************************************************************************
' Microsoft Update カタログから更新プログラムをダウンロードするスクリプト
' 
' 2019/6/14 @kinuasa
'*************************************************************************

Option Explicit

Call DownloadWindowsUpdatesFromCatalog()
Wscript.Echo "処理が完了しました。"

Private Sub DownloadWindowsUpdatesFromCatalog()
'Microsoft Update カタログから更新プログラムをダウンロード
  Dim fso, fol
  Dim nodes, node
  Dim res
  Dim yyyy, mm
  Dim update_title, update_id, update_url, update_filename
  Dim parent_folderpath, dl_folderpath, dl_filepath
  Dim pub_date, guid
  Dim flg
  Dim v
  Dim i, cnt
  
  '[累積 更新 Windows-10]を検索キーワードとしたフィードURL
  Const url = "http://catalog.update.microsoft.com/v7/site/Rss.aspx?q=%e7%b4%af%e7%a9%8d+%e6%9b%b4%e6%96%b0+Windows-10&lang=ja"
  'title絞り込み用のキーワード(Or条件)
  Const keyword = "1803,1809,1903"
  '対象プラットフォーム
  Const platform = "x64"
  
  'フォルダ設定
  Set fol = CreateObject("Shell.Application") _
            .BrowseForFolder(0, "ダウンロード先フォルダ選択", &H10, 0)
  If fol Is Nothing Then Exit Sub
  parent_folderpath = fol.Self.Path
  Set fso = CreateObject("Scripting.FileSystemObject")
  
  '年月設定
  yyyy = InputBox("対象年を入力してください。", , Year(Now()))
  mm = InputBox("対象月を入力してください。", , Right("0" & Month(Now()), 2))
  
  'RSSフィード取得
  With CreateObject("WinHttp.WinHttpRequest.5.1")
    .Open "GET", url, False
    .setRequestHeader "User-Agent", "Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; rv:11.0) like Gecko"
    .Send
    Select Case .Status
      Case 200
        res = .responseText
      Case Else
        Wscript.Echo "!!RSSフィード取得失敗!! ステータスコード:" & .Status
        Exit Sub
    End Select
  End With
  
  cnt = 0
  v = Split(keyword, ",")
  With CreateObject("MSXML2.DOMDocument")
    .async = False
    If .LoadXML(res) = True Then
      Set nodes = .SelectNodes("//item")
      If nodes.Length > 0 Then
        For Each node In nodes
          flg = False
          update_title = node.SelectSingleNode("title").Text
          pub_date = node.SelectSingleNode("pubDate").Text
          
          '日付(pubDate)とプラットフォーム(title)で絞り込み
          If (InStr(pub_date, yyyy & "-" & mm) > 0) And (InStr(update_title, platform) > 0) Then
            'キーワードによる絞り込み(title)
            For i = LBound(v) To UBound(v)
              If InStr(update_title, v(i)) > 0 Then flg = True: Exit For
            Next
          End If
          
          If flg = True Then
            If cnt > 0 Then WScript.Sleep 10000 '負荷を考えて10秒処理待ち
            guid = node.SelectSingleNode("guid").Text
            update_id = GetUpdateID(guid)
            update_url = GetDownloadUrl(update_id)
            update_filename = GetDownloadFileName(update_url)
            
            'ダウンロード先フォルダ作成
            dl_folderpath = fso.BuildPath(parent_folderpath, update_title)
            If fso.FolderExists(dl_folderpath) = False Then fso.CreateFolder dl_folderpath
            
            'ダウンロード実行
            dl_filepath = fso.BuildPath(dl_folderpath, update_filename)
            Wscript.Echo "[ダウンロード中]" & update_title & ", " & update_url & ", " & dl_filepath
            DownloadFile update_url, dl_filepath
            cnt = cnt + 1
          End If
        Next
        Wscript.Echo "----- " & cnt & "ファイルダウンロード完了 -----"
      End If
    Else
      Wscript.Echo "!!RSSフィード読込失敗!!"
    End If
  End With
End Sub

Private Sub DownloadFile(ByVal url, ByVal save_filepath)
'XMLHTTPRequest + ADODB.Streamでファイルをダウンロード
  Dim req
  Const adTypeBinary = 1
  Const adSaveCreateOverWrite = 2
   
  Set req = CreateObject("Msxml2.XMLHTTP")
  req.Open "GET", url, False
   
  'キャッシュ対策
  'http://vird2002.s8.xrea.com/javascript/XMLHttpRequest.html#XMLHttpRequest_Cache-Control
  'http://www.atmarkit.co.jp/ait/articles/0305/10/news002.html 参考
  req.setRequestHeader "Pragma", "no-cache"
  req.setRequestHeader "Cache-Control", "no-cache"
  req.setRequestHeader "If-Modified-Since", "Thu, 01 Jun 1970 00:00:00 GMT"
   
  req.Send
  Select Case req.Status
    Case 200
      With CreateObject("ADODB.Stream")
        .Type = adTypeBinary
        .Open
        .Write req.responseBody
        .SaveToFile save_filepath, adSaveCreateOverWrite
        .Close
      End With
    Case Else
      Wscript.Echo "!!ダウンロード失敗!! ステータスコード:" & .Status
      Exit Sub
  End Select
End Sub

Private Function GetUpdateID(ByVal guid)
'アップデートID取得
  GetUpdateID = Left(guid, InStr(guid, "#") - 1)
End Function

Private Function GetDownloadUrl(ByVal update_id)
'ダウンロードURL取得
  Dim dat
  Dim html
  Dim matches
  Dim match
  Dim ret
  Const url = "http://www.catalog.update.microsoft.com/DownloadDialog.aspx"
  
  dat = "updateIDs=%5B%7B%22size%22%3A0%2C%22languages%22%3A%22%22%2C%22uidInfo%22%3A%22" & update_id & _
        "%22%2C%22updateID%22%3A%22" & update_id & "%22%7D%5D"
  With CreateObject("WinHttp.WinHttpRequest.5.1")
    .Open "POST", url, False
    .setRequestHeader "Content-Type", "application/x-www-form-urlencoded"
    .Send dat
    Select Case .Status
      Case 200: html = .responseText
    End Select
  End With
  
  '[downloadInformation[0].files[0].url =...]からURL取得
  With CreateObject("VBScript.RegExp")
    .IgnoreCase = True
    .Global = True
    .Pattern = "downloadInformation.*url.*"
    If .Test(html) Then
      Set matches = .Execute(html)
      
      'パターンをシンプルにして後から不要な文字を削除
      match = matches(0).Value
      match = Replace(match, vbCrLf, "")
      match = Replace(match, vbLf, "")
      match = Replace(match, vbCr, "")
      match = Replace(match, ";", "")
      match = Replace(match, ",", "")
      match = Replace(match, "'", "")
      match = Replace(match, " ", "")
      
      ret = Mid(match, InStr(match, "=") + 1)
    End If
  End With
  
  GetDownloadUrl = ret
End Function

Private Function GetDownloadFileName(ByVal update_url)
'ダウンロード対象のファイル名取得
  Dim v
  
  v = Split(update_url, "/")
  GetDownloadFileName = v(UBound(v))
End Function

これで面倒な定例作業が一つ自動化できました。

ちなみに、VBScriptにした理由は単に過去のコードを流用しただけなので、適当に変更するなりPowerShellに書き換えるなり、ご自由にお使いいただければと思います。

また、上記コードは、Microsoft Updateカタログの仕様変更に伴って動作しなくなる可能性がありますので、その点はご注意ください。

関連記事

  1. Windows関連

    [Windows 8]お気に入りのアプリ

    ここでは私が実際に使用して気に入ったWindows 8のアプリを紹介し…

  2. VBScript

    ファイル選択ダイアログ

    ファイル選択ダイアログを表示するVBScriptをまとめてみま…

  3. Windows 10

    [Selenium]ExecuteScriptで指定した要素のIDを取得する。

    MSDN フォーラムにあった質問「Edge向けWebDriverでDO…

  4. Windows 10

    続・Microsoft Edgeを操作するVBAマクロ(DOM編)

    以前VBAからMicrosoft Edgeを操作するマクロについて記事…

コメント

  • コメント (0)

  • トラックバックは利用できません。

  1. この記事へのコメントはありません。

Time limit is exhausted. Please reload CAPTCHA.

最近の記事

アーカイブ

RapidSSL_SEAL-90x50
PAGE TOP