0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【PowerShell 業務自動化レシピ #9】REST APIからデータ取得してCSV/Slack通知に流す(Invoke-RestMethod)

0
Posted at

株式会社Good Labでエンジニアをしている コータロー です。
日々、Java・SQL・Gitなどの技術情報や、新人エンジニア向けの学習ノウハウ、
AI活用についての情報を発信しています。

Good Labについて気になった方は、コーポレートサイトもぜひご覧ください。
コーポレートサイト


本記事は 「PowerShell 業務自動化レシピ」 シリーズの第9回です。このシリーズは、文法解説ではなく 「1記事=1つの業務課題」を、コピペで動く検証済みスクリプトで解決することを目的とした実務レシピ集です(全12回想定)。SES・受託・社内運用の現場で、いまだに手作業でやっている地味な作業を片っ端から自動化していきます。

第1回 からファイル整理・ログ削除・Excel台帳化・CSV結合集計と「ローカルのファイル」を相手にしてきて、第8回 では「サーバの稼働点検(死活・リソース・サービス状態)」を自動化しました。第9回からは外に出ます。テーマは 「REST APIからデータを取得してCSV/通知に流す」

社内の管理画面・SaaS・自社サービスの多くは REST API を持っています。そこに Invoke-RestMethod でアクセスし、返ってきた JSONをそのままPowerShellのオブジェクトとして加工して、CSVに保存したりSlack/Teamsに通知したり——「毎朝ブラウザでログインして数字を手で転記」をまるごと無くします。

本記事は PowerShell 7(pwsh、クロスプラットフォーム) を基準にしています。Windows標準の Windows PowerShell 5.1 での差異は、各所と最後の章で明記します(特にTLS設定は5.1で要注意です)。

⚠️ セキュリティ最重要:APIキー・トークンは スクリプトに直書きしないでください。本記事はすべて、環境変数($env:)や Get-Credential から取得する形で書いています。サンプルの YOUR_API_KEY 等のプレースホルダは、必ず自分の安全な保管方法に置き換えてください(後述)。


Before:手作業のつらいシーン

シーン1:毎朝、管理画面の数字を手で転記している

朝イチで勤怠SaaSや売上ダッシュボードにログインし、表示された「昨日の申込件数」「未対応チケット数」を、報告用のExcelやSlackに手でコピペ。

  • ログイン → 画面遷移 → 数字を探す → コピペ。1サービス2〜3分 × 複数サービス
  • 毎朝のことなので 月20営業日で軽く数時間。しかも転記ミス・貼り忘れが起きる
  • そのサービスにはたいてい API があるのに使っていない

シーン2:APIで取れるデータを画面からポチポチ集めている

「全ユーザーの一覧をCSVで欲しい」と言われ、管理画面のユーザー一覧を1ページずつ表示してはコピー。100件ずつ50ページ…。

  • ページ送り → コピー → Excelに貼る を延々と繰り返す
  • 途中でページを飛ばす・重複する事故。半日仕事

これ、APIを1回叩いてJSONを受け取り、必要な項目を選んでCSVに出す/Slackに投げる——全部スクリプト一本で終わります。しかもタスクスケジューラに載せれば(次回予告)、人間が起きる前に終わっています。


After:スクリプト一本で完了

進め方は ①GETでJSONを取得してオブジェクトとして扱う → ②必要項目を選んでCSV化 → ③認証ヘッダー・クエリ付きリクエスト → ④Slack/Teamsへ通知(POST)→ ⑤ページネーション対応のループ の順です。

題材として、よくある「ユーザー一覧API」を想定します。GET https://api.example.com/v1/users を叩くと、こんなJSONが返る前提で進めます。

{
  "page": 1,
  "per_page": 2,
  "total": 5,
  "users": [
    { "id": 101, "name": "山田 太郎", "email": "yamada@example.com", "status": "active",   "created_at": "2026-06-01T09:30:00+09:00" },
    { "id": 102, "name": "佐藤 花子", "email": "sato@example.com",   "status": "inactive", "created_at": "2026-06-02T14:10:00+09:00" }
  ]
}

ポイントは、JSONのトップレベルがオブジェクトpage / total / users を持つ)で、実データの配列は users の下にネストしていることです。現場のAPIはこの形が大半です。

