PowerShellで HTTPアクセスする いくつかの方法

PowerShellを使って手軽にHTTPアクセスしたいと思い、調べてみました。
手軽にできたのですが、色々やり方があり、少し整理したくなったので、まとめてみました。

# HTTPクライアント

HTTPアクセスするためのクライアント機能は、Invoke-WebRequestコマンドレット、Invoke-RestMethodコマンドレットとして提供されていました。
ただし、PowerShell 3.0 以降です。
古い環境では、これらのコマンドレットが使えないので、替わりに .NET Framework を使うことで、実現できました。

主要コマンドレット/クラス

- Invoke-WebRequestコマンドレット：PowerShell 3.0 以降
- Invoke-RestMethodコマンドレット：PowerShell 3.0 以降
- HttpClientクラス：.NET Framework 4.5 以降（なので、PowerShell 4.0 以降。Invoke-WebRequest/Invoke-RestMethodコマンドレットで概ね同じことができると思ったので今回未使用。）
- WebClientクラス：.NET Framework 1.1 以降（なので、どのPowerShellバージョンでも使用可能。HttpWebRequest/HttpWebResponseクラスより少し便利。）
- HttpWebRequest/HttpWebResponseクラス：.NET Framework 1.1 以降（なので、どのPowerShellバージョンでも使用可能。WebClientクラスより細かいことができる。）

上記のように、HTTPアクセスするためのコマンドレットやクラスが色々用意されています。
正直、どれを使えばよいのか迷ってしまったのですが、一通り触ってみて、次のように使い分ければよいのかな、と思いました。

- PowerShell 3.0 以降の環境
  - APIをコールするなら、Invoke-RestMethodコマンドレット
  - Webページを取得するなら、Invoke-WebRequestコマンドレット
- PowerShell 2.0 以前の環境
  - 基本、WebClientクラス
  - リクエスト/レスポンスを細かく制御したいなら、HttpWebRequest/HttpWebResponseクラス

## Invoke-WebRequestコマンドレット

### GETメソッド

例：Google検索（GETメソッド、KeyValue風リクエスト、HTMLレスポンス）

```powershell
$url = 'https://www.google.com/search'
$params = @{"q"="Powershell"}

# 方式１．コンテンツをレスポンスオブジェクトとして取得
$res = Invoke-WebRequest $url -Body $params

# 方式２．コンテンツをファイルに保存
Invoke-WebRequest $url -Body $params -OutFile 'D:\tmp\out.txt'

# 補足．エラー考慮
try {
  $res = Invoke-WebRequest 'https://www.google.com/存在しないページ'
  $res.StatusCode.ToString() + " " + $res.StatusDescription 
  $res.Headers
  $res.Content
} catch {
  # ステータスコード
  echo $_.Exception.Response.StatusCode.value__
  # レスポンスボディ
  $stream = $_.Exception.Response.GetResponseStream()
  $reader = New-Object System.IO.StreamReader $stream
  $reader.BaseStream.Position = 0
  $reader.DiscardBufferedData()
  echo $reader.ReadToEnd()
  $reader.Close()  # Closeは$readerと$streamの一方を呼び出せばよい。両方呼び出しても害はない
  $stream.Close()
}

# 補足．レスポンスHTMLを解析
$res = Invoke-WebRequest 'https://weather.yahoo.co.jp/weather/jp/13/4410.html'
$res.ParsedHtml.title
$res.ParsedHtml.getElementById("wrnrpt").innerText
$res.ParsedHtml.getElementsByName("description") | %{ $_.GetAttribute("content") }
$res.ParsedHtml.getElementsByTagName("title") | %{ $_.innerText }
```

- Invoke-WebRequestに、-SessionVariableパラメータを設定するとセッション情報を変数に保存できて、次のInvoke-WebRequest実行時に、-WebSessionパラメータを設定してセッション情報を引き継いだアクセスができるらしい。

例：Yahoo! JAPAN 日本語形態素解析WebAPI（GETメソッド、KeyValue風リクエスト、XMLレスポンス）

```powershell
$clientid = "あなたの Yahoo! JAPAN WebAPI 用クライアントID を設定する"
$url = "https://jlp.yahooapis.jp/MAService/V1/parse"
$params = @{
  "appid" = $clientid;
  "results" = "ma,uniq";
  "uniq_filter" = "9|10";
  "sentence" = "庭には二羽ニワトリがいる。"}
$res = Invoke-WebRequest $url -Body $params
$xmlobj = [Xml]$res.Content
$xmlobj.OuterXml
```

