セットに合わせてユーザーの参加、退出を自動化
人事異動で部署が変わると、Teams のチームもそれに合わせて退出・参加を行う必要があります。
しかし実際には、
- 新しい部署のチームに入り忘れる
- 前の部署のチームに残ったままになる
といったことが少なくありません。
そこで、所属させたい Teams の組み合わせに「セット名」を付け、Power Automate で一括管理する仕組みを考えてみました。
Teamsセットの例
| セット名 | 全社員 | 東京オフィス | 大阪オフィス | 営業部 | 開発部 | マネージャー連絡 | 新入社員 | 研修受講者 | ITサポート | プロジェクトA |
|---|---|---|---|---|---|---|---|---|---|---|
| 新入社員(東京) | ✓ | ✓ | ✓ | ✓ | ✓ | |||||
| 新入社員(大阪) | ✓ | ✓ | ✓ | ✓ | ✓ | |||||
| 東京営業担当 | ✓ | ✓ | ✓ | ✓ | ||||||
| 大阪営業担当 | ✓ | ✓ | ✓ | ✓ | ||||||
| 東京開発担当 | ✓ | ✓ | ✓ | ✓ | ||||||
| 大阪開発担当 | ✓ | ✓ | ✓ | ✓ | ||||||
| 東京営業マネージャー | ✓ | ✓ | ✓ | ✓ | ✓ | |||||
| プロジェクトAメンバー(大阪) | ✓ | ✓ | ✓ | ✓ | ✓ |
必要なライセンスやロール
- Power Automate クラウドフローが利用できる環境(既定環境でもOK)
- プレミアムライセンス不要
- 実行ユーザーは以下のいずれかである必要があります
- 対象チームの所有者
- Microsoft Entra ID の Groups Administrator
- グローバル管理者
セキュリティ上、グローバル管理者は使わないほうが良いですね。
この仕組みの特徴
- あらかじめ用意したExcel表に従ってユーザーが参加するチームをコントロールできる
- Formsを使ってユーザー自身にセット名を選択することをトリガーにすることも可能
- セット名に含まれていないチームには影響しない
- EngageのコミュニティやPlannerのメンバーもコントロール可能 →参考
Excelのテーブルでメンバーとセット名を用意する
以下のようなテーブルを用意しました。テーブルには名前をつけておきましょう。
TeamsはM365グループで管理されている
グループIDを調べる
今回使うチームを用意しました。もちろん既存のチームで構いません。
TeamsのチームはMicrosoft 365 グループ という仕組みで管理されています。
今回の仕組みをPower Automateで動かすユーザーは、これらのチームに参加していて「所有者」であることが前提ですので、下記のURLにアクセスすることで調べることができます。
https://myaccount.microsoft.com/groups/groups-i-own
グループ名をクリックして開く画面のURLに表示されているのが、そのチームの裏側でメンバーを制御しているMicrosoft 365 グループのグループIDです。コピーしてメモしておきましょう。
engageのコミュニティーや、チームを選択せずにPlanner上で作成したプランなどは、Microsoft 365 グループが自動的に作成されていますので、コミュニティーの名称やプランの名前を同じ容量で探すと出てきます。
クラウドフローを開き、「作成」アクションをクリックします。
名前をグループ名に変更して、先程の方法で調べてグループ名を貼り付けていきます。
数が多いので、スコープ内に集めておきました。
セットJSONを作る
あらかじめテキストエディタで下記のようなJSONを作ります。
キーがセット名で、角カッコのなかに配列として先程「作成」で名前をつけて作ったグループIDが入っています。
{
"新入社員_東京": [
@{outputs('東京オフィス')},
@{outputs('新入社員')},
@{outputs('研修受講者')},
@{outputs('ITサポート')}
],
"新入社員_大阪": [
@{outputs('大阪オフィス')},
@{outputs('新入社員')},
@{outputs('研修受講者')},
@{outputs('ITサポート')}
],
"東京営業担当": [
@{outputs('東京オフィス')},
@{outputs('営業部')},
@{outputs('ITサポート')}
],
"大阪営業担当": [
@{outputs('大阪オフィス')},
@{outputs('営業部')},
@{outputs('ITサポート')}
],
"東京開発担当": [
@{outputs('東京オフィス')},
@{outputs('開発部')},
@{outputs('ITサポート')}
],
"大阪開発担当": [
@{outputs('大阪オフィス')},
@{outputs('開発部')},
@{outputs('ITサポート')}
],
"東京営業マネージャー": [
@{outputs('東京オフィス')},
@{outputs('営業部')},
@{outputs('マネージャー連絡')},
@{outputs('ITサポート')}
],
"プロジェクトAメンバー_大阪": [
@{outputs('大阪オフィス')},
@{outputs('開発部')},
@{outputs('プロジェクトA')},
@{outputs('ITサポート')}
]
}
テキストとして作成したら CTRL + Cでコピーして、別途作った「作成」の中に貼り付けます。@マーク付きなので式として評価されていい具合です。
ところどころ色が違う「出力」は、グループIDを用意していなかったためです。名前の対応が間違っていたこともこれでわかるので修正すると良いでしょう。
もちろん動的なコンテンツをGUI上で選択しても良いですが、ミス防止のために貼り付け方式をおすすめします。
「表内に存在する行を一覧表示」アクションを使って従業員のテーブルを取り込んだら、「スイッチ」アクションをその下に配置します。
スイッチの「オン」にはExcelの「選択するセット」を動的名コンテンツから選択します。
すると、「選択するセット」はExcelからの出力のValueのなかの配列要素のなかの1つのキーなので、自動的に「Apply to each」の中に配置されます。
そこで、ケースを増やして各セット名を入力していきます。セットJSONの中で記述した名前と対応しているため間違えないように。
横に長くなります。
ユーザーが今どのグループに参加しているのか調べる
指定したグループのうち、どこに参加しているかを知ることができます。
POST https://graph.microsoft.com/v1.0/users/taro.yamada@contoso.com/checkMemberGroups
Content-Type: application/json
{
"groupIds": [
"11111111-1111-1111-1111-111111111111",
"22222222-2222-2222-2222-222222222222"
]
}
グループIDスコープの下にOffice365ユーザーの「HTTP要求を送信します」アクションを配置します。URIにはテストユーザーのメールアドレスをそのまま入れてみます。
上の画像のURIでは /users/ が抜けてました。下のアドレスを使ってください。
https://graph.microsoft.com/v1.0/users/{useridまたはmail}/checkMemberGroups
{
"groupIds": [
"@{outputs('東京オフィス')}",
"@{outputs('大阪オフィス')}",
"@{outputs('営業部')}",
"@{outputs('マネージャー連絡')}",
"@{outputs('新入社員')}",
"@{outputs('研修受講者')}",
"@{outputs('ITサポート')}",
]
}
今回のテストユーザー、アデルさんをあらかじめ大阪オフィスチームのメンバーに入れておきました。
実行すると、本文に指定したグループIDのなかから、アデルさんが参加している大阪オフィスのIDだけが結果として帰ってきました。
うまく行ったので、作成したHTTP要求のアクションをループの中、スイッチの前にいれてやります。
ユーザーのメールアドレス部分は動的なコンテンツからMailを選びます。
これによって、Excelに記載された各ユーザーのメールアドレスをループのなかでHTTP要求アクションに渡して、対象となるグループのうち、どのグループ(つまりチーム)に参加しているかを知ることができます。
HTTP要求アクションの名前はわかりやすくcurrentGroupsに変更しておきました。
どこに追加して、どこから取り除くのか?
ここまでで2つの要素ができました。
| 要素名 | 内容 |
|---|---|
| currentGroups | ユーザーがどのグループに参加しているるグループIDの配列 |
| セットJSONの各要素 | セット名と複数のグループIDの配列 |
最終的にユーザーが参加するチームをコントロールするには、グループへの追加と削除を行います。各ユーザーに対してどのグループに追加して、どのグループから削除するかを明らかにすれば良いということになります。
それぞれ以下のような名前の配列を変数として用意することにします。
| 変数名 | 内容 |
|---|---|
| addGroups | 追加先グループIDの配列 |
| removeGroups | 削除対象グループIDの配列 |
| selectedGroups | ユーザーが参加するべきグループIDの配列 |
変数なので、フローの最初の方で変数の初期化を行います。型はアレイです。
アレイの値には、空っぽを表す角カッコ[]を入れても空欄でも良いです
selectedGroups を用意する
ユーザーによって選択されるセットは異なるので、ループの中でスイッチによってどのグループを使うか選ばれて、配列に格納されるようにしてみます。
selectedGroups変数には、スイッチで分岐したセット名の値であるgroupIDの配列を設定します。

