Tech Note

Azure, Office 365, Power Platform, etc... の勉強手記

CSOM が .NET Standard に対応したらしいので試してみた

CSOM が .NET Standard に対応したとのことです。
使用方法は次のドキュメントで解説されているので、それに沿って進めていきます。

docs.microsoft.com

環境情報

Microsoft.SharePointOnline.CSOM をインストール

コンソール アプリケーション(.NET Core)プロジェクトを作成します。
NuGet で Microsoft.SharePointOnline.CSOM Version 16.1.20211.12000 をインストールします。

f:id:shibatea:20200624015649p:plain

ソースコードは弄らずにそのままビルドしたところ、警告はありませんでした。

f:id:shibatea:20200624020205p:plain

ちなみに、ひとつ前のバージョン(16.1.20211.12000)だと次のような警告が出てました。 最新バージョンではちゃんと .NET Standard に対応してるみたいですね。

f:id:shibatea:20200624020058p:plain

認証処理について

今までのようにユーザーIDとパスワードを用いた基本認証は、CSOM .NET Standard 版ではもうできないようです。代わりに、OAuth アクセストークンを用いた認証方式を使用します。
SharePoint Online のアクセストークンを取得するための推奨アプローチは、Azure AD アプリケーションを登録し、SharePoint のアクセス許可を与えること、とのこと。

というわけで、Azure AD アプリケーションの登録方法は次の通りです。

Azure AD アプリケーションの登録

Azure Active Directory > アプリの登録 からアプリケーションを登録します。
作成したアプリケーションのアプリケーションIDは控えておきましょう。

f:id:shibatea:20200624025523p:plain

API のアクセス許可から SharePoint を選択します。

f:id:shibatea:20200624025723p:plain

委任されたアクセス許可 を選択します。

f:id:shibatea:20200624030005p:plain

今回は、AllSites.Manage を選択します。

f:id:shibatea:20200624030104p:plain

管理者の同意を与えます をクリックします。

f:id:shibatea:20200624030205p:plain

サイドナビゲーションから「認証」をクリックします。

f:id:shibatea:20200624030444p:plain

「既定のクライアントの種類」の選択肢を「はい」に変更します。

f:id:shibatea:20200624030616p:plain

ソースコード

サンプルコードは Program.cs と AuthenticationManager.cs の 2 ファイルがあります。 Program.cs は一部記載が省略されている部分があったので補足しています。

using System;
using System.Security;
using System.Threading.Tasks;

namespace ConsoleApp2
{
    class Program
    {
        static async Task Main(string[] args)
        {
            var site = new Uri("https://contoso.sharepoint.com/sites/hoge");
            var user = "shibatea@consoto.onmicrosoft.com";
            var password = GetSecureString($"Password for {user} : ");

            // Note: The PnP Sites Core AuthenticationManager class also supports this
            using var authenticationManager = new AuthenticationManager();
            using var context = authenticationManager.GetContext(site, user, password);

            context.Load(context.Web, p => p.Title);
            await context.ExecuteQueryAsync();
            Console.WriteLine($"Title: {context.Web.Title}");
        }

        static SecureString GetSecureString(string message)
        {
            SecureString sStrPwd = new SecureString();
            try
            {
                Console.Write(message);

                for (ConsoleKeyInfo keyInfo = Console.ReadKey(true); keyInfo.Key != ConsoleKey.Enter; keyInfo = Console.ReadKey(true))
                {
                    if (keyInfo.Key == ConsoleKey.Backspace)
                    {
                        if (sStrPwd.Length > 0)
                        {
                            sStrPwd.RemoveAt(sStrPwd.Length - 1);
                            Console.SetCursorPosition(Console.CursorLeft - 1, Console.CursorTop);
                            Console.Write(" ");
                            Console.SetCursorPosition(Console.CursorLeft - 1, Console.CursorTop);
                        }
                    }
                    else if (keyInfo.Key != ConsoleKey.Enter)
                    {
                        Console.Write("*");
                        sStrPwd.AppendChar(keyInfo.KeyChar);
                    }

                }
                Console.WriteLine("");
            }
            catch (Exception e)
            {
                sStrPwd = null;
                Console.WriteLine(e.Message);
            }

            return sStrPwd;
        }
    }

}

AuthenticationManager.cs は、サンプルコードのままコピペすると画像の通り FormDigestHandlingEnabled プロパティが見つからなくてコンパイルエラーになります。

f:id:shibatea:20200624043152p:plain

Microsoft.SharePointOnline.CSOM の一つ前のバージョンとソースコードを逆コンパイルして比較したら、FormDigestHandlingEnabled プロパティは削除されていました。
( FormDigestHandlingEnabled = false 相当と同じ動きになるように修正されている )

コンパイルエラーが発生している行をコメントアウトすれば動作確認ができました。

f:id:shibatea:20200624042307p:plain
左が最新バージョン、右が一つ前のバージョン

( おまけ ) SharePointPnPCoreOnline を試しにインストール

すると、大量に警告が表示されました。SharePointPnPCoreOnline が依存してるライブラリの中に、.NET Framework しか対応してないものが含まれてるようです。

