ルドルフもわたるふもいろいろあってな

Microsoft 365、Power Platform、PowerShellについて調べたことや検証したことなどを投稿します。技術の話は面白い。

SharePoint PnP Cmdlet を使ってSharePoint Online のプロパティを取得する方法 3パターン

CSOM を使ったプロパティの取得は面倒くさい

Client Side Object Model (CSOM) を使って SharePoint Online の サイトやリストのプロパティを取得するためのお作法を覚えたときに最初に思ったことは「手間がかかる、、」でした。

CSOM を使う

PowerSell CSOM を使って
SharePoint のデータを扱うには ClientContext にある2つのメソッドを使います。
・ Load() メソッド
・ ExecuteQuery() メソッド

そして、サイトやリストのプロパティを使って情報を得たり設定を変更したりするには、あらかじめこの2つのメソッドを使って SharePoint からプロパティを呼び出しておく必要があります。

#CSOM のDLLを読み込む。DLLのパスを明示的に指定する場合はこちらを使う。
Add-Type -Path .\Microsoft.SharePoint.Client.dll
Add-Type -Path .\Microsoft.SharePoint.Client.Runtime.dll

$user = "xxx@xxx.onmicrosoft.com"
$pass = "xxx"

$credentials = New-Object Microsoft.SharePoint.Client.SharePointOnlineCredentials($user,(ConvertTo-SecureString $pass -AsPlainText -Force))

$siteUrl = "https://xxx.sharepoint.com/sites/xxx"

$ctx = New-Object Microsoft.SharePoint.Client.ClientContext($siteUrl)
$ctx.Credentials = $credentials

$Web = $ctx.web
$ctx.Load($Web)
$ctx.ExecuteQuery()

$ctx.Load($web.SiteUserInfoList)
$ctx.ExecuteQuery()

Load() メソッド

オブジェクトやプロパティを呼び出すために使います。
正確には ExecuteQuery() を実行するときに呼び出すデータを、指定するために使います。
このメソッドを実行した時点ではサーバーとの通信は発生しません。
ExecuteQuery() を実行するまでにこのメソッドを複数回実行して指定を積み重ねることが可能です。

ExecuteQuery() メソッド

Load() で指定したデータ操作を実行します。
このメソッドを実行したときにサーバーとの通信が発生します。
ExecuteQuery() を実行することでサーバーとの通信が発生するため、このメソッドの実行回数が多いほどプログラムの処理が重くなります。

反対に、実行回数を減らしてあげることでプログラムの処理を軽くすることができます。

何が手間がかかるのか?

前述の通り、プロパティを使って情報を得たり設定したりするためには先に Load() と ExecuteQuery() を使ってそのプロパティを呼び出す必要があります。

呼び出していないプロパティを使おうとするとエラーが発生します。
「The collection has not been initialized.」

An error occurred while enumerating through a collection: The collection has not been initialized. It has not been requ
ested or the request has not been executed. It may need to be explicitly requested..
At line:1 char:1
+ $web.RoleAssignments
+ ~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidOperation: (Microsoft.Share...RoleAssignment]:<GetEnumerator>d__0) [], RuntimeExc
   eption
    + FullyQualifiedErrorId : BadEnumeration

例としてWebクラスのオブジェクトを使用する際に Title(サイト名)や URL などは Load を使った呼び出しをしなくても使えます。
RoleAssignments (権限)や RecycleBin(ごみ箱)は呼び出しを行ってから使わないと前述のエラーが発生します。

ただ、この呼び出しを行わなくても使えるものとそうでないものの区別のつけかたが私は分かっていないです。おそらく、明確に区別する方法は無いと思います。

なので、使いたいプロパティがあったら取り合えず呼び出しをせずに実行します。そうして、エラーになった場合は Load を使って呼び出しを行うようにしています。これがとても手間がかかります。

それを SharePoint PnP Cmdlet が助けてくれる

プロパティ呼び出しのエラーと戦いながらコード作成を進めるととても手間がかかります。
なので私は、実現性確認や処理の流れを組むときには SharePoint PnP Cmdlet を使います。#コードの最終形に SharePoint PnP Cmdlet を使うかどうかは別として。

SharePoint PnP Cmdlet を使ったプロパティの取得方法は次の3つのパターンがある。

パターン1:サイトやリストを取得時に Includesパラメータを使ってプロパティを呼び出す

コードの最終形で使う場面が多いのはこれかと思います。
サイトやリストを取得するときにプロパティも合わせて取得します。
複数のプロパティを一括で取得することも可能です。

$web = Get-PnPWeb -Includes Alerts,RoleAssignments
$lists = Get-PnPList -Web $web -Includes RoleAssignments

パターン2: Get-PnPProperty でプロパティを取得する

コードの作成過程で一番使うのはこれです。
パターン1と異なり、サイトやリストに限らず ClientObjectパラメータで指定したオブジェクトのプロパティは何でも呼び出せるので柔軟性が高いです。
ですが、使いすぎると(おそらく)サーバーとの通信頻度があがりパフォーマンスに影響がでる場合があると考えられますので、使いどころには注意が必要です。

$web = Get-PnPWeb
Get-PnPProperty -ClientObject $web -Property Alerts,RoleAssignments

パターン3: コンテキスト情報を使った取得方法もある

SharePoint PnP Cmdlets でもコンテキスト情報を使ったプロパティ取得ができます。

$ctx = Get-PnPContext
$web = Get-PnPWeb
$ctx.Load($web.Alerts)
$ctx.Load($web.RoleAssignments)
$ctx.ExecuteQuery()

結局どのパターンを使えばよいのか?

コードの作成過程ではパターン2 を使って、処理の流れが固まってきたらなるべくサーバーの通信頻度が抑えられるように、かつ、コードが読みやすいようにパターン1 とパターン3 、もしくは全体的に無印の(= PnP Cmdletsではない)CSOM に置き換えるのがよいかと思います。

パターン2 のプロパティ呼び出しを無印の CSOM で実現する方法

元ネタは以下のページです。

SharePoint Online: PoweShell script to reassign Full permission to any subsite and lists into
techcommunity.microsoft.com

以下、引用です。

function Invoke-LoadMethod()
{
    param
    (
        [Microsoft.SharePoint.Client.ClientObject]$object,
        [string]$propertyName
    ) 

    $ctx = $object.Context
    $load = [Microsoft.SharePoint.Client.ClientContext].GetMethod("Load") 
    $type = $object.GetType()
    $clientLoad = $load.MakeGenericMethod($type) 

    $parameter = [System.Linq.Expressions.Expression]::Parameter(($type), $type.Name)
    $expression = [System.Linq.Expressions.Expression]::Lambda(
            [System.Linq.Expressions.Expression]::Convert(
                [System.Linq.Expressions.Expression]::PropertyOrField($parameter,$propertyName),
                [System.Object]
            ),
            $($parameter)
    )
    $expressionArray = [System.Array]::CreateInstance($expression.GetType(), 1)
    $expressionArray.SetValue($expression, 0)
    $clientLoad.Invoke($ctx,@($object,$expressionArray))
}

このファンクションは以下のように使います。

Invoke-LoadMethod -Object $web -PropertyName "HasUniqueRoleAssignments"