Power AutomateでGraph APIから得た自分がアクセスできるSPOサイト一覧のCSVファイルにする

## こんな人のための記事です
ロールを持っていないのでPowerShellからConnect-SPOServiceのようなコマンドが使えないけれど、自分や指定したユーザーがアクセスできるチームに紐づくSPOサイトの一覧を取得したいようなニーズを持つ人にお勧めです。

うまくPower Appsに組み込めば、サイト管理アプリのようなものも作れると思います。
文末のおまけに、他のユーザーが実行すると、その人のチャットに結果が届く仕組みも紹介してみます。

## 概要はこんな感じ
1. Power Automateのクラウドフローで、自分がどのグループに所属しているのかIDの一覧を取得します
2. Graph APIでグループが紐づくサイトの一覧を順番に取得して配列に記録します

ちなみに、自分がどのグループに所属しているかもGraph APIを使って取得することができますが、若干違いがありました。このあたりも違いについて書きました。

## グループを調べる方法
### 「グループの一覧」と「自分が所有および所属しているグループの一覧」の違い

標準の中にあるOffice 365 Groupsコネクターを使います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/285141/fc31f76a-b31b-c901-31a1-05b4ccdb8537.png)

コネクターの中には、「グループの一覧」と「自分が所有および所属しているグループの一覧」という使えそうな2種類のアクションがありました。それぞれ実行してみて、件数を比較してみます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/285141/f7a4f62c-5763-b6a3-c243-cb07e6d2c69b.png)

私の環境の場合、結果は「グループの一覧」が14件、「自分が所有および所属しているグループの一覧」は10件でした。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/285141/5be67d41-debc-13ef-c46e-40f1c2c80424.png)

ちなみに、件数を数えるには、アクションの中でlength関数を「作成」を使います。配列の数を数えています。
```
length(outputs('グループの一覧')?['body/value'])
```
Entra IDをつかってテナント内のグループ数を数えると全部でグループが14ありました。どうやら、全社はすべてのグループ。後者は自分が所属しているグループという扱いのようです。今回は後者の自分が所有および所属しているグループの一覧」を使いましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/285141/c0939e2f-31f3-298a-81ff-ebfaf2591205.png)



