🦈

新しいWindows Updateを自動で取ってくる

2024/09/26に公開

はじめに

はじめまして。大阪公立大学 現代システム科学域 知識情報システム学類 3年のしんのすけです。
こういう記事書くのは初めてなので何卒ご容赦ください🙏

この記事について

勤務先の大学で貸し出しされているノートPCのメンテナンス作業を行っており、複数台のWindows Updateを同時に行うとどうしても時間がかかっていました。Windows Updateは設定アプリから更新する方法と、更新プログラムのファイルをMicrosoft Updateカタログからダウンロードしてインストールする方法があります。
https://learn.microsoft.com/ja-jp/troubleshoot/windows-client/installing-updates-features-roles/download-updates-drivers-hotfixes-windows-update-catalog
今回はMicrosoft Updateカタログから自動で更新プログラムをダウンロードする方法について記事にしたいと思います。

導入

必要なもの

  • Python
  • requestsライブラリ
  • seleniumライブラリ
  • ご使用のブラウザに対応するWebdriver
    Webdriverのバージョン合わせにはwebdriver_managerライブラリを用いました。

https://github.com/SergeyPirogov/webdriver_manager

インストール

3つともpipでインストールします。
requestsについてはseleniumをインストールする際に自動でインストールされます。

pip install selenium
pip install webdriver-manager

Windows Updateの種類

ここでは、Windows 10の場合について紹介します。
Windows 10のWindows Updateでは、大きなものとして

  • Windows 10 サービススタック更新プログラム
  • Windows 10の累積更新プログラム
  • .NET Frameworkの累積的な更新プログラム
  • 悪意のあるソフトウェアの削除ツール

などが挙げられます。
これらの名前を使うことによってダウンロードするプログラムを検索することができます。

Microsoft Updateカタログの使い方

検索