### POSTメソッド

例：Yahoo! JAPAN 日本語形態素解析WebAPI（POSTメソッド、KeyValue風リクエスト、XMLレスポンス）

```powershell
$url = "https://jlp.yahooapis.jp/MAService/V1/parse" 
$headers = @{
  "User-Agent" = "Yahoo AppID: $clientid";
  "Content-Type" = "application/x-www-form-urlencoded"}

# 方式１．POSTデータを文字列で指定。
$poststr = "results=ma,uniq" +
  "&uniq_filter=" + [System.Web.HttpUtility]::UrlEncode("9|10") +
  "&sentence=" + [System.Web.HttpUtility]::UrlEncode("庭には二羽ニワトリがいる。")
$res = Invoke-WebRequest $url -Method 'POST' -Headers $headers -Body $poststr
$xmlobj = [Xml]$res.Content
$xmlobj.OuterXml

# 方式２．POSTデータを連想配列で指定。自動でURLエンコードされる。
$params = @{
  "results" = "ma,uniq"; 
  "uniq_filter" = "9|10"; 
  "sentence" = "庭には二羽ニワトリがいる。"}
$res = Invoke-WebRequest $url -Method 'POST' -Headers $headers -Body $params
$xmlobj = [Xml]$res.Content
$xmlobj.OuterXml
```

例：日本語係り受け解析（POSTメソッド、JSONリクエスト/レスポンス）

```powershell
$url = 'https://jlp.yahooapis.jp/DAService/V2/parse'
$headers = @{
  "User-Agent" = "Yahoo AppID: $clientid";
  "Content-Type" = "application/x-www-form-urlencoded"}
# JSONリクエスト。もしASCII文字だけならエンコード不要。
$poststr = ConvertTo-Json @{
  id="123"; jsonrpc="2.0"; method="jlp.daservice.parse";
  params=@{q="うちの庭には二羽鶏がいます"}}
$postbytes = [System.Text.Encoding]::UTF8.GetBytes($poststr)
$res = Invoke-WebRequest $url -Method 'POST' -Headers $headers -Body $postbytes
$jsonobj = ConvertFrom-Json $res.Content
Write-Output (ConvertTo-Json $jsonobj -Depth 100)
```

## Invoke-RestMethodコマンドレット

### GETメソッド

例：Yahoo! JAPAN 日本語形態素解析WebAPI（GET）

```powershell
$clientid = "あなたの Yahoo! JAPAN WebAPI 用クライアントID を設定する"
$url = "https://jlp.yahooapis.jp/MAService/V1/parse"
$params = @{
  "appid" = $clientid;
  "results" = "ma,uniq";
  "uniq_filter" = "9|10";
  "sentence" = "庭には二羽ニワトリがいる。"}
$xmldoc = Invoke-RestMethod $url -Body $params
Write-Output $xmldoc.OuterXml

# エラーを考慮する場合
try {
  $xmldoc = Invoke-RestMethod "https://jlp.yahooapis.jp/存在しないページ" -Body $params
  Write-Output $xmldoc.OuterXml
} catch {
  # ステータスコード
  echo $_.Exception.Response.StatusCode.value__
  # レスポンスボディ
  $stream = $_.Exception.Response.GetResponseStream()
  $reader = New-Object System.IO.StreamReader $stream
  $reader.BaseStream.Position = 0
  $reader.DiscardBufferedData()
  echo $reader.ReadToEnd()
  $reader.Close()  # Closeは$readerと$streamの一方を呼び出せばよい。両方呼び出しても害はない
  $stream.Close()
}
```

### POSTメソッド

例：Yahoo! JAPAN 日本語形態素解析WebAPI（POST）

```powershell
$url = "https://jlp.yahooapis.jp/MAService/V1/parse" 
$headers = @{
  "User-Agent" = "Yahoo AppID: $clientid";
  "Content-Type" = "application/x-www-form-urlencoded"}

# 方式１．POSTデータを文字列で指定。
$poststr = "results=ma,uniq" +
  "&uniq_filter=" + [System.Web.HttpUtility]::UrlEncode("9|10") +
  "&sentence=" + [System.Web.HttpUtility]::UrlEncode("庭には二羽ニワトリがいる。")
$xmldoc = Invoke-RestMethod $url -Method 'POST' -Headers $headers -Body $poststr
Write-Output $xmldoc.OuterXml

# 方式２．POSTデータを連想配列で指定。自動でURLエンコードされる。
$params = @{
  "results" = "ma,uniq"; 
  "uniq_filter" = "9|10"; 
  "sentence" = "庭には二羽ニワトリがいる。"}
$xmldoc = Invoke-RestMethod $url -Method 'POST' -Headers $headers -Body $params
Write-Output $xmldoc.OuterXml
```

