1分で読了エンジニアリング

Invoke-RestMethodでPowerShellのURLを短縮する方法

1つのコマンドレットでPowerShellのURLを短縮し、429エラーに対処し、APIキーをスクリプトの外に置き、ForEach-Object -ParallelでCSV列全体を短縮する方法を解説します。

Marius Voß
DevRel · edge infra
PowerShellでURLを短縮する方法:Invoke-RestMethodで遷移先URLを短縮サービスのAPIへPOSTし、短縮リンクをオブジェクトとして返す

PowerShellでURLを短縮する処理は、1つのコマンドレットで済みます。Invoke-RestMethodは遷移先を短縮サービスのAPIへPOSTし、APIキーをBearerヘッダーとして送り、JSON文字列ではなく解析済みのオブジェクトを返します。PowerShellがすでに入っているマシンなら、何もインストールする必要はありません。

これは、PythonJavaScriptC#Goも扱うシリーズの、システム管理者向けの記事です。エンドポイントの形式と認証モデルは無料のURL短縮サービスAPI概要に、ダッシュボードの経路はURL短縮の一般ガイドに記載されています。

最速の方法:Invoke-RestMethodを1回呼び出す

$headers = @{
    Authorization  = "Bearer $env:ELIDO_API_KEY"
    'Content-Type' = 'application/json'
}

$body = @{
    destination_url = 'https://example.com/spring-sale?utm_source=newsletter'
} | ConvertTo-Json

$link = Invoke-RestMethod -Method Post `
    -Uri 'https://api.elido.app/v1/links' `
    -Headers $headers `
    -Body $body `
    -TimeoutSec 10

$link.short_url   # https://s.elido.me/ab12cd

Invoke-RestMethodはレスポンスをデシリアライズします。これがInvoke-WebRequestとの違いです。ConvertFrom-Jsonの手順も、ラップを外すための.Contentも必要ありません。$linkはすでにidshort_urlを持つオブジェクトです。

ConvertTo-Jsonは省略できません。ハッシュテーブルをそのまま-Bodyに渡すと、PowerShellはそれをフォームエンコードします。APIにはapplication/x-www-form-urlencodedとして認識され、サーバーの問題のように見える400エラーが返ります。

PowerShell固有の意外な点が1つあります。ここでは401や404は戻り値ではなく終了エラーです。このシリーズの他のクライアントではステータスコードを確認しますが、PowerShellでは例外を捕捉する必要があります。

429エラーに対処し、スクリプトを再実行可能にする

スケジュール実行するなら、3つ追加する必要があります。上記のスロー動作に対応するtry/catch、Retry-Afterを尊重する待機処理、そして再試行したリクエストが同じ遷移先に対して2つ目のリンクを作らず、元のリンクを返すためのIdempotency-Keyです。

