組織内個人別の OneDrive 上に Power Automate で外部共有用のフォルダを自動生成

## Microsoft 365 組織内のそれぞれの個人 OneDrive 領域にフォルダを作成して共有リンクを作る
外部のお客様とのファイル共有に SharePoint のドキュメント ライブラリを使ったフォルダ等の外部共有はとても簡易に使用できて素晴らしい機能だと思います。
1. Microsoft アカウントが必要な特定のユーザーへの共有も、パスワードを設定したユーザーを限定しない共有も可能
1. 期限を設定して共有を自動的に終了させることが可能
1. フォルダ単位でもファイル単位でも共有リンクを作成可能

このように便利である反面、 SharePoint 領域の容量は限られておりファイルサーバーとして使用していたりすると外部共有で容量を消費してしまうのはちょっと気が引けておりました。

そんな中でも Microsoft 365 のアカウントごとに OneDrive for Business 領域は 1TB 用意されており、端末のバックアップで使用していたとしても業務用 PC のバックアップ程度であれば十分な容量が確保できます。
さらに以前はできなかった（と記憶している）フォルダ共有時のパスワードと有効期限付きの全公開（ Microsoft アカウント不要の共有方法）アクセスの設定ができるようになっていることに気が付いて、これを使わない手はないと考えました。

しかし、ユーザーのリテラシーを考えると…
1. 必ず強力なパスワードを設定してくれるか
1. 必ず有効期限を設定して共有しっぱなしにしないか

等の心配があり、しかも個人の OneDrive 領域は管理者でも常に監視するのが難しい（と感じている）こともあり、一歩を踏み出せずにいました。幸い期限設定に関してはシステム側で共有リンク作成時に日数を設定することが可能であったため、最低限 10 日で設定することで回避はできそうでしたが、OneDrive の共有リンク作成に自動的にパスワードを生成する機能が存在していないため安易なパスワードを設定してしまうことは容易に考えられました。

そこで Power Automate を利用して自動的にパスワードと期限を設定した共有フォルダを自動作成してしまうことを考えたわけです。

## 前提条件
1. Microsoft 365 で SharePoint が利用できるライセンスを持っており、各ユーザーに OneDrive for Business の設定が行われている
1. Power Automate でフローを作成するユーザー（管理者）に Microsoft Graph のアクセス権限が適切に付与されている
※ おそらく必要な権限は以下です
Files.ReadWrite 、 Files.ReadWrite.All 、Sites.ReadWrite.All
1. 上記同様に Microsoft 365 のユーザー情報が取得できる管理者権限が適切に付与されている
1. フローを使用するユーザーの OneDrive ルート上に特定の名前でフォルダを作成（今回は「外部共有専用フォルダ」としています）しフローの管理者に編集権限で共有設定が実施されている

## 実際のフロー
<details><summary>クリックして表示</summary>

長いのでキャプチャがずれていて見ずらいですが…
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12937/838a7d33-d001-47a3-abac-7e46d0ceb2cb.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12937/d5b8229a-4206-4e09-a9ca-8292523ebc4a.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12937/3388d6cf-b2f6-4fe2-9c2b-9490fa6fe0a4.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12937/38715ed7-5225-456e-bf5f-b9e07d63943f.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12937/9e96f8f6-6a4a-4ac9-a4df-1a0091bbb118.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12937/fe3ad97c-60ad-4cbc-9513-4db2ded5ec1f.png)

</details>

