Power Automate DesktopからOneDriveにファイルをアップロードして期限パスワード付きの共有リンクを作成する

## 概要

Power Automate Desktopを使ってローカルファイルをMicrosoft GraphからOneDriveにアップロードし期限パスワード付きの共有リンクを作成します。

* Microsoft Graph Rest APIを使うアプリはAzure ADで身元を確認（認証）し、アクセス権限付与を承認する手続きが必要になります。
* そのためAzure ADにアプリを登録する必要があります。
* 今回は認証承認フローにMSAL.PSを使用します。
* MSAL.PSを使用することで複雑なOAuth2.0承認コードフローを用意しなくても行うことができます。
* Power Automate Desktopからアクセストークンが有効期限内であれば、再認証なしに使いたかったのでフローが少し長めです。
* 都度、認証する単純なものであれば少ないアクションでアップロード可能です。（最後にコピペ可能サンプルコードを載せています）
![One.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/680039/1e1d560c-9b30-5431-1885-cbea0a5b5ea3.gif)


## 環境

Windows 10 Pro　21H1
PowerShell 5.1.19041.1237
MASL.PS 4.36.1.1
Microsoft 365 E5 Developer
Power Automate Desktop 2.12.171.2.21216

## 注意事項

* Azure ADにアプリを登録できる権限が必要です。
* MASL.PSをインストールする必要があります。
* アクセストークンを暗号化してローカルに保存する方法を取っています。
* 細かい例外処理はしていません。
* このフローでアップロードできるファイルサイズは4MBまでです。
* 自己責任でお願いいたします。
* 2021年9月の情報です。
* 認証と承認の定義についてはDocsを参考にしています。

https://docs.microsoft.com/ja-jp/azure/active-directory/develop/authentication-vs-authorization

## 準備

### MSAL.PSのインストール

GitHubにあるAzure Active DirectoryのMSAL.PSレポジトリを参考にインストールします。PowerShellは**管理者権限**で立ち上げます。

https://github.com/AzureAD/MSAL.PS

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/680039/5383882b-5610-e6b1-c416-92280488eeb5.png)

### アプリの登録

Azure Active Directoryの管理センターにアクセスしてアプリの登録を行います。
![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/680039/9df2f6d7-2bd0-87e0-b537-99ba6b5c036e.png)

任意の名前をつけて登録します。

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/680039/0ee0beff-1396-b905-3a8b-8f7e7ed71d14.png)

登録ができたら認証タブに移動してプラットフォームを追加します。

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/680039/7171080c-1213-17d9-642d-089725cf3446.png)

`https://login.microsoftonline.com/common/oauth2/nativeclient`をチェックして構成します。
![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/680039/66e1c7f7-3942-3153-5559-07b935d19b25.png)

APIのアクセス許可タブに移動しファイルに対する権限を追加します。

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/680039/b9435f56-a6da-f3e6-4a16-11c1ba9f5d36.png)

委任されたアクセス許可を選択します。

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/680039/2800eb0e-3aef-1a1a-d4f2-2e4c001e51fd.png)

Filesを展開してFiles.ReadWriteをチェックしアクセス許可を追加します。

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/680039/95a86394-26c0-be84-7a8c-2202764207bd.png)

アプリの登録は以上です。

### MSAL.PSで承認コードフローを実行して確認してみる

今回はWindows PowerShell ISEを使用します。
MSAL.PSのREADMEのようにPowerShellにコマンドを入力し使用例を表示します。
`Get-Help Get-MsalToken -Examples`
![](2021-09-19-02-05-54.png)
![2021-09-19-02-05-54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/680039/eb8e96c4-ab9e-d79a-3f3f-503cd84e1743.png)

例2の承認コードフローを使用しますのでエディターにコピペします。
```
Get-MsalToken -ClientId '00000000-0000-0000-0000-000000000000' -TenantId '00000000-0000-0000-0000-000000000000' -Interactive -Scope
     'https://graph.microsoft.com/User.Read' -LoginHint user@domain.com
```