例：日本語係り受け解析（POSTメソッド、JSONリクエスト）

```powershell
$url = 'https://jlp.yahooapis.jp/DAService/V2/parse'
$headers = @{
  "User-Agent" = "Yahoo AppID: $clientid";
  "Content-Type" = "application/x-www-form-urlencoded"}

# JSONリクエスト。もしASCII文字だけならエンコード不要。
$poststr = ConvertTo-Json @{
  id="123"; jsonrpc="2.0"; method="jlp.daservice.parse";
  params=@{q="うちの庭には二羽鶏がいます"}}
$postbytes = [System.Text.Encoding]::UTF8.GetBytes($poststr)
$res = Invoke-RestMethod $url -Method 'POST' -Headers $headers -Body $postbytes
Write-Output (ConvertTo-Json $res -Depth 100)
```

## WebClientクラス

### GETメソッド

例：Google検索（GETメソッド）

```powershell
$url = 'https://www.google.com/search'
$wc = New-Object System.Net.WebClient 
$wc.QueryString.Add("q", "Powershell")

# 方式１．コンテンツを取得し保存。テキストのエンコード変換なしの模様。 
$wc.DownloadFile($url, 'D:\tmp\out.txt')

# 方式２．コンテンツをバイト配列として取得 
$resbytes = $wc.DownloadData($url) 

# 方式３．コンテンツを文字列として取得。必要なら$wc.Encodingにエンコードを指定する。 
$resstr = $wc.DownloadString($url)

# エラーを考慮する場合
try {
  $resstr = $wc.DownloadString('https://www.google.com/存在しないページ')
} catch {
  # ステータスコード
  echo $_.Exception.InnerException.Response.StatusCode.value__
  # レスポンスボディ
  $stream = $_.Exception.InnerException.Response.GetResponseStream()
  $reader = New-Object System.IO.StreamReader $stream
  $reader.BaseStream.Position = 0
  $reader.DiscardBufferedData()
  echo $reader.ReadToEnd()
  $reader.Close()  # Closeは$readerと$streamの一方を呼び出せばよい。両方呼び出しても害はない
  $stream.Close()
}

# 後片付け
$wc.Dispose()
```

例：Yahoo! JAPAN 日本語形態素解析WebAPI（GET）

```powershell
$clientid = "あなたの Yahoo! JAPAN WebAPI 用クライアントID を設定する"
$url = "https://jlp.yahooapis.jp/MAService/V1/parse"
$wc = New-Object System.Net.WebClient
$wc.Encoding = [System.Text.Encoding]::UTF8
$wc.QueryString.Add("appid", $clientid)
$wc.QueryString.Add("results", "ma,uniq")
$wc.QueryString.Add("uniq_filter", "9|10")
$wc.QueryString.Add("sentence", "庭には二羽ニワトリがいる。")
$resstr = $wc.DownloadString($url)
$wc.Dispose()
Write-Output $resstr
```

### POSTメソッド

例：Yahoo! JAPAN 日本語形態素解析WebAPI（POST）

```powershell
Add-Type -AssemblyName System.Web
$url = "https://jlp.yahooapis.jp/MAService/V1/parse"
$wc = New-Object System.Net.WebClient
$wc.Headers.Add("User-Agent", "Yahoo AppID: $clientid")
$wc.Headers.Add("Content-Type", "application/x-www-form-urlencoded")
$wc.Encoding = [System.Text.Encoding]::UTF8

# 方式１．POSTデータを文字列で設定
$poststr = "results=ma,uniq" +  
  "&uniq_filter=" + [System.Web.HttpUtility]::UrlEncode("9|10") + 
  "&sentence=" + [System.Web.HttpUtility]::UrlEncode("庭には二羽ニワトリがいる。")
$resstr = $wc.UploadString($url, $poststr) 
Write-Output $resstr

# 方式２．POSTデータを名前と値のコレクションで設定
$posthash = New-Object System.Collections.Specialized.NameValueCollection 
$posthash.Add("results", "ma,uniq") 
$posthash.Add("uniq_filter", "9|10") 
$posthash.Add("sentence", "庭には二羽ニワトリがいる。") 
$resbytes = $wc.UploadValues($url, $posthash) 
$resstr = [System.Text.Encoding]::UTF8.GetString($resbytes)
Write-Output $resstr

# 方式３．POSTデータをバイト配列で設定（byte[] UploadData(string url, byte[] postdata)）
# →省略

# 後片付け
$wc.Dispose() 
```