## 実際のフローと解説
※ 一部詳細は省きます（それぞれの公式情報をご確認ください）
まず Microsoft Forms でお客様の名前だけを登録するフォームを作成し、組織内のみ使用でユーザー情報が取得できるように設定します。
Power Automate で「新しい応答が送信されるとき」をトリガーとして設定し、応答の詳細を取得するフローを使用、トリガーの応答 ID を設定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12937/ce4d0d59-4e21-4c22-b510-c4e517d32ad6.png)
次に変数を初期化するフローにて「メールアドレス」変数を作成、 Type は String で応答の詳細を取得するフローから E メールを取得して設定してしまいます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12937/5b3ddcd2-a9e5-4654-9b63-02feab2aee74.png)
次にユーザー プロフィールの取得（V2）フローを使用し User に変数のメールアドレスを設定してフォームを送ってきたユーザー情報を取得します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12937/7d662f71-2082-4984-a704-7499b27730bd.png)
次に変数を初期化するフローを使用し「ユーザー ID」変数を作成、 Type は String で先のフローで取得したユーザー プロフィールから Nickname を設定してしまいます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12937/3732e899-ef4d-476c-8c3a-11b723aae6ba.png)
同じく「フォルダ名」変数を作成しフォームから送信されてきたお客様名と、今回はフォルダに日付を付与する形にて被りにくい共有フォルダを作成するために以下のようなコードで日付を取得して入力しています。
```powershell
convertFromUtc(utcNow(),'Tokyo Standard Time','yyyyMMdd')
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12937/36d90db9-2f2b-4ddb-90d3-d8e9c8774cca.png)
同様に「共有専用フォルダチェック」変数を作成し Type は Boolean で初期値は false として設定しています。
これに関しては前提条件にもありますが、この先のフローで実行する共有フォルダを作成する際に他人の OneDrive 領域には権限がなく共有リンクの作成ができないため、権限がなくてできないのであれば権限を付けてしまえと言うことで、事前作成されているか確認するために変数を用意しています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12937/1cbf30b3-3c85-4a5c-8c09-58a04e3d2ffe.png)
同じく共有リンクを作成する際にパスワードを設定しますが、 Power Automate ではランダムパスワードが生成できなかったため自力で生成して保存するための箱を「生成フォルダパスワード」として変数を用意しています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12937/28e6a7ab-4c8f-4125-8c9e-7ce76b5529b3.png)
次に共有リンクに有効期限を設定しますが、これはフローで設定するのではなく Microsoft 365 管理センター上にて最低 10 日の期限設定を自動設定しているため、未来の時間の取得フローにて 10 日後の日付を取得しています。これは最後のメール送信時の記載内容として使用します。システムで設定している有効期限を適切に設定されてください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12937/2b3222a3-333c-4f65-af36-7b636a4560f2.png)
次にループを作成してランダムパスワードを生成しています。まずはループ内に「作成」フローを配置して入力に以下のコードを設定しています。
```powershell
substring('abcdefghijklmnopqrstuvwxyz0123456789-+^@ABCDEFGHIJKLMNOPQRSTUVWXYZ',rand(1,66),1)
```
参考にしたのはこの辺

https://xds.co.jp/%E3%80%90powerapps%E3%80%91%E3%83%A9%E3%83%B3%E3%83%80%E3%83%A0%E6%96%87%E5%AD%97%E5%88%97%E4%BD%9C%E6%88%90%E6%A9%9F%E8%83%BD%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6

Power Automate でも`rand()`は使えるのでパスワード用の文字列を用意してそこからランダムに 1 文字取り出してパスワード用の文字として利用、ループで必要な文字列分（今回は 9 文字としています）繰り返して変数に追加していけばという発想です。
ちなみに記号として`<>`も最初は使用していましたが、メール送信の段階で本文に加わると html のコードとして認識されてしまうので使うのをやめました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12937/001eb0df-2d39-49c1-857b-c530c77e75f7.png)
ループの設定では以下のようにコードで設定しています。
```powershell
range(1, 9)
```
`range()`関数でループしたい数の配列を作って利用しています。
参考にしたのは以下

https://koruneko.hatenablog.com/entry/2023/07/10/004648

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12937/b3dd7776-564e-459a-9ed6-2e0bc874a6db.png)
ほとんどは上記パスワード生成で問題ないですが、 OneDrive で共有リンク作成時にパスワードを設定していると数字、半角大文字、半角小文字、さらに記号が含まれて 9 文字になると「強い」判定がされていたので上記ループで記号が含まれていなかった場合に 1 文字だけ記号を含める設定を追加しています。
生成されたパスワードを確認し記号が含まれていなければ作成フローで以下のようなコードを追加してパスワード文字列を置き換えています。
```powershell
replace(variables('生成フォルダパスワード'),substring(variables('生成フォルダパスワード'),rand(1,9),1),substring('-+^@',rand(1,4),1))
```
単純に記号の文字列を用意し`rand()`で 1 文字抜き出して同様に生成されたパスワード文字列の`rand()`で選択した 1 文字と置き換えています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12937/fe767b0b-ad43-4d29-b592-2649a57b21c1.png)
条件ではパスワード文字列内に記号文字が含まれているかを以下のようにコードでチェックして判断しています。
```powershell
indexOf(variables('生成フォルダパスワード'),'-')
```
それぞれ使われている可能性のある記号文字を 1 文字ずつ判定し AND でどれか 1 文字でも含まれていなければ追加するようにしています。
この辺りもっと容易に判定するコードとかありますかね？
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12937/026ee6b1-7d8d-460e-bd24-705f0086bc16.png)
次に Power Automate の SharePoint コネクタにあるフォルダーの一覧フローを利用して該当ユーザーの OneDrive ルートのフォルダ情報を取得していきます。
この時通常は他人の OneDrive の情報は取得できないのですが、 SharePoint コネクタを利用してサイトのアドレスにユーザーの情報を使用することで他人の OneDrive 情報の一覧を取得することに成功しています。
参考にしたのは以下の情報

https://qiita.com/yamad365/items/615150a1a66c7b7b82a8

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12937/58f2f614-358a-489e-ae92-861ca3b379a6.png)
取得したフォルダーの情報をループで取り出し共有用に権限を付与して作成されたフォルダが存在しているかを確認しています。ここではフォルダが存在していれば変数を`true`に設定しているだけです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12937/7527f52a-aee1-478f-8611-e00ee52fcd27.png)
共有用のベースフォルダが作成されていなければメールを送って終了フローで`cancel`として処理を中断しています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12937/cadb10cf-094d-487d-b5dc-0b5852ec3e6b.png)
SharePoint コネクタの新しいフォルダーの作成フローにて共有用のベースフォルダー内に事前に作成しておいたお客様名のフォルダー名でフォルダを作成します。
ここで「一覧またはライブラリ」に OneDrive のルートを意味するコードを入力する必要がありますが、先ほどのフォルダー一覧取得時に参考にしたサイトに詳しく情報が記載されています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12937/c07a9da0-9a93-44f1-bbe6-62928715fad5.png)
次にこの先で使用するフォルダのパスを作成フローにて用意しています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12937/2d73b4aa-7365-4c7b-a8e4-b08e3578429f.png)
ここから Microsoft Graph を利用して少し複雑な処理を設定する必要が出てきます。この設定でエラーが発生する場合、フローの管理者に Microsoft Graph で実行する適切な権限が付与されていない、各ユーザーの OneDrive 領域に作成した共有用のベースフォルダーにフローの実行者への編集権限が付与されていない等の要件が満たされていない可能性が高いです。

フローとしては Microsoft 365 ユーザーコネクタの「 HTTP 要求を送信します」フローを利用します。 URI には Graph の API の URL を入力します。公式の情報は以下です。

https://learn.microsoft.com/ja-jp/graph/api/driveitem-get?view=graph-rest-1.0&tabs=http

メソッドは`GET`で詳細パラメーターには`Content-Type`で`application/json`を必ず指定してください。
ユーザー ID の部分にはユーザープロファイルの取得から`id`を利用し、ドキュメントのパスは以下のコードで URL エンコードして指定しています。

```powershell
encodeUriComponent(outputs('作成（パス作成）'))
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12937/6f748c28-50c7-4fac-9090-4364e2d2b6a3.png)
次に JSON の解析で取得した本文を以降で使用できるように変換しています。スキーマに関しては一度情報を取得し本文を「サンプル ペイロードを使用してスキーマを生成する」から自動生成してください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12937/303b29e4-f859-4ed2-9066-82187e60579a.png)
いよいよ共有リンクの作成です。公式の情報は以下になります。