レシピ1:GETでJSONを取得し、オブジェクトとして扱う

まずは1回叩いて、返ってきたJSONをそのまま操作します。

# ===== 設定 =====
$baseUrl = "https://api.example.com/v1/users"

# ===== 本体 =====
# Invoke-RestMethod はレスポンスのJSONを自動でオブジェクトに変換して返す
$res = Invoke-RestMethod -Uri $baseUrl -Method Get

# トップレベルのプロパティにそのままアクセスできる
Write-Host "総件数: $($res.total) 件 / 現在ページ: $($res.page)"

# 実データの配列は $res.users(ここを回す)
$res.users | ForEach-Object {
    Write-Host "  [$($_.id)] $($_.name) <$($_.email)> - $($_.status)"
}

Invoke-RestMethodレスポンスのJSONを自動でパースして PSCustomObject に変換して返します。だから $res.total$res.users のように、最初からドットでプロパティアクセスできます。上のサンプルJSON(2件入り)なら、出力はこうなります。

総件数: 5 件 / 現在ページ: 1
  [101] 山田 太郎 <yamada@example.com> - active
  [102] 佐藤 花子 <sato@example.com> - inactive

ここが Invoke-RestMethod の真価です。Invoke-WebRequest だと生のレスポンス(本文は文字列)が返り、$res.Content | ConvertFrom-Json と自分でパースする一手間が必要です。JSON APIを叩くなら、基本は Invoke-RestMethod を選びます(違いは要点解説で詳述)。

レシピ2:必要項目を選んでCSVに保存する

取得したユーザー一覧から、必要な列だけを選んでCSVに落とします。シリーズおなじみの Select-ObjectExport-Csv の流れです。

# ===== 設定 =====
$baseUrl = "https://api.example.com/v1/users"
$outCsv  = "C:\work\users.csv"

# ===== 本体 =====
$res = Invoke-RestMethod -Uri $baseUrl -Method Get

$res.users |
    Select-Object `
        id,
        name,
        email,
        status,
        @{Name='CreatedDate'; Expression={ ([datetime]$_.created_at).ToString('yyyy-MM-dd') }} |
    Export-Csv -LiteralPath $outCsv -Encoding UTF8BOM -NoTypeInformation

Write-Host "CSV出力完了: $outCsv$($res.users.Count) 件)"

created_at は ISO 8601 形式の文字列(2026-06-01T09:30:00+09:00)なので、[datetime] にキャストして yyyy-MM-dd だけ取り出しています。サンプルの2件で出力すると、users.csv の中身はこうなります。

id,name,email,status,CreatedDate
101,山田 太郎,yamada@example.com,active,2026-06-01
102,佐藤 花子,sato@example.com,inactive,2026-06-02

CSV出力の -Encoding UTF8BOM / -NoTypeInformation は、第4回・第5回と同じ理由(Excelでの文字化け防止・型情報行の抑制、5.1との両対応)です。詳細はそちらを参照してください。

レシピ3:認証トークンとクエリパラメータを付けてリクエストする

実際のAPIは認証が要ります。多くは「Authorization ヘッダーにトークンを載せる(Bearer認証)」方式です。さらに「active なユーザーだけ」「100件ずつ」のようなクエリパラメータも付けます。

ここで最重要なのが、トークンをスクリプトに直書きしないことです。まず環境変数に入れておきます。

# ===== 事前に1回だけ:トークンを環境変数へ(スクリプトには書かない)=====
# 現在のユーザーに恒久保存(PowerShellやPCを再起動しても残る)
[Environment]::SetEnvironmentVariable('EXAMPLE_API_TOKEN', 'YOUR_API_TOKEN', 'User')
# ↑ 実行後、新しいPowerShellを開くと $env:EXAMPLE_API_TOKEN で読めるようになる

そのうえで、スクリプト側は環境変数から読み出して使います。

# ===== 設定 =====
$baseUrl = "https://api.example.com/v1/users"

# トークンは環境変数から取得(直書き厳禁)
$token = $env:EXAMPLE_API_TOKEN
if (-not $token) { throw "環境変数 EXAMPLE_API_TOKEN が未設定です" }

# 認証ヘッダー
$headers = @{
    Authorization = "Bearer $token"
    Accept        = "application/json"
}

# クエリパラメータ(?status=active&per_page=100 が付く)
$query = @{
    status   = "active"
    per_page = 100
}

# ===== 本体 =====
$res = Invoke-RestMethod -Uri $baseUrl -Method Get -Headers $headers -Body $query

Write-Host "active なユーザー: $($res.users.Count) 件"

ポイントは2つです。

  • 認証は -Headers @{ Authorization = "Bearer $token" } で渡す。ハッシュテーブルでヘッダーを組み立てます。
  • GETのクエリは -Body にハッシュテーブルを渡すと、Invoke-RestMethod が自動で ?status=active&per_page=100 というクエリ文字列にしてURLに付けてくれます(GETのときは -Body がクエリ文字列になる、という仕様です。POSTのときは本文になります。後述)。

サンプルJSONは statusactive / inactive が混在していました。?status=active で絞れば、サーバ側で active のみ(この例なら id:101 等)が返ります。クライアント側で Where-Object する前に、APIのクエリで絞れるならサーバに絞らせるのが転送量・速度の面で有利です。

レシピ4:取得した数値をSlack/Teamsに通知する(POST)

「毎朝の数字をSlackに投げる」を実装します。Slack の Incoming Webhook(または Teams の Incoming Webhook)にPOSTでメッセージを送ります。Webhook URL もトークン同様、直書きしません

# ===== 事前に1回だけ:Webhook URL を環境変数へ =====
# [Environment]::SetEnvironmentVariable('SLACK_WEBHOOK_URL', 'https://hooks.slack.com/services/XXXX/YYYY/ZZZZ', 'User')

# ===== 設定 =====
$apiUrl     = "https://api.example.com/v1/users"
$webhookUrl = $env:SLACK_WEBHOOK_URL
if (-not $webhookUrl) { throw "環境変数 SLACK_WEBHOOK_URL が未設定です" }

# ===== ① APIから数字を取得 =====
$res         = Invoke-RestMethod -Uri $apiUrl -Method Get
$activeCount = ($res.users | Where-Object { $_.status -eq 'active' }).Count
$total       = $res.total

# ===== ② Slackへ通知(POST)=====
$text = "おはようございます☀️`n本日の状況:`n・総ユーザー: ${total}`n・アクティブ: ${activeCount} 件"

