概要
- PowerShellでSharePointリストに大量のデータを登録します。
- PnPやCSOMではなく、勉強を兼ねてMicrosoft Graphを使います。
- Graphのバッチ要求により、複数リクエストを一度に送信することで多数のデータを登録します。
- AzureADのアクセスの種類は要件的に「委任されたアクセス許可」によりユーザーとして登録します。
簡単な要件
- 管理者は、ユーザーにGraph APIの使用許可を与えSharePointリソースの操作権限を与える。
- ただし、全てのサイトを操作されては困るためユーザー自身にアクセス権があるサイトに制限したい。
- その他、ログインにMFAが設定されている場合でも利用でき、利便性を考えある程度の期間は再認証不要としたい。
準備
必要なリソース
①AzureADのアプリ登録
GraphAPIを実行できるよう、事前にAzureADのアプリ登録を行います。
今回の設定では以下の記事が詳しいです。
重要なポイント
今回は「委任されたアクセス許可」により利用するユーザーの権限で接続します。
デスクトップアプリでSSOなどを実現するときに使う方式ですね。アプリにシークレットキーを埋め込む必要はありません。
それから、SharePointサイトが操作できる権限を付与します。
今回の場合Sites.ReadWrite.AllだけでOKです。
②PowerShell
AzureADの認証に必要なアクセストークンの取得を簡単にする、MSALライブラリをインストールします。
※管理者として起動が必要です。
Install-Module -Name MSAL.PS
③SharePointリスト
登録先となるSharePointリストです。
色々なデータ型を試したかったので、Person、DateTime、Choices、LookUp列を作成しています。
必要なパラメータ
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から取得します。}
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
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
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
解説
①必要なパラメータを設定
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をストレージに保存します。
MSAL.PSの場合、トークン情報は以下の場所に保存されます。
~\AppData\Local\MSAL.PS\
三行目でアクセストークンを取得します。
-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 を採用すると、アプリケーションのパフォーマンスが向上し、調整が減る可能性があります。