https://learn.microsoft.com/ja-jp/graph/api/driveitem-createlink?view=graph-rest-1.0&tabs=http

`POST /drives/{driveId}/items/{itemId}/createLink`の`{itemId}`に対応する部分は先の JSON 変換で取得した id を指定します。いくつか id の取得が行われているため適切な id が設定できるように行ってください。
メソッドは`POST`で詳細パラメーターには`Content-Type`で`application/json`を必ず指定してください。本文には今回参照専用で作成しているため以下のように設定しています。
```json
{
  "type": "view",
  "password": "@{variables('生成フォルダパスワード')}",
  "scope": "anonymous"
}
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12937/a187c36d-c766-4415-a65d-36820971bcf0.png)
取得した情報を JSON の解析フローで使用できるように設定しておきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12937/189f7746-d19a-4e11-b5f6-2e08b690833c.png)
今回は後で確認できるように SharePoint Lists でリストに情報を残しています。ここは必要に応じて適宜設定してみてください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12937/2a0da547-b27c-48d1-a103-334fdfdc7faa.png)
最後に生成された共有リンクやパスワード情報をフォームを送ってきたユーザーにメールで送信しています。本文は適宜考えてみてください。
なお、先に取得していた未来の時間の取得から有効期限を以下のようにコードで調整して表示しています。
```powershell
formatDateTime(body('未来の時間の取得'),'yyyy年MM月dd日')
```
ファイルリンク部分は HTML 表示で以下のような設定にしています。
```html
ファイルリンク: <a href="@{body('JSON_の解析（共有フォルダの解析）')?['link']?['webUrl']}" class="editor-link">@{variables('フォルダ名')}</a></p>
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12937/9878171c-39c2-4c06-a654-3f5234104322.png)
## まとめ
容量の十分に確保できる個人の OneDrive for Business 領域に Power Automate でフォルダを作成できないか試行錯誤していましたが、権限ないなら付けてしまえと言うことで、ここは個別の対応が必要ではありますが権限を付与した専用のフォルダを用意することで解決の糸口が見えました。
いろいろと工夫されている方々の情報がありとても参考になりましたが、みなさん試行錯誤されているのですね。こういうのは嫌いじゃないです。

Power Automate 上だけで安易にパスワードが生成できないとか、文字列からこの中の一文字でも一致したらの条件があまりスマートに設定できていなかったりとまだまだ改良の余地はありそうです。まず検討するとしたら共有用の専用フォルダに管理者の編集権限が付与されていなかった時のエラー処理が必要かなぁと考えています。