だるろぐ

明日できることは、今日しない。

PowerShell:Windows ストアのアプリの更新履歴をチェックする

f:id:daruyanagi:20170303143002p:plain

これをチェックするツールがほしかったんだけど、WPF や ASP.NET Web Pages ではストアに「おまえ BOT だろ」と怒られてうまくいかなかった。けれど、似たようなコードを PowerShell で試すと、なぜかうまくいく……。原因はわかんないけど(調べる気が起きない)、PowerShell でできるんなら、PowerShell でやるか―って感じで頑張ってみた。

基本方針は

  • ストアから #version-notes をスクレイピングして保存
  • 新旧 #version-notes を比較してステータス(!?*+-)をつける
  • 結果を PSCustommObject の配列で返す

って感じ。

function Check-WindowsStore
{
    $urls =@(
        "https://www.microsoft.com/ja-jp/store/p/swarm/9wzdncrdrsq1",
          :(途中省略)
    )

    # 重複は無駄なので削る
    $urls = $urls | Get-Unique

    foreach ($url in $urls)
    {
        try
        {
            # ストアからテキストをダウンロード 
            $request = Invoke-WebRequest -Uri $url
            
            # アプリの表示名を記憶
            $title = $request.AllElements
                             .FindById("page-title").innerText

            # 新しいバージョンノートを記憶
            $new = $request.AllElements
                           .FindById("version-notes").innerText

            if ($new)
            {
                # 最初だけ:更新履歴を保存する .temp フォルダーを掘る
                $path = $env:USERPROFILE 
                      | Join-Path -ChildPath  ".temp"
                if (Test-Path $path) { } 
                else { New-Item $path -ItemType Directory }

                # バージョンノートを保存するファイル名を記憶
                # (パスに使えない文字は排除しておく)
                $path = $path | Join-Path -ChildPath
                    ($title -replace "/", " " -replace ":", " ")
                $old_path = $path + ".old.txt" # 質素な差分管理
                $new_path = $path + ".txt"

                # 前の更新履歴が保存されていたら……
                if (Test-Path $new_path)
                {
                    # 名前を変えて保存
                    $old = [System.IO.File]::ReadAllText($new_path)

                    if ($old -eq $new)
                    {
                        $status = "-" # 変化なし
                    
                    }
                    else
                    {
                        [System.IO.File]::
                            WriteAllText($old_path, $old)
                        [System.IO.File]::
                            WriteAllText($new_path, $new)
                        $status = "*" # 変化あり
                    }
                }
                else # 新しい更新履歴
                {
                    [System.IO.File]::WriteAllText($new_path, $new)
                    $status = "+" # 新規作成
                }
            }
            else # $new が nil
            {
                $status = "?" # そもそもリリースノートがない
            }

            [PSCustomObject] @{ 
                Status = $status; Title = $title; Url=$url
            }
        }
        catch [Exception]
        {
            $status = "!" # エラー

            [PSCustomObject] @{ 
                Status = $status; Title = $title; Url=$url
            }
        }
        finally
        {
            Write-Host "$status $url"
            $request = $null
        }
    }

    Write-Host "Complete"
}

この前は Slack のバージョン履歴が更新されたので、

Check-WindowsStore
    | where { $_.Status -ne "?" } 
    | where { $_.Status -ne "-" } 
    | Format-Table

を実行すると、

Status Title Url 
------ ----- ---
*      Slack https://www.microsoft.com/ja-jp/store/p/……

が出力される。

個人的お勉強ポイント

PowerShell はいろいろ省略記法とかあって、カッコよく書くのはまだまだ難しいなって思った。ぼちぼちうまくなりたい。

エスケープは ` で(今回は結局使わなかったけど)

タブは `t で入力できる。改行は `n だけど、CRLF にしたかったら `r`n にする。

ループでオブジェクトをポイポイすれば yield return みたいになる

$result = @()

に結果をストアしていかなくてもいいみたい。なーんだ!

JoinPath は | で繋ぐとネストが避けられて美しい

sevenb.jp

Invoke-WebRequest はすっげえ便利

特定の id の中身がほしい場合はこれだけ。

$request = Invoke-WebRequest $url
$request.AllElements.FindById("page-title").innerText

リンクや画像だけをストアしてるメンバーもある!

XPath 使えたらもっと便利なのになぁ(涎