f:id:shibatea:20200624023506p:plain

最後に

ひとまず .NET Standard で CSOM が動くことがわかりました。これで SharePoint CSOM を使うために Azure Functions V1 を使っていたものが V3 に移行できそうですね。(PnP を使ってるとダメですが…)

それと以前から疑問に思っていたのが…、Microsoft.SharePointOnline.CSOM のソースコードは公開されていないんでしたっけ?
探し方が下手なのか見つからないので、私は逆コンパイルして中身を確認しています。

Power Automate で指定した日付の予定表のイベント一覧を取得する

コネクタ

今回使うコネクタは イベントの取得 (V4) [ Get events (V4) ] です。

コネクタの説明にもある通り、内部的には Microsoft Graph API が呼ばれています。
コネクタでわからないことがある場合は Microsoft Graph API のドキュメントを見に行くと良いでしょう。

docs.microsoft.com

やりたいこと

  • まずは予定表のイベント一覧を取得する
  • さらに指定した日付に絞り込んでイベント一覧を取得する(例として 6/22 のイベント一覧を取得します)
  • 取得したイベント一覧をイベントの開始時刻順に並べる

設定内容

結論から書くと、6/22 のイベント一覧を取得するために設定したパラメーターは以下の通りです。

f:id:shibatea:20200622203625p:plain

パラメーター
予定表ID 取得したい予定表ID
フィルター クエリ start/dateTime ge '2020-06-21T15:00:00' and start/dateTime lt '2020-06-22T15:00:00'
並べ替え順 start/dateTime
上から順に取得 (なし)
スキップ数 (なし)

6/22 のイベント一覧を取得する場合は、イベントの開始時刻を『6/22 0時0分 以上』、『6/23 0時0分 未満』で絞り込む必要があります。

しかし、Power Automate で日付を扱うときはタイムゾーンUTC にする必要があります。その為、フィルター クエリに設定する時刻は次のような UTC 時刻を設定する必要があります。

  • '2020-06-21T15:00:00' 以上(ge)
  • '2020-06-22T15:00:00' 未満(lt)

ODATA フィルタークエリについては、以下のドキュメントが参考になります。

docs.microsoft.com

苦労したこと

フィルター クエリに指定するプロパティを探し当てるのに苦労しました。

フィルター クエリなしでイベント一覧を取得したときの出力結果は以下の通りでした。(詳細は省略)

[
  {
    "subject": "夕会",
    "start": "2020-06-22T08:00:00.0000000",
    "end": "2020-06-22T08:30:00.0000000",
  },
  {
    "subject": "朝会",
    "start": "2020-06-22T00:00:00.0000000",
    "end": "2020-06-22T00:30:00.0000000",
  }
]

開始時刻のプロパティは "start" なんだなぁと思って、フィルター クエリに "start" でフィルターしたところエラーになりました。

f:id:shibatea:20200622203831p:plain

Microsoft Docs でイベントのプロパティを確認すると、開始時刻は dateTimeTimeZone という型らしい。 Microsoft Graph API『イベントを取得する』の応答結果の例を確認するとわかりやすいと思います。

docs.microsoft.com docs.microsoft.com

以下のように入れ子になってる場合は、スラッシュを交えて指定する必要があります。今回のケースで言うと "start/dateTime" となります。

f:id:shibatea:20200622205839p:plain

まとめ

フィルター クエリの指定方法、特に日付を指定する場合はハマりポイントかなと思い記事を書きました。

Microsoft Graph API に対する理解が深まれば、Power Automate の勘所も備わってくると思います。 何か躓いたら Microsoft Graph API のドキュメントを見ると良いでしょう。

Microsoft Docs の日本語⇔英語切り替えのブックマーク

Twitter のタイムラインで Microsoft Docs の日本語⇔英語切り替えの話題を目にしたので、私がやってるやり方を書き残しておこうと思います。

ブラウザのブックマークを1つ作って、ブックマークの URL に下記のスクリプトを設定すればOK。

javascript:(function(){e='/en-us/';j='/ja-jp/';if(document.location.href.indexOf(e)==-1){location.href=document.location.href.replace(j,e);}else{location.href=document.location.href.replace(e,j);}})(location.href);

Power Automate で文字列中央にある数値文字を抽出する

abcd123efgh という文字列があったときに、中央にある連続する数値文字 123 を抽出したいケースを Power Automate で考えます。

前提

過去の記事の応用編になります。まずはこちらを参照してください。

shibatea.hatenablog.jp

検討事項

  • 文字列の中から連続する文字を抽出したいので substring 関数が使える
  • 数値文字の先頭インデックスと末尾インデックスが定まれば抽出可能
  • 先頭インデックスの取り方は前提記事を参照
  • 末尾インデックスの取り方は前提記事でやっていたことの逆バージョン
  • つまり indexOf 関数を使ってるところは lastIndexOf 関数を使い、最小値 を取得していたところは 最大値 を取得すればいい

作成した Power Automate

f:id:shibatea:20200610080443p:plain
対象文字列の中央にある連続する数値文字を抽出する