おや??
ここまで作ってみて、スイッチの「オン」にはセット名が入っているのだから、別にスイッチを使わなくても「セットJSON」の要素を直接式で取得できるのでは?
試しにスイッチの下に変数の設定を追加してみます。式はちょっと複雑になります。
outputs('セットJSON')?[item()?['選択するセット']]
テスト実行した結果です。1人目のアデルさんは東京営業担当です。
東京営業担当のグループセットはこちらでした。
selectedGroupsは東京営業担当セットのグループIDが配列として設定できました。
selectedGroupsは東京営業担当セットのグループIDが配列として設定できました。
2人目、新入社員(大阪)のアレックさんは何故かselectedGroupsが空です。
セットJSONの中をみると、Excelとキーの名前が違います。
今回はExcel側の選択するセット名を修正することにします。
しかしこれでExcelで選択されているセット名に応じてグループセットが取得できていることが確かめられました。
カッコ()は全角半角の見分けがつきにくいので、アンダーバーなどを使うほうが無難ですね。
スイッチは不要になりましたので削除しておきます。随分シンプルになりました。リファクタリング成功です。
addGroups を用意する
追加対象のグループは、まだ対象ユーザーが参加していないグループです。以下のような式で求められます。
[addGroups] = [selectedGroups] - [currentGroups]
例えば、
現在参加しているのが [G1] で、参加すべきは [G1,G2,G3] ならば、追加で [G2,G3] に参加する
となります。言い換えると
参加するべきグループのなかで、現在参加しているグループではない対象を残す
ことを行えばよいと言えます。
これをPower Automateで表現するには、「アレイのフィルター処理」を使います。
まず「差出人」はselectedGroupsです。左辺にはcurrentGroupsを配置するのですが、動的なコンテンツではbodyしか選択できません。

