LoginSignup
2
8

More than 1 year has passed since last update.

GraphAPIを使ってSharePointリストに大量のデータを投入する

Last updated at Posted at 2022-02-02

概要

  • PowerShellでSharePointリストに大量のデータを登録します。
  • PnPやCSOMではなく、勉強を兼ねてMicrosoft Graphを使います。
  • Graphのバッチ要求により、複数リクエストを一度に送信することで多数のデータを登録します。
  • AzureADのアクセスの種類は要件的に「委任されたアクセス許可」によりユーザーとして登録します。

簡単な要件

  • 管理者は、ユーザーにGraph APIの使用許可を与えSharePointリソースの操作権限を与える。
  • ただし、全てのサイトを操作されては困るためユーザー自身にアクセス権があるサイトに制限したい。
  • その他、ログインにMFAが設定されている場合でも利用でき、利便性を考えある程度の期間は再認証不要としたい。

準備

必要なリソース

①AzureADのアプリ登録
GraphAPIを実行できるよう、事前にAzureADのアプリ登録を行います。

今回の設定では以下の記事が詳しいです。

重要なポイント
今回は「委任されたアクセス許可」により利用するユーザーの権限で接続します。
デスクトップアプリでSSOなどを実現するときに使う方式ですね。アプリにシークレットキーを埋め込む必要はありません。
image.png

それから、SharePointサイトが操作できる権限を付与します。
今回の場合Sites.ReadWrite.AllだけでOKです。
image.png

リダイレクトURIは以下にチェックを入れておきます。
image.png

②PowerShell
AzureADの認証に必要なアクセストークンの取得を簡単にする、MSALライブラリをインストールします。
※管理者として起動が必要です。

Install-Module -Name MSAL.PS

③SharePointリスト
登録先となるSharePointリストです。
色々なデータ型を試したかったので、Person、DateTime、Choices、LookUp列を作成しています。
image.png

必要なパラメータ

Graph APIを利用するにあたり必要なパラメータです。

以下はAzureADアプリ登録のページから取得します。

①$clientID = '{AzureADから取得したクライアントID}'
②$tenantID ='{テナントID}'
③$redirectURI = 'https://login.microsoftonline.com/common/oauth2/nativeclient'

以下はGraph Explorerから取得した方が早いです。

④$siteID = {サイトID、Graph Explorerから取得します。}

サイト名で検索するクエリ
https://graph.microsoft.com/v1.0/sites?$filter=displayName eq '{your site name}'

\$listID = {リストID、Graph Explorerから取得します。}

サイトID/リスト名で検索するクエリ
https://graph.microsoft.com/v1.0/sites/{your site id}/lists?$filter=displayName eq '{your list name}'

事前知識

Microsoft Graphのバッチリクエスト

公式ドキュメントを参照し、仕様や制約を確認しておきます。

  • $batchリソースを使用すると、単一の要求で複数のリクエストを処理させることができる。
  • 指定のJSONスキーマで複数リクエストをまとめる。
  • 一バッチ20リクエスト以下。
  • 順番はidで指定する。
  • ただし前の要求が完了してから次の要求を処理させたい場合はdependsOnプロパティで順序指定する
    • SharePointリストに新規アイテムを登録する場合は、順序指定しないと競合か何かで失敗しました。

スロットリング制限はGraph API全体的なものと各サービス個別の制限があるようです。
ただしSharePointについては明確な閾値は開示できないようです。
要求するときに一定の間隔を開けるなどして配慮します。

フィールドへの値のセット方法

GraphAPIで新規アイテムをPOSTする場合の値の設定方法です。
まず一件手動でSharePointリストに登録し、値を確認してみます。

https://graph.microsoft.com/v1.0/sites/{your site id}/lists/{your list id}/items?expand=fields

image.png

Bool列はtrue/false
Choices(選択肢)列は値をそのままセットすれば良いことが分かります。
奇妙なのはPerson(ユーザー)列やLookup(参照)列です。
自分で設定したPerson列などがなく、「列名+LookupId」の列になっています。標準列のEditor、Authorも同様です。
そしてその値は数字がセットされています。