トップページから検索すると、
https://www.catalog.update.microsoft.com/Search.aspx?q={検索文字列} のURLとして検索結果が表示されます。
検索結果
検索結果
検索文字列は、空白スペースでOR検索が行われ、ダブルクォーテーション(")で挟むことによって空白も含めた部分一致検索ができます。

ダウンロードする更新プログラムの行のダウンロードボタンをクリックすると、別ウインドウが開き、その中のリンクからダウンロードできます。
ダウンロード画面
ダウンロード画面

本題

スクレイピングしていく

先ほど述べたMicrosoft Updateカタログの使い方を自動で行えるようにやっていきたいと思います。

まず、seleniumの準備を行います。ここではMicrosoft Edgeをヘッドレスモードで動かしています。

import requests
from selenium import webdriver
from selenium.webdriver.edge.service import Service as EdgeService
from selenium.webdriver.common.by import By
from webdriver_manager.microsoft import EdgeChromiumDriverManager

options = webdriver.EdgeOptions()
options.add_argument("--headless=new")
driver = webdriver.Edge(service=EdgeService(EdgeChromiumDriverManager().install()), options=options)

これでseleniumを用いてページ操作が可能となりました!

検索を行うには、seleniumのgetメソッドを用いて、現在のページを移動します。

driver.get(f"https://www.catalog.update.microsoft.com/Search.aspx?q={software_name}")

software_nameには検索対象文字列を入れましょう。

  • "2024-09 x64 ベース システム用 Windows 10 Version 22H2 サービス スタック更新プログラム"
  • "2024-09 x64 ベース システム用 Windows 10 Version 22H2 の累積更新プログラム"
  • 2024-09 x64 "向け Windows 10 Version 22H2 用 .NET Framework 3.5、4.8 および 4.8.1 の累積的な更新プログラム"
  • "悪意のあるソフトウェアの削除ツール x64"

のように入れると検索が綺麗にできます。

検索結果の一覧を取得するには、XPATHを使います。
このように、開発者ツールから取得することができます。

完全なXPATHの取得

result_count = len(driver.find_elements(By.XPATH, "/html/body/div/form[2]/div[3]/table/tbody/tr[1]/td/div/div/div[2]/table/tbody/tr"))

このようにすることで、検索結果のtbody中にある行数を取得でき、検索結果の個数を取得することができます!

driver.find_element(By.XPATH, "/html/body/div/form[2]/div[3]/table/tbody/tr[1]/td/div/div/div[2]/table/tbody/tr[2]/td[8]/input").click()

ボタンの要素を取得して、クリックし、ダウンロード画面を出し、、、

driver.switch_to.window(self.driver.window_handles[1])

これでダウンロード画面にウインドウを切り替えます。

file_names = []
file_urls = []
download_buttons = driver.find_elements(By.TAG_NAME, "a")

ダウンロード画面からリンクの要素を取得し、

for download_btn in download_buttons:
    file_names.append(download_btn.accessible_name)
    file_urls.append(download_btn.get_attribute("href"))
driver.close()
driver.switch_to.window(self.driver.window_handles[0])

各リンクについてファイルのURLを取得することができました!
後はお好みの方法でファイルをダウンロードできます。
今回はrequestsモジュールのgetメソッドを用いてファイルをダウンロードし、保存してみました。

for file_url, file_name in zip(file_urls, file_names):
    url_data = requests.get(file_url).content
    with open("./tmp/"+file_name, mode="wb") as f:
        f.write(url_data)

これでWindows Updateの更新プログラムを自動でダウンロードすることができます!

色々な機能を持たせたコード例

from selenium import webdriver
from selenium.webdriver.edge.service import Service as EdgeService
from selenium.webdriver.common.by import By
from webdriver_manager.microsoft import EdgeChromiumDriverManager
import os
import requests
import time
import datetime


class DownloadWindowsUpdate:
    def __init__(self) -> None:
        options = webdriver.EdgeOptions()
        options.add_argument("--headless=new")
        self.driver = webdriver.Edge(service=EdgeService(EdgeChromiumDriverManager().install()), options=options)
        self.today = datetime.datetime.now()
        if not os.path.exists("tmp"):
            os.makedirs("tmp")

    def search_and_download(self, software: str, today: datetime.datetime) -> list[str] | None:
        if software == "悪意のあるソフトウェアの削除ツール x64":
            self.driver.get(f"https://www.catalog.update.microsoft.com/Search.aspx?q={software}")
        else:
            self.driver.get(f"https://www.catalog.update.microsoft.com/Search.aspx?q={today.strftime("%Y-%m")}{software}")
        time.sleep(1)

        result_count = len(self.driver.find_elements(By.XPATH, "/html/body/div/form[2]/div[3]/table/tbody/tr[1]/td/div/div/div[2]/table/tbody/tr"))
        if result_count == 0:
            print(software + f" の{today.strftime("%m")}月度の更新はありません。")
            if self.today == today:
                print("前月度の更新を調べます...")
                return self.search_and_download(software, today-datetime.timedelta(30))
            else:
                return software, None
        find = False
        for i in range(2, result_count+1):
            update_name = product_name = self.driver.find_element(By.XPATH, f"/html/body/div/form[2]/div[3]/table/tbody/tr[1]/td/div/div/div[2]/table/tbody/tr[{i}]/td[2]").accessible_name
            product_name = self.driver.find_element(By.XPATH, f"/html/body/div/form[2]/div[3]/table/tbody/tr[1]/td/div/div/div[2]/table/tbody/tr[{i}]/td[3]").accessible_name
            release_date = self.driver.find_element(By.XPATH, f"/html/body/div/form[2]/div[3]/table/tbody/tr[1]/td/div/div/div[2]/table/tbody/tr[{i}]/td[5]").accessible_name
            if today.strftime("%Y/%m") in release_date and "Windows 10" in product_name:
                self.driver.find_element(By.XPATH, "/html/body/div/form[2]/div[3]/table/tbody/tr[1]/td/div/div/div[2]/table/tbody/tr[2]/td[8]/input").click()
                find = True
                break
        print(update_name)
        if not find:
            print(software + f" の{today.strftime("%m")}月度の更新はありません。")
            if self.today == today:
                print("前月度の更新を調べます...")
                return self.search_and_download(software, today-datetime.timedelta(30))
            else:
                return update_name, None
        
        self.driver.switch_to.window(self.driver.window_handles[1])
        time.sleep(1)

        file_names = []
        file_urls = []
        download_buttons = self.driver.find_elements(By.TAG_NAME, "a")
        for download_btn in download_buttons:
            file_names.append(download_btn.accessible_name)
            file_urls.append(download_btn.get_attribute("href"))
        self.driver.close()
        self.driver.switch_to.window(self.driver.window_handles[0])
        for file_url, file_name in zip(file_urls, file_names):
            url_data = requests.get(file_url).content
            with open("./tmp/"+file_name, mode="wb") as f:
                f.write(url_data)
        
        return update_name, file_names
    
    def __del__(self) -> None:
        self.driver.quit()


if __name__ == "__main__":
    update_softwares = [
        " \"x64 ベース システム用 Windows 10 Version 22H2 サービス スタック更新プログラム\"",
        " \"x64 ベース システム用 Windows 10 Version 22H2 の累積更新プログラム\"",
        " x64 \"向け Windows 10 Version 22H2 用 .NET Framework 3.5、4.8 および 4.8.1 の累積的な更新プログラム\"",
        "悪意のあるソフトウェアの削除ツール x64"
    ]

    dwu = DownloadWindowsUpdate()
    for software in update_softwares:
        print(software, ":", dwu.search_and_download(software, dwu.today))


出力例

 "x64 ベース システム用 Windows 10 Version 22H2 サービス スタック更新プログラム" の09月度の更新はありません。
前月度の更新を調べます...
 "x64 ベース システム用 Windows 10 Version 22H2 サービス スタック更新プログラム" の08月度の更新はありません。
 "x64 ベース システム用 Windows 10 Version 22H2 サービス スタック更新プログラム" : (' "x64 ベース システム用 Windows 10 Version 22H2 サービス スタック更新プログラム"', None)
 "x64 ベース システム用 Windows 10 Version 22H2 の累積更新プログラム" の09月度の更新はありません。
前月度の更新を調べます...
2024-08 x64 ベース システム用 Windows 10 Version 22H2 の累積更新プログラム (KB5041580)
 "x64 ベース システム用 Windows 10 Version 22H2 の累積更新プログラム" : ('2024-08 x64 ベース システム用 Windows 10 Version 22H2 の累積更新プログラム (KB5041580)', ['windows10.0-kb5041580-x64_b3de56748ec2ba6f57af49e58690585ed0c385ec.msu'])
 x64 "向け Windows 10 Version 22H2 用 .NET Framework 3.5、4.8 および 4.8.1 の累積的な更新プログラム" の09月度の更新はありません。
前月度の更新を調べます...
2024-08 x64 (KB5042352) 向け Windows 10 Version 22H2 用 .NET Framework 3.5、4.8 および 4.8.1 の累積的な更新プログラム
 x64 "向け Windows 10 Version 22H2 用 .NET Framework 3.5、4.8 および 4.8.1 の累積的な更新プログラム" : ('2024-08 x64 (KB5042352) 向け Windows 10 Version 22H2 用 .NET Framework 3.5、4.8 および 4.8.1 の累積的な更新プログラム', ['windows10.0-kb5042056-x64-ndp48_1a8e9a5a6c21a29210af21efd965f0706cedcda3.msu', 'windows10.0-kb5042097-x64-ndp481_128df6c49bca1d0d7287649d35c9e3bb093329a3.msu'])
悪意のあるソフトウェアの削除ツール x64 - 2008 年 1 月 (KB890830)
悪意のあるソフトウェアの削除ツール x64 の09月度の更新はありません。
前月度の更新を調べます...
悪意のあるソフトウェアの削除ツール x64 - v5.127 (KB890830)
悪意のあるソフトウェアの削除ツール x64 : ('悪意のあるソフトウェアの削除ツール x64 - v5.127 (KB890830)', ['windows-kb890830-x64-v5.127_acb38d66466232b6ab1697b29d3561deb1f9fefd.exe'])

プログラムと同じディレクトリにtmpフォルダが生成されて、その中に保存されます。

まとめ

seleniumライブラリを用いてスクレイピングを行うことで、Microsoft Updateカタログから最新のWindows Updateのアップデートファイルをダウンロードすることができました。他のサイトにおいても、同じような手法で最新のバージョン情報の取得などができそうですね。

お読みいただきありがとうございました!

TryAngle@大阪公立大学

Discussion