function Get-ShortLink {
    param(
        [Parameter(Mandatory)][string] $Destination,
        [int] $Attempts = 3
    )

    # stable key: the same destination always produces the same one
    $sha  = [System.Security.Cryptography.SHA256]::Create()
    $key  = [BitConverter]::ToString(
                $sha.ComputeHash([Text.Encoding]::UTF8.GetBytes($Destination))
            ).Replace('-', '')

    $headers = @{
        Authorization     = "Bearer $env:ELIDO_API_KEY"
        'Content-Type'    = 'application/json'
        'Idempotency-Key' = $key
    }
    $body = @{ destination_url = $Destination } | ConvertTo-Json

    for ($attempt = 0; $attempt -lt $Attempts; $attempt++) {
        try {
            return (Invoke-RestMethod -Method Post -Uri 'https://api.elido.app/v1/links' `
                        -Headers $headers -Body $body -TimeoutSec 10).short_url
        }
        catch {
            $status = $_.Exception.Response.StatusCode.value__

            if ($status -eq 429) {
                $wait = $_.Exception.Response.Headers['Retry-After']
                if (-not $wait) { $wait = 2 }        # works on 5.1 too; no ?? operator
                Start-Sleep -Seconds ([int]$wait)
            }
            elseif ($status -ge 500) {
                Start-Sleep -Seconds ([math]::Pow(2, $attempt))   # 1s, 2s, 4s
            }
            else {
                throw   # 401, 403, 422: retrying will not help
            }
        }
    }

    throw "shorten failed after $Attempts attempts"
}

PowerShell 7では、-SkipHttpErrorCheckを使うと例外の詳細を調べる手間の大半を省けます。スローする代わりにレスポンスオブジェクトが返るため、APIのエラー本文を直接読み取れます。422エラーのデバッグで、APIがどのフィールドに異議を唱えたのか確認したい場合に便利です。

PowerShellのInvoke-RestMethodがBearerトークンとIdempotency-Keyを付けて遷移先URLを短縮サービスのlinksエンドポイントへPOSTし、short_urlを含む解析済みオブジェクトを返す流れ

キーをスクリプトの外に置く

$env:ELIDO_API_KEYは手間のかからない方法で、専用のサービスアカウントを使うスケジュールタスクなら十分です。.ps1ファイルに入力したキーは適切ではありません。ソース管理、Get-History、そして環境で有効にしているトランスクリプトログに残ってしまいます。

共有するものなら、SecretManagementモジュールに保存するのが適しています。

$env:ELIDO_API_KEY = Get-Secret -Name ElidoApiKey -AsPlainText

キーのローテーションは、4台のサーバーにあるスクリプトを探し回るのではなく、1つのボールトエントリを更新するだけで済みます。

このまま実行してみますか。無料プランでキーを作成し、$env:ELIDO_API_KEYを設定すれば、このページの内容を変更せずに実行できます。

CSV列全体を短縮する

現実によくある作業は、1つのURLではなく、URLが並んだスプレッドシートです。必要なのは4つのコマンドレットとスロットルです。

$rows    = Import-Csv .\campaign-urls.csv     # columns: id, destination
$funcDef = ${function:Get-ShortLink}.ToString()

$done = $rows | ForEach-Object -ThrottleLimit 8 -Parallel {
    $function:Get-ShortLink = $using:funcDef   # ship the function into each runspace
    [pscustomobject]@{
        id          = $_.id
        destination = $_.destination
        short_url   = try   { Get-ShortLink -Destination $_.destination }
                      catch { "ERROR: $($_.Exception.Message)" }
    }
}

$done | Export-Csv .\campaign-urls-short.csv -NoTypeInformation

-ThrottleLimit 8がすべてのポイントです。ファイルが50行でも5万行でも、処理中のリクエストは常に8つです。-Parallelには2つ注意点があります。各反復処理は独自のRunspaceで実行されるため、$using:で渡さない限り、呼び出し元の関数や変数は見えません。またPowerShell 7が必要です。Windows PowerShell 5.1で数百行を処理するなら、通常のforeachが現実的な選択肢です。

エラーをスローするのではなく出力列に書き込むと、処理を再実行できます。冪等性キーは遷移先から生成されるため、3行を修正した後でファイル全体を再実行しても費用はかからず、重複も作成されません。

遷移先のCSVを読み込み、スロットル制限付きで各行の短縮サービスAPIを呼び出し、短縮リンクを書き出して結果を抜き取り確認する4段階のPowerShellパイプライン

印刷やメール送信をする前に、抜き取り確認を行ってください。3行に対してcurl.exe -sI https://s.elido.me/ab12cdを実行するだけなら10秒で済み、列が1つずれていた場合も発見できます。

PowerShellが適している場合

データがすでにCSVやActive Directoryにある場合、ジョブがWindowsのスケジュールタスクで動く場合、または保守担当者がアプリケーション開発者ではなくシステム管理者である場合に適しています。アプリケーションに組み込んで出荷するものなら、型付きクライアントと依存性注入を使うC#版の方がテストしやすいでしょう。

いずれの場合も、キーは環境変数またはボールトから取得し、明示的な-TimeoutSecを指定し、呼び出しをtry/catchで囲み、2回実行される可能性があるものには安定した冪等性キーを付けてください。APIとSDKのページでは生成済みクライアントを、開発者向けソリューションではAPIで公開されているその他の機能を扱っています。

基礎解説シリーズを読む

この記事はengineeringクラスターに属しています。まず無料のURL短縮サービスAPIガイドを読み、次にレート制限と冪等性へ進んでください。最新のリファレンスはAPIドキュメントにあり、Googleスプレッドシートから短縮リンクを一括インポートする方法では、同じCSV作業をノーコードで行う方法を解説しています。

ブログ内の関連記事

よくある質問

PowerShellでURLを短縮するにはどうすればよいですか?

短縮サービスのlinksエンドポイントに対して、-Method Postを指定したInvoke-RestMethodを呼び出し、AuthorizationヘッダーにAPIキーを入れ、遷移先をJSONとして渡します。コマンドレットがレスポンスを解析するため、ConvertFrom-Jsonの手順なしで、$link.short_urlをすぐに利用できます。

PowerShellでURLを短縮するためにモジュールは必要ですか?

いいえ。Invoke-RestMethodはWindows PowerShell 3.0とすべてのPowerShell 7に組み込まれており、短縮サービスへの呼び出しは1つのリクエストで済みます。1回のPOSTではなく、多数のエンドポイントに対してコマンドレット形式のラッパーを使いたい場合は、モジュールを導入する価値があります。

なぜInvoke-RestMethodは404や401でエラーをスローするのですか?

4xxや5xxを終了エラーとして扱うためです。これは、ほとんどのHTTPクライアントとは逆の動作です。呼び出しをtry/catchで囲むか、PowerShell 7で-SkipHttpErrorCheckを渡してください。そうすればレスポンスオブジェクトが返され、APIが返したエラー本文を読み取れます。

PowerShellスクリプトの外にAPIキーを置くにはどうすればよいですか?

$env:ELIDO_API_KEYで環境変数から読み込むか、SecretManagementモジュールで保存してGet-Secretで取得します。.ps1ファイルに貼り付けたキーは、ソース管理、コンソール履歴、そして有効になっているトランスクリプトログに残ってしまいます。

CSVのURL列全体を短縮するにはどうすればよいですか?

ファイルをImport-Csvで読み込み、ForEach-Object -Parallelに-ThrottleLimit 8を指定してパイプし、元のURLの隣に短縮リンクを置いた結果をExport-Csvで書き出します。実行をAPIのレート制限内に保つのがスロットルです。これがないと、大きなファイルではすべてのリクエストが同時に開始されます。

ForEach-Object -ParallelはWindows PowerShell 5.1で動作しますか?

いいえ。PowerShell 7以降が必要です。5.1での現実的な選択肢は、数百行程度なら十分な通常のforeachループか、バッチが大きく追加コードに見合う場合のRunspaceプールです。

Elidoを試す

URLを貼り付けて短縮リンクを取得

登録不要。リンクは30日間有効。永久に保存するには登録してください。

Free、登録不要 · 1日あたり2件

Elidoを試す

EUホスティングのURL短縮サービス。カスタムドメイン、詳細な分析、オープンAPI付き。無料プラン - クレジットカード不要。

タグ
how to shorten a url in powershell
powershell url shortener
invoke-restmethod post json
url shortener api powershell
powershell bulk shorten urls
powershell csv api script

続きを読む