Power Appsをつかって脱PPAPアプリ作成① ～Power Automate編～

以前、「[Power Automateをつかって脱PPAP](https://mssp.tenda.co.jp/blog/office365/3715/)」で、Power Automateを使った脱PPAPを紹介しました。今回は、ユーザーがより使いやすいようにPower Appアプリで実装する方法を紹介します。

## PPAPとは
取引先などにファイルを共有する際、パスワード付きZIPファイルを送信し、別のメールでパスワードを送信するといった流れを一度は経験したことがあると思います。この一連の手順を「PPAP」と呼びます。
- P：パスワード付きZIP暗号化ファイルを送ります
- P：パスワードを送ります
- A：暗号化
- P：プロトコル

# Power Appsでアプリ化
「[Power Automateをつかって脱PPAP](https://mssp.tenda.co.jp/blog/office365/3715/)」では、脱PPAPの手段として、Power Automateでファイルを共有する方法を紹介しました。しかし、以下のような課題がありました。

- ファイル共有時に毎回フローの変数を変更、保存してから実行する必要がある
- 詳細な共有履歴を確認できない

そこで、これらの課題を解消するために、Power Appsでファイル共有アプリを作成する方法を紹介します。
下記の３部構成でご紹介します。今回は第１回目です。
- 第１回：Power Appsをつかって脱PPAPアプリ作成① ～Power Automate編～
- 第２回：Power Appsをつかって脱PPAPアプリ作成② ～Power Apps編～
- 第３回：Power Appsをつかって脱PPAPアプリ作成③ ～実装編～

## 事前準備
Power AutomateはMicrosoft 365のライセンスを所持していれば利用できます。
必要なものを準備します。
- SharePointリスト
共有した履歴を記録するためにSharePointリストを用意します。以下はSharePointリストのイメージです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3992089/66c357d4-ccea-4d15-8dd1-15d26ef9ef47.png)

リストは以下のとおり設定します。項目名は適宜変更してください。
<table>
  <thead>
    <tr>
      <th>No.　</th> <th>項目</th> <th>内部名　　　　　　</th> <th>種類</th> <th>必須　</th> <th>備考</th>
    </tr>
  </thead>
  <tr>
    <td> 1 </td> <td>サイトのアドレス</td> <td>siteUrl</td> <td>1行テキスト</td> <td>〇</td> <td></td>
  </tr>
  <tr>
    <td> 2 </td> <td>ライブラリ名</td> <td>libraryName</td> <td>1行テキスト</td> <td>〇</td> <td></td>
  </tr>
  <tr>
    <td> 3 </td> <td>ファイル名</td> <td>fileName</td> <td>1行テキスト</td> <td>〇</td> <td></td>
  </tr>
  <tr>
    <td> 4 </td> <td>権限</td> <td>role</td> <td>選択肢</td> <td>〇</td> <td>選択肢：閲覧、編集</td>
  </tr>
  <tr>
    <td> 5 </td> <td>URL</td> <td>Url</td> <td>ハイパーリンクまたは画像</td> <td></td> <td></td>
  </tr>
  <tr>
    <td> 6 </td> <td>通知</td> <td>Notification</td> <td>はい/いいえ</td> <td></td> <td></td>
  </tr>
  <tr>
    <td> 7 </td> <td>共有先アドレス</td> <td>mailAddress</td> <td>1行テキスト</td> <td>〇</td> <td></td>
  </tr>
  <tr>
    <td> 8 </td> <td>メッセージ</td> <td>message</td> <td>複数行テキスト</td> <td></td> <td></td>
  </tr>
    <tr>
    <td> 9 </td> <td>登録日時</td> <td>Created</td> <td>日付と時刻</td> <td></td> <td></td>
  </tr>
</table>

- 共有ファイル
共有するファイルは、同SharePointサイトのドキュメントライブラリ配下に配置します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3992089/5667a5b3-c75e-4699-b39b-c6f32eb422a0.png)

# アプリの構成
作成するアプリの構成図です。
今回はPower Automate編ですので、事前準備としてSharePointリストを作成し、Power Automate部分を作成します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3992089/4f18a3fd-d48c-4ade-a91f-e26f19397684.png)

▼SharePoint：
リストを作成します。共有するファイルはライブラリに配置します。
▼Power Apps：
3画面作成します。SharePoint、Power Automateと連携させます。
▼Power Automate：
Power Appsと連携し、Power Appsをトリガーにフローを起動させます。

