Box CLI でページ割りを使用する

Box CLI でページ割りを使用する

Box CLI でページ割りを使用する

Box CLI では、listコマンド(例: box users:listなど、:list部分は省略可)を使用することでオブジェクト(ユーザーなど)の一覧を取得することができますが、上限が1000となっており、またページ割り機能も未実装のようなので(執筆時点)、1000件を超える場合は全部のデータが取得できないようです(関連Issue)。
そこで、listコマンドを使用せずにbox requestコマンドでページ割りを行って1000件以上ある場合でも、全部のデータを取得するスクリプトを作成しました。内容を以下に記載します。

利用環境

以下の環境を使用しました

  • Box CLI: ver 3.1.0(バージョン依存しないと思いますが、一応記載)
  • OS: Windows 10
  • Powershell: 5.1.19041.3031

MacOSおよびLinuxでも、pwshをインストールすることで利用できるはずです。
Ref: https://ja.developer.box.com/guides/cli/quick-start/powershell-script-templates/#前提条件

完成系

いきなり完成系から記載しちゃいます。以下の例は、Boxのゴミ箱のアイテム全件をマーカーベースのページ割りを使用して取得しCSVに出力するスクリプトになります(ゴミ箱のアイテムってすぐ多くなってウェブアプリで見るのが大変ですよね)。

# 0. Save the current encoding and switch to UTF-8. (Ref: https://stackoverflow.com/questions/58438095/powershell-string-variable-with-utf-8-encoding#answer-58438716)
$prev = [Console]::OutputEncoding
[Console]::OutputEncoding = [System.Text.UTF8Encoding]::new()

# 1. Get 1st page via marker-based pagination
$raw_data = box request /folders/trash/items --query="""usemarker=true&limit=100""" --fields=body.entries,body.next_marker --json

# 2. Convert JSON string to JSON
$json = $raw_data | ConvertFrom-Json

# 3. Convert JSON to CSV and write it to output.csv
$json.body.entries | Export-Csv -NoTypeInformation -Path .\output.csv -Encoding utf8

$cnt = 0
while( $json.body.next_marker ) {
  # 4. Build the query parameter for the next page
  $next_query = "usemarker=true&limit=100&marker=" + $json.body.next_marker

  # 5. Get 2nd page via marker-based pagination
  $next_data =  box request /folders/trash/items --query="""$next_query""" --fields=body.entries,body.next_marker --json

  # 6. Convert JSON string to JSON
  $json = $next_data | ConvertFrom-Json

  # 7. Append the data for 2nd page to output.csv
  $json.body.entries | Export-Csv -Path .\output.csv -Append -Encoding utf8

  # 8. Repeat step #4 to step #7 until you reach the last page (aka next_marker is null).
  $cnt++
  "Page $cnt, next_marker: " + $json.body.next_marker
}

# Restore the previous encoding.
[Console]::OutputEncoding = $prev

解説

コメントをちょこちょこ入れておりますが、一応各行の処理内容について記載します。

ステップ1

  • BoxCLIにはbox trash(もしくはbox trash:list)というコマンドレットがありますが、前述の通りこちらはページ割りに対応しておらず、1000件以上のアイテムを取得することができません。
  • そこで、box requestという任意のURLやパラメータを指定できるコマンドレットを使用して、マーカーベースのページ割りのパラメータであるusemarker=trueをCLIへと渡します。
  • なお、box requestを使用すると、以下のようにステータスコードやレスポンスヘッダーなど不要な情報も含まれます。
PS C:\Users\kojimaru> box request /folders/trash/items --query="""usemarker=true&limit=100"""
Status Code: 200
Headers:
    Date: 'Sun, 17 Sep 2023 22:47:32 GMT'
    Content-type: application/json
    Transfer-encoding: chunked
    X-envoy-upstream-service-time: '364'
    Box-request-id: 185e9224815c2641f06cad53eeb33f71
    Cache-control: 'no-cache, no-store'
    Strict-transport-security: max-age=31536000
    Via: 1.1 google
    Alt-svc: 'h3=":443"; ma=2592000,h3-29=":443"; ma=2592000'
