Pocket

SharePoint Online は、CSOM や REST API の呼び出しにスロットリングが設けられており、頻繁にリクエストを送っているとサーバー側から実行が制限されてしまいます。どのくらいの頻度でリスエストを送ると制限されるのかについては、情報が公開されていないので悩むところではあるのですが…、この制限を超えるとサーバーからは「429」または「503」のコードが返ってきます。

この制限の回避策についてはリファレンスが公開されています。

SharePoint Online で調整またはブロックを回避する
https://docs.microsoft.com/ja-jp/sharepoint/dev/general-development/how-to-avoid-getting-throttled-or-blocked-in-sharepoint-online

回避策のひとつは、制限されることを前提にしたリトライ処理をあらかじめ組み込んでおくことです。PowerShell での実装については、以前に Microsoft のサポートチームがブログで書いてくれていますね。

PowerShell サンプル : SharePoint Online HTTP 調整 (応答コード : 429) 対策の増分バックオフ リトライ
https://blogs.technet.microsoft.com/sharepoint_support/2016/10/08/powershell-csom-sample-code-for-spo-http-429-incremental-backoff-retry/

もうひとつの回避策は、HTTP リクエストに決められた命名規則の User Agent を含めることとあります。リファレンスには「適切に装飾されたトラフィックは、不適切な装飾を施したトラフィックより優先されます。」とあるので、制限される頻度を低くできる可能性があるのだと思います。

というわけで、PowerShell で CSOM を扱うときにどのように User Agent を含めたら良いかについてメモしておきます。

さっそくコード

Add-Type -Path "C:\Program Files\PackageManagement\NuGet\Packages\Microsoft.SharePointOnline.CSOM.16.1.7115.1200\lib\net45\Microsoft.SharePoint.Client.dll"
Add-Type -Path "C:\Program Files\PackageManagement\NuGet\Packages\Microsoft.SharePointOnline.CSOM.16.1.7115.1200\lib\net45\Microsoft.SharePoint.Client.Runtime.dll"

Function AddUserAgentWebRequest()
{
  param([Parameter(Mandatory=$true)][object]$clientContext)  
  Add-Type -TypeDefinition @"
  using System;
  using Microsoft.SharePoint.Client;
  
  namespace IDEA.SPOHelpers
  {
    public static class ClientContextHelper
    {
      public static void AddRequestHandler(ClientContext context)
      {
        context.ExecutingWebRequest += new EventHandler(RequestHandler);
      }

      private static void RequestHandler(object sender, WebRequestEventArgs e)
      {
        // Set User-Agent
        // UserAgent format: NONISV|CompanyName|AppName/Version
        e.WebRequestExecutor.WebRequest.UserAgent = "NONISV|CONTOSO|PowerShellScript/1.0";
      }
    }
  }
"@ -ReferencedAssemblies "C:\Program Files\PackageManagement\NuGet\Packages\Microsoft.SharePointOnline.CSOM.16.1.7115.1200\lib\net45\Microsoft.SharePoint.Client.dll","C:\Program Files\PackageManagement\NuGet\Packages\Microsoft.SharePointOnline.CSOM.16.1.7115.1200\lib\net45\Microsoft.SharePoint.Client.Runtime.dll"
  [IDEA.SPOHelpers.ClientContextHelper]::AddRequestHandler($clientContext);
}

$siteUrl = "https://<tenant>.sharepoint.com/sites/samplesite"
$credentials = Get-Credential
$context = New-Object Microsoft.SharePoint.Client.ClientContext($siteUrl)
$context.Credentials = New-Object Microsoft.SharePoint.Client.SharePointOnlineCredentials($credentials.UserName,$credentials.Password)

AddUserAgentWebRequest $context

$lists = $context.Web.Lists
$context.Load($lists)
$context.ExecuteQuery()

$lists | %{ "Title: {0}" -f $_.Title }

Add-Type-Path-ReferencedAssemblies に指定している Microsoft.SharePoint.Client.dllMicrosoft.SharePoint.Client.Runtime.dll のパスは環境に応じて書き直してください。あとはサイトの URL もですね。

これを実行すると、独自に書いた AddUserAgentWebRequest に渡された ClientContext では、これ以後のリクエストで User Agent をヘッダーに含めてくれるようになります。Fiddler で見る限りは、/_vti_bin/sites.asmx/_vti_bin/client.svc/ProcessQuery へのリクエスト ヘッダーにちゃんと User-Agent: NONISV|CONTOSO|PowerShellScript/1.0 が含まれているようでした。

さいごに

あとで思い出そうとしても絶対忘れているだろうなということでメモ書きとして記事に残しておきます。ただ、最大の問題点と言いますか懸念点は、この方法が本当に効果があるのかよく分からないという点ですw

ちなみに、SharePointPnP.PowerShell でもこの User Agent を追加する処理はちゃんと実装されており、このあたりのややこしいことを考えずに利用できるようです。

SharePoint/PnP-PowerShell: SharePoint / Office 365 Dev PnP PowerShell CmdLets
https://github.com/SharePoint/PnP-PowerShell

実装しているのはこのあたりですかね。

PnP-PowerShell/SPOnlineConnection.cs at master · SharePoint/PnP-PowerShell
https://github.com/SharePoint/PnP-PowerShell/blob/master/Commands/Base/SPOnlineConnection.cs

便利なコマンドもぞくぞくと増えてきていますし、PnP の PowerShell の利用を積極的に進めた方が良さそうではありますね。

ただ、この User Agent による回避方法(回避できるのか分からないけど)も、いつかは必要になるときが来るかもしれません。