HTTP要求で取得できたグループの配列はbody配下のvalueの中にあります。配列のなかに差出人の1要素が含まれるかどうかを判定させるので左辺は以下の通りになります。
contains(body('currentGroups')?['value'],item())
左辺の結果はtrueまたはfalseで返ってくるので、以下のとおりに設定します。
次の値に等しくない
true
テストケースとして、アデルさんにはあらかじめ東京オフィスと大阪オフィスのグループに参加した状態でテストしてみます。
東京営業担当です。入っておくべきグループは以下のとおりです。
"東京営業担当": [
"c081550f-fa47-4614-b527-b8e9ea454bdc",
"43ab9fe8-7f19-4fc0-9bde-e46ae4e7373d",
"b17df2a7-e184-4329-a9ed-dcde281c1977"
]
現在参加しているグループは以下のとおりです。
{
"@odata.context": "https://graph.microsoft.com/v1.0/$metadata#Collection(Edm.String)",
"value": [
"c081550f-fa47-4614-b527-b8e9ea454bdc",
"6baf1645-1f6f-4f30-af3d-c45ddaa1747a"
]
}
出力の結果はアデルさんがまだ入っていないグループIDが抜き出されていることが確認できました。

removeGroups を用意する
アデルさんは東京営業担当ですが、大阪オフィスのグループに入っています。大阪オフィスグループからは抜ける必要があります。考え方はaddGroupsのときとは逆になります。
[removeGroups] = [currentGroups] - [selectedGroups]
例えば、
現在参加しているのが [G1,G4] で、参加すべきは [G1,G2,G3] ならば、 [G4] からは退出するべき、となります。言い換えると
参加するべきグループのなかから、現在参加している余分なグループを削除する
ことを行えばよいと言えます。
これを Power Automateで表現するには、同じく「アレイのフィルター処理」を使います。
前述のフィルタの名前はaddGroupsに変更し、removeGroupsの方もそれとわかる名前に変更しました。
body('currentGroups')?['value']
contains(variables('selectedGroups'),item())
次の値に等しくない
true
テスト実行した結果、東京営業担当のアデルさんが参加しているべきではない大阪オフィスのグループIDが出力されました。
追加と削除を実行
あとはアクションを使って追加と削除を行えばよいのです。並列に処理を分けてみました。
addGroupsとremoveGroupsは配列なので、1要素ずつをアクションに与えるためにApply to eachを使っています。
ループを使わずにHTTP要求をつかってバッチでユーザーをまとめて複数のグループに追加することもできるようです。このあたりの試みはまた別の記事で書こうと思います。
追加、削除のアクションを加える前に、アレイのフィルター処理で得られたグループIDが意図通りのチームに紐づくものかを確認しましょう。本番環境でチームから大量にユーザーを削除してしまったり事故が起きないように最新の注意を払いましょう。
結果
3名分の複雑な追加と削除が、実行時間12秒で完了しました。
ご承知の通り、currentGroupsはその時点でのユーザーが参加しているグループを拾ってきますので、同じフローを2回目に実行した場合には各ユーザーがすでに移動する必要がありませんので、処理は早くなります。2回目は5秒に短縮しました。
まとめ
実行時点のユーザーが参加しているグループを、指定したグループIDのなかから限定的に取り出すことができる
POST /directoryObjects/{id}/checkMemberGroups が使えたことが今回のポイントです。このGraph APIエンドポイントは M365グループ系のHTTP要求アクション(赤いやつ)を使えるのでプレミアムライセンスは必要ありません。
何を足して、何を引くか を配列で整理してアクションに与えています。 アレイのフィルターの部分が少し複雑ですが、差集合を扱うときにはよく使うパターンなのでマスターしておくと何かと役立ちます。こちらの記事がわかりやすいです。
Formsで従業員に選択させてチーム移動させたい
別記事で紹介します。
すでに参加しているチームから脱退までさせたくない場合は?
処理はグループへの追加とグループからの削除で別になっていますから、後者のアクションを実行しなければ良いだけです。
フロー全体像
最後に全体のスクショを貼っておきます。最初に addGroupsとremoveGroupsの変数を初期化していましたが、アレイのフィルター処理をそのまま使ったので不要となりました。

こんな人が書いてます。
職場でPower Platformの管理者さんをしています。ブログではPower AutomateのTipsのようなものを書いています。QiitaではPower BIとクラウドフローのちょこ技を記事にしています。そちらも読んで「いいね」などしていただけるととっても喜びます。
Power Platform系のコミュニティに参加しています。関西でのリアルイベントにはなるべく参加するようにしているので、アイコンを見かけたらぜひ声をかけてください。



