## Power Automateフローの全体像
今回作成するPower Automateフロー（以下フロー）の全体像です。
Power Appsで作成したアプリを実行すると、フローが自動で実行されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3992089/1eabc9d7-2cf9-492b-8513-111635c1ec32.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3992089/f720374b-96ad-45fe-b5e1-a114d66e89d8.png)

大まかな構成としては、下記の順にフローが進みます。
1.	Power Appsから値を取得
2.	ファイルのプロパティ取得
3.	変数の初期化
4.	ファイルのアクセス権付与
5.	共有情報の取得・JSONの解析
6.	共有リンクの取得
7.	Power Appsに戻り値を設定
8.	JSONの解析「スキーマ」の編集

SharePointリストの準備ができたら、まずはPower Automateフローから作成していきましょう。

## トリガーの設定
トリガーを「Power Apps」に設定し、Power Appsから連携された値を取得します。

1.	トリガーする方法から「PowerApps」を選択し、フローを作成します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3992089/3471e2cf-0054-4257-b565-1f4813dcd014.png)

2.	トリガーを選択すると「PowerApps」アクションがフローに追加されるので、Power Appsから受け取る項目を設定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3992089/c56fa2cd-05b4-486c-b522-6e837a7f10fd.png)

７つの項目を設定します。説明（右側の入力ボックス）は、任意で設定してください。
※Power Apps側で渡す項目の順番と合わせる必要があります。項目順が異なると、処理が正常に動かないので注意してください。
<table>
    <tr>
      <th>順序　</th> <th>種類　　　　</th> <th>テキスト</th> <th>説明（例）</th>
    </tr>
  </thead>
  <tr>
    <td> ① </td> <td>テキスト</td> <td>サイトのアドレス</td> <td>サイトのアドレスを入力してください</td>
  </tr>
  <tr>
    <td> ② </td> <td>テキスト</td> <td>ライブラリ名</td> <td>ライブラリ名を入力してください</td>
  </tr>
  <tr>
    <td> ③ </td> <td>テキスト</td> <td>ファイル名</td> <td>ファイル名を入力してください</td>
  </tr>
  <tr>
    <td> ④ </td> <td>テキスト</td> <td>受信者</td> <td>受信者を入力してください</td>
  </tr>
  <tr>
    <td> ⑤ </td> <td>テキスト</td> <td>ロール</td> <td>ロールを入力してください</td>
  </tr>
  <tr>
    <td> ⑥ </td> <td>テキスト</td> <td>メッセージ</td> <td>メッセージを入力してください</td>
  </tr>
  <tr>
    <td> ⑦ </td> <td>はい/いいえ</td> <td>はい/いいえ</td> <td>[はい]または[いいえ] を選択してください</td>
  </tr>
</table>

## ファイルのプロパティ取得
「ファイルの取得（プロパティのみ）」アクションを追加します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3992089/baee977a-e618-4f40-955e-10ac9597d152.png)

<table>
 <caption>設定値</caption>
  <thead>
    <tr>
      <th>No.</th> <th>項目</th> <th>設定値</th>
    </tr>
  </thead>
  <tr>
    <td> ① </td> <td>サイトのアドレス</td> <td>＜サイトのアドレス＞</td>
  </tr>
  <tr>
    <td> ② </td> <td>ライブラリ名</td> <td>＜ライブラリ名＞</td>
  </tr>
  <tr>
    <td> ③ </td> <td>フィルタークエリ</td> <td>FileLeafRef eq '＜ファイル名＞'</td>
  </tr>
</table>

設定値は、動的なコンテンツの「PowerApps」から選択します

## 変数の初期化
ファイル共有リンクのURL、URLの個数、共有先ユーザーのメールアドレス、ファイルのIDを格納するための変数を初期化します。具体的には、「変数を初期化する」アクションを４つ追加します。以下のとおり設定します。（順不同）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3992089/d5c7f3cb-aa92-4949-908f-67207f2df732.png)

<table>
 <caption>設定値</caption>
  <thead>
    <tr>
      <th>No.　</th> <th>名前　　　　</th> <th>種類　　</th> <th>値</th>
    </tr>
  </thead>
  <tr>
    <td> ① </td> <td>URL</td> <td>文字列</td> <td>＜ブランク＞</td>
  </tr>
  <tr>
    <td> ② </td> <td>配列数</td> <td>整数</td> <td>＜ブランク＞</td>
  </tr>
  <tr>
    <td> ③ </td> <td>メール</td> <td>文字列</td> <td>＜ブランク＞</td>
  </tr>
  <tr>
    <td> ④ </td> <td>ID</td> <td>整数</td> <td>outputs('ファイルの取得_(プロパティのみ)')?['body/value']?[0]?['ID']</td>
  </tr>
