ルドルフもわたるふもいろいろあってな

Microsoft 365、Power Platform、PowerShellについて調べたことや検証したことなどを投稿します。技術の話は面白い。

【Power Automate】【解説編】フローの親子関係を一覧化

掲題のフローについての解説編です。

掲題のフローをGitHubで公開しました。下記リンク先からダウンロードしてください。試し実行ができるようにサンプルの親フローと子フローを含めたソリューションにしています。

github.com

解説の経緯

今回の投稿は概要編の続きです。

フローの使用イメージ

フローの使用イメージについては概要編に記載しています。概要編を未読のかたはそちらを先に参照ください。

解説の範囲について

「OneDriveにCSV保存してチャットで保存先を通知する」ステップについては別投稿を参照してください

OneDriveにCSV保存するアクション(下図の赤枠)については過去に投稿したこちらの記事で解説したフローと同じです。そのため、今回は下図の赤枠部分は説明を割愛します。

wataruf.hatenablog.com

解説する範囲

このフローについて解説する範囲は下図の赤枠の部分です。

開いた状態のフロー図

ステップごとの解説

「手動でフローをトリガーします」トリガー

名前の通り、フローを手動で実行するトリガーです。今回はこのトリガーでの入力情報はありません。

「管理者としてフローの一覧を取得 (V2)」アクション