例：日本語係り受け解析（POSTメソッド、JSONリクエスト/レスポンス）

```powershell
Add-Type -AssemblyName System.Web
$url = 'https://jlp.yahooapis.jp/DAService/V2/parse'
$wc = New-Object System.Net.WebClient
$wc.Headers.Add("User-Agent", "Yahoo AppID: $clientid")
$wc.Headers.Add("Content-Type", "application/json")
$wc.Encoding = [System.Text.Encoding]::UTF8
# JSONリクエスト。もしASCII文字だけなら$wc.UploadString()を使用可能。
$poststr = '{"id":"123","jsonrpc":"2.0","method":"jlp.daservice.parse","params":{' +
  '"q":"うちの庭には二羽鶏がいます"' +
  '}}'
$postbytes = [System.Text.Encoding]::UTF8.GetBytes($poststr)
$resbytes = $wc.UploadData($url, $postbytes)
$resstr = [System.Text.Encoding]::UTF8.GetString($resbytes)
Write-Output $resstr
# 後片付け
$wc.Dispose()
```

## HttpWebRequest/HttpWebResponseクラス

- HTTPリクエスト時のヘッダー情報は、HttpWebRequest のプロパティとして設定可能。（例：$res.UserAgent = xxx）
- プロパティで用意されていないヘッダー情報は、HttpWebRequest の Headers プロパティに設定する。（例：$res.Headers['Authorization'] = xxx）

### GETメソッド

例：Google検索（GETメソッド）

```powershell
$url = 'https://www.google.com/search?q=Powershell'
$req = [System.Net.WebRequest]::Create($url)
$res = $req.GetResponse()
echo ($res.StatusCode.value__.ToString() + " " + $res.StatusDescription)
$stream = $res.GetResponseStream()
$reader = New-Object System.IO.StreamReader($stream, [System.Text.Encoding]::UTF8)
echo $reader.ReadToEnd()
$res.Close()  # $stream.Close(),$reader.Close()でも良いし、3つとも実行しても良い。

# エラーを考慮する場合
try {
  $url = 'https://www.google.com/存在しないページ'
  $req = [System.Net.WebRequest]::Create($url)
  $res = $req.GetResponse()
  echo ($res.StatusCode.value__.ToString() + " " + $res.StatusDescription)
  $stream = $res.GetResponseStream()
  $reader = New-Object System.IO.StreamReader($stream, [System.Text.Encoding]::UTF8)
  echo $reader.ReadToEnd()
  $res.Close()
} catch {
  # ステータスコード
  echo $_.Exception.InnerException.Response.StatusCode.value__
  # レスポンスボディ
  $stream = $_.Exception.InnerException.Response.GetResponseStream()
  $reader = New-Object System.IO.StreamReader $stream
  $reader.BaseStream.Position = 0
  $reader.DiscardBufferedData()
  echo $reader.ReadToEnd()
  $stream.Close()  # $reader.Close()でも良いし、2つとも実行しても良い。
}
```

例：Yahoo! JAPAN 日本語形態素解析WebAPI（GETメソッド）

```powershell
$clientid = "あなたの Yahoo! JAPAN WebAPI 用クライアントID を設定する"
$url = "https://jlp.yahooapis.jp/MAService/V1/parse" +
  "?appid=" + $clientid +
  "&results=ma,uniq" +
  "&uniq_filter=9|10" +
  "&sentence=庭には二羽ニワトリがいる。"
$url = [Uri]::EscapeUriString($url)
$req = [System.Net.WebRequest]::Create($url)
$res = $req.GetResponse()
echo ($res.StatusCode.value__.ToString() + " " + $res.StatusDescription)
$stream = $res.GetResponseStream()
$reader = New-Object System.IO.StreamReader($stream, [System.Text.Encoding]::UTF8)
echo $reader.ReadToEnd()
$res.Close()
```