Body:
    Entries:
        -
            Type: folder
            ID: '85557482328'
            Sequence ID: '1'
            ETag: '1'
            Name: '26061'
    Limit: 1
    Next Marker: >-
        eyJ0eXBlIjoiT3duZWRCeUZvbGRlciIsImRpciI6Im5leHQiLCJ0YWlsIjoiZXlKMmFXVjNVMk52Y0dVaU9pSmhiR3dpTENKc1lYTjBTV1FpT2pnMU5UVTNORGd5TXpJNGZRIn0

ので、--fields=body.entries,body.next_markerで必要なフィールドのみが出力されるように指定します。また、--jsonでJSON形式になるように指定します

PS C:\Users\kojimaru> box request /folders/trash/items --query="""usemarker=true&limit=100""" --fields=body.entries,body.next_marker --json
{
    "body": {
        "entries": [
            {
                "type": "folder",
                "id": "85557482328",
                "sequence_id": "1",
                "etag": "1",
                "name": "26061"
            }
        ],
        "next_marker": "eyJ0eXBlIjoiT3duZWRCeUZvbGRlciIsImRpciI6Im5leHQiLCJ0YWlsIjoiZXlKMmFXVjNVMk52Y0dVaU9pSmhiR3dpTENKc1lYTjBTV1FpT2pnMU5UVTNORGd5TXpJNGZRIn0"
    }
}

※ちなみに

ステップ2

  • ステップ1で取得したデータはJSON形式に成形されてますが、データ型としては文字列なのでConvertFrom-JsonでJSONオブジェクトに変換します。