$payload = @{ text = $text }

Invoke-RestMethod -Uri $webhookUrl `
    -Method Post `
    -ContentType 'application/json; charset=utf-8' `
    -Body ($payload | ConvertTo-Json -Depth 5)

Write-Host "Slackへ通知しました"

Slackに投稿されるメッセージ(サンプルJSONの2件で active は1件、total は5なので):

おはようございます☀️
本日の状況:
・総ユーザー: 5 件
・アクティブ: 1 件

POSTで押さえるべき点は3つです。

  • -Method Post を指定する。
  • 本文は -Body ($payload | ConvertTo-Json)。PowerShellのハッシュテーブルを、ConvertTo-JsonJSON文字列に変換してから渡します。
  • -ContentType 'application/json; charset=utf-8' を付ける。日本語を含むJSONを送るとき、charset=utf-8 を明示しないと文字化けすることがあります。

Teams の Incoming Webhook もやり方はほぼ同じです。送るJSONの形だけ違います(Teams は @{ text = "..." } でもシンプルなテキストは届きますが、正式には MessageCard / Adaptive Card 形式を使います)。まずは @{ text = "..." } で疎通確認するのがおすすめです。

レシピ5:ページネーションに対応して全件取得する

APIは1回で全件返さず、ページ分割して返すのが普通です(サンプルJSONも per_page: 2total: 53ページに分かれる想定)。全ページを回して1つの配列にまとめるループを書きます。

# ===== 設定 =====
$baseUrl = "https://api.example.com/v1/users"
$token   = $env:EXAMPLE_API_TOKEN
if (-not $token) { throw "環境変数 EXAMPLE_API_TOKEN が未設定です" }

$headers = @{ Authorization = "Bearer $token"; Accept = "application/json" }
$perPage = 100   # 1ページあたり件数(APIの上限に合わせる)

# ===== 本体:全ページをループで取得 =====
$allUsers = [System.Collections.Generic.List[object]]::new()
$page = 1

while ($true) {
    $query = @{ page = $page; per_page = $perPage }
    $res = Invoke-RestMethod -Uri $baseUrl -Method Get -Headers $headers -Body $query

    # このページのデータを蓄積
    if ($res.users.Count -gt 0) {
        $allUsers.AddRange([object[]]$res.users)
    }

    Write-Host "page $page 取得: $($res.users.Count) 件(累計 $($allUsers.Count) / 全 $($res.total) 件)"

    # 終了条件:累計が総件数に達した or 空ページが返ってきた
    if ($allUsers.Count -ge $res.total -or $res.users.Count -eq 0) { break }

    $page++
    Start-Sleep -Milliseconds 300   # サーバへの配慮(レート制限対策)
}

Write-Host "全件取得完了: $($allUsers.Count) 件"

total: 5per_page: 2 のAPIなら、ループのログはこうなります(3ページで全5件)。

page 1 取得: 2 件(累計 2 / 全 5 件)
page 2 取得: 2 件(累計 4 / 全 5 件)
page 3 取得: 1 件(累計 5 / 全 5 件)
全件取得完了: 5 件

ループ設計のポイントです。

  • 終了条件は「累計が total に達したら」+「空ページが返ったら」の二段構えtotal を信じすぎず、空ページでも止まるようにしておくと、件数がズレるAPIでも無限ループしません。
  • Start-Sleep でウェイトを入れる。連続で叩くとレート制限(429)を食らったり、相手サーバに負荷をかけます。300ミリ秒程度の間隔は最低限の礼儀です。
  • 蓄積は List[object]AddRange を使うと、$arr += ... の毎回コピー(遅い)を避けられます。

全件取れたら、あとはレシピ2と同じく Select-ObjectExport-Csv でCSV化できます。


コードの要点解説(なぜこの書き方か)

文法そのものではなく、現場でハマらないための選択を中心に解説します。

1. 【最重要】Invoke-RestMethodInvoke-WebRequest の使い分け

名前が似ていて混同しがちですが、用途が違います。

Invoke-RestMethod Invoke-WebRequest
返ってくるもの JSONを自動パースしたオブジェクト レスポンス全体(StatusCode / Headers / Content〔生文字列〕)
JSON APIを叩くとき そのまま $res.users 等で使える $res.Content | ConvertFrom-Json が別途必要
向いている用途 REST/JSON APIの呼び出し HTML取得・ステータスコードやヘッダーを見たいとき・ファイルDL

JSONを返すAPIなら Invoke-RestMethod 一択です。自分でパースしなくていいぶん、コードが短く事故りにくい。逆に「HTTPステータスコードを厳密に分岐したい」「レスポンスヘッダーを読みたい」ときは Invoke-WebRequest が要ります(Invoke-RestMethod は本文しか返さないため)。

2. 認証ヘッダーは -Headers にハッシュテーブルで渡す

-Headers @{ Authorization = "Bearer $token" } が、Bearerトークン認証の基本形です。APIによっては Authorization ではなく独自ヘッダー(X-API-Key 等)の場合もあります。その場合も同じく -Headers @{ 'X-API-Key' = $token } で渡せます。キー名はAPIのドキュメントに厳密に合わせてください(大文字小文字・ハイフンまで)。

3. GETのクエリは -Body、POSTの本文も -Body(意味が変わる)

-Bodyメソッドによって役割が変わります

  • GET のとき-Body @{ status = 'active' }クエリ文字列?status=active)になりURLに付与される。
  • POST のとき-Bodyリクエスト本文になる。JSONを送るなら ConvertTo-Json した文字列を渡し、-ContentType 'application/json' を添える。

この違いを知らないと「POSTのつもりがGETのクエリになっていた」「JSONを送ったつもりがフォーム形式で送られていた」といった事故が起きます。POSTでJSONを送るときは ConvertTo-Json-ContentType をセットで覚えてください。

4. ConvertTo-Json-Depth に注意

ConvertTo-Json既定でネストの深さ2までしか変換しません(PowerShell 7系。5.1も既定2)。それより深い階層は System.Collections.Hashtable のような文字列に化けて、正しいJSONになりません。入れ子のあるペイロードを送るときは、-Depth を十分大きく(例:-Depth 10)指定します。

# ネストが深いペイロードは -Depth を必ず付ける
$payload | ConvertTo-Json -Depth 10

通知が「なぜか送れない/中身が壊れる」ときは、まずこの -Depth 不足を疑ってください。

5. 日本語を送る/受けるときの charset=utf-8

POSTで日本語を含むJSONを送るとき、-ContentType 'application/json; charset=utf-8'charsetまで明示すると文字化けを避けやすくなります。受信側(Invoke-RestMethod の戻り)も、PowerShell 7 は UTF-8 を既定で扱うため概ね素直ですが、5.1 では文字コード解釈が弱く文字化けすることがあります(後述の差異表)。

6. HTTPエラーは try/catch で受ける

Invoke-RestMethod400/500系などHTTPエラーが返ると例外を投げます(終端エラー)。try/catch で囲まないと、スクリプトがそこで止まります。逆に言えば、try/catch すれば「401なら認証エラー、429ならリトライ」といったステータスコード別の対処が書けます。

try {
    $res = Invoke-RestMethod -Uri $baseUrl -Method Get -Headers $headers -ErrorAction Stop
}
catch {
    # HTTPステータスコードを取り出す(PowerShell 7)
    $status = $_.Exception.Response.StatusCode.value__
    Write-Warning "APIエラー: HTTP $status / $($_.Exception.Message)"
}

-ErrorAction Stop を付けておくと、catch に確実に入ります。$_.Exception.Response.StatusCode.value__ で数値のステータスコード(401, 429, 500 …)が取れます。


応用・注意点

① タイムアウトとリトライ(429/一時的エラーへの備え)

ネットワークやサーバは一時的に失敗します。タイムアウトを切り、リトライを入れておくと安定します。

# PowerShell 7 なら標準パラメータでリトライできる
$res = Invoke-RestMethod -Uri $baseUrl -Method Get -Headers $headers `
    -TimeoutSec 30 `
    -MaximumRetryCount 3 `
    -RetryIntervalSec 5
  • -TimeoutSec … 応答が無いときに何秒で諦めるか。
  • -MaximumRetryCount / -RetryIntervalSecPowerShell 7 で追加された再試行パラメータ。429(レート制限)や5xxのときに自動でリトライします。
  • 5.1 には -MaximumRetryCount がありません。5.1で再試行したいときは、自前で fortry/catchStart-Sleep のループを書きます(下記)。
