前回:未経験からのPowerShell挑戦記 第1回:基本編
はじめに
前回、ファイル一覧やプロセス一覧を通して、PowerShellが「すべてをオブジェクトとして扱う」設計であることを体験した。今回は、外部のAPIと連携するところまで踏み込んでみる。
こんな方におすすめ
- PowerShellで外部APIを扱ったことがない方
- JSONというデータ形式について、なんとなくしか理解していない方
- 「なぜPowerShellはJSONを自動変換してくれるのか」が気になる方
環境がリセットされていたら、再インストール
前回同様、Cloud Shellの環境は一定時間でリセットされる。pwshが見つからない場合は、以下でインストールし直す。
sudo apt-get update && sudo apt-get install -y wget
wget -q https://packages.microsoft.com/config/ubuntu/$(source /etc/os-release && echo $VERSION_ID)/packages-microsoft-prod.deb
sudo dpkg -i packages-microsoft-prod.deb
rm packages-microsoft-prod.deb
sudo apt-get update && sudo apt-get install -y powershell
外部APIと連携する:Invoke-RestMethod
無料で公開されているGitHubのAPIを使って、外部サービスとの連携を試す。
Invoke-RestMethod -Uri "https://api.github.com/users/octocat"
GitHubのサンプルアカウントoctocatの情報が、これまで触ってきたファイル一覧などと同じ「項目名を持ったデータ」として返ってきた。
$user = Invoke-RestMethod -Uri "https://api.github.com/users/octocat"
$user | Select-Object login, name, public_repos, followers
Get-ChildItem | Select-Object Nameと全く同じ書き方で、欲しい項目だけを取り出せた。
そもそもJSONとは何か
ここで、そもそも何が起きているのかを整理しておく。
JSONは、データをやり取りするための世界共通のフォーマットで、{ "項目名": 値 }という形でデータを並べる、という決まったルールを持つテキスト。インターネット上で色々なプログラム同士がデータをやり取りするとき、共通で読み書きできる書式として広く使われている。GitHubのサーバーは、リクエストに対してこのJSON形式の文字列を返してくる。
普通、JSONは「ただの文字の並び」でしかなく、そこから欲しい情報を取り出すには「パース(解析)」という処理が別途必要になる。ところがPowerShellは、Invoke-RestMethodというコマンド自体に「Webから取ってくる」と「JSONを解析してオブジェクトにする」という2つの処理をセットで組み込んでいて、これを自動でやってくれていた。
なぜ自動変換してくれるのか
理由は、PowerShellが「すべてをオブジェクトとして扱う」という設計思想を徹底しているから。ファイル一覧、プロセス一覧、ログの中身、これまで対象が何であれ、同じ「オブジェクトとして扱う」という一貫性を保ってきた。もし外部APIの結果だけ「ただの文字列のまま」という例外を作ってしまうと、この一貫性が崩れてしまう。多くの言語では、JSONを取得した後に自分で解析処理を書く必要があるが、PowerShellはコマンド自体にその変換をあらかじめ組み込むことで、この一貫性を守っている。
変換される前と後を、実際に見比べる
自動変換をしない、生のJSONも見ておく。
$rawResponse = Invoke-WebRequest -Uri "https://api.github.com/users/octocat"
$rawResponse.Content
結果は{"login":"octocat","id":583231,...}という、1行に詰まった文字の羅列だった。Invoke-RestMethodは、裏側でこの生JSONを受け取り、PowerShell自身のJSON解析エンジンに通して、オブジェクトに変換してから渡してくれている、ということが実際に見比べて分かった。
複数の情報をまとめる:PSCustomObject
複数のAPIレスポンスを、自分の望む形にまとめ直す。
$users = @("octocat", "torvalds", "gvanrossum")
$results = foreach ($u in $users) {
$data = Invoke-RestMethod -Uri "https://api.github.com/users/$u"
[PSCustomObject]@{
Login = $data.login
Name = $data.name
Repos = $data.public_repos
Followers = $data.followers
}
}
$results | Format-Table -AutoSize
3人分のGitHubユーザー情報が、統一されたフォーマットの表にまとまった。[PSCustomObject]は、元の項目名(loginなど)を、自分の好きな項目名(Loginなど)に付け替えて、新しいオブジェクトとして組み立て直す仕組み。複数の違うシステムから集めてきた情報を、自分たちの都合の良い形式にまとめ直す、という実務でよくあるパターンの基本形。
エラーに備える:try/catch
存在しないユーザー名を指定して、エラーの挙動を確認する。
try {
Invoke-RestMethod -Uri "https://api.github.com/users/this-user-definitely-does-not-exist-12345"
} catch {
Write-Output "エラーが発生しました: $($_.Exception.Message)"
}
404 Not Foundのエラーが、プログラムを止めることなくキャッチできた。tryの中で処理を試し、エラーが起きたらcatchの中の処理に移る、という仕組み。「エラーが起きること自体は想定内として、起きたときにどう振る舞うかをあらかじめ決めておく」という考え方は、以前触れたTerraformやFluentdのエラー対応とも通じる。
複合レポートを作る:Measure-Object
外部の情報(GitHub)と、ローカルの情報(ファイル数)を1つのレポートにまとめる。
$githubUser = Invoke-RestMethod -Uri "https://api.github.com/users/octocat"
$localFiles = Get-ChildItem | Measure-Object
$report = [PSCustomObject]@{
確認日時 = Get-Date
GitHubユーザー = $githubUser.login
フォロワー数 = $githubUser.followers
ローカルファイル数 = $localFiles.Count
}
$report | Format-List
Measure-Objectは件数や合計を集計するコマンドで、.Countで件数を取り出せる。外部APIの情報とローカル環境の情報が、1枚のレポートとしてまとまった。これをCSVとして保存すれば、そのまま記録として残せる。
$report | Export-Csv -Path daily_report.csv -NoTypeInformation
まとめ
今回、以下を体験できた。
-
Invoke-RestMethodで外部APIと連携し、JSONが自動でオブジェクトに変換されることを確認する -
Invoke-WebRequestとの比較で、変換される前・後の実物を見比べる -
[PSCustomObject]で、複数の情報源を自分の望む形にまとめ直す -
try/catchでエラーに備える -
Measure-Objectを使い、複合レポートを作成・保存する
最後に、それぞれ何のためにやったのか、狙いを簡潔にまとめておく。
-
Invoke-WebRequestで生のJSONをあえて見たこと:「勝手に変換してくれる」を言葉の説明で終わらせず、変換前・変換後を自分の目で見比べて実感するため - GitHubとローカルファイル数という無関係な情報を1つのレポートにまとめたこと:対象が何であれ、「複数の情報源を1つのオブジェクトにまとめ上げる」という構造自体は同じであることを体感するため
これで、「取得→自動変換→加工→まとめる→エラーに備える→記録に残す」という、監視・レポート系スクリプトの基本形が一通り揃った。