### Graph APIでも取得できる
余談ですが、Graph APIでも自分が所属するグループを取得することができます。[みんな大好きGraph Explorer](https://developer.microsoft.com/en-us/graph/graph-explorer)を使って確かめます。以下のエンドポイントを入力して「Run query」をクリックしてみてください。
```
https://graph.microsoft.com/v1.0/me/memberof?$top=100
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/285141/5dcef078-8fb7-9770-d36c-46389aa34938.png)
$top=100 というのは、100件まで表示しなさいというおまじない。一定数以上はこれをつけておかないと端折られてしまうようなのでいちおうつけています。

こちらのエンドポイントも、クラウドフローに組み込んでみましょう。

get /me/memberof は、Office 365 Usersコネクターの 「HTTP要求を送信します」アクションを使って実行できます。いろんなコネクターにHTTP要求を行えるアクションがありますが、得手不得手があります。

HTTP要求はPremiumコネクターが必要と思ってしまいがちですが、Graph APIにかぎっては意外と標準コネクターで使えるものがありますので試してみるとお得です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/285141/48fe2209-d35f-e734-ad93-3666734618e7.png)

結果を見てみると、Groupsアクションで取得したほうは10件に対して、Graph APIだと11件取れました。
多い１件のIDをEntra IDで確認してみたところ、すでに存在しないグループIDでした。　

どうやらGraph APIでは消えたIDも覚えていて、Groupsコネクタのほうは削除されたグループを無視してくれるようです。無視してくれたほうが実態に合っていますので、こちらを使いましょう。


## フローを組み立てる
### JSONの中身を見て項目を検討する
自分が所属するグループの一覧から、１グループ分の中身を見てみましょう。
displayNameはグループ名です。グループ名はチーム名でもあります。
resourceProvisioningOptions　は、このグループがチームに紐づくグループであることを示しています。じつは、ViVa Engageで作成されたコミュニティーや、Projectなどもチームと同じように背後にSharePointサイトを持つことができます。種類がわかるのでこの項目も取得しておきます。
createdDateTimeはグループが作成された日時。これはチームが作成された日時でもあり面白いので使おうと思います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/285141/1d9ba3c3-9486-7eca-a21e-01040f5416bb.png)

### Apply to eachでIDごとにGraph APIでSPOサイトのURLを取得する
いよいよ、取得したIDからSPOサイトのURLを取得します。実際には以下のようなエンドポイントを使います。
```
https://graph.microsoft.com/v1.0/groups/グループID/sites/root
```

ためしに、先ほど取得したIDをつかって実行してみます。
Office 365 Groupsコネクターの「HTTP 要求 V2 を送信する」アクションを使います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/285141/445f4ff7-f7f6-d9f0-897e-c521bed9bc5c.png)

IDを直接URLの一部に組み込んで実行します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/285141/84b477d8-edc9-b0fc-45c5-7944e80dee5b.png)

取得できたのが以下のような値です。「webUrl」の項目でSPOサイトのURLが取得できました。うまくいきそうです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/285141/0d5405b8-1df3-68ce-bec1-ce3bc81992ba.png)

アイテムのIDをGraph APIのサイトIDの部分に差し込んで回します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/285141/791e3520-3cea-e95b-a90a-26018c57149b.png)

グループの一覧ですでに取得できている３つの項目と、HTTP要求を通じて取得できた項目とを、「作成」アクションをつかってひとつのJSONにまとめました。これが1グループ分になります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/285141/4e8c38ad-afc0-92c9-7d0c-e2b341b20526.png)
Apply to eachの内容を取得するための式は以下の通りですので参考にしてください。
```
item()?['displayName']
```
```
item()?['resourceProvisioningOptions']
```
```
item()?['createdDateTime']
```
```
outputs('HTTP_要求_V2_を送信する')?['body']?['webUrl']
```
エラーが出る場合は、波括弧の形がちがうか、各変数の後ろのカンマが抜けていると思います。

### １ループごとに配列に追加する
ループの中に「作成」で要素をまとめることができましたが、このままでは１回ループが回るごとに上書きされてしまいます。ループの外に結果を持って出るには変数が必要です。

そこで、Apply to eachの前に「アレイ」型で初期化をした変数を用意します。
ループの中では、「作成」の結果を「配列変数に追加」アクションを使って、先ほど初期化したアレイ変数に入れていきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/285141/569b7acb-a937-ff2d-486f-0031a7d535be.png)

### 仕上げのCSV 出力
配列になったJSONを作成することができたので、仕上げにCSV化してSharePoint Onlineの適当なドキュメントライブラリに保存してみます。

ファイルコンテンツの直前に加えている式は、UTF-8のBOMです。これを付けておくとCSVファイルをExcelで開いたときにも文字化けしません。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/285141/03f666cc-3096-4337-d9d1-894317f3ecbf.png)
```
decodeUriComponent('%EF%BB%BF')
```
このあたりの事情については、こちらの記事を参考にしてください。

https://qiita.com/DaddyDaddy/items/95f5fd716a585d5575e0

### resourceProvisioningOptionsは単独の配列
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/285141/1eb39537-2b6c-e89c-3dd4-d922f5dd0e72.png)

出力できたCSVファイルをExcelで開いてみました。良い感じに私が参加しているチームとそのサイトURLが取得できました。
ただ、よく見てみるとresourceProvisioningOptionsが配列のようになっています。値が入っていますが１つしか値のない配列のようです。値がひとつしかないなら、一つ目だけ取得したほうがすっきりします。

配列の中身を決めているのはこの部分でしたね。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/285141/a4f1ba6b-919f-b6a1-6e25-185b5266ffa8.png)

２番目の式の中身を以下のようにお尻に[0]をつけて0番めの値だけを取得するように変更してやります。ただ、そのままだと配列が空の場合にエラーとなって他の値も取得できなくなってしまうため、以下のようにちょっと複雑な感じがしますが条件分けすることでエラー回避しました。
```
items('Apply_to_each')?['resourceProvisioningOptions']
↓
if(equals(length(items('Apply_to_each')?['resourceProvisioningOptions']), 0),'',items('Apply_to_each')?['resourceProvisioningOptions'][0])
```
すっきりしました！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/285141/83b3295f-2972-c11c-c0a6-04bbecc419c3.png)

## おまけ
今回はCSV化してSPOドキュメントライブラリに保存しました。クラウドフローは実行のみユーザーを指定することで、このクラウドフローを実行した人のアカウントに対してフローを実行させることもできます。

例えば、実行すると、そのユーザーのTeamsにボットから結果が届くなんてどうでしょうか？

### HTML化してトリガーを押したユーザーにチャット送信
CSVの代わりにHTMLテーブルを使います。Apply to eachから取り出したグループの配列変数を与えてやります。
Teamsの「チャットまたはチャネルでメッセージを投稿する」のRecipientには、クラウドフローのトリガーから「
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/285141/3a1a04f7-b1c3-afe6-0876-fb8fff9fe421.png)

ユーザーの電子メールを設定してやります。ボタンを押した人がこれでわかります。本人に送ってあげる仕組みがこれで実現できています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/285141/be6540dd-b7a4-9a67-6593-a93375e08855.png)


### 実行のみユーザー設定
クラウドフローの詳細画面表示右側にある「実行のみのユーザー」の「編集」ボタンをクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/285141/8d7fd067-c2af-76b9-d0f7-d44029c64445.png)

実行させたいユーザーや、そのユーザーが含まれているグループを入力します。
SharePointへのCSVは管理者として保存します。チャットも管理者から送信します。チャットの送信先はトリガーのIDを使うので問題ありません。グループはクラウドフローのボタンを押したユーザーの情報を取得したいので、「実行専用ユーザーによって提供されました」を選択します。

この状態で、なんのロールも持たないユーザーにクラウドフローのURLを渡して実行してもらいましょう。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/285141/ff6c68da-97ba-da25-fcf5-651b2c9526cb.png)

本人のチャットに、ユーザーさんが参加しているグループと、そのグループが紐づくSPOサイトのURL一覧が届きました！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/285141/c578116f-ed25-267e-40c7-f913882ea83f.png)

## 　まとめ
Graph API をつかうと、用意されたアクションだけでは対応できない要望にも応えられます。HTTP要求を送信するアクションのコネクターにはいくつも種類があるので、Graph Explorerで試して使えそうなものがあれば、片っ端からどのコネクターのものが利用できるか使ってみるとよいでしょう。

実行専用アクセス許可にはグループが設定できるので、トリガーの情報もつかうと簡単なボットのように使えます。

## こちらもどうぞ
Power Automateのクラウドフローについて、TIPSのような記事を書いています。
こちらも参考にしていただけると嬉しいです。
皆様良いPower Automate Lifeを！

https://mocabrown.com/blog/powerautomate