# 【5.1向け】自前リトライの例
$max = 3
for ($i = 1; $i -le $max; $i++) {
    try {
        $res = Invoke-RestMethod -Uri $baseUrl -Method Get -Headers $headers -ErrorAction Stop
        break
    }
    catch {
        if ($i -eq $max) { throw }   # 最後の試行でも失敗したら諦めて例外を投げる
        Write-Warning "失敗($i 回目)。$($i * 5) 秒後に再試行..."
        Start-Sleep -Seconds ($i * 5)   # 待ち時間を徐々に伸ばす(指数的バックオフ)
    }
}

② 認証情報を安全に扱う(直書き厳禁の徹底)

スクリプトにトークンを直書きすると、Git・共有フォルダ・チャット経由で漏れます。本記事のように環境変数経由が基本ですが、より丁寧にやるなら以下です。

  • 環境変数(手軽。今回の基本形)… $env:EXAMPLE_API_TOKEN で読む。
  • Get-Credential + SecureString(対話的に入力させる)… 画面入力させ、メモリ上で安全に扱う。
# 実行時にパスワード欄でトークンを入力させる(画面・履歴に平文が残らない)
$cred  = Get-Credential -UserName "api" -Message "APIトークンを入力してください"
# SecureString から実際の文字列を取り出してヘッダーに使う
$token = $cred.GetNetworkCredential().Password
$headers = @{ Authorization = "Bearer $token" }

