ささみ学習帳 - sasami's study book

ささみ学習帳

Microsoft365 や Power Platform について学んだこと・アイデアのメモ

Microsoft365 管理センターのサービス正常性のメール通知を翻訳してTeamsチャネルに投稿する PowerAutomate クラウドフロー (1/3)

(2023/10/18)

エラーの頻度が多くなってきた・新しいTeamsに変わりつつある事から全面的に作り直してみました。

sasami-axis.hatenablog.com

 

(2023/9/19 更新しました)

  • >7.説明文の取得

    • メール本文中に“:U+201C, ”:U+201D が使われているとエラーになることに対処

    • 日本語訳した際に"のエスケープ処理が解除されてしまうケースに対処

  • >10.URL文字列の取得

    • 障害IDにメッセージセンターへのリンクがないケースに対処
  • >11.アダプティブカードで送信
    • 日本語訳した際に"のエスケープ処理が解除されてしまうケースに対処
  • このフローの課題を追記

(2023/6/22 更新しました)

  • Hiroさんのアドバイスでフィード通知を追加。今後改善したい事が解決!
  • >5.タイトル文字列の取得
    • メール本文中に"が含まれると正常動作しない問題に対処
  • >7.説明文の取得
    • メール本文中に"が含まれると正常動作しない問題に対処
    • 対応完了した際のメールには"Are you experiencing this issue?"が含まれない為、Detailsの取得に失敗する問題に対処 

 

 

はじめに

Microsoft365管理センターのサービス正常性には、標準機能として新しい問題が発生するとメール通知する機能があります。

私はこの通知メールの送信先に、チャネルのメールアドレスを設定しているのですが2つの問題点がありました。

  • 通知メールは翻訳されていない
  • チャネルにはメール全文が表示されない

翻訳はともかく、メール本文がほぼ表示されないのが致命的です。「元のメールをダウンロード」のリンクからemlファイルとしてメールをダウンロードすることはできますが、emlファイルを毎回開いていては使い勝手がいまいちです。

この辺りの問題をPower Automate クラウドフローを使って解消しました。

 

完成イメージ

  • アダプティブカードを利用して全文が省略されないように
  • Microsoft Translator V2コネクタ で日本語に翻訳

 

準備すること

  • サービス正常性のメール通知を設定する
  • 投稿先のTeamsチャネルを作成する
  • OneDrive for Business にアダプティブカードに表示する画像を配置しておく
    • ※ファイルサイズは28KBが上限です

 

フロー全体

長いので9分割してみました

 

フロー解説

1.トリガー :Office 365 Outlookコネクタ「新しいメールが届いたとき」アクション

サービス正常性のメール通知の fromアドレス "0365mc@microsoft.com" を指定しています。

ユーザーメールボックスで受信する場合は、こちらのアクションですが、共有メールボックスの場合は未確認ですが「新しいメールが共有メールボックスに届いたとき (V2)」トリガーで同様の動作が実現できるかと思います。

2.変数の定義

このフローでは2つの変数を利用します。

改行:改行文字の置換処理用

BodyText:メール本文の変換処理用

 

3.OneDrive for Businessから画像を読み込む

アダプティブカードに添付する画像をOneDrive for Businessから読み込んでいます。

 

4.メール本文をテキストに変換

通知メールはHTMLで構成されているので、テキストに変換します。

変換したメール本文テキストはこのようになります。

 

5.タイトル文字列の取得

※5~9までほぼ同じ作業の繰り返しです。

  • 部分文字列-Title
    • 開始位置: @{add(indexof(variables('BodyText'), ']'), 1)}
    • 長さ:@{add(indexof(variables('BodyText'), 'ID: '), mul(add(indexof(variables('BodyText'), ']'), 1), -1))}
  • 作成-Title
trim(
    replace(
        replace(
            replace(
                replace(body('部分文字列-Title'), variables('改行'), ' ')
            , '"', '\"')    
        , '“', '\"')
    , '”', '\"')
)
  • テキストの翻訳-Title-Ja
trim(
    replace(
        replace(
            replace(
                replace(body('部分文字列-Title-Ja'), variables('改行'), ' ')
            , '"', '\"')    
        , '“', '\"')
    , '”', '\"')
)

 

メール本文のこの辺りを取得します。直前のURLの末尾にある"]"から、直後の"ID: "までの範囲をざっくり取得した後で、改行を' 'に置換した上でtrimで先頭・末尾の' 'を除去し、Translator V2 テキストの翻訳アクションで日本語に変換しています。また、タイトル文字列に"が含まれると後続処理でエラーが発生する為、\"に置換しています。