Azure Active Directoryの管理センターから先ほど登録したアプリの情報に書き換えます。
概要タブにある
ClientIdはアプリケーション (クライアント) ID
TenantIdはディレクトリ (テナント) ID

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/680039/5292855a-ce37-9d7d-ee94-a988a2129075.png)

APIのアクセス許可から```https://graph.microsoft.com/Files.ReadWrite```をコピーしスクリプトに追加します。

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/680039/05f88e78-6005-e6b0-01aa-e746773c89ef.png)

-LoginHintは使わないので削除し実行します。

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/680039/d818069d-9104-9382-1fa0-2cd8a73f63c1.png)

必要な情報を入れたら実行してみます。
サインイン情報を入力しAuthenticatorを設定している場合はサインイン要求を承認します。

![2021-09-19-02-35-48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/680039/d950b754-c8d7-be8f-78e0-7c297c124a47.png)

はじめてアプリに承認コードフローをおこなう場合、アクセス許可を与える必要があります。

![2021-09-19-03-46-44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/680039/e1241c08-66a0-849c-47a6-9339230cc2b1.png)

承認コードフローが完了するとアクセストークンを取得できます。

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/680039/1449d1bb-6987-f028-29dc-ae27b1544b02.png)

MSAL.PSを利用することで、わずか1行のスクリプトでアクセストークンを取得することができました。

### MSAL.PSのこのスクリプトで何がおこなわれているか確認してみる

Fiddlerをつかって何がおこなわれているのかざっくり確認してみます。

OAuth 2.0 承認エンドポイント(v2)`https://login.microsoftonline.com/$tenantIDoauth2/v2.0/authorize`
にS256code ChallengeオプションをつかってAuthrization Codeを要求しています。

![2021-09-19-04-11-46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/680039/60156e96-4e57-3426-5575-b258adb6fe29.png)

redirectで応答がきてIDを認証、Auethenticatorで承認後、Authrization codeが発行されます。

![2021-09-19-11-46-38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/680039/bcb09a2e-7ab1-c070-c145-7997e07723cf.png)

OAuth 2.0 トークン エンドポイント(v2)`https://login.microsoftonline.com/tenantID/oauth2/v2.0/token`
にcode_verifierを含めた情報を送信してアクセストークンを発行依頼します。

![2021-09-19-11-55-50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/680039/021c6219-d34b-b600-2021-290d709848ce.png)

確認されるとアクセストークンが発行されるというながれでした。

![2021-09-19-12-01-02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/680039/999519c9-b0b3-2602-9889-599d82ad6f48.png)

OAuth 2.0認証、承認コードフローについてDocsに詳細な解説があります。たった1行のスクリプトで同様のフローが行えることを確認できました。

https://docs.microsoft.com/ja-jp/azure/active-directory/develop/v2-oauth2-auth-code-flow

### MSALのコードをPower Automate Desktopのフローを考えて加工する

MSALで取得したクセストークンは同じPowerShellセッション内ではキャッシュから呼び出せます。しかし、セッションが変わるとキャッシュを呼び出せませんでした。方法はあるかもしれませんがわからないです。Power Automate Desktopの「PowerShell実行アクション」はアクション毎でセッションがリセットされるため毎回認証を要求されます。何度かフローを利用したいときに、毎回パスワード入力やAuthenticatorをするのはさすがに面倒です。そこでアクセストークンの有効期間3599秒（1時間）はアクセストークンを再利用したいので、どこかに安全に保管する必要があります。
アクセストークンのライフタイムはAzure ADのポリシーで設定できますが、デフォルトでは1時間です。

https://docs.microsoft.com/ja-jp/azure/active-directory/develop/active-directory-configurable-token-lifetimes#access-id-and-saml2-token-lifetime-policy-properties

アクセストークンはJWT（JSON Web Token）というタイプです。ランダムな文字列に見えますが簡単にデコードできてしまいます。

https://docs.microsoft.com/ja-jp/azure/active-directory/develop/access-tokens

https://jwt.ms/

アクセストークを貼り付けてみると内容を確認できます。JWTをローカルに保存するのはあまり推奨されていません。そこでJWT自体をPower Automate Desktop暗号化して保存し、再利用時に複号化する方法を考えました。そのためにあらかじめ少しだけコードを加工しておきます。各値を変数化して見やすくし、アクセストークンと有効期限（UTC）を出力するようにしました。