このアクションは「Power Automate Management」>「管理者としてフローの一覧を取得 (V2)」です。指定したPower Platform環境内にあるフローを一覧取得します。削除されたフローを取得対象から除外するために「論理的に削除されたフローを含める」プロパティを「いいえ」にしています。

    "inputs": {
        "parameters": {
            "environmentName": "Default-9c090045-a219-4ceb-9ac8-9e80cf18437e",
            "includeSoftDeletedFlows": false
        }

このアクションの出力イメージは下図の通りです。

「変数を初期化する-フローの親子関係表」アクション

このアクションは「変数」>「変数を初期化する」です。このフローの最終的なアウトプットである「フローの親子関係表」の格納先とする配列変数を定義します。

    "inputs": {
        "variables": [
            {
                "name": "フローの親子関係表",
                "type": "array"
            }
        ]
    }

「選択-環境内のフロー一覧」アクション

このアクションは「データ操作」>「選択」です。「管理者としてフローの一覧を取得 (V2)」アクションで取得したデータから、このフローの後続の処理で必要な情報のみを取得します。

    "inputs": {
        "from": "@outputs('管理者としてフローの一覧を取得_(V2)')?['body/value']",
        "select": {
            "FlowName": "@item()?['name']",
            "FlowDisplayName": "@item()?['properties']?['displayName']",
            "EnvironmentName": "@item()?['properties']?['environment']?['name']",
            "workflowEntityId": "@item()?['properties']?['workflowEntityId']"
        }
   }

今回のフローで取得しているプロパティは下記の4つです。

  •  FlowName     ・・・ フローを一意に表すGUID
  •  FlowDisplayName ・・・ フローの表示名
  •  EnvironmentName ・・・ フローが保存されている環境名
  •  workflowEntityId ・・・ フローを一意に表すGUID(子フローの呼び出しで使用

フローの作成に慣れているかたにとってもなかなか見慣れないのは「workflowEntityId」プロパティかと思います。このプロパティは「子フローの実行」アクションで子フローを指定する際に利用します。「子フローの実行」アクションを使用しているフロー(= 親フロー)とそれによって呼ばれるフロー(= 子フロー)の両方にこの「workflowEntityId」プロパティ情報が付加されます。「子フローの実行」アクションを使用していないフローであり、かつ、他のフローから呼び出される側でもないフローには「workflowEntityId」プロパティは存在しません。

このアクションの出力は下図の通りです。

「Apply to each-環境内のフロー」アクション

このアクションは「コントロール」>「それぞれに適用する」です。

繰り返し処理のインデクサは前述の選択アクションによって出力された配列です。

tryとcatchという名前をつけているスコープはこの繰り返し処理中にエラーが発生した場合であってもフローが停止せずに次の繰り返し処理に進ませるために配置しています。

tryというスコープ内に配置しているアクションについて解説します。

「作成-このループの入力-現在のアイテム」アクション

このアクションは「データ操作」>「作成」です。繰り返し処理における”現在のアイテム”の値を確認するために配置しています。このアクションはフローの可読性をあげるために使用しています。

 #このアクションを削除した場合であっても、フロー全体の入出力には影響ありません。

"inputs": "@items('Apply_to_each-環境内のフロー')"

このアクションの出力イメージは下図の通りです。

「フローの取得」アクション

このアクションは「Power Automate Management」>「フローの取得」アクションです。

"parameters": {
   "environmentName": "@items('Apply_to_each-環境内のフロー')?['EnvironmentName']",
   "flowName": "@items('Apply_to_each-環境内のフロー')?['FlowName']"
}
この情報に「子フローの実行」アクションで呼び出している子フローが何かを示すIDが含まれています。
このアクションの出力イメージは下図の通りです。

上図の赤枠部分に紐つくのは「フロー定義」の部分。

注意点がひとつあります。
上記の「フロー定義」が取得できるアクションは「フローの取得」アクションです。「フローを管理者として取得」アクションで取得できる情報には「フロー定義」は含まれません。

「作成-子フローの呼び出しを取得」アクション

このアクションは「データ操作」>「作成」です。XPath関数を使って子フローを参照しているプロパティ(workflowReferenceName)の値を取得します。

xpath(
    xml(
        json(
            concat(
                '{',
                'actions:',
                outputs(
                    'フローの取得'
                ) ?['body/properties/definition/actions'],
                '}'
            )
        )
    ),
    '//workflowReferenceName'
)

関数の過程を段階的に解説します。

まずは「フローの取得」アクションで取得した「フロー定義」(definitionプロパティ)からactionsプロパティを取得します。

その取得結果にconcat関数を使って「{」「actions:」「}」の文字列を結合します。こうする理由はXPath関数を使用した値の検索を行うためには対象がXML形式である必要があるためです。検索を行う対象のJSONXMLに変換する際にはルートの要素が単一のプロパティを持っている必要があります。そのため、concat関数を使って単一のプロパティを持つルート要素を追加します。

ルートの要素が複数のプロパティを持っているJSONXMLに変換しようとするとエラーになります。

エラー内容:JSON root object has multiple properties. The root object must have a single property in order to create a valid XML document. 

concat関数の結果は下記の通りです。

{
    "actions": {
        "子フローの実行": {
            "runAfter": {},
            "metadata": {
                "operationMetadataId": "fc79a2fd-f0a0-40d9-b5ea-e4dfa08a7667"
            },
            "type": "Workflow",
            "inputs": {
                "host": {
                    "workflowReferenceName": "ffcc5bf7-03aa-ee11-be37-6045bd63d558"
                }
            }
        },
        "子フローの実行_2": {
            "runAfter": {
                "子フローの実行": [
                    "Succeeded"
                ]
            },
            "metadata": {
                "operationMetadataId": "701e85ff-cc7a-4c52-b25d-e8f02c606acc"
            },
            "type": "Workflow",
            "inputs": {
                "host": {
                    "workflowReferenceName": "4a142908-04aa-ee11-be37-6045bd63d558"
                }
            }
        },
        "子フローの実行_3": {
            "runAfter": {
                "子フローの実行_2": [
                    "Succeeded"
                ]
            },
            "metadata": {
                "operationMetadataId": "3152b371-79f4-4280-98c2-6f2585be2c87"
            },
            "type": "Workflow",
            "inputs": {
                "host": {
                    "workflowReferenceName": "35cd7f12-04aa-ee11-be37-6045bd63d558"
                }
            }
        }
    }
}

次に、concat関数の結果はJSON形式ではなく文字列形式であるためjson関数を使ってjsonに再度戻します。そのあとにxml関数を使ってjson形式からxml形式に変換します。

xml関数の結果は下記の通りです。

<actions>
  <子フローの実行>
    <runAfter />
    <metadata>
      <operationMetadataId>fc79a2fd-f0a0-40d9-b5ea-e4dfa08a7667</operationMetadataId>
    </metadata>
    <type>Workflow</type>
    <inputs>
      <host>
        <workflowReferenceName>ffcc5bf7-03aa-ee11-be37-6045bd63d558</workflowReferenceName>
      </host>
    </inputs>
  </子フローの実行>
  <子フローの実行_2>
    <runAfter>
      <子フローの実行>Succeeded</子フローの実行>
    </runAfter>
    <metadata>
      <operationMetadataId>701e85ff-cc7a-4c52-b25d-e8f02c606acc</operationMetadataId>
    </metadata>
    <type>Workflow</type>
    <inputs>
      <host>
        <workflowReferenceName>4a142908-04aa-ee11-be37-6045bd63d558</workflowReferenceName>
      </host>
    </inputs>
  </子フローの実行_2>
  <子フローの実行_3>
    <runAfter>
      <子フローの実行_2>Succeeded</子フローの実行_2>
    </runAfter>
    <metadata>
      <operationMetadataId>3152b371-79f4-4280-98c2-6f2585be2c87</operationMetadataId>
    </metadata>
    <type>Workflow</type>
    <inputs>
      <host>
        <workflowReferenceName>35cd7f12-04aa-ee11-be37-6045bd63d558</workflowReferenceName>
      </host>
    </inputs>
  </子フローの実行_3>
</actions>

このXMLに対してXPathを使って対象のプロパティの値を検索します。その結果がこのアクションの出力である下記のjson配列です。要素が2つあるのはXPath関数による検索結果が2つあることを表しています。$content というプロパティに検索結果として返された値がbase64形式で格納されています。

「選択-子フローのworkflowEntityIdを取得」アクション

このアクションは「データ操作」>「選択」です。

 "inputs": {
     "from": "@union(outputs('作成-子フローの呼び出しを取得'),outputs('作成-子フローの呼び出しを取得'))",
     "select": "@replace(replace(base64ToString(item()?['$content']),'<workflowReferenceName>',''),'</workflowReferenceName>','')"
 },

「開始」欄では、前述の「作成-子フローの呼び出しを取得」アクションの結果として得られた配列に対してunion関数を使って要素の重複排除をしています。これは、ひとつのフローで同じ子フローが複数回呼び出されるケースを考慮しているためです。

「作成-子フローの呼び出しを取得」アクションで得られたbase64形式の文字列をbase64ToString関数を使ってもとの文字列に戻します。ですが、それだけでは下記の通り、<workflowReferenceName>という要素のタグが残ってしまいます。

後続のアクションにこれらの値を渡す際にタグは不要であるため、replace関数を使ってこのタグを取り除きます。

選択アクションの最終的な出力は下図の通りです。

「Apply to each-親フローが呼び出している子フロー」アクション

このアクションは「コントロール」>「それぞれに適用する」です。前述のアクションで取得したworkflowReferenceNameの値に紐づく子フローが何かを調べます。

この繰り返し処理のインデクサは「選択-子フローのworkflowEntityIdを取得」アクションで出力した配列です。

「作成-このループの入力-現在のアイテム 2」アクション

このアクションは「データ操作」>「作成」です。繰り返し処理における”現在のアイテム”の値を確認するために配置しています。このアクションはフローの可読性をあげるために使用しています。

"inputs": "@items('Apply_to_each-親フローが呼び出している子フロー')"

このアクションの出力イメージは下図の通りです

「アレイのフィルター処理-workflowEntityIdをキーにして子フローの情報を取得」アクション

このアクションは「データ操作」>「アレイのフィルター処理」です。「管理者としてフローの一覧を取得 (V2)」アクションで取得したフロー一覧の配列のうち、workflowEntityIdが現在のアイテムと一致する要素をフィルタ取得します。仕組み上この結果は必ずひとつになります。

    "inputs": {
        "from": "@body('選択-環境内のフロー一覧')",
        "where": "@equals(item()?['workflowEntityId'], '')"
    }

このアクションの出力イメージは下図の通りです。

「作成-このループで作成した親子関係表の行」アクション

このアクションは「データ操作」>「作成」です。このフローの最終的な出力である「フローの親子関係表」の行を定義します。

    "inputs": {
        "親フロー表示名": "@{outputs('フローの取得')?['body/properties/displayName']}",
        "親フロー名": "@{outputs('フローの取得')?['body/name']}",
        "⇒": "⇒",
        "子フロー表示名": "@{first(body('アレイのフィルター処理-workflowEntityIdをキーにして子フローの情報を取得'))?['FlowDisplayName']}",
        "子フロー名": "@{first(body('アレイのフィルター処理-workflowEntityIdをキーにして子フローの情報を取得'))?['FlowName']}"
    }

前述の「アレイのフィルター処理」の出力に対してfirst関数を使用している理由は、「アレイのフィルター処理」の出力は要素の個数に限らず配列形式になるためです。配列から要素を取りだすためにfirst関数を使用しています。

このアクションの出力イメージは下図の通りです。

「配列変数に追加-親子関係表に行を追加」アクション

このアクションは「配列」>「配列変数に追加」アクションです。親子関係表という名前の配列変数に前述のアクションで作成した行を追加します。

    "inputs": {
        "name": "フローの親子関係表",
        "value": "@outputs('作成-このループで作成した親子関係表の行')"
    }

このアクションの出力イメージは下図の通りです。

「作成-このループの出力-親子関係表」アクション

このアクションは「データ操作」>「作成」です。このアクションは「作成-このループの入力」アクションと同じくフローの可読性をあげるために使用しています。

"inputs": "@variables('フローの親子関係表')"

このアクションの出力イメージは下図の通りです。

「スコープ-catch」アクション

このアクションは「コントロール」>「スコープ」です。このスコープは繰り返し処理内でエラーが発生した場合であっても処理を継続する目的で配置しています。今回のフローでは特にアクションは配置していません。

tryスコープ内でエラーが発生した場合でもこのcatchスコープが実行されるように「実行条件の構成」でエラーに該当する実行条件のチェックボックスをオンにしています。

CSV テーブルの作成」アクション

このアクションは「データ操作」>「CSV テーブルの作成」です。「フローの親子関係表」をCSVに出力するためにテーブル形式に変換します。

    "inputs": {
        "from": "@variables('フローの親子関係表')",
        "format": "CSV"
    }

このアクションの出力イメージは下図の通りです。

「スコープ-OneDriveにCSV保存してチャットで保存先を通知する」アクション

こちらのスコープ内のアクション(OneDriveにCSV保存するための一連のアクション)については過去に投稿したこちらの記事で解説しています。今回は解説を割愛します。

このフローをGitHubで公開しました。(再掲)

フローをGitHubで公開しました。下記リンク先からダウンロードしてください。この投稿の冒頭で記載しているリンクと同じです。試し実行ができるようにサンプルの親フローと子フローを含めたソリューションにしています。

github.com

参考情報

itc-engineering-blog.netlify.app

最後に

フローの親子関係を一覧化するためにフロー定義をフローで取得方法を今回見つけました。これはまた別のことに応用して公開したいと考えています。

(`・ω・´)シャキーン

 

今回は以上です。