</table>
IDの値は「式」に入力します。

## ファイルのアクセス権付与
「アイテムまたはフォルダーへのアクセス権の付与」アクションを追加します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3992089/0e9556c4-01e8-43e6-bd34-4bb7c4fe6c16.png)

<table>
  <caption>設定値</caption>
  <thead>
    <tr>
      <th>No.　</th> <th>項目</th> <th>設定値</th> <th>備考</th>
    </tr>
  </thead>
  <tr>
    <td> ① </td> <td>サイトのアドレス</td> <td>＜サイトのアドレス＞</td> <td>動的なコンテンツの「PowerApps」から選択</td>
  </tr>
  <tr>
    <td> ② </td> <td>リストまたはライブラリ名</td> <td>＜ライブラリ名＞</td> <td>動的なコンテンツの「PowerApps」から選択</td>
  </tr>
  <tr>
    <td> ③ </td> <td>ID</td> <td>＜ID＞</td> <td>動的なコンテンツの「変数」から選択</td>
  </tr>
  <tr>
    <td> ④ </td> <td>受信者</td> <td>＜受信者＞</td> <td>動的なコンテンツの「PowerApps」から選択</td>
  </tr>
  <tr>
    <td> ⑤ </td> <td>ロール</td> <td>if(equals(triggerBody()['text_4'],'閲覧'),'role:1073741826','role:1073741830')</td> <td>「式」に入力<br>
・閲覧：1073741826<br> 
・編集：1073741830 
</td>
  </tr>
  <tr>
    <td> ⑥ </td> <td>メッセージ</td> <td>＜メッセージ＞</td> <td>動的なコンテンツの「PowerApps」から選択</td>
  </tr>
  <tr>
    <td> ⑦ </td> <td>受信人への通知</td> <td>＜はい/いいえ＞</td> <td>動的なコンテンツの「PowerApps」から選択</td>
  </tr>
</table>
本来ロールの設定値は、動的なコンテンツの「PowerApps」から「ロール」を選択すべきですが、「ロール」を選択するとIDに変換されず文字列のままセットされてしまう事象が発生したので、今回はPower Automate側でロールIDに変換してセットします。
※Power Apps側で変換するでも良いと思います。

## 共有情報の取得
共有するファイルの情報を取得し、解析します。
- HTTP要求（共有情報の取得）
「SharePointにHTTP要求を送信します」アクションを追加します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3992089/78a39538-bc8b-40ba-8b61-6ce59668423f.png)

<table>
  <caption>設定値</caption>
  <thead>
    <tr>
      <th>No.　</th> <th>項目</th> <th>設定値</th>
    </tr>
  </thead>
  <tr>
    <td> ① </td> <td>サイトのアドレス</td> <td>＜サイトのアドレス＞</td>
  </tr>
  <tr>
    <td> ② </td> <td>方法</td> <td>POST</td>
  </tr>
  <tr>
    <td> ③ </td> <td>URI</td> <td>_api/web/lists/getByTitle('＜ライブラリ名＞')/items(＜アイテムID＞)/GetSharingInformation?\$select=permissionsInformation&$Expand=permissionsInformation</td>
  </tr>
<tr>
  <td rowspan="2">④</td>
  <td rowspan="2">ヘッダー</td>
  <td>Accept：application/json</td>
</tr>
<tr>
  <td>Content-Type：application/json</td>
</tr>
</table>

サイトのアドレスと、URIに設定するライブラリ名は、動的なコンテンツの「PowerApps」から選択します。
URIに設定するアイテムID は、動的なコンテンツの「変数」から「ID」を選択します。
③のAPIで共有情報（リンクのURL、メールアドレス等）を取得します。

このアクションを実行することで、SharePointの「アクセス許可の管理」リンク部分の情報が取得できます（下図）。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3992089/e9552f2f-5c0b-423c-8527-79b4ebcfdf00.png)

- JSONの解析
取得した共有情報を解析します。
「JSONの解析」アクションを追加し、「SharePointにHTTP要求を送信します」アクションで、取得した共有情報を解析します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3992089/f8ede7cc-71f4-4888-9780-5586ce3769be.png)

【設定値】
- コンテンツ：

＜本文＞