```ps1:PowerShell
$appid='00000000-0000-0000-0000-000000000000' #Your App ID
$tenantid='00000000-0000-0000-0000-000000000000' #Your Tenant ID
$scopes=('https://graph.microsoft.com/User.Read', 'https://graph.microsoft.com/Files.ReadWrite') #Your App Scpopes

$Response = Get-MsalToken -ClientId $appid -TenantId $tenantid -Interactive -Scope $scopes

Write-Output $Response.AccessToken
Write-Output $Response.ExpiresOn.ToString("yyyy/MM/dd HH:mm:ss")
```

## Power Automate Desktopのフロー作成

### MSAL.PSを使ったアクセストークンの取得と暗号化

任意の名前でフローを作成します。自分は「MSAL Get Token」としました。

![2021-09-19-13-15-27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/680039/e6713475-8c08-a5b0-491d-e5327abb0670.png)

1. Windows環境変数を取得
   TEMPを指定しました。
   ![2021-09-19-13-17-51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/680039/78668b82-cb14-112e-defe-0da0c74bcd2a.png)

2. PowerShellスクリプトの実行
   先ほど作ったMSALのコードを貼り付けます。
   ![2021-09-19-13-20-23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/680039/14b0050b-937c-049b-f87c-f57b364402bf.png)

3. if
   %ScriptError%を判定します。
   ![2021-09-19-13-21-36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/680039/d75ed105-4ccc-94ff-6bd2-c741a8cbe6d1.png)

4. フローを停止する
   ![2021-09-19-13-22-29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/680039/bafbfb05-cbcc-0f5a-84b4-c7c9a1b77b7a.png)

5. End
   ![2021-09-19-13-23-18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/680039/99aeb6fb-d923-3591-60fe-0d3199bba5e4.png)

6. ASEでテキストを暗号化する
   %PowershellOutput%（アクセストークンと有効期限）を暗号化します。暗号化キーやオプションは任意で設定してください。ただし複号するときも同じ条件にしてください。
   ![2021-09-19-13-27-43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/680039/47f2bfcb-436d-c512-31e0-4c8db96d195c.png)

7. テキストをファイルに書き込みます
   %EnvironmentVariableValue%\token.txtに暗号化した%EncryptedText%を書き込みます。
   ![2021-09-19-13-29-25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/680039/5f75ed18-9a9b-b6cf-987e-5b21a9d6b223.png)

### アクセストークンを複号化し、Graph APIでOneDriveにファイルをアップロード

任意の名前でフローを作成します。自分は「OneDrive Upload&Create Link」としました。大まかな流れは次のとおりです。
![MAINflow.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/680039/f74648f4-8a90-d641-d194-631748ff0572.png)

![Flow.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/680039/9ecde110-ee0d-1824-2855-d66659ef14a0.jpeg)

1.  Windows環境変数を取得
   TEMPを指定しました。
2. 現在日時を取得します
   特定のタイムゾーンで国地域をUTCとします。UTCは選択肢にないので書き込みます。
   ![2021-09-19-15-59-40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/680039/9df2eec6-e7a3-1542-fba5-5f2dae7095e0.png)

3. ラベル
   TokenCheckとしました
   ![2021-09-19-16-00-26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/680039/a4303fe3-c882-2d8e-f395-72ee542465ac.png)

4. ファイルからテキストを読み取ります。
   `%EnvironmentVariableValue%\token.txt`を指定しています。
   ![2021-09-19-16-02-46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/680039/ae890c1b-de26-ace5-20eb-ab602b192757.png)

   またエラー発生時の処理として`FileContents`を`%''%`として空を代入します。
   ![2021-09-19-16-03-39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/680039/88248839-b430-a7c7-7e08-e4aed3a3853f.png)

5. If
   ![2021-09-19-16-05-53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/680039/bff72554-5f8f-027d-82b7-6bd65ce4bde2.png)

