1
2

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 + curl.exe で REST API を扱った際につまずいたあれこれ~Redmine へのREST API アクセス~

Posted at

はじめに

本記事はPowerShell でcurl.exe を使用し、REST APIを用いた操作をする際につまずいた内容と解決方法をまとめたものです。
職場で自由にlinuxが使えない方Windows環境に縛られている方 などのお役に立てれば幸いです。

通常ならInvoke-WebRequest を使用しますが、今回クライアント証明書による認証が必要なためcurl.exe を使用しています。

目次

環境

PowerShell

Name                           Value
----                           -----
PSVersion                      5.1.19041.5007
PSEdition                      Desktop
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0...}
BuildVersion                   10.0.19041.5007
CLRVersion                     4.0.30319.42000
WSManStackVersion              3.0
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1

curl.exe

curl 8.9.1 (Windows) libcurl/8.9.1 Schannel zlib/1.3 WinIDN
Release-Date: 2024-07-31
Protocols: dict file ftp ftps http https imap imaps ipfs ipns mqtt pop3 pop3s smb smbs smtp smtps telnet tftp
Features: alt-svc AsynchDNS HSTS HTTPS-proxy IDN IPv6 Kerberos Largefile libz NTLM SPNEGO SSL SSPI threadsafe Unicode UnixSockets

REST API は Redmine 5.0.9 に対して行った。

結論

  • PowerSheell でlinux と同じようなcurl が使用したい場合は curl.exe で始める
  • [Console]::OutputEncoding をREST API で帰ってくるデータの文字コードに合わせる
    • [Console]::OutputEncoding=[Text.Encoding]::UTF8 など
  • curl.exe でもなどがエスケープされるので回避するためにPOSTのデータはファイルに書き出す
  • curl.exe のファイル読み込みではBOM付きUTF-8を解釈できないためBOMなしUTF-8 で保存する
    • PowerShell 5.1では Set-Content-utf8NoBOM が使えない
    • 代わりに[Text.Encoding]::UTF8.GetBytes($reqJson) | Set-Content -Encoding Byte を使う

つまずいた内容

curl が使えない

PowerShell でcurl example.com をするとちゃんと帰ってくる。
だが、実態はInvoke-WebRequest のエイリアス。
なので -d とかを使おうとするとエラーになる。

PS Path\To\Desktop> curl -d "" example.com
Invoke-WebRequest : 引数 'example.com' を受け入れる位置指定パラメーターが見つかりません。

解決策

curl.exe で実行ファイルを明示にする

変数に入れると文字化けする

curl.exe https://www.yahoo.co.jp/ をするとちゃんと帰ってくる
だが、

$res = https://www.yahoo.co.jp/
$res

とすると文字化けする。

<title>Yahoo! JAPAN</title><meta name="description" content="縺ゅ↑縺溘・豈取律繧偵い繝・・繝・・繝医☆繧区ュ蝣ア繝昴・繧ソ繝ォ縲よ、懃エ「縲√ル繝・繝シ繧ケ縲∝、ゥ豌励€√せ繝昴・繝・€√Γ繝シ繝ォ縲√す繝ァ繝・ヴ繝ウ繧ー縲√が繝シ繧ッ繧キ繝ァ繝ウ縺ェ縺ゥ萓ソ蛻ゥ縺ェ繧オ繝シ繝薙せ繧貞ア暮幕縺励※縺・∪縺吶€・/><meta name="robots" content="noodp"/>

この事象が発生する原因は、PowerShellスクリプトが外部コマンドの実行結果を文字コードがSJISであることを期待しているため。
UTF-8 で帰ってきた外部コマンドの実行結果をPowerShellがSJISと思い文字コードの変換を行うため文字化けする。

解決策

詳しくは分からないが、PowerShellとやり取りする文字コードをUTF-8 にする。

[Console]::OutputEncoding=[Text.Encoding]::UTF8

をスクリプトの頭に書いた。

変数に入れると進捗状況が紛れ込む

今はどうも再現できないが レスポンスを変数に格納すると進捗状況が紛れ込むようになった。
再現出来たら詳細を書く。

解決策