- スキーマ：
```json
{
    "type": "object",
    "properties": {
        "permissionsInformation": {
            "type": "object",
            "properties": {
                "links": {
                    "type": "array",
                    "items": {
                        "type": "object",
                        "properties": {
                            "linkDetails": {
                                "type": "object",
                                "properties": {
                                    "Url": {
                                        "type":
                                            "string"
                                    }
                                }
                            }
                        },
                        "required": [
                            "linkDetails"
                        ]
                    }
                }
            }
        }
    }
}
```
コンテンツは、動的なコンテンツの「SharePoint に HTTP 要求を送信します（共有情報取得）」から「本文」を選択します。

後ほど説明しますが、上記で設定するスキーマは一時的なものです。フローが完成したら最後に一部修正します。

## 共有リンクの取得

- 共有リンクのNULL判定
1.　コントロールから「条件」アクションを追加し、共有リンクのNULL判定を行います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3992089/9c08160d-18df-4068-97c1-dd7833d95d23.png)

<table>
  <caption>設定値</caption>
  <thead>
    <tr>
      <th>左側の値</th> <th>中央（比較ロジック）</th> <th>右側の値</th>
    </tr>
  </thead>
  <tr>
    <td> ＜Url＞ </td> <td>次の値に等しい</td> <td>null</td>
  </tr>
</table>

左側の値は、動的なコンテンツの「JSONの解析」から「Url」を選択します。

※条件式の左側の値を設定すると自動でApply to eachが設定されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3992089/43e1da59-4f56-4554-90e5-677d9731c49b.png)


2.　「はいの場合」に「変数の設定」アクションを追加します。
「いいえの場合」の処理は不要です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3992089/dd43ef1f-2151-43c4-813c-c90962d74e43.png)
<table>
  <caption>設定値</caption>
  <tr>
    <td> 名前 </td> <td>配列数</td>
  </tr>

  <tr>
    <td> 値（式） </td> <td>length(items('Apply_to_each')?['linkDetails']?['Invitations'])</td>
  </tr>
</table>

ここでは「SharePointにHTTP要求を送信します」アクションで取得した情報から、共有情報が格納されている「Invitations」という配列の数を変数にセットします。
共有するURLの情報はこの配列の中に含まれています。

異なる権限でリンクを既に共有している場合など、共有リンクが複数存在する可能性もあるため、ファイルによって配列数は異なることがあります。

 - 共有者の有無判定
1.　コントロールから「条件」アクションを追加し、共有者の有無判定を行います。
この処理は「共有リンクのNULL判定」の「はいの場合」に追加します。
「変数の設定（配列数）」アクションの下に設定してください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3992089/1c2dea66-49ca-4b65-91e1-508351af9265.png)

<table>
  <caption>設定値</caption>
  <thead>
    <tr>
      <th>左側の値</th> <th>中央（比較ロジック）</th> <th>右側の値</th>
    </tr>
  </thead>
  <tr>
    <td> ＜配列数＞ </td> <td>次の値以上</td> <td>1</td>
  </tr>
</table>

左側の値は、動的なコンテンツの「変数」から「配列数」を選択します。

共有リンクは存在するものの、ユーザーが設定されていない場合（※）は、「Invitations」配列の中に要素がありません。（Invitations配列とは「SharePointにHTTP要求を送信します」アクションで取得した、共有情報が格納されている配列です）
要素がない場合は後続の処理は必要ないため、このアクションで判定します。

※	SharePointリストの「アクセス許可の管理」リンク部分が以下のような状態になっているパターンです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3992089/fb95001e-2b06-4e26-ade6-65121d0ed487.png)

2.　「共有者の有無判定」が「はいの場合」の設定をします。
コントロールから「Apply to each」（それぞれに適用する）アクションを追加します。
「いいえの場合」の処理は不要です。

3.　「以前の手順から出力を選択」入力ボックスの式に下記を設定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3992089/a97923b4-7327-4725-9883-c16d06688001.png)
```
range(0, variables('配列数'))
```

4.　「変数の値を減らす」アクションを追加します。
配列は0から始まるためこのまま進めるとエラーになってしまいます。そのため値を1減らします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3992089/5bb33d41-65bc-493d-99a8-ab488518827d.png)


<table>
  <caption>設定値</caption>
  <tr>
    <td> 名前 </td> <td>配列数</td>
  </tr>

  <tr>
    <td> 値（式） </td> <td>1</td>
  </tr>
</table>

5.　「変数の設定」アクションを追加し、共有者のメールアドレスを変数に格納します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3992089/3c793c4b-5154-4ab4-8ea8-39412ba9abc9.png)

<table>
  <caption>設定値</caption>
  <tr>
    <td> 名前 </td> <td>メール</td>
  </tr>
  <tr>
    <td> 値（式）　 </td> <td>items('Apply_to_each')?['linkDetails']?['Invitations']?[variables('配列数')]?['invitee']?['email']</td>
  </tr>