どの方式でも、スクリプト本体・ログ・コンソール出力にトークンを平文で残さないのが鉄則です。Write-Host $token のようなデバッグ出力をうっかり残さないよう注意してください。共有レポジトリにコミットする場合は、.gitignore と合わせて**設定値を別ファイル化しない(=環境変数で外出しする)**のが安全です。

③ ネストの深いJSONから値を取り出す

実APIのJSONは、users のさらに下に profile があったり、配列の配列だったりします。Invoke-RestMethod が返すのは普通のオブジェクトなので、ドットでそのまま掘れます

# 例: $res.data.items[0].profile.department のような深い階層もドットで辿れる
$res.users | ForEach-Object {
    [pscustomobject]@{
        Id         = $_.id
        Name       = $_.name
        # ネストした値もドットで取得(無ければ $null になる)
        Department = $_.profile.department
    }
}

階層が深くて構造が分かりにくいときは、いったん $res | ConvertTo-Json -Depth 10全体を整形表示して、どこに目的の値があるか目視で確認すると早いです。

④ ページネーションの方式はAPIごとに違う

レシピ5は「page / per_pagetotal」方式でしたが、APIによっては方式が異なります。

  • next URL 方式 … レスポンスに「次ページのURL(next)」が入っている。while ($nextUrl) { ... $nextUrl = $res.next } のように次URLが無くなるまで回す。
  • カーソル(cursor)方式next_cursor を次リクエストに渡す。while ($cursor) { ... }
  • Link ヘッダー方式(GitHub等)… ヘッダーに次ページ情報。この場合はヘッダーを読む必要があるので Invoke-WebRequest を使い、$res.Headers.Link を解析します。