調べてみると以下のようでした。
Lookup列
単純に「列名+LookupId」のフィールド名に対して、参照先テーブルのIDをセットするだけです。
FieldNameLookupId : "1"

ユーザー列
こちらも「列名+LookupId」のフィールド名に対してセットすれば良いのは同じです。
厄介なのがその値です。
上記画像では6や14といった数字がセットされており、ユーザーのClaimsでもオブジェクトIdでもありません。

この数字は、SharePointサイト内部で非表示のユーザー情報テーブルが作成されており、そこに登録されているユーザーのアイテムidとなるようです。
SharePointサイト内の以下のURLにアクセスすると表示されます。
https://{tenant name}.sharepoint.com/sites/{site name}/_catalogs/users/detail.aspx
image.png

Graph Explorerで以下のリソースを開くとアイテムが表示されます。
日本語環境では「ユーザー情報リスト」という名前でした。

https://graph.microsoft.com/v1.0/sites/{your site id}/lists?$filter=displayName eq 'ユーザー情報リスト'

参考サイト

データ登録方法

PowerShellのコード

前置きが長くなりましたが、今回のコードです。
登録するデータは今回はランダムな値などにします。


$ErrorActionPreference = "stop"

function main(){

    # 必要なパラメータを設定
    $clientID= '{Your clientID}'
    $tenantID ='{Your tenantID}'
    $redirectURI =  'https://login.microsoftonline.com/common/oauth2/nativeclient'
    $loginHint = '{User email}'
    $siteID = '{Your siteID}'
    $listID = '{Your listID}'

    # MSAL.psでアクセストークンを取得(TokenCache保存付き)
    $MsalClientApplication = New-MsalClientApplication -ClientId $clientID -TenantId $tenantID -RedirectUri $redirectURI
    Enable-MsalTokenCacheOnDisk $MsalClientApplication
    $Token = Get-MsalToken -PublicClientApplication $MsalClientApplication -LoginHint $loginHint

    # アクセストークンを付加
    $headers = @{
        Authorization = 'Bearer ' + $Token.AccessToken
    }

    # バッチリクエストを格納する配列
    $requests = @()
    $itemHeader = @{ "Content-Type" = "application/json" }

    # バッチ処理回数の設定
    $itemLimit = 100
    $batchCount = 0
    $batchSize = 20

    for ($i =1; $i -lt $itemLimit+1; $i++) {

        $batchCount++

        # 各リクエストのBodyを定義
        $itemBody = @{
            fields = @{
                'Title' = 'ItemTitle' + $i
                'Number' = Get-Random -Maximum 100 -Minimum 0
                'Bool' = Get-Random $true,$false
                'Choices' = Get-Random "High","Normal","Low"
                'DateTime' = Get-Date -Format "yyyy-MM-dd"
                'PersonLookupId' = Get-Random "14","6","13"
                'LookUpLookupId' = Get-Random "1","2","3","4","5","6","7"
            }
        }        


        # 各リクエストのスキーマを定義
        $request = @{
            id      = "$batchCount"
            method  = "POST"
            url     = "/sites/${siteID}/lists/${listID}/items"
            body    = $itemBody
            headers = $itemHeader
        }
        # 要求順序指定
        if($batchCount -ne 1) {$request["dependsOn"] = @("$($batchCount-1)")}

        # バッチ配列に追加
        $requests += $request     

        # バッチサイズごとにリクエスト
        if ($batchCount -eq $batchSize -or $i -eq $itemLimit) {

            # jsonスキーマに変換
            $batchRequests = @{
                requests = $requests
            }
            $batchBody = $batchRequests | ConvertTo-Json -Depth 4

            # Graph APIにbatchリクエスト
            $response = Invoke-RestMethod -Method Post -Uri 'https://graph.microsoft.com/v1.0/$batch' -Headers $headers -ContentType "application/json" -Body $batchBody
            $countSuccess = @($response.responses | Where-Object { $_.status -eq 201 }).Count
            \Write-Host "{$i} Try:$($response.responses.Count), Success: $countSuccess"

            # リセット
            $batchCount = 0
            $requests = @()

            # 500ms待機
            Start-Sleep -Milliseconds 500
        }

    }     
}