</table>

- 共有者メールアドレスの判定
1.　コントロールから「条件」アクションを追加します。
PowerAppsから連携されたメールアドレスと、「SharePointにHTTP要求を送信します」アクションで取得した共有情報のメールアドレスを比較します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3992089/b93bd84a-f7cb-4289-bff0-1ba0a7eacf5c.png)
<table>
  <caption>設定値</caption>
  <thead>
    <tr>
      <th>左側の値</th> <th>中央（比較ロジック）</th> <th>右側の値</th>
    </tr>
  </thead>
  <tr>
    <td> ＜受信者＞ </td> <td>次の値に等しい</td> <td>＜メール＞</td>
  </tr>
</table>
左側の値は動的なコンテンツの「PowerApps」から選択、
右側の値は動的なコンテンツの「変数」から選択します。

2.　「はいの場合」に「変数の設定」アクションを追加します。
「いいえの場合」の処理は不要です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3992089/3a412b78-606d-4256-9517-af29d4efb7e6.png)

<table>
  <caption>設定値</caption>
  <tr>
    <td> 名前 </td> <td>URL</td>
  </tr>
  <tr>
    <td> 値　 </td> <td>＜Url＞</td>
  </tr>
</table>

値は動的なコンテンツの「JSONの解析」から「Url」を選択します。

- Power Appsに戻り値を設定
Apply to each処理の下に、「PowerAppsまたはFlowに応答する」アクションを追加します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3992089/217ca6ce-28c9-4b7a-8fb5-bd654bb0c4c0.png)

<table>
  <caption>設定値</caption>
  <thead>
    <tr>
      <th>順序</th> <th>種類</th> <th>テキスト</th> <th>説明</th>
    </tr>
  </thead>
  <tr>
    <td> ① </td> <td>テキスト</td> <td>flowMsg</td> <td>（例）処理が完了しました。</td>
  </tr>
  <tr>
    <td> ② </td> <td>テキスト</td> <td>URL</td> <td>＜URL＞</td>
  </tr>
</table>

URLは、動的なコンテンツの「変数」から「URL」を選択します。

- JSONの解析「スキーマ」の編集
最後にJSONの解析で設定した「スキーマ」を修正します。
Urlのtypeを配列に変更して、”null”を追加します。

修正前のスキーマ（一部抜粋）：
```json
                                   "Url": {
                                        "type":
                                            "string"
                                    }

```

修正後のスキーマ：
```json
{
    "type": "object",
    "properties": {
        "permissionsInformation": {
            "type": "object",
            "properties": {
                "links": {
                    "type": "array",
                    "items": {
                        "type": "object",
                        "properties": {
                            "linkDetails": {
                                "type": "object",
                                "properties": {
                                    "Url": {
                                        "type": [
                                            "string",
                                            "null"
                                        ]
                                    }
                                }
                            }
                        },
                        "required": [
                            "linkDetails"
                        ]
                    }
                }
            }
        }
    }
}
```

なぜ最後に編集する必要があるのか。理由は２つあります。
①スキーマを修正せず実行すると、JSONの解析アクションでエラー（"Invalid type. Expected String but got Null."）が発生します。これは、Urlがnullを許容していないため発生するエラーです。エラーを回避するために、nullを設定しておく必要があります。

②nullを許容すると、動的なコンテンツの選択肢にUrlが表示されません。選択肢に表示されていれば項目をクリックすれば設定できます。しかし選択肢にない場合、手入力で式を設定する必要があるため、難易度も上がり、工数もかかってしまいます。

以上のことから、フローを完成させてから最後にスキーマを正しい形に編集することが望ましいと考えています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3992089/d14f054c-86c6-44e4-a884-bde8253bed63.png)

## 次回の予告
Power Automateのフローが完成しました。
第２回は、Power Appsでアプリを作成し、今回作成したPower Automateと連携する手順を紹介します。ぜひご確認ください！

## 最後に
テンダでは、「こんなプロジェクトに挑戦したい」「こんなチームで働きたい」「理想のチームを創りたい」と願う仲間を求めています。
カジュアル面談も随時受付中です。ぜひ一度お話ししましょう:angel:

 - [募集職種一覧](https://hrmos.co/pages/tenda/jobs)
 - [カジュアル面談の申込](https://hrmos.co/pages/tenda/jobs/031002/apply)
 - [テンダで働く人や社風について](https://note.com/tenda_recruit)
 - [テンダのMicrosoftサービス](https://mssp.tenda.co.jp/)