解説

過去記事の差分箇所を解説します。

選択アクションで数値文字の末尾インデックスを取得

f:id:shibatea:20200610081024p:plain

既存の選択アクションをクリップボードにコピーして貼り付けます。(初めてクリップボード機能を使って感動した!)
indexOf 関数を使ってた箇所を lastIndexOf 関数に書き換えるだけ。

アレイのフィルター処理で lastIndexOf 関数の結果を絞り込み

f:id:shibatea:20200610081326p:plain

これも既存のフィルターアクションをクリップボードにコピーして貼り付けてから、差出人 部分を上記の選択アクションの出力に書き換えます。

数値文字の末尾インデックスを取得

f:id:shibatea:20200610081615p:plain

max 関数を用いて、フィルターした結果の配列の中から最大値を取りだします。

docs.microsoft.com

部分文字列を抽出

f:id:shibatea:20200610081759p:plain

取得した数値文字の先頭インデックスと末尾インデックスを用いて、抽出する数値文字の長さを計算します。
末尾インデックスから先頭インデックスを減算した結果に 1 を足すことを忘れずに。

まとめ

Power Automate で文字列中央にある数値文字を抽出しました。
連続する数値文字であればどこでも抽出可能なので、過去記事の拡張版になります。

Power Automate で文字列末尾にある数値文字を抽出する

abcd123 という文字列があったときに、末尾の数値文字 123 を抽出したいケースを Power Automate で考えます。

検討事項

  • 文字列の末尾をまとめて抽出したいので substring 関数を使うのが楽
  • substring 関数を使うには開始位置を定めなければならない
  • 位置(インデックス)を取得するには indexOf 関数や lastIndexOf 関数が使える
  • 数値文字の先頭インデックスが取得できればOK
  • でもどの数値が先頭にくるかわからない
  • だったら全部の数値(0 ~ 9)のインデックスを取得しよう
  • その時の最小値が欲しいインデックス!

作成した Power Automate

f:id:shibatea:20200610002748p:plain
対象文字列の末尾にある数値文字列を抽出する

解説

0 ~ 9 数値配列の定義

f:id:shibatea:20200610003208p:plain

全部の数字(0~9)で indexOf 関数を適用したいので、予め 0 ~ 9 の配列を定義しておきます。

選択アクションで数値のインデックスをまとめて取得

f:id:shibatea:20200610003418p:plain

選択アクションは、配列(アレイ型の変数)に対して 要素の数は変えずに、配列の要素をインプットとして、何かしらの処理を実施する ことができます。
選択アクションについては Qiita に書いたので参考にしてください。

qiita.com

今回のケースで言えば、0 ~ 9 の数字が入った配列を、それぞれの数値で indexOf 関数を適用した結果が欲しいので、選択アクションが使えます。

アレイのフィルター処理で indexOf 関数の結果を絞り込み

f:id:shibatea:20200610004308p:plain

indexOf 関数は、検索文字が見つからない場合は -1 を返却します。

docs.microsoft.com

最終的に、数値文字のインデックスの 最小値 が欲しいので -1 が含まれていると困ります。(必ず -1 が最小になる)
その為、アレイのフィルター処理アクションを利用して、選択アクションの結果から -1 を取り除く必要があります。

数値文字の先頭インデックスを取得

f:id:shibatea:20200610004914p:plain

min 関数を用いて、フィルターした結果の配列の中から最小値を取りだします。

docs.microsoft.com

満を持して部分文字列を抽出

f:id:shibatea:20200610005143p:plain

文字列を抽出する substring 関数がありますが、コネクタとしても用意されていたので今回はそちらを利用しました。
引数は substring 関数と同じです。

docs.microsoft.com

まとめ

データを加工する際に Apply to each を利用すると、思いのほか時間を要する場合があります。
しかし、データ操作コネクタのアクションを使うと爆速で処理されるので、出来るだけこれらを使うように検討すると良いでしょう。

参考

今回は文字列の末尾にまとめて数値文字があるケースですが、数値文字が入り乱れてる場合についてはこちらの記事が参考になると思います。

mofumofupower.hatenablog.com

ReSharper のリファクタリング時にクラスファイルを生成すると文字コードがおかしくなる

環境

問題の事象

Visual StudioC# クラスファイルを作成すると、文字コードUTF-8 with BOM で作れらます。
しかし、ReSharperリファクタリング機能で静的メソッドを移動する際に生成したクラスファイルは、文字コードSJIS になってしまいました。

pleiades.io

再現手順

  • Visual Studio でコンソールアプリケーション プロジェクトを作成します。
  • Program.cs ファイルを確認すると UTF-8 with BOM で作成されます。

f:id:shibatea:20200527043758p:plain

  • Ctrl + R + O ショートカットを利用して、メソッドを新しいクラスファイルに移動させます。

f:id:shibatea:20200527044204p:plain

  • すると、新しく生成された Hello.cs ファイルの文字コードSJIS になります。

f:id:shibatea:20200527044331p:plain

対処方法

調べてみたもののわからなかったのでサポートに問い合わせ予定...