6.ステータス文字列の抽出

  • 部分文字列-Status
    • 開始位置: @{add(indexof(variables('BodyText'), concat('Status', variables('改行'))), 7)}
    • 長さ:@{add(indexof(variables('BodyText'), 'Impacted services'), mul(add(indexof(variables('BodyText'), concat('Status', variables('改行'))), 7), -1))}
  • 作成-Status
    • 出力:@{trim(replace(body('部分文字列-Status'), variables('改行'), ' '))}

メール本文のこの辺りを取得します。"Status"から、直後の"Impacted services"までの範囲をざっくり取得した後で、改行を' 'に置換した上でtrimで先頭・末尾の' 'を除去しています。

 

7.説明文の取得



  • 部分文字列-Details
    • 開始位置
add(
    indexof(variables('BodyText'), concat('Details', variables('改行')))
    ,7
)
    • 長さ
add(
    if(equals(indexof(variables('BodyText'), 'Are you experiencing this issue?'), -1), 
        indexof(variables('BodyText'), 'To customize what’s included in this email'), 
        indexof(variables('BodyText'), 'Are you experiencing this issue?')
    ), 
    mul(add(indexof(variables('BodyText'), concat('Details', variables('改行'))), 7), -1)
)
  • 作成-Details
    • 出力
trim(
    replace(
        replace(
            replace(
                replace(body('テキストの翻訳-Details'), variables('改行'), ' ')
            , '"', '\"')    
        , '“', '\"')
    , '”', '\"')
)

メール本文のこの辺りを取得します。"Details"から、直後の"Are you experiencing this issue?"または ”To customize what’s included in this email"までの範囲をざっくり取得した後で、trimで先頭・末尾の' 'を除去し、Translator V2 テキストの翻訳アクションで日本語に変換しています。文章なので改行は除去していません。また、タイトル文字列に"が含まれると後続処理でエラーが発生する為、\"に置換しています。

 

8.ID文字列の取得

  • 部分文字列-ID
    • 開始位置: @{add(indexof(variables('BodyText'), 'ID: '), 4)}
    • 長さ:8
  • 作成-ID
    • 出力:@{trim(body('部分文字列-ID'))}

メール本文のこの辺りを取得します。"ID: "から8文字を取得した後で、trimで先頭・末尾の' 'を除去しています。IDの形式は固定のようなのでここだけシンプルです。

 

9.影響があるサービス文字列の取得

  • 部分文字列-Service
    • 開始位置: @{add(indexof(variables('BodyText'), 'Impacted services'), 17)}
    • 長さ:@{add(indexof(variables('BodyText'), 'Details'), mul(add(indexof(variables('BodyText'), 'Impacted services'), 17), -1))}
  • 作成-Service
    • 出力:@{trim(replace(body('部分文字列-Service'), variables('改行'), ' '))}

メール本文のこの辺りを取得します。"Impacted services"から、直後の"Details"までの範囲をざっくり取得した後で、改行を' 'に置換した上でtrimで先頭・末尾の' 'を除去しています。

 

10.URL文字列の取得

  • 条件
add(
    add(
        indexof(variables('BodyText'), 'Status'), 
        -3
    ), 
    mul(
        add(
            indexof(variables('BodyText'), 'ID: '), 
            14
        ), 
        -1
    )
)
  •  部分文字列-Url
    • 開始位置: @{add(indexof(variables('BodyText'), 'ID: '), 14)}
    • 長さ
add(
    add(
        indexof(variables('BodyText'), 'Status'), 
        -3
    ), 
    mul(
        add(
            indexof(variables('BodyText'), 'ID: '), 
            14
        ), 
        -1
    )
)
  • 作成-Url
    • 出力:@{trim(replace(body('部分文字列-URL'), variables('改行'), ' '))}

メール本文のこの辺りを取得します。"ID: "+14文字から、直後の"Status"の3文字前までの範囲をざっくり取得した後で、改行を' 'に置換した上でtrimで先頭・末尾の' 'を除去しています。

 

11.アダプティブカードで送信

取得した文字列をアダプティブカードでチャネルに送信します。

アダプティブカードのjsonはこちらを使っています。