6. Desktopフローを実行
   先に作成したMSAL Get Token　フローを指定しています。
   ![2021-09-19-16-07-31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/680039/7fd45a19-8c8e-4091-6280-9043f9de9e93.png)

7. 移動先
   ![2021-09-19-16-15-22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/680039/176de60a-6296-d738-84bb-528d705c87af.png)

8. End
9. AESでテキストを解読する
   MSAL Get Tokenで暗号化したときと同じ設定にします。
   ![2021-09-19-16-10-11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/680039/f7d24202-0a7f-ce3e-cee4-63293e79fee7.png)

10. テキストの分割
    復号化した%DecryptedText%はテキストです。このアクションでアクセストークンと有効期限にわけてリスト化します。改行コードで区切ればOKです。
    ![2021-09-19-16-13-17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/680039/fa45b628-e47f-c230-d1a9-654d7b0787ae.png)

11. 変数の設定
    ![2021-09-19-16-18-10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/680039/6274f043-5cce-27ff-3ff3-3c9a03609c5b.png)

12. テキストをdatetimeに変換
    アクセストークンの有効期限をテキスト形式からdatetime形式に変換します。
    ![2021-09-19-16-19-35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/680039/4390ac7b-ed15-ae88-950b-bfdedef27660.png)

13. 日付の減算
    アクセストークンの有効期限から現在日時を減算して秒で表します。この計算のためUTCにしています。
    ![2021-09-19-16-22-32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/680039/45a5ffb6-dc0e-3400-3366-9f13a9da7c86.png)

14. If
    300秒を切ったらアクセストークンを再取得するための判定です。
    ![2021-09-19-16-26-20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/680039/2485f5ab-0b1e-a921-534a-04fedd41ce83.png)

15. Desktopフローを実行
   先に作成したMSAL Get Token　フローを指定しています。
   ![2021-09-19-16-07-31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/680039/895cbd83-c9cd-77fa-573a-e5ac808aee12.png)

16. 移動先
   ![2021-09-19-16-15-22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/680039/47312a06-bae4-2e13-6a97-416f65b67ee2.png)

17. End
18. 特別なフォルダーを取得
    いつものです。デスクトップを指定しています。
    ![2021-09-19-16-29-32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/680039/5ea76246-8d50-441e-c169-3a581cf724bb.png)

19. ファイルの選択ダイアログを表示
    アップロードするファイルを選択します。
    ![2021-09-19-16-31-43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/680039/d51ea4fc-dbb4-beaf-8d1a-dde0dcc0f79e.png)

20. 変数の設定
    共有リンク作成時のパスワードを設定するためです。今回はテストのため12345にしてます(;´∀｀)入力できるようにしたり、任意で設定してください。
    ![2021-09-19-16-38-05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/680039/f9707dee-bc24-d89f-c075-9b3ea51844d5.png)

21. 加算する日時
    アップロード時からの共有リンクの期限を設定します。とりあえず3日にしてみました。
    ![2021-09-19-16-40-48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/680039/8e201022-0898-99fa-5878-e904cc569655.png)

22. datetimeをテキストに変換
    共有リンクを作成する際の期限設定に、必要な形式にします。
    例2020-05-19T23:59:59Zの形式が必要になります。ｚはリクエスト時に追加しています。
    ![2021-09-19-16-49-11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/680039/b25c096e-0bb9-9325-43ea-bd06a786dc56.png)

23. PowerShellスクリプトの実行
    Invoke-RestMethodを使いファイルのアップローおよびパス付期限付きリンクの作成を行います。アップロードするOneDriveのフォルダーはテストのためハードコーディングしているので適宜指定してください。今回は`$onedriveFolderName="FolderA"`としています。
    ファイルはバイナリで読み込みます。
    `$fileBytes = [System.IO.File]::ReadAllBytes("%SelectedFile%")`として`-Body`の引数に指定すればOKです。`-ContentType 'application/octet-stream'`とすることでさまざまなファイルがアップロードできます。
    ただし単純にPUTしているだけなので同じファイル名が存在する場合置き換えになります。
    またアップロードできるファイルは4MB以下です。

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

    Link作成の部分は22で述べたように`"expirationDateTime":"%FormattedDateTime%Z"`でZを追加します。
    また`"scope": "anonymous"`にしているので注意してください。

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