標準エラー出力をファイルへリダイレクト

$(curl.exe example.com 2>garbage.txt)

ファイルではなく nul (Linuxの/dev/nullに該当?)にするとエラーとなったため適当なファイルに吐き出している。

ステータスコードが欲しい!

curl.exe からステータスコードを取得できずに苦戦。
コマンド自体の成功、失敗を記録する$LASTEXITCODE があるがステータスコードはとれないため不採用に。

解決策

かなり強引なやり方。-w "%{thhp_code}"を使用した。

$res = curl.exe -w "{%thhp_code}" example.com
$statusCode = $res.Substring($res.Length - 3)
$resJson = $res.Substring(0,$res.Length - 3)

あまりにも強引すぎる

curl.exe でPOSTをするとエラーになる

上記の問題をクリアしたらcurl.exe で GETをすることはできるようになったと思う。
だが、POSTリクエストで -d を使うとエラーになる

curl.exe -d "空白があると バグる" example.com
これを直接PowerShell で実行する分にはバグらなかった。(困惑)

結論から言うと -d で設定したデータの""やら がcurl.exe 実行時、コマンドの一部と解釈されていることが原因。
エスケープしてみるとか試したがうまくいかない。

解決策

データを ファイルで書き出して -d "@data.txt" のようにして参照する。
この方がエスケープとか考えなくていいから楽まであった。
書き出し方は後述。

POST データをファイルにしたのに400が返ってくる

ここでさらに問題が発生。
$data | Set-Content -Path "Path\To\Temp\data.txt" -Encoding UTF8
で書き出してPOSTを行ったが400 (Bad Request) が返ってきた。

原因はSet-Contentのエンコードにある。
WindwosのUTF-8はUTF-8であるがBOMありなのである。
このBOMの有無はcurl.exeは吸収せずそのまま送るっぽいのでBOM付きでサーバに送信され、400となった。
しかも立ち悪いことにSet-ContentでUTF-8 BOMなしを選べない。(PowerShell 7なら可能)

解決策

Qiitaの記事を参考にさせていただいた。

[Text.Encoding]::UTF8.GetBytes($data) | Set-Content -Encoding Byte -NoNewLine -Path .\data.txt

[Text.Encoding]::UTF8.GetBytes($data)でBOMなしUTF-8に変換して Set-Content-Encoding Byteでそのまま流しているらしい。

過程で作成したもの

  • 受けたPOSTデータを標準出力に出力し、そのまま返すスクリプト
    • Chat GPT に書いてもらいました

プロンプト

localhost:8081 にアクセスし、POSTリクエストのデータを受け取り標準出力すると同時に
まったく同一のデータを返すPowershellスクリプトを作成してください。
管理者権限が不要な構成でお願いします

スクリプト

# ポート8081でリスニングするためのHttpListenerオブジェクトを作成
$listener = New-Object System.Net.HttpListener
$listener.Prefixes.Add("http://localhost:8081/")
$listener.Start()
Write-Host "Listening on http://localhost:8081/ ..."

# 無限ループでリクエストを処理
while ($true) {
    # 非同期で接続待ち
    $context = $listener.GetContext()

    # POSTリクエストかを確認
    if ($context.Request.HttpMethod -eq "POST") {
        # リクエストからデータを取得
        $reader = New-Object System.IO.StreamReader($context.Request.InputStream)
        $requestData = $reader.ReadToEnd()
        $reader.Close()

        # 標準出力にリクエストデータを表示
        Write-Host "Received POST data: $requestData"

        # レスポンスとして同じデータを返す
        $response = $context.Response
        $buffer = [System.Text.Encoding]::UTF8.GetBytes($requestData)
        $response.ContentLength64 = $buffer.Length
        $response.OutputStream.Write($buffer, 0, $buffer.Length)
        $response.OutputStream.Close()
    }
}

# 停止する場合はCtrl+C

さいごに

ここまで見てくれてありがとうございます。
誤りなどありましたらコメントいただけますと幸いです。

Windowsは文字コードとかでかなり悩まされますね...
職場でもLinuxが(というかPythonとか)使いたい...

参考

1
2
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
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?