{
    "type": "AdaptiveCard",
    "body": [
        {
            "type": "TextBlock",
            "size": "Medium",
            "weight": "Bolder",
            "text": "[@{outputs('作成-ID')}] @{outputs('作成-Title-Ja')}",
            "id": "subject"
        },
        {
            "type": "ColumnSet",
            "columns": [
                {
                    "type": "Column",
                    "items": [
                        {
                            "type": "Image",
                            "style": "Person",
                            "url": "@{variables('ImageUrl')}",
                            "size": "Medium"
                        }
                    ],
                    "width": "auto"
                },
                {
                    "type": "Column",
                    "items": [
                        {
                            "type": "TextBlock",
                            "weight": "Bolder",
                            "text": "@{outputs('作成-ImpactedServices')}",
                            "wrap": true,
                            "id": "service"
                        },
                        {
                            "type": "TextBlock",
                            "spacing": "None",
                            "text": "@{convertFromUtc(formatDateTime(triggerOutputs()?['body/receivedDateTime'], 'yyyy-MM-ddTHH:mm:ssZ'), 'Tokyo Standard Time', 'yyyy年MM月dd日 HH:mm:ss')}",
                            "isSubtle": true,
                            "wrap": true,
                            "id": "mailRecieved"
                        }
                    ],
                    "width": "stretch"
                }
            ]
        },
        {
            "type": "FactSet",
            "facts": [
                {
                    "title": "ID:",
                    "value": "@{outputs('作成-ID')}"
                },
                {
                    "title": "Title:",
                    "value": "@{outputs('作成-Title')}"
                },
                {
                    "title": "Service:",
                    "value": "@{outputs('作成-ImpactedServices')}"
                },
                {
                    "title": "Status:",
                    "value": "@{outputs('作成-Status')}"
                }
            ]
        },
        {
            "type": "TextBlock",
            "text": "@{outputs('作成-Details')}",
            "wrap": true,
            "id": "details"
        },
        {
            "type": "ActionSet",
            "actions": [
                {
                    "type": "Action.OpenUrl",
                    "title": "続きはm365管理センターで",
                    "url": "@{outputs('作成-Url')}"
                }
            ]
        }
    ],
"msteams": {
        "width": "Full"
    },
    "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
    "version": "1.4"
}

末尾の"msteams":{"width:Full"}がポイントです。これを指定する事で、アダプティブカードの幅が最大化されます。今回の長文を表示するようなカードでは必須の設定です。

 

12.チームメンバーにフィード通知を投稿する

※2023/6/22 追記

Teamsコネクタのアダプティブカードを送信するアクションは通知されるメッセージが固定で内容が判断できません。iOSのTeamsモバイルアプリの通知に至っては"Flow Card"のみの非常に簡素なものです。

簡素な通知の代替として、Office 365 Groups コネクタの「グループ メンバーを一覧表示する」アクションでチームメンバーを取得し、「フィード通知を投稿する」アクションで各チームメンバーに通知を投稿しています。

Hiroさんにコメントでアドバイスいただきました。ありがとうございます!

 

 

このフローの課題

  • メール本文のレイアウトに依存するのですべてのメールを処理できる保証はない
    • フローを作成した時点で受信していたメールを処理できることを確認したのみの為、今後のメールをすべて完全に処理できる保証はありません。
    • 新しいパターンのメールが届きエラーが発生する都度、原因を調査しフローを修正する必要があります。
    • 例えば「The length of substring can't be longer than 'xxxxxxx' which is the length of the source string.」というエラーが出る場合は、部分文字列アクションの長さの値が適切ではない可能性が高いです。特定のメールでのみ発生する場合は、そのメールのレイアウトが他のメールと異なっていると思われます。メールの状況に合わせて式を調整が必要になります。
      はてなブログはコメントに返信ができないようなのでこちらに追記しました。
  • AI BuilderやAzure OpenAI Service、OpenAI API等を使ってメール本文の解析をAIに任せることである程度メール本文の表記ゆれに対処が可能です。

 

さいごに

障害の通知をするのにPowerAutomateやTranslatorを経由させて障害ポイントを増やすのはどうかとも思いましたが、内容が把握しやすくなったのは良いですね。

今回は、Power Automate for Office 365 ライセンスでも使えるようにプレミアムコネクタを使わずメール本文から文字列検索して抽出する手段をとってみましたが、この辺りプレミアムコネクタを使ってAPI利用すればもっとシンプルなフローにすることができそうです。(その2へ続く)

 

 

参考にしたページ

mofumofupower.hatenablog.com