```PS1:PowerShell
#################Upload File under 4MB Only#######################
$headers = @{Authorization = " Bearer %Token%" }
$fileBytes = [System.IO.File]::ReadAllBytes("%SelectedFile%")
$onedriveFolderName="FolderA"
$response = Invoke-RestMethod -Uri "https://graph.microsoft.com/v1.0//me/drive/root:/$onedriveFolderName/%SelectedFile.Name%:/content" -Method 'PUT'-ContentType 'application/octet-stream' -Headers $headers -Body $fileBytes
$itemID = $response.id
#################Create Link#####################################
$requestbody = '{
    "type": "view",
    "password": "%PassWord%",
    "scope": "anonymous",
    "expirationDateTime":"%FormattedDateTime%Z"
  }'
$response = Invoke-RestMethod -Uri "https://graph.microsoft.com/v1.0//me/drive/items/$itemID/createLink" -Method 'POST'-ContentType 'application/json' -Headers $headers -Body $requestbody
$response | ConvertTo-Json
```

24. JSONをカスタムオブジェクトに変換
    ![2021-09-19-17-30-48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/680039/0dab09ef-4761-a69f-ec35-b6811ddb0c7e.png)

    
25. クリップボードテキストを設定
    生成した共有リンクをクリップボードに格納します。
    ![2021-09-19-17-32-31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/680039/39d6af4b-a911-19b9-beab-3a4f78e453f9.png)

26. メッセージを表示
    終了メッセージです。任意で💦
    ![2021-09-19-17-35-00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/680039/648260e3-8c74-0532-3d5f-d5adf64f8646.png)


## まとめ

Microsoft IDプラットフォームとMicrosoft Graphのよい基礎学習になりました。
MSAL.PSを使って認証、承認フローは簡単にできますがPower Automate Desktopからアクセストークンを何度も使うには工夫が必要でした。自動化で実際使うにはここから先のアイデア次第だと思いました。

都度認証してもよいならファイル選択してOneDriveアップロードするフローは2アクションで作れます。最後にそれを記しておきます。またコピペで試せるようにしています。
※ClientID TenantID Scope OneDriveのフォルダー指定はハードコードです。適宜書き換えてください。

![2021-09-19-18-11-55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/680039/9edcf623-dd5c-9e12-9194-614afe67d956.png)


```r:PowerAutomateDesktop
Display.SelectFile Title: $'''ファイル選択''' IsTopMost: True CheckIfFileExists: False SelectedFile=> SelectedFile ButtonPressed=> ButtonPressed
System.RunPowershellScript Script: $'''$appid=\'00000000-0000-0000-0000-000000000000\' #Your App ID
$tenantid=\'00000000-0000-0000-0000-000000000000\' #Your Tenant ID
$scopes=(\'https://graph.microsoft.com/User.Read\', \'https://graph.microsoft.com/Files.ReadWrite\') #Your App Scpopes
$Response = Get-MsalToken -ClientId $appid -TenantId $tenantid -Interactive -Scope $scopes
$Token= $Response.AccessToken
#################Upload File under 4MB Only#######################
$headers = @{Authorization = \" Bearer $Token\" }
$fileBytes = [System.IO.File]::ReadAllBytes(\"%SelectedFile%\")
$onedriveFolderName=\"YourFoldeName\" ###########Your OneDrive Upload Folder
Invoke-RestMethod -Uri \"https://graph.microsoft.com/v1.0//me/drive/root:/$onedriveFolderName/%SelectedFile.Name%:/content\" -Method \'PUT\'-ContentType \'application/octet-stream\' -Headers $headers -Body $fileBytes''' ScriptOutput=> PowershellOutput ScriptError=> ScriptError
```

これだけ長々書いて2アクション💦

## 参考

https://docs.microsoft.com/ja-jp/azure/active-directory/develop/

https://docs.microsoft.com/ja-jp/graph/?view=graph-rest-1.0

https://docs.microsoft.com/ja-jp/azure/active-directory/develop/msal-overview

https://github.com/AzureAD/MSAL.PS