### POSTメソッド

例：Yahoo! JAPAN 日本語形態素解析WebAPI（POSTメソッド、KeyValue風リクエスト）

```powershell
# データ準備
Add-Type -AssemblyName System.Web
$url = "https://jlp.yahooapis.jp/MAService/V1/parse"
$postStr = "results=ma,uniq" +  
  "&uniq_filter=" + [System.Web.HttpUtility]::UrlEncode("9|10") + 
  "&sentence=" + [System.Web.HttpUtility]::UrlEncode("庭には二羽ニワトリがいる。")
$postBytes = [System.Text.Encoding]::UTF8.GetBytes($postStr)
# リクエスト送信
$req = [System.Net.WebRequest]::Create($url)
$req.Method = "POST"
$req.UserAgent = "Yahoo AppID: $clientid"
$req.ContentType = "application/x-www-form-urlencoded"
$req.ContentLength = $postBytes.Length
$stream = $req.GetRequestStream()
$stream.Write($postBytes, 0, $postBytes.Length)
$stream.Close()
# レスポンス受信
$res = $req.GetResponse()
echo ($res.StatusCode.value__.ToString() + " " + $res.StatusDescription)
$stream = $res.GetResponseStream()
$reader = New-Object System.IO.StreamReader($stream, [System.Text.Encoding]::UTF8)
echo $reader.ReadToEnd()
$res.Close()
```

例：日本語係り受け解析（POSTメソッド、JSONリクエスト）

```powershell
Add-Type -AssemblyName System.Web
$url = 'https://jlp.yahooapis.jp/DAService/V2/parse'
$postStr = '{"id":"123","jsonrpc":"2.0","method":"jlp.daservice.parse","params":{' +
  '"q":"うちの庭には二羽鶏がいます"' +
  '}}'
$postBytes = [System.Text.Encoding]::UTF8.GetBytes($postStr)
# リクエスト送信
$req = [System.Net.WebRequest]::Create($url)
$req.Method = "POST"
$req.UserAgent = "Yahoo AppID: $clientid"
$req.ContentType = "application/json"
$req.ContentLength = $postBytes.Length
$stream = $req.GetRequestStream()
$stream.Write($postBytes, 0, $postBytes.Length)
$stream.Close()
# レスポンス受信
$res = $req.GetResponse()
echo ($res.StatusCode.value__.ToString() + " " + $res.StatusDescription)
$stream = $res.GetResponseStream()
$reader = New-Object System.IO.StreamReader($stream, [System.Text.Encoding]::UTF8)
echo $reader.ReadToEnd()
$res.Close()
```

# エンコーディング

HTTPアクセスするときに必要となるエンコーディングについて、メモしておきます。

## テキストのエンコーディング

.NET Framwork の Encodingクラスを使用して、テキストのエンコーディングを変更することができました。

