BEACHSIDE BLOG

Azure と GitHub と C# が好きなエンジニアの個人メモ ( ・ㅂ・)و ̑̑

Microsoft Graph API で グループ 情報を操作する ( C#, .NET SDK )

前回のブログでは、C# の SDK を使ってユーザーの操作について触れましたが、今回はグループの操作をしていきましょう。

事前準備

この3つは前回のブログと同様なので省略します。

  • Azure AD で Microsoft Graph のアクセス許可を追加 (必要に応じて)
  • コンソールアプリの作成
  • GraphServiceClient の初期化

blog.beachside.dev

グループの作成

グループを作成するうえでまず Azure ポータルで Azure AD を作成する際の UI を見てイメージをつかんでおきましょう。

  • グループの種類 は、利用するアプリ側の認可目的で作成するのであれば セキュリティ でよいです。
  • メンバーシップの種類 は、割り当て済み でよいです。もう一つ選べる動的メンバーシップ はルールベース(例えば部署がどこだったらどのグループに割り当てるとか)で Premium plan の機能ですがここでは使用しません。

グループの作成(ユーザー追加無し)

上図の UI をコードで書くと以下の感じ。Group のプロパティの細かい設定が必要な場合はこちらの公式ドキュメントをチェックするとして、サクッと作るにはこれで十分です。

// Group の作成 ( Group の namespace は "Microsoft.Graph" です)
var group1 = new Group
{
    DisplayName = "Group 1",
    Description = "Group 1 description",
    SecurityEnabled = true,
    MailEnabled = false,
    // 必須のプロパティのためメールを使わなくても入力が必要なので、任意のものを入れておく。
    MailNickname = "mail"
};
// graphServiceClient のインスタンス化は前回のブログにて
var createdResponse = await graphServiceClient.Groups.Request().AddAsync(group1);
// レスポンスから生成された Group の Id を確認できます。
Console.WriteLine($"{createdResponse.Id} - DisplayName: {createdResponse.DisplayName}; Description: {createdResponse.Description}");

グループの作成(ユーザー追加もする)

グループの作成と同時にユーザーまたはオーナーを追加する場合、ユーザーの ID が必要になります。

いくつか癖があるので、公式のドキュメントを眺めた方がよさそうです。まず、このブロブ書いてる時点じゃ公式ドキュメントのコードじゃ動かないことが最大の癖です(GitHub の issue でなんか書いてたのでそれ以上のことはフィードバックしませんが...)。
あとは、1つの要求で20人までしか追加できないようなので人数が多いときは考慮する必要があります。

作成と同時にユーザーを追加するには、AdditionalData プロパティに値を追加します。
具体的なコードはこんな感じ。

// Owner に追加したいユーザーの一覧があるとする
var ownerIdsToAdd = new[] { "80490496-a683-4e68-9175-017ffd7f99bd" };
// グループに追加したいユーザーの一覧があるとする
var userIdsToAdd = new[] { "80490496-a683-4e68-9175-017ffd7f99bd", "9fa001c0-04da-4440-ac7c-f5389cebe7d1" };

var group101 = new Group
{
    DisplayName = "Group 101",
    Description = "Group 101 description",
    SecurityEnabled = true,
    MailEnabled = false,
    MailNickname = "mail",
    AdditionalData = new Dictionary<string, object>
    {
        // Owner の追加
        ["owners@odata.bind"] = ownerIdsToAdd.Select(id => $"https://graph.microsoft.com/v1.0/users/{id}"),
        // User の追加
        ["members@odata.bind"] = userIdsToAdd.Select(id => $"https://graph.microsoft.com/v1.0/users/{id}")
    }
};

var createdResponse = await graphServiceClient.Groups.Request().AddAsync(group101);

人数の制約とかあって気にする必要がある場合作成と同時にユーザーやると面倒なので、グループの作成 (+必要あればオーナーの追加、オーナーは少数しか追加しない想定ですが)で作成してから、別途ユーザーを追加した方がシンプルにはなりそうです。

グループにメンバーを追加する

上記の流れできたので別途メンバーを追加してきましょうか。メンバーの追加はひとりずつ追加する方法と複数人を一括追加する方法があります。

ひとりずつ追加

以下のように DirectoryObjectAddAsync すればよいです。

// TODO: 対象のグループの ID をセット
var targetGroupId = "";
// TODO: 追加したいユーザーの ID をセット
var userIdToAdd = "";

var target = new DirectoryObject { Id = userIdToAdd };

await graphServiceClient.Groups[targetGroupId].Members.References.Request().AddAsync(target);

既にグループ内にいるユーザーを追加しようとすると Exception が発生します。そりゃそうだろって感じですが、自分の中で確認したい点の一つだったので念のため書いておきます、こーゆーとこあんまり信用してないので。

Exception の Code は BadRequest で ErrorMessageはこんな感じ: One or more added object references already exist for the following modified properties: 'members'.

複数人を一括追加

(これも公式ドキュメントに記載されてるコードでは動作しませんが...)
複数人を一括追加する場合は、前述で group 作成時にユーザーを追加する方法と同じ感じです。最後に UpdateAsync を使うって流れです。

// TODO: 対象のグループの ID をセット
var targetGroupId = "";
// グループに追加したいユーザーの一覧があるとする
var userIdsToAdd = new[] { "80490496-a683-4e68-9175-017ffd7f99bd", "9fa001c0-04da-4440-ac7c-f5389cebe7d1" };
// 追加するグループのオブジェクトを作成
var group = new Group
{
    AdditionalData = new Dictionary<string, object>()
    {
        ["members@odata.bind"] = userIdsToAdd.Select(id => $"https://graph.microsoft.com/v1.0/users/{id}")
    }
};

await graphServiceClient.Groups[targetGroupId].Request().UpdateAsync(group);

このコードでは考慮してませんが、一括の最大が20人なので必要に応じて処理を追加すればよいです。 ちなみにこちらも既にグループ内にいるユーザーを追加しようとすると前述同様のエラーになります。Exception の ErrorMessage も同じです。

グループの取得

group を ID を指定して情報を取得するはドキュメント通りで動きます。ユーザーを ID 指定で取得する方法を知ってると予想がつくやつです。

// TODO: 対象のグループの ID をセット
var targetGroupId = "";
var g1 = await graphServiceClient.Groups[targetGroupId].Request().GetAsync();
Console.WriteLine($"{g1.Id}; DisplayName:{g1.DisplayName}; Description:{g1.Description}");

グループ内のメンバーを取得

Azure AD の Group は Group のメンバーに Group を入れることができるので。以下の2通りの取得方法が可能です。

  • グループの直下のメンバーのみを取得する
  • グループの直下のメンバー + ネストされたグループのメンバーも取得する

グループの直下のメンバーのみを取得する

Members メソッドを使って取得してみましょう。メンバーにはユーザーとグループが存在する場合は入ってくるので、一覧を見るならこんな感じでみることができます。

メンバーの中にいるグループの中のユーザーの情報はこれでは見えません。

// TODO: メンバーを取得したいグループの ID をセット
var targetGroupId = "";

var membersOfGroup1 = await graphServiceClient.Groups[targetGroupId].Members.Request().GetAsync();

foreach (var page in membersOfGroup1)
{
    switch (page)
    {
        case User user:
            Console.WriteLine($"USER: {user.Id} DisplayName: {user.DisplayName}; mail: {user.Mail}");
            break;

        case Group group:
            Console.WriteLine($"GROUP: {group.Id} DisplayName: {group.DisplayName}; Description: {group.Description}");
            break;

        default:
            Console.WriteLine("---");
            break;
    }
}

ユーザーのみを指定したいなら、前回のブログでも紹介したように OfType<T> を使って抽出できます。
また、前回のブログでも触れましたが、Select を使って最小限のデータを取得しましょうって場合、今回のように Group と User の複数の型が入ってくると多少面倒ですね。目的がユーザーの ID と名前だけ取得したいなら、シンプルにこう書けます。

// TODO: メンバーを取得したいグループの ID をセット
var targetGroupId = "";
// 本来は `Select` を使って必要な情報を最小限に取得しましょうね。
var membersOfGroup1 = await graphServiceClient.Groups[targetGroupId].Members
                                                       .Request()
                                                       .Select("id,displayName")
                                                       .GetAsync();

foreach (var user in membersOfGroup1.OfType<User>())
{
    // Select で Mail を指定してないので常に空になる
    Console.WriteLine($"USER: {user.Id} DisplayName: {user.DisplayName}; mail: {user.Mail}");
}

グループの直下のメンバー + ネストされたグループのメンバーも取得する

ネストされた?ネストした?...どうでもいっか...

ユーザーの一覧を取得したい場合は、(私個人の場合は)ネストされたグループ(グループの中のグループ)のメンバーの取得できるこちらを使うケースがメインです。

コードは先ほどとほぼ一緒ですが、Members ではなく TransitiveMembers を見ます。前述同様にユーザーもグループも入ってくるので、ユーザーの情報だけほしいなら、定番の OfType<T> です。

// TODO: メンバーを取得したいグループの ID をセット
var targetGroupId = "";
var transitiveMembers = await graphServiceClient.Groups[targetGroupId].TransitiveMembers
                                                    .Request()
                                                    .GetAsync();
// User のみを出力
foreach (var user in transitiveMembers.OfType<User>())
{
    Console.WriteLine($"{user.Id} DisplayName: {user.DisplayName}; mail: {user.Mail}");
}

どうでもいい余談ですが .Request() とかの改行してる場所微妙なのはブログの css の都合です。

その他の操作

全部を試したわけではないですが、ここまで書いた書き方や癖を把握しておくと、あとはドキュメントのままでサクッと動かせそうなのでここまでとします。

やりたいことに応じて以下のドキュメントを参考にコードを書いていけばばっちりです、たぶん。

docs.microsoft.com