からめもぶろぐ。

SharePoint が得意なフレンズなんだね!すごーい!

SPClient 0.10 をリリースしました

www.powershellgallery.com

オンライン ヘルプを作りました。PowerShell のヘルプ ドキュメントから Markdown への変換は platyPS というモジュールが役に立ちます。Microsoft の PowerShell Team で開発しており、なかなかいい感じです。

github.com

あとは細かい修正ですが、Use-SPClientType 関数で C:\Program Files\Common Files\Microsoft Shared\Web Server Extension を参照するのをやめて、GAC を参照するようにしました。複数バージョンのアセンブリが入っている場合は新しい方を参照するので、どうしても 2013 (15.0.0.0) バージョンを使いたい場合はパスを指定してください。

SharePoint Online の Excel Services (SOAP API) を叩いてみる

Excel Services を使うと SharePoint のドキュメント ライブラリに保存されている Excel ファイルを直接編集することができます。プログラムから Excel を編集というと Open XML SDK を使うか、サードパーティー製のコンポーネントを使うかという話になるのですが*1、Excel Services を使えばセルの読み書き程度であれば簡単にできてしまいます。SOAP なので Visual Studio からは [サービス参照の追加] で簡単に呼び出すことができます。

ただし SharePoint Online の場合はこの方法では問題があります。SharePoint Online は OAuth なので、対応していないと思われる WCF ではアクセス許可が通りません。なので、自力で HTTP ヘッダーを追加してあげる必要があります。

サンプル コード

github.com

Access Token の取得方法は以下の記事を参考にしてください。今回のサンプルでは Refresh Token を使った Access Token の再取得も実装しています。

blog.karamem0.jp

WCF でカスタム HTTP ヘッダーを追加するには Message Inspectors という機能を使えばいいようです。

IClientMessageInspector を実装するクラスを作ります。メッセージを送信する前に Bearer Token を追加します。

public class BearerClientMessageInspector : IClientMessageInspector {

    public object BeforeSendRequest(ref Message request, IClientChannel channel) {
        var property = new HttpRequestMessageProperty();
        property.Headers.Add("Authorization", "Bearer <Access Token>");
        request.Properties[HttpRequestMessageProperty.Name] = property;
        return null;
    }

    (snip)

}

IEndpointBehavior を実装するクラスを作り、ClientRuntime.ClientMessageInspectors にメッセージ インスペクターを追加します。

public class BearerEndpointBehavior : IEndpointBehavior {

    public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime) {
        clientRuntime.ClientMessageInspectors.Add(new BearerClientMessageInspector());
    }

    (snip)

}

SoapClient の EndpointBehaviors にビヘイビアーを追加します。

using (var client = new ExcelServiceSoapClient()) {
    client.Endpoint.EndpointBehaviors.Add(new BearerEndpointBehavior());
    var status = default(Status[]);
    var sessionId = client.OpenWorkbook(FilePath, "", "", out status);
    var cell = client.GetCellA1(sessionId, "Sheet1", "A1", false, out status);
    client.CloseWorkbook(sessionId);
}

*1:まさかこのご時世に Excel COM でやろうとする人はいないですよね?

SPClient 0.9 をリリースしました

www.powershellgallery.com

ファイルとフォルダーに関する関数が追加されました。リスト アイテムとの変換を行う関数も用意しています。
ちなみに ListItem.Folder プロパティの説明だと変換できないときに null を返すと書いてあるのに、実際は ServerObjectIsNull を true にして返すという。ListItem.File プロパティの説明は合ってるのになあ。

ListItem.Folder property (Microsoft.SharePoint.Client)

SPClient 0.8 をリリースしました

www.powershellgallery.com

データ取得で * (アスタリスク) による指定ができるようになりました。つまり、こんな風に呼び出せます。

Get-SPClientWeb -Default -Retriavals "*,Lists.Include(*)"

これは内部的に以下のように変換されます。

clientContext.Load(clientContext.Web, web => web, web => web.Lists.IncludeWithDefaultProperties(list => list))

また、データの取得において、キャッシュを使うのをやめました。これにより、コマンドの実行ごとにサーバーへのリクエストが発生することになり、パフォーマンスの劣化が想定されますが、キャッシュをすることによる弊害のほうが大きいため、このような変更となりました。ちなみに、SharePoint 2016 の CSOM ではキャッシュの有無を指定できるようになっているようです。ぐぬぬ。

ClientRuntimeContext.DisableReturnValueCache property (Microsoft.SharePoint.Client)

CSOM で実行される Web リクエストを取得する

CSOM では内部的に Web リクエストを発行していて、どういうリクエストが流れているかを見るためには Fiddler などのツールを使わないといけないのですが、とても面倒くさいので、コードから取得できるようにしてみました。ちなみに ExecutingWebRequest イベントで取れる e.WebRequestExecutor.GetRequestStream は CanRead が false なので読み取りできません。なのでリフレクションでごにょごにょしています。

public static class Program {

    private static void Main(string[] args) {
        var url = "<サイトの URL>";
        var userName = "<ユーザー名>";
        var rawPassword = "<パスワード>";
        var securePasseord = new SecureString();
        foreach (var c in rawPassword) {
            securePasseord.AppendChar(c);
        }
        using (var ctx = new ClientContext(url)) {
            ctx.Credentials = new SharePointOnlineCredentials(userName, securePasseord);
            ctx.ExecutingWebRequest += (sender, e) => {
                var clientRequestType = ctx.PendingRequest.GetType();
                var buildQueryMethod = clientRequestType.GetMethod("BuildQuery", BindingFlags.Instance | BindingFlags.NonPublic);
                var chunkStringBuilderObject = buildQueryMethod.Invoke(ctx.PendingRequest, null);
                var chunkStringBuilderType = chunkStringBuilderObject.GetType();
                var createTextReaderMethod = chunkStringBuilderType.GetMethod("CreateTextReader", BindingFlags.Instance | BindingFlags.Public);
                var textReader = createTextReaderMethod.Invoke(chunkStringBuilderObject, null) as TextReader;
                Console.WriteLine(textReader.ReadToEnd());
            };
            var web = ctx.Web;
            ctx.Load(web);
            ctx.ExecuteQuery();
        }
        Console.ReadLine();
    }

}

実行するとこんな感じ。

<Request AddExpandoFieldTypeSuffix="true" SchemaVersion="15.0.0.0" LibraryVersion="16.0.0.0" ApplicationName=".NET Library" xmlns="http://schemas.microsoft.com/sharepoint/clientquery/2009">
    <Actions>
        <ObjectPath Id="2" ObjectPathId="1" />
        <ObjectPath Id="4" ObjectPathId="3" />
        <Query Id="5" ObjectPathId="3">
            <Query SelectAllProperties="true">
                <Properties />
            </Query>
        </Query>
    </Actions>
    <ObjectPaths>
        <StaticProperty Id="1" TypeId="{3747adcd-a3c3-41b9-bfab-4a64dd2f1e0a}" Name="Current" />
        <Property Id="3" ParentId="1" Name="Web" />
    </ObjectPaths>
</Request>