ステップ3

  • JSONオブジェクトの要素であるbody.entriesの内容(最初のページの100件)をExport-CsvでCSVファイルへと書き出します。
  • -Encoding utf8を指定することでBOM付きのCSVとして出力します(Excelで開く際の文字化け防止)。
  • -NoTypeInformationを指定することでカラムヘッダーの上の行に挿入される不要な行(#TYPE情報ヘッダー)を削除します。

ステップ4

  • body.next_markerが存在する場合(次のページが存在する場合)は、そのmarkerを使用して次のページを取得するためのクエリ文字列を生成します。

ステップ5~8

  • ステップ4のクエリ文字列を使用してリクエストを送信します。
  • 結果をJSONオブジェクトに変換し(ステップ2と同様)、それをCSVファイルに追記(-Append)します
  • body.next_markerが存在する場合(さらに次のページが存在する場合)は、そのmarkerを使用して次のページを取得するため、ステップ4~7を繰り返します。body.next_markerが存在しない場合はこれ以上のアイテムがない(最終ページに到達した)ということなので、ループから抜けて終了します。

ステップ0

  • ここまでの解説ではスルーしてましたが、この前処理は一体なんなのか?これは文字化け防止の処理になります。
  • 今回のスクリプトでは、Box CLI(外部プログラム)で取得した出力結果を変数に代入しているのですが、その際に[Console]::OutputEncodingで定義されたエンコード方式を使用してしまうようです。よって、今回の私の日本語OSのような環境だと、以下のようにshift-jisが使用されることでBox CLIの出力エンコード方式と一致せず、文字化けが起きているようでした。
PS C:\Users\kojimaru> [Console]::OutputEncoding

BodyName          : iso-2022-jp
EncodingName      : 日本語 (シフト JIS)
HeaderName        : iso-2022-jp
WebName           : shift_jis
WindowsCodePage   : 932
IsBrowserDisplay  : True
IsBrowserSave     : True
IsMailNewsDisplay : True
IsMailNewsSave    : True
IsSingleByte      : False
EncoderFallback   : System.Text.InternalEncoderBestFitFallback
DecoderFallback   : System.Text.InternalDecoderBestFitFallback
IsReadOnly        : True
CodePage          : 932

実行

  • スクリプトの内容をコピーして「GetTrashedFolders.ps1」というファイル名に保存して、Powershellから実行してみます。
  • すると以下のようにコンソール上にページ遷移の進捗が記録され、「output.csv:にもりもり各ページのアイテムが追記されていきます。
PS C:\Development\BoxCLI> .\GetTrashedFolders.ps1
Page 1, next_marker: eyJ0eXBlIjoiT3duZWRCeUZvbGRlciIsImRpciI6Im5leHQiLCJ0YWlsIjoiZXlKMmFXVjNVMk52Y0dVaU9pSmhiR3dpTENKc1lYTjBTV1FpT2pnMU5UVTNOVFE0TXpJNGZRIn0
Page 2, next_marker: eyJ0eXBlIjoiT3duZWRCeUZvbGRlciIsImRpciI6Im5leHQiLCJ0YWlsIjoiZXlKMmFXVjNVMk52Y0dVaU9pSmhiR3dpTENKc1lYTjBTV1FpT2pnMU5UVTNOVGcxTVRNeWZRIn0
Page 3, next_marker: eyJ0eXBlIjoiT3duZWRCeUZvbGRlciIsImRpciI6Im5leHQiLCJ0YWlsIjoiZXlKMmFXVjNVMk52Y0dVaU9pSmhiR3dpTENKc1lYTjBTV1FpT2pnMU5UVTNOakU0T1RZM2ZRIn0
Page 4, next_marker: eyJ0eXBlIjoiT3duZWRCeUZvbGRlciIsImRpciI6Im5leHQiLCJ0YWlsIjoiZXlKMmFXVjNVMk52Y0dVaU9pSmhiR3dpTENKc1lYTjBTV1FpT2pnMU5UVTNOalV4TlRJNGZRIn0
Page 5, next_marker: eyJ0eXBlIjoiT3duZWRCeUZvbGRlciIsImRpciI6Im5leHQiLCJ0YWlsIjoiZXlKMmFXVjNVMk52Y0dVaU9pSmhiR3dpTENKc1lYTjBTV1FpT2pnMU5UVTNOamcxT1RNeWZRIn0
Page 6, next_marker: eyJ0eXBlIjoiT3duZWRCeUZvbGRlciIsImRpciI6Im5leHQiLCJ0YWlsIjoiZXlKMmFXVjNVMk52Y0dVaU9pSmhiR3dpTENKc1lYTjBTV1FpT2pnMU5UVTNOekV5TnpJNGZRIn0
Page 7, next_marker: eyJ0eXBlIjoiT3duZWRCeUZvbGRlciIsImRpciI6Im5leHQiLCJ0YWlsIjoiZXlKMmFXVjNVMk52Y0dVaU9pSmhiR3dpTENKc1lYTjBTV1FpT2pnMU5UVTNOelF3TXpJNGZRIn0
Page 8, next_marker: eyJ0eXBlIjoiT3duZWRCeUZvbGRlciIsImRpciI6Im5leHQiLCJ0YWlsIjoiZXlKMmFXVjNVMk52Y0dVaU9pSmhiR3dpTENKc1lYTjBTV1FpT2pnMU5UVTNOelkyTnpJNGZRIn0
Page 9, next_marker: eyJ0eXBlIjoiT3duZWRCeUZvbGRlciIsImRpciI6Im5leHQiLCJ0YWlsIjoiZXlKMmFXVjNVMk52Y0dVaU9pSmhiR3dpTENKc1lYTjBTV1FpT2pnMU5UVTNOemt5TnpNeWZRIn0
Page 10, next_marker: eyJ0eXBlIjoiT3duZWRCeUZvbGRlciIsImRpciI6Im5leHQiLCJ0YWlsIjoiZXlKMmFXVjNVMk52Y0dVaU9pSmhiR3dpTENKc1lYTjBTV1FpT2pnMU5UVTNPREUzT1RNeWZRIn0
<snip>

おわりに

  • 今回の例ではBox APIで2種類あるページ割りのうち、マーカーベースのページ割りを使用しましたが(こっちのほうがめんどくさそうだったので敢えて)、もう片方のオフセットベースのページ割りbox requestでパラメータとしてoffsetの値を指定しインクリメントしながらページ遷移することで同様のことができると思います。
  • また、ゴミ箱のアイテム取得を例にしましたが、他のリソース(ユーザーやコラボレーションなど)でも同様の処理が可能です。
  • 公式でもCLIを使用したPowershellのスクリプトをいくつか用意しているようなので、そちらも見てみるとおもしろいかも https://ja.developer.box.com/guides/cli/scripts/#powershellスクリプト