きっかけ
出向先の会社合併に伴い、新会社ではM365ライセンスが別テナント発行され、現会社のTeams情報は移行されない(必要なら自力移行すべし)とアナウンスがありました。
全会話チャットの移行は厳しいにしても、対象チャットを限定し、最低限残したい項目に絞って移行したいと考え、フローを作成しました。
元々は Teamsチャネル移行 を先行して対応してましたが、会話チャット移行もあったらいいと考え、Teamsチャネル移行を加工して作ったので、一部無駄処理があるかもです💦
このフローをつくると…
会話チャットを以下条件でTeamsチャネルにメッセージ移行できます。
- 移行メッセージの最大件数は500件
→上限ページ数変数を変えればそれ以上に変更可能だが、オンメモリ処理が多いので限界値は大きくないかも… - 移行先Teamsチャネルの投稿者はPowerAutomate実行者名となるが、メッセージ冒頭に移行元メッセージの投稿者・投稿日時を記載
→投稿者やメンション、リアクションなどユーザに紐づく情報は移行されない - 添付ファイルは移行先TeamsのSharePoint(/Shared Documents/移行先チャネル名フォルダ配下)に移行
→移行されないメッセージやメッセージ添付されない共有ファイルは移行されない
→添付ファイル容量の上限は100MB程度(PowerAutomateの上限はそれより小さいかも)、移行できない場合はエラーメッセージを表記 - メッセージ添付画像はメッセージ内包データ(HostedContents)として埋め込み
→メッセージデータのHostedContents含めたペイロード最大容量は4MBのため、順次処理で4MBを超えた以降のファイルは移行されずエラーメッセージを表記 - 引用メッセージは引用元メッセージが移行されれば引用情報(MessageReference)として移行
→引用メッセージが移行できなかった場合はエラーメッセージを表記 - その他、上記に記載のない情報は移行されない
なお、クロステナントの場合、PowerAutomateフローは移行元で実行、移行先にも接続できる前提です。クロステナントでなく、同一テナント内でも移行可能です。
移行イメージのデータはCopilotに以下指示をしてサンプルデータ作成してます。
以下のフォーマットで試験データを作成してください。
・レコード項目はid(3桁数字)、replyToId(null固定)、subject(null固定)、user(架空日本人名)、body(日本語、HTML形式)、createdDateTime(yyyy/MM/dd HH:mm形式)、attachments([]固定)
・レコード数は30件、通信業界をテーマとしたディスカッション
・本文に実在するURLリンク(正しいAタグ形式)を加えたものを混ぜ、20文字~50文字、1~2行程度とする
移行先Teamsチャネルのレイアウトを「スレッド」形式にしたイメージです。
フロー全体像
①トリガ設定
移行時に手動実行します。
②変数初期化
(変数)『変数を初期化する』で以下10個の変数を設定します。
| 名前 | 種類 | 初期値/補足 |
|---|---|---|
| 移行元チャットURL | 文字列 | Teamsの該当チャット:3点メニューの「リンクのコピー」の値(https://teams.microsoft.com/l/chat/XXXXXXXXXXXXXXXX/conversations?context=%7B%22contextType%22%3A%22chat%22%7D) |
| 移行先Team名 | 文字列 | Teams:Teamsとチャネルに表示されるTeam名、またはTeam ID |
| 移行先チャネル名 | 文字列 | Teams:Teamsとチャネルに表示される該当Team配下のチャネル名、またはチャネルID |
| 上限ページ数 | 整数 | 10(1ページあたりメッセージ50件取得、10の場合は500件となる) |
| メッセージ配列 | アレイ | (空値) |
| 画像配列 | アレイ | (空値) |
| 添付配列 | アレイ | (空値) |
| 前後メッセージIDオブジェクト | オブジェクト | {}(空オブジェクト) |
| テンポラリ文字列 | 文字列 | (空値) |
| テンポラリ変数 | 整数 | (空値) |
Teamsチャネル移行 との違いは以下の通りです。
- 変数定義が「移行元チャットURL」か「移行元Team名/チャネル名」
- 上限ページ数の値(チャネル移行はリプライ200件が含まれるため値を小さくしている)
③スコープ1:移行先設定
Teamsチャネル移行 のスコープ1と同じ処理です。
③-1) 移行先チーム一覧表示
(Microsoft Teams)『参加済みチームをリストする』で自身が所属するTeamsチーム一覧を取得します。
クロステナント移行する場合、三点メニューの接続参照で接続先を移行先に変更します
③-2) 移行先Team抽出
(データ操作)『アレイのフィルター処理』で以下パラメータを設定し、表示名またはチームIDで指定されたチーム情報を抽出します。
- 差出人:
@outputs('移行先チーム一覧表示')?['body/value'] - 条件:(式)
@or(contains(item()?['displayName'], variables('移行先Team名')),contains(item()?['id'], variables('移行先Team名')))
③-3) 移行先Team抽出結果=1件?
(コントロール)『条件』で「③-2) 移行先Team抽出」が1件かを判定します。
- 左辺: (式)
length(body('移行先Team抽出')) - 条件:次の値に等しい
- 右辺: (値)1
③-4) 失敗(移行先Team名不正)
(コントロール)『終了』で処理を停止します。
- 状態:失敗
- メッセージ:
移行先Team名(@{variables('移行先Team名')})が正しくありません
③-5) 移行先チャネル一覧表示
(Microsoft Teams)『チームの取得』で指定チーム情報を取得します。
- チーム:(カスタム値の入力)@body('移行先Team抽出')?[0]?['id']
クロステナント移行する場合、三点メニューの接続参照で接続先を移行先に変更します
③-6) 移行先チャネル抽出
(データ操作)『アレイのフィルター処理』で以下パラメータを設定し、表示名またはチャネルIDで指定されたチャネル情報を抽出します。
- 差出人:
@outputs('移行先チャネル一覧表示')?['body/value'] - 条件:(式)
@or( contains(item()?['id'], variables('移行先チャネル名')), contains(item()?['displayName'], variables('移行先チャネル名')), contains(item()?['displayName'], if(equals(variables('移行先チャネル名'),'一般'),'General',variables('移行先チャネル名'))) )
③-7) 移行先チャネル抽出結果=1件?
(コントロール)『条件』で「③-6) 移行先チャネル抽出」が1件かを判定します。
- 左辺: (式)
length(body('移行先チャネル抽出')) - 条件:次の値に等しい
- 右辺: (値)1
③-8) 失敗(移行先チャネル名不正)
(コントロール)『終了』で処理を停止します。
- 状態:失敗
- メッセージ:
移行先チーム(@{variables('移行先Team名')})に指定チャネル(@{variables('移行先チャネル名')})がありません
③-9) HTTP要求:指定TeamIDのグループ詳細を取得
(Office365 Group)『HTTP 要求 V2 を送信する』で以下パラメータを設定し、SharePointサイト情報を取得します。
| パラメータ名 | 値 |
|---|---|
| URI | https://graph.microsoft.com/v1.0/groups/@{body('移行先Team抽出')?[0]?['id']}/sites/root |
| メソッド | GET |
| 本文 | |
| コンテンツの種類 | application/json |
クロステナント移行する場合、三点メニューの接続参照で接続先を移行先に変更します
③-10) 移行先SharePointURL作成
(データ操作)『作成』で経過日数をふまえた日付変数を作成します。
- 入力:
@outputs('HTTP要求:指定TeamIDのグループ詳細を取得')?['body']?['webUrl']
③-11) すべてのリストとライブラリを取得
(SharePoint)『すべてのリストとライブラリを取得』で「③-10) 移行先SharePointURL作成」のライブラリを取得します。
- サイトのアドレス:
@outputs('移行先SharePointURL作成')
クロステナント移行する場合、三点メニューの接続参照で接続先を移行先に変更します
③-12) ライブラリ情報抽出
(データ操作)『アレイのフィルター処理』で以下パラメータを設定し、表示名がドキュメントのデータを抽出します。
- 差出人:
@outputs('すべてのリストとライブラリを取得')?['body/value'] - 左辺: (式)
@item()?['DisplayName'] - 条件:次の値に等しい
- 右辺: (値)
ドキュメント
スコープ2:移行データ抽出
本スコープ2 ④-2~9、10~13とTeamsチャネル移行 のスコープ2 ④-9~16、23~26の実装は同じです(Teams Graph APIのタイトルは異なる)。
④-1) チャットID作成
(データ操作)『作成』で変数「移行元チャットURL」からチャットIDを作成します。
- 入力:
first(split(last(split(variables('移行元チャットURL'),'/chat/')),'/'))
④-2) テンポラリ変数の設定(ページカウンタリセット)
(変数)『変数の設定』でテンポラリ変数をリセット設定します。
- 値:
0
④-3) テンポラリ文字列の設定(メッセ―ジ取得URL)
(変数)『変数の設定』でテンポラリ変数にメッセージ取得Graph APIのURLを設定します。
- 式:
https://graph.microsoft.com/v1.0/me/chats/@{outputs('チャットID作成')}/messages?$top=50
④-4) テンポラリ変数<最大ページ数で繰り返し
(コントロール)『Do Until』で抽出した空き会議室配列を繰り返し処理します
- 左辺: (式)
@variables('テンポラリ変数') - 条件:次の値以上
- 右辺: (式)
@ variables('上限ページ数')
④-5) HTTP要求:Teamsチャットメッセージ一覧取得
(Microsoft Teams)『Microsoft Graph HTTP 要求を送信する』で以下パラメータを設定し、指定チャットのメッセージ一覧を取得します。
| パラメータ名 | 値 |
|---|---|
| URI | @{variables('テンポラリ文字列')} |
| メソッド | GET |
| 本文 | |
| コンテンツの種類 | application/json |
④-6) メインスレッド選択
(データ操作)『選択』で、「④-5) HTTP要求:Teamsチャットメッセージ一覧取得」の応答値を指定マップに選択します。
- 開始:(式)
@{body('HTTP要求:Teamsチャットメッセージ一覧取得')?['value']} - マップ:以下参照
| パラメータ名 | 値 |
|---|---|
| id | @item()?['id'] |
| replyToId | @null |
| subject | @null |
| createdDateTime | @convertFromUtc(item()?['createdDateTime'],'Tokyo Standard Time','yyyy/MM/dd HH:mm') |
| user | @first(split(if(equals(item()?['from']?['user']?['displayName'],null),'',item()?['from']?['user']?['displayName']),'(')) |
| body | @item()?['body']?['content'] |
| attachments | @item()?['attachments'] |
④-7) 本文なしメインスレを除外抽出
(データ操作)『アレイのフィルター処理』で「④-6) メインスレッド選択」のうち、本文が設定されたデータを抽出します。
- 差出人:
@body('メインスレッド選択') - 条件:
@and(not(equals(length(if(equals(item()?['body'],null),'',item()?['body'])), 0)),not(equals(if(equals(item()?['body'],null),'',item()?['body']), '<systemEventMessage/>')))
詳細設定モードで編集します
④-8) 今回取得分と取得済みデータの結合作成
(データ操作)『作成』で「④-7) 本文なしメインスレを除外抽出」を逆順にした配列とメッセージ配列の結合配列を作成します。
- 値:
@union(reverse(body('本文なしメインスレを除外抽出')),variables('メッセージ配列'))
④-9) メッセージ配列にメインスレッドを設定
(変数)『変数の設定』でメッセージ配列に「④-8) 今回取得分と取得済みデータの結合作成」の値を設定します。
- 式:
@{outputs('今回取得分と取得済みデータの結合作成')}
④-10) 次ページリンクあり?
(コントロール)『条件』で「④-5) HTTP要求:Teamsチャットメッセージ一覧取得」に次ページリンクの値が設定されてるかを判定します。
<And条件1>
- 左辺: (式)
@body('HTTP要求:Teamsチャットメッセージ一覧取得') - 条件:次の値を含む
- 右辺: (式)
concat(decodeUriComponent('%40'),'odata.nextLink')
④-11) テンポラリ文字列を設定(次ページURL)
(変数)『変数の設定』で「テンポラリ文字列」にGraph APIの次ページリンクURLを設定します。
- 式:
@{body('HTTP要求:Teamsチャットメッセージ一覧取得')?[concat(decodeUriComponent('%40'),'odata.nextLink')]}
④-12) テンポラリ変数を設定(上限ページ)
(変数)『変数の設定』で「テンポラリ変数」を上限ページ数に設定します。
- 式:
@{variables('上限ページ数')}
④-13) テンポラリ変数インクリメント
(変数)『変数の値を増やす』で「テンポラリ変数」を1増やします。
- 値:
1
⑤スコープ3:画像データ抽出
Teamsチャネル移行 のスコープ3と同じ処理です。
⑤-1) 画像あり投稿の抽出
(データ操作)『アレイのフィルター処理』で以下パラメータを設定し、画像タグを含む本文を抽出します。
- 差出人:
@variables('メッセージ配列') - 左辺: (式)
indexOf(item()?['body'], '<img ') - 条件:次の値より大きい
- 右辺: (値)0
⑤-2) 画像あり投稿で繰り返し
(コントロール)『それぞれに適用する』で抽出した画像タグありメッセージを繰り返し処理します。
- 出力選択:
@{body('画像あり投稿の抽出')}
⑤-3) テンポラリ変数リセット
(変数)『変数の設定』でテンポラリ変数をリセット設定します。
- 値:
0
⑤-4) imgタグ選択
(データ操作)『選択』で、メッセージ本文に含まれる画像タグを抜き出し選択します。
- 開始:(式)
@{skip(split(item()?['body'],'<img '),1)} - マップ:
@concat('<img ',substring(item(),0,add(indexOf(item(),'>'),1)))
マップをテキストモードに切り替えて入力します
⑤-5) imgタグで繰り返し
(コントロール)『それぞれに適用する』で抽出したimgタグを繰り返し処理します。
- 出力選択:
@{body('imgタグ選択')}
⑤-6) 画像index作成
(データ操作)『作成』で画像配列内の連番を作成します。
- 値:
@{add(length(variables('画像配列')),1)}
⑤-7) 画像URL作成
(データ操作)『作成』でimgタグ内のsrc属性の値を抽出作成します。
- 値:
@{replace(substring(last(split(item(),'src="')),0,indexOf(last(split(item(),'src="')),'"')),'/chats/','/me/chats/')}
チャット貼付画像はそのままのURLではGraph APIで画像データ取得できないため、url内に「/chats/」が含まれる場合、「/me/chats/」に置換します。
⑤-8) HTTP要求:画像取得
(Office365 Group)『HTTP 要求 V2 を送信する』で「⑤-7) 画像URL作成」の画像データを取得します。
| パラメータ名 | 値 |
|---|---|
| URI | @{outputs('画像URL作成')} |
| メソッド | GET |
| 本文 | |
| コンテンツの種類 | application/json |
⑤-9) 画像取得OK?
(コントロール)『条件』で「⑤-8) HTTP要求:画像取得」の応答が正常か判定します。
<And条件1>
- 左辺: (式)
@body('HTTP要求:画像取得') - 条件:次の値に等しくない
- 右辺: (式)
null
<And条件2>
- 左辺: (式)
@body('HTTP要求:画像取得')?['$content-type'] - 条件:次の値に等しくない
- 右辺: (式)
null
前段処理が失敗した場合も処理されるよう三点メニュー→「実行条件の構成」で「に失敗しました」、「がタイムアウトしました」にもチェックを入れます。
⑤-10) テンポラリ変数閾値以内?
(コントロール)『条件』でテンポラリ変数に取得画像容量を加算した値が4,000,000Byte(≒4MB)以下か判定します。
- 左辺: (式)
add(variables('テンポラリ変数'),length(body('HTTP要求:画像取得')?['$content'])) - 条件:次の値以下
- 右辺: (値)
4000000
Graph API:Teamsメッセージ送信は全体ペイロードが4MB(4,194,304Byte)を超えるとエラーになります。添付ファイル以外の情報も含まれるため、ここでの条件は4,000,000Byteにしています。
⑤-11) 画像配列に追加
(変数)『配列変数に追加』で「画像配列」に以下オブジェクトを追加します。
- 値:
{ "messageId": @{items('画像あり投稿で繰り返し')?['id']}, "imgTag": "@{items('imgタグで繰り返し')}", "@{decodeUriComponent('%40')}microsoft.graph.temporaryId": "@{outputs('画像index作成')}", "contentType": "@{body('HTTP要求:画像取得')?['$content-type']}", "contentBytes": "@{body('HTTP要求:画像取得')?['$content']}" }
⑤-12) テンポラリ変数を増やす
(変数)『変数の値を増やす』でテンポラリ変数に取得した画像容量を増やします。
- 式:
length(body('HTTP要求:画像取得')?['$content'])
⑥スコープ4:添付ファイル
スコープ4追加時はスコープ2の下の+メニューで「並列分岐の追加」を選択します。
Teamsチャネル移行 のスコープ4と同じ処理です。
⑥-1) 添付ありメッセージ抽出
(データ操作)『アレイのフィルター処理』で以下パラメータを設定し、添付ファイル(Attachement属性)を含むメッセージを抽出します。
- 差出人:
@variables('メッセージ配列') - 左辺: (式)
length(item()?['attachments']) - 条件:次の値より大きい
- 右辺: (値)0
⑥-2) 添付メッセージで繰り返し
(コントロール)『それぞれに適用する』で抽出した添付ありメッセージを繰り返し処理します。
- 出力選択:
@{body('添付ありメッセージ抽出')}
⑥-3) attachmentタグで繰り返し
(コントロール)『それぞれに適用する』で抽出した添付データ(attachments)を繰り返し処理します。
- 出力選択:
@{items('添付メッセージで繰り返し')?['attachments']}
⑥-4) サイトコンテンツ?
(コントロール)『条件』で添付データのContent-Typeがreference、かつパス第2階層が指定値かを判定します。
<And条件1>
- 左辺: (式)
@items('attachmentタグで繰り返し')?['contentType'] - 条件:次の値に等しい
- 右辺: (値)
reference
<And条件2>
- 左辺: (式)
@createArray('sites','personal') - 条件:次の値を含む
- 右辺: (式)
split(item()?['contentUrl'],'/')?[3]
Content-Type=MessageReference(引用)はここでは加工しません。
⑥-5) 移行元ファイルコンテンツの取得
(SharePoint)『ファイル コンテンツの取得』で移行元サイトアドレスのコンテンツを取得します。
- サイトのアドレス:(式)
@join(take(split(item()?['contentUrl'],'/'),5),'/') - ファイル識別子:(式)
@encodeUriComponent(concat('/',decodeUriComponent(join(skip(split(item()?['contentUrl'],'/'),5),'/'))))
⑥-6) コンテンツ取得正常?
(コントロール)『条件』で「⑥-5) 移行元ファイルコンテンツの取得」が正常応答、かつファイル容量が100MB以下かを判定します。
<And条件1>
- 左辺: (式)
@outputs('移行元ファイルコンテンツの取得')?['statusCode'] - 条件:次の値に等しい
- 右辺: (式)200
<And条件2>
- 左辺: (式)
@outputs('移行元ファイルコンテンツの取得')?['body/$content'] - 条件:次の値に等しくない
- 右辺: (式)
null
<And条件3>
- 左辺: (式)
int(outputs('移行元ファイルコンテンツの取得')?['headers/Content-Length']) - 条件:次の値以下
- 右辺: (値)100000000
前段処理が失敗した場合も処理されるよう三点メニュー→「実行条件の構成」で「に失敗しました」、「がタイムアウトしました」にもチェックを入れます。
⑥-7) 移行先ファイルの作成
(SharePoint)『ファイルの作成』で移行先SharePointに「⑥-5) 移行元ファイルコンテンツの取得」のファイルを作成します。
- サイトのアドレス:(式)
outputs('移行先SharePointURL作成') - フォルダパス:
/Shared Documents/(式)body('移行先チャネル抽出')?[0]?['displayName'] - ファイル名:(式)
items('attachmentタグで繰り返し')?['name'] - ファイルコンテンツ:(式)
base64ToBinary(body('移行元ファイルコンテンツの取得')?['$content']) - OverWrite:(値)いいえ
⑥-8) 移行先ファイルメタデータの取得
(SharePoint)『パスによるファイルメタデータの取得』で移行先SharePointファイルのメタデータを取得します。
- サイトのアドレス:(式)
outputs('移行先SharePointURL作成') - ファイルパス:(式)
decodeUriComponent(concat('/Shared Documents/',body('移行先チャネル抽出')?[0]?['displayName'],'/',item()?['name']))
前段処理が失敗した場合も処理されるよう三点メニュー→「実行条件の構成」で「に失敗しました」、「がタイムアウトしました」にもチェックを入れます。
⑥-9) 移行先ファイル共有リンクの作成
(SharePoint)『ファイルまたはフォルダーの共有リンクの作成』で移行先SharePointのファイル共有リンクを作成します。
- サイトのアドレス:(式)
outputs('移行先SharePointURL作成') - ライブラリ名:(式)
body('ライブラリ情報抽出')?[0]?['Name'] - 項目ID:(式)
outputs('移行先ファイルメタデータの取得')?['body/ItemId'] - リンクの種類:
View and edit - リンクスコープ:
People in your organization
前段処理が失敗した場合も処理されるよう三点メニュー→「実行条件の構成」で「に失敗しました」、「がタイムアウトしました」にもチェックを入れます。
⑥-10) 共有リンク作成正常?
(コントロール)『条件』で「⑥-9) 移行先ファイル共有リンクの作成」の値が設定されているかを判定します。
- 左辺: (式)
outputs('移行先ファイル共有リンクの作成')?['body/link/webUrl'] - 条件:次の値に等しくない
- 右辺: (式)
null
前段処理が失敗した場合も処理されるよう三点メニュー→「実行条件の構成」で「に失敗しました」、「がタイムアウトしました」にもチェックを入れます。
⑥-11) 添付配列に追加
(変数)『配列変数に追加』で「添付配列」に添付データオブジェクトを追加します。
- 式:
{ "messageId": @{items('添付メッセージで繰り返し')?['id']}, "attachmentId": "@{items('attachmentタグで繰り返し')?['id']}", "contentUrl": "@{outputs('移行先ファイル共有リンクの作成')?['body/link/webUrl']}" }
⑦スコープ5:移行先Teamsへの投稿
スコープ5追加時は最下部の「+新しいステップ」ボタンからアクション追加し、スコープ3-4を合流させます。
Teamsチャネル移行 のスコープ5はリプライ処理が加わるため、実装内容が異なります。
⑦-1) メッセージ配列で繰り返し
(コントロール)『それぞれに適用する』で「メッセージ配列」で繰り返し処理します。
- 出力選択:
@{variables('メッセージ配列')}
⑦-2) テンポラリ文字列に本文仮設定
(変数)『変数の設定』で「テンポラリ文字列」にメッセージ本文を設定します。
- 式:
@{items('メッセージ配列で繰り返し')?['body']}
⑦-3) emojiタグ選択
(データ操作)『選択』で「テンポラリ文字列」からemojiタグを抽出します。
- 開始:(式)
@{skip(split(variables('テンポラリ文字列'),'<emoji'),1)} - マップ:
@concat('<emoji',first(split(item(),'/emoji>')),'/emoji>')
マップをテキストモードに切り替えて入力します
⑦-4) alt・title空欄タグ抽出
(データ操作)『アレイのフィルター処理』でemojiタグのうち、alt/title属性のいずれかが空のタグを取得します。
- 差出人:
@body('emojiタグ選択') - 条件:
@or(contains(item(), 'alt=""'),contains(item(), 'title=""'))
詳細設定モードで編集します
⑦-5) 空欄タグで繰り返し
(コントロール)『それぞれに適用する』で「⑦-4) alt・title空欄タグ抽出」で繰り返し処理します。
- 出力選択:
@{body('alt・title空欄タグ抽出')}
⑦-6) emojiサニタイズ作成
(データ操作)『作成』で「テンポラリ文字列」からalt・titleが空欄のemojiタグを除去します。
- 値:
@{replace(variables('テンポラリ文字列'),item(),'')}
⑦-7) テンポラリ文字列にサニタイズ本文設定
(変数)『変数の設定』で「テンポラリ文字列」にサニタイズ文字列を設定します。
- 式:
@{outputs('emojiサニタイズ作成')}
emojiタグのalt・title属性が空欄の場合、Graph API:Teamsメッセージ投稿がエラーになるため除去します。
本件は試験データでたまたま見つかりました。他にも罠があるかもしれません…
⑦-8) サニタイズ本文退避
(データ操作)『作成』でサニタイズが完了した「テンポラリ文字列」を退避します。
- 値:
@{variables('テンポラリ文字列')}
⑦-9) テンポラリ文字列に整形本文設定
(変数)『変数の設定』で「テンポラリ文字列」に移行元メッセージの投稿者情報を加えた「⑦-8) サニタイズ本文退避」を設定します。
- 式:
<div style="background-color:#666; color:#fff; font-size:80%;">[移行データ] @{items('メッセージ配列で繰り返し')?['user']}さんが@{items('メッセージ配列で繰り返し')?['createdDateTime']}に投稿しました。</div> @{outputs('サニタイズ本文退避')}
⑦-10) 画像抽出
(データ操作)『アレイのフィルター処理』で「画像配列」から移行元メッセージと同一のメッセージIDデータを抽出します。
- 差出人:
@variables('画像配列') - 左辺: (式)
@item()?['messageId'] - 条件:次の値に等しい
- 右辺: (値)
@items('メッセージ配列で繰り返し')?['id']
⑦-11) hostedContents選択
(データ操作)『選択』で、「③-10) 画像抽出」からメッセージのhostedContentsに埋め込むオブジェクト値を選択します。
- 開始:(式)
@{body('画像抽出')} - マップ:以下参照
| パラメータ名 | 値 |
|---|---|
@concat(decodeUriComponent('%40'),'microsoft.graph.temporaryId') |
@item()?[concat(decodeUriComponent('%40'),'microsoft.graph.temporaryId')] |
| contentType | @item()?['contentType'] |
| contentBytes | @item()?['contentBytes'] |
⑦-12) 本文Imgタグ選択
(データ操作)『選択』で、「テンポラリ文字列」からimgタグを選択します。
- 開始:(式)
@{skip(split(variables('テンポラリ文字列'),'<img '),1)} - マップ:
@concat('<img ',first(split(item(),'>')),'>')
マップをテキストモードに切り替えて入力します
⑦-13) 本文Imgタグで繰り返し
(コントロール)『それぞれに適用する』で「③-12) 本文Imgタグ選択」で繰り返し処理します。
- 出力選択:
@{body('本文Imgタグ選択')}
⑦-14) 画像配列一致確認
(データ操作)『アレイのフィルター処理』で以下パラメータを設定し、「③-10) 画像抽出」のうち、本文imgタグが一致するデータを抽出します。
- 差出人:
@body('画像抽出') - 左辺: (式)
@item()?['imgTag'] - 条件:次の値に等しい
- 右辺: (値)
@items('本文Imgタグで繰り返し')
⑦-15) ImgSrc作成
(データ操作)『作成』でimgタグのsrc属性を作成します。
- 値:
@{first(split(last(split(items('本文Imgタグで繰り返し'),'src="')),'"'))}
⑦-16) Img置換本文作成
(データ操作)『作成』で「テンポラリ文字列」のimg src属性をhostedContentsに差し替えした値を作成します。差し替えできなかった場合は注意文言を代入します。
- 値:
@{replace(variables('テンポラリ文字列'), if(equals(length(body('画像配列一致確認')),1), outputs('ImgSrc作成'), items('本文Imgタグで繰り返し')), if(equals(length(body('画像配列一致確認')),1), concat('../hostedContents/',body('画像配列一致確認')?[0]?[concat(decodeUriComponent('%40'),'microsoft.graph.temporaryId')],'/$value'), '<div style="background-color:#f00; color:#fff; font-size:80%;">注意:画像は移行されませんでした</div>'))}
⑦-17) Img入れ替え本文の設定
(変数)『変数の設定』で「テンポラリ文字列」を「⑦-16) Img置換本文作成」に置換設定します。
- 式:
@{outputs('Img置換本文作成')}
⑦-18) Attachment抽出
(データ操作)『アレイのフィルター処理』で「添付配列」から移行元メッセージと同一のメッセージIDデータを抽出します。
- 差出人:
@variables('添付配列') - 左辺: (式)
@item()?['messageId'] - 条件:次の値に等しい
- 右辺: (値)
@items('メッセージ配列で繰り返し')?['id']
⑦-19) Attachmentの変換選択
(データ操作)『選択』で、「⑦-18) Attachment抽出」から移行元データのAttachmentIDと参照URLを選択します。
- 開始:(式)
@{body('Attachment抽出')} - マップ:以下参照
| パラメータ名 | 値 |
|---|---|
@item()?['attachmentId'] |
@item()?['contentUrl'] |
⑦-20) Attachmentオブジェクト作成
(データ操作)『作成』で「⑦-19) Attachmentの変換選択」を文字列加工→JSON変換し、配列値をオブジェクト値に変換します。
- 値:
@{json(replace(replace(replace(replace(string(body('Attachmentの変換選択')),'{',''),'}',''),'[','{'),']','}'))}
⑦-21) Attachementの差し替え選択
(データ操作)『選択』で、メッセージ配列のattachmentsから移行用attachmentsデータを選択します。
- 開始:(式)
@{items('メッセージ配列で繰り返し')?['attachments']} - マップ:以下参照
| パラメータ名 | 値 |
|---|---|
| id | @if(not(equals(variables('前後メッセージIDオブジェクト')?[item()?['id']],null)),variables('前後メッセージIDオブジェクト')?[item()?['id']],item()?['id']) |
| contentType | @item()?['contentType'] |
| contentUrl | @outputs('Attachmentオブジェクト作成')?[item()?['id']] |
| content | @if(and(equals(item()?['contentType'],'messageReference'),not(equals(variables('前後メッセージIDオブジェクト')?[item()?['id']],null))),concat('{"messageId":"',variables('前後メッセージIDオブジェクト')?[item()?['id']],'","messagePreview":',last(split(item()?['content'],'"messagePreview":'))),null) |
| name | @item()?['name'] |
| thumbnailUrl | null |
| teamsAppId | null |
⑦-22) Attachmentオブジェクトリファレンス抽出
(データ操作)『アレイのフィルター処理』で「⑦-21) Attachementの差し替え選択」からcontentかcontentUrlが設定されたデータを抽出します。
- 差出人:
@body('Attachementの差し替え選択') - 条件:
@or(not(equals(item()?['content'], null)),not(equals(item()?['contentUrl'], null)))
詳細設定モードで編集します
⑦-23) 未添付データ抽出
(データ操作)『アレイのフィルター処理』で「⑦-21) Attachementの差し替え選択」からcontent・contentUrlの両方がnullのデータを抽出します。
- 差出人:
@body('Attachementの差し替え選択') - 条件:
@and(equals(item()?['content'], null),equals(item()?['contentUrl'], null))
詳細設定モードで編集します
⑦-24) 未添付データで繰り返し
(コントロール)『それぞれに適用する』で「⑦-23) 未添付データ抽出」で繰り返し処理します。
- 出力選択:
@{body('未添付データ抽出')}
⑦-25) テンポラリ文字列に未添付ファイル名を追加
(変数)『文字列変数に追加』で「テンポラリ文字列」に添付できなかったファイル名付き注意文言を追加します。
- 式:
<div style="background-color:#f00; color:#fff; font-size:80%;">注意:@{if(equals(item()?['contentType'],'reference'),concat('添付ファイル「',items('未添付データで繰り返し')?['name'],'」'),'引用メッセージ')}は移行されませんでした</div>
⑦-26) 本文Attachment選択
(データ操作)『選択』で、「テンポラリ文字列」のattachmentタグのメッセージID値を移行後メッセージIDに置換したデータを選択します。
- 開始:(式)
@{skip(split(variables('テンポラリ文字列'),'<attachment id="'),1)} - マップ:
@concat(if(not(equals(variables('前後メッセージIDオブジェクト')?[first(split(item(),'"'))],null)),variables('前後メッセージIDオブジェクト')?[first(split(item(),'"'))],''),'"',join(skip(split(item(),'"'),1),'"'))
マップをテキストモードに切り替えて入力します
⑦-27) メッセージID置換本文作成
(データ操作)『作成』で「テンポラリ文字列」のattachment idを「⑦-26) 本文Attachment選択」に入れ替えた文字列を作成します。
- 入力:
@{first(split(variables('テンポラリ文字列'),'<attachment id="'))}@{if(equals(length(body('本文Attachment選択')),0),'',concat('<attachment id="',join(body('本文Attachment選択'),'<attachment id="')))}
⑦-28) テンポラリ文字列のメッセージID置換設定
(変数)『変数の設定』で「テンポラリ文字列」を「⑦-27) メッセージID置換本文作成」からattachmentのID空値を除去した文字列に設定します。
- 式:
@{replace(outputs('メッセージID置換本文作成'),'<attachment id=""></attachment>','')}
⑦-29) HTTP要求:メインメッセージ送信
(Microsoft Teams)『Microsoft Graph HTTP 要求を送信する』で以下パラメータを設定し、指定チーム・チャネルにメッセージ送信します。
| パラメータ名 | 値 |
|---|---|
| URI | https://graph.microsoft.com/v1.0/teams/@{body('移行先Team抽出')?[0]?['id']}/channels/@{body('移行先チャネル抽出')?[0]?['id']}/messages |
| メソッド | POST |
| 本文 | { "body": { "contentType": "html", "content": "@{replace(replace(variables('テンポラリ文字列'), '\', '\\'), '"', '\"')}"}, "hostedContents": @{body('hostedContents選択')}, "attachments" : @{body('Attachmentオブジェクトリファレンス抽出')}} |
| コンテンツの種類 | application/json |
⑦-30) 前後メッセージID作成
(データ操作)『作成』で「⑦-29) HTTP要求:メインメッセージ送信」で払い出されるメッセージIDを「前後メッセージIDオブジェクト」にプロパティ設定した値を作成します。
- 値:
@{addProperty(variables('前後メッセージIDオブジェクト'),items('メッセージ配列で繰り返し')?['id'],outputs('HTTP要求:メインメッセージ送信')?['body']?['id'])}
⑦-31) 前後メッセージIDの設定
(変数)『変数の設定』で「前後メッセージIDオブジェクト」に「⑦-30) 前後メッセージID作成」の変更を設定します。
- 式:
@{outputs('前後メッセージID作成')}
(変数)『変数の値を増やす』で入力文字列の右から指定位置文字が「1」の場合は返り値に係数変数の値を加算します。(「1」以外は加算しない)
- 値:
@{if(equals(substring(triggerBody()?['text'],sub(length(triggerBody()?['text']),item()),1),'1'),variables('係数'),0)}
(変数)『文字列変数に追加』で「内容」に読取行文字列を追加します。
- 値:@{outputs('本文先頭行取り出し')}