main


参考サイト
https://michalsacewicz.com/add-items-to-a-sharepoint-list-with-powershell-pnp-vs-batch-requests-to-graph/

解説

①必要なパラメータを設定
clientIDなどの値を事前に設定しておきます。
$loginHintは設定しなくても問題ありません。複数アカウント管理している方は優先するログインメールアドレスを設定します。
コードを実行すると認証ダイアログが開きますのでログインします。
MSAL.psの機能でリフレッシュトークンを含むトークン情報をストレージに格納するため、次回からは入力不要です。

②バッチ処理回数の設定
$itemLimitに登録数を設定します。

③各リクエストのBodyを定義
ここにHashTableでフィールドと値をセットします。

④要求順序指定
dependsOn属性で、前のリクエストが終わってから処理されるようにします。
設定しないと登録順がバラバラになり、また競合などで登録に失敗します。

⑤Graph APIにbatchリクエスト
20回に1回GraphAPIにbatchリクエストします。
batchRequests をJSONに変換したものをPOSTします。

MSAL.psとアクセストークン

MSAL.NETを使って簡単にアクセストークンを取得するモジュールです。
取得されたトークンは、更新トークンを含むトークンキャッシュとしてメモリ内に保存されますが、
アプリやセッションが終了すると消えてしまい、次回起動時にダイアログにて再認証が必要になります。

トークンキャッシュをストレージに保存しておき、再利用したい場合は以下のように呼び出します。

    $MsalClientApplication = New-MsalClientApplication -ClientId $clientID -TenantId $tenantID -RedirectUri $redirectURI
    Enable-MsalTokenCacheOnDisk $MsalClientApplication
    $Token = Get-MsalToken -PublicClientApplication $MsalClientApplication -LoginHint $loginHint

一行目でPublicClientApplicationインスタンスを初期化します。
二行目で以下のイベントを挿入してストレージキャッシュを有効化します。
・SetBeforeAccess:tokenCacheを参照しようとする前、ストレージからトークン情報を読み込みます。
・SetAfterAccess:tokenCacheに値がセットされた後、tokenCacheをストレージに保存します。
image.png

MSAL.PSの場合、トークン情報は以下の場所に保存されます。
~\AppData\Local\MSAL.PS\
image.png

三行目でアクセストークンを取得します。
-SilentなどのSwitchを指定しなければ、
まず.AcquireTokenSilentでトークンキャッシュ利用がトライされ、無ければ.AcquireTokenInteractiveなどで認証画面を出してアクセストークンを取得するといった流れです。

また、tokenCacheをストレージに保存する必要がなければ以下の一行だけでOKです。
-PublicClientApplicationを渡さなければ、内部で勝手にNew-MsalClientApplicationを実行してPublicClientApplicationを生成してくれます。

$Token = Get-MsalToken -ClientId $clientID -TenantId $tenantID -RedirectUri $redirectURI

素晴らしいモジュールです。
https://github.com/AzureAD/MSAL.PS/tree/master/

C#であればdocsのサンプルコードにより自分で実装する必要がありましたね・・
https://docs.microsoft.com/ja-jp/azure/active-directory/develop/msal-net-acquire-token-silently
https://github.com/Azure-Samples/active-directory-dotnet-desktop-msgraph-v2/tree/msal3x/active-directory-wpf-msgraph-v2

その他

一般に、Microsoft Graph は、同じ機能を実現するために CSOM や REST よりも消費するリソースが少なくて済みます。 そのため、Microsoft Graph を採用すると、アプリケーションのパフォーマンスが向上し、調整が減る可能性があります。

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