「総件数で止める」のレシピ5は最も素直なパターンです。相手APIのドキュメントでページング方式を確認し、終了条件を合わせてください。

⑤ Windows PowerShell 5.1 との差異(特にTLSに注意)

Invoke-RestMethod 自体は 5.1 にもありますが、重要な差があります。

項目 PowerShell 7(pwsh) Windows PowerShell 5.1
TLS TLS 1.2/1.3 を既定で使う 古い既定のままだと TLS 1.0/1.1 で接続失敗することがある。Tls12 の明示が必要な場合あり(下記)
-MaximumRetryCount / -RetryIntervalSec あり(自動リトライ可) なし(自前ループで実装)
文字コード(日本語) UTF-8 を既定で適切に扱う レスポンスの日本語が文字化けすることがある
ConvertTo-Json の既定 -Depth 2 2(同じ。深い構造は要 -Depth
エラー時の例外 例外(try/catch 可) 同じく例外(try/catch 可)

5.1で「HTTPSのAPIに繋がらない/基になる接続が閉じられました エラー」が出たら、まず TLS 1.2 の明示を試してください。

# 【5.1向け】HTTPS接続前に TLS 1.2 を有効化する(このセッション内で1回)
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocol]::Tls12

# その後で Invoke-RestMethod を呼ぶ
$res = Invoke-RestMethod -Uri "https://api.example.com/v1/users" -Method Get

PowerShell 7 ではこの1行は不要です(既定でTLS 1.2/1.3)。TLS設定・自動リトライ・文字コードの安定性のどれを取っても、API連携は PowerShell 7 で動かすのが断然ラクです。可能なら7を入れて統一しましょう。


まとめ

  • JSON APIは Invoke-RestMethod(自動パース)。生レスポンスやヘッダーが要るときだけ Invoke-WebRequest
  • 取得したJSONは $res.users のようにドットでそのまま加工Select-ObjectExport-Csv でCSV化
  • 認証は -Headers @{ Authorization = "Bearer $token" }、GETのクエリは -Body @{...}
  • 通知(POST)は -Method Post-Body (ConvertTo-Json)-ContentType 'application/json; charset=utf-8'ConvertTo-Json-Depth 不足に注意
  • 全件取得は ページネーションのループ(終了条件は二段構え+Start-Sleep でレート制限対策)
  • HTTPエラーは try/catch-ErrorAction Stop、安定運用に タイムアウト・リトライ
  • トークン・Webhook URLは絶対に直書きしない$env: / Get-Credential
  • 5.1 は TLS 1.2 の明示が必要なことがある自動リトライが無い日本語が化けやすい。API連携は 7 推奨

これで、毎朝の手転記や画面ポチポチが、コマンド一本(やがては自動実行)に置き換わります。

次回 第10回は「毎朝決まった時刻に自動実行(タスクスケジューラ登録)」。今回作ったAPI取得&通知スクリプトを、Windowsのタスクスケジューラに登録して、人が出社する前に自動で走らせるレシピを扱います。お楽しみに。


参考


@kotaro_ai_lab
AI活用や開発効率化について発信しています。フォローお気軽にどうぞ!

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?