- .NET Framework 1.1 以降
- [Encoding クラス (System.Text) | Microsoft Docs](https://docs.microsoft.com/ja-jp/dotnet/api/system.text.encoding?view=net-5.0)

```powershell
$enc = [System.Text.Encoding]::UTF8
$enc = [System.Text.Encoding]::GetEncoding("SJIS")
# 指定したエンコーディングに変換した文字列のバイト配列を取得
$bytes = $enc.GetBytes("日本語テキストです")
# バイト配列を指定したエンコーディングとして解釈して文字列を取得
$str = $enc.GetString($bytes)
```

## パーセントエンコーディング

次の2種類がある。

- パーセントエンコーディングによる符号化 ： URLのパス部分を符号化する方式。半角スペースが「%20」となる。RFC3986。
- application/x-www-form-urlencodedによる符号化 ： HTTPのPOSTメソッドで送信する文字列に対する符号化方式。半角スペースが「+」となる。

パーセントエンコーディングによる符号化は、.NET Framework の Uriクラスを使用して、実現できました。

- .NET Framework 2.0 以降（PowerShell 2.0 以降）
- .NET Framework 4.0以前と4.5以降で、エンコード対象の記号が少し異なる。

```powershell
$url = 'https://www.google.com/search?q=Powershell 日本語'
# 文字列全体をエンコード
$encstr = [Uri]::EscapeDataString($url)      #=> "https%3A%2F%2Fwww.google.com%2Fsearch%3Fq%3DPowershell%20%E6%97%A5%E6%9C%AC%E8%AA%9E"
$decstr = [Uri]::UnescapeDataString($encstr)
# 予約文字以外をエンコード
$encstr = [Uri]::EscapeUriString($url)       #=> "https://www.google.com/search?q=Powershell%20%E6%97%A5%E6%9C%AC%E8%AA%9E"
$decstr = [Uri]::UnescapeDataString($encstr)
```

application/x-www-form-urlencodedによる符号化は、.NET Framework の HttpUtilityクラスを使用して、実現できました。

- .NET Framework 1.1 以降（PowerShell 1.0 以降）

```powershell
Add-Type -AssemblyName System.Web
$str = "POSTデータ abc :/?&=<>"
# エンコード。デフォルトでUTF8を使用。必要なら第2引数にEncodingを指定する。
$encstr = [System.Web.HttpUtility]::UrlEncode($str)  #=> "POST%e3%83%87%e3%83%bc%e3%82%bf+abc+%3a%2f%3f%26%3d%3c%3e"
# デコード。デフォルトでUTF8を使用。必要なら第2引数にEncodingを指定する。
$decstr = [System.Web.HttpUtility]::UrlDecode($encstr)
```

# コンテンツのデータ形式

HTTPレスポンスで受信するコンテンツのデータ形式について、メモしておきます。
JSONはリクエスト時にも使用する場合あり。
もしかすると、XMLでリクエストすることもあり得るかも。

## HTML

ConvertTo-Htmlコマンドレット

- ConvertFrom-Htmlは存在しない。
- [ConvertTo-Html (Microsoft.PowerShell.Utility) - PowerShell | Microsoft Docs](https://docs.microsoft.com/ja-jp/powershell/module/microsoft.powershell.utility/convertto-html)

COMを使用して、HTMLをパースする方法です。

```powershell
$sample = "<html><head><title>サンプル</title></head>" +
  "<body><span id='text1' name='text1'>サンプルページです。</span></body></html>"
$html = New-Object -com "HTMLFILE"
$html.IHTMLDocument2_write($sample)
$html.Close()
echo $html.title
echo $html.getElementById("text1").innerText
$html.getElementsByName("text1") | %{ echo $_.innerText }
$html.getElementsByTagName("span") | %{ echo $_.innerText }
```

## XML

.NET Framework の XmlDocumentクラスを使用して、XMLの解析や組み立てを行ってみました。

- .NET Framework 1.1 以降（PowerShell 1.0 以降）
- [XmlDocument クラス (System.Xml) | Microsoft Docs](https://docs.microsoft.com/ja-jp/dotnet/api/system.xml.xmldocument)

```powershell
# XMLオブジェクトの作成
$doc = New-Object System.Xml.XmlDocument
$doc.AppendChild($doc.CreateXmlDeclaration("1.0", "utf-8", $null)) | Out-Null
$root = $doc.AppendChild($doc.CreateElement("Root"))
$item1 = $root.AppendChild($doc.CreateElement("Item1"))
$item1.AppendChild($doc.CreateTextNode("text")) | Out-Null
# 文字列表現
Write-Output $doc.OuterXml
# ファイルに保存（BOM付きUTF8）
$doc.Save('D:\tmp\tmp.xml')

# XML文字列の読み込み
$doc = [Xml]'<?xml version="1.0" encoding="utf-8"?><Root><Item1>text</Item1></Root>'
$doc = [System.Xml.XmlDocument]'<?xml version="1.0" encoding="utf-8"?><Root><Item1>text</Item1></Root>'
Write-Output $doc.Root.Item1 

# XMLファイルから読み込み 
$doc = New-Object System.Xml.XmlDocument 
$doc.Load('D:\tmp\tmp.xml') 
Write-Output $doc.Root.Item1
```

## JSON

ConvertFrom-Jsonコマンドレット、ConvertTo-Jsonコマンドレットを利用しました。

- PowerShell 3.0 以降
- [ConvertFrom-Json (Microsoft.PowerShell.Utility) - PowerShell | Microsoft Docs](https://docs.microsoft.com/ja-jp/powershell/module/microsoft.powershell.utility/convertfrom-json)
- [ConvertTo-Json (Microsoft.PowerShell.Utility) - PowerShell | Microsoft Docs](https://docs.microsoft.com/ja-jp/powershell/module/microsoft.powershell.utility/convertto-json)

```powershell
# オブジェクトをJSON文字列に変換。Depthの最大値は100
$jsonstr = ConvertTo-Json -Depth 100 @{
  Number = 123;
  String = '日本語 記号''"{}<>';
  DateTimeStr = Get-Date -Format "yyyy/MM/dd HH:mm:ss";
  Array = @(1,2,3);
  Hash = @{Key1="value1"; Key2="value2"};
  Null = $null;
}
# JSON文字列をオブジェクトに変換
$jsonobj = ConvertFrom-Json '{"Number":123, "String":"日本語 記号''\"{}<>", "DateTimeStr":"2021/1/1 01:02:03", "Array":[1,2,3], "Hash":{"Key1":"value1", "Key2":"value2"}, "Null":null}'
```

上記だと、PowerShell 3.0 以降でないと動作しないので、古い環境でも動作するように、簡易的な関数を考えてみました。
ただし、ハッシュデータをJSON文字列に変換する関数のみ。その逆の変換は、手間がかかりそうなので割愛。

```powershell
function ConvertToJson($data) {
  if ($data -is [string]) {
    '"' + $data + '"'
  } elseif ($data -is [Array]) {
    $arr = $data | %{
      ConvertToJson $_
    }
    '[' + ($arr -join ', ') + ']'
  } elseif ($data -is [Hashtable]) {
    $arr = $data.GetEnumerator() | sort Key | %{
      '"' + $_.Key + '": ' + (ConvertToJson $_.Value)
    }
    '{' + ($arr -join ', ') + '}'
  } else {
    $data
  }
}
$hash = @{
  Number = 123;
  String = "abc";
  Array = @(1,2,3,@(4,5));
  Hash = @{Key1="value1"; Key2="value2"};
}
$jsonstr = ConvertToJson $hash
```
# 補足

## 動作確認環境

- Windows 10
- PowerShell 5.1

## 参考にしたサイト

Invoke-WebRequest/Invoke-WebRequestコマンドレット

- [Invoke-WebRequest (Microsoft.PowerShell.Utility) - PowerShell | Microsoft Docs](https://docs.microsoft.com/ja-jp/powershell/module/microsoft.powershell.utility/invoke-webrequest)
- [Invoke-RestMethod (Microsoft.PowerShell.Utility) - PowerShell | Microsoft Docs](https://docs.microsoft.com/ja-jp/powershell/module/microsoft.powershell.utility/invoke-restmethod)
- [curl/PowerShellでHTTPアクセスいろいろ - Qiita](https://qiita.com/zaki-lknr/items/8950f6acea20961a8afc)
- [PowerShellでスクレイピング　前編　文字列を取得する - PowerShell Scripting Weblog](http://winscript.jp/powershell/304)
- [PowerShellでスクレイピング 後編 HTMLをパースする - PowerShell Scripting Weblog](http://winscript.jp/powershell/305)

WebClient/HttpWebRequest/HttpWebResponseクラス

- [WebClient クラス (System.Net) | Microsoft Docs](https://docs.microsoft.com/ja-jp/dotnet/api/system.net.webclient?view=net-5.0)
- [HttpWebRequest クラス (System.Net) | Microsoft Docs](https://docs.microsoft.com/ja-jp/dotnet/api/system.net.httpwebrequest)
- [HttpWebResponse クラス (System.Net) | Microsoft Docs](https://docs.microsoft.com/ja-jp/dotnet/api/system.net.httpwebresponse)
- [ファイルをダウンロードし表示する - .NET Tips (VB.NET,C#...)](https://dobon.net/vb/dotnet/internet/webclientopenread.html)
- [POSTでデータを送信する - .NET Tips (VB.NET,C#...)](https://dobon.net/vb/dotnet/internet/webrequestpost.html)

その他

- [パーセントエンコーディング - Wikipedia](https://ja.wikipedia.org/wiki/%E3%83%91%E3%83%BC%E3%82%BB%E3%83%B3%E3%83%88%E3%82%A8%E3%83%B3%E3%82%B3%E3%83%BC%E3%83%87%E3%82%A3%E3%83%B3%E3%82%B0)
- [日本語形態素解析 - Yahoo!デベロッパーネットワーク](https://developer.yahoo.co.jp/webapi/jlp/ma/v1/parse.html)
- [日本語係り受け解析（V2） - Yahoo!デベロッパーネットワーク](https://developer.yahoo.co.jp/webapi/jlp/da/v2/parse.html)