🎃

Power Automate Web API を使ってクラウドフローの中身を一部書き換えながらコピーする

2022/12/09に公開

Power Automate Advent Calendar 2022 12月10日担当の記事です。
https://qiita.com/advent-calendar/2022/powerautomate

Power Automate を使っていると、開発用、検証用、本番用とか同じ内容の似たフローを複数作成することがあります。
Dataverse を使っていて、Dataverse のテーブルを主なデータソースとしていれば、開発環境でフローを作成したのち、ソリューションにまとめて、検証環境、本番環境と順番にインポートしていけば問題ないのですが、SharePoint を絡めていたりすると、中身がそのまんまとはいかず、検証用の SharePoint サイトにするだとか、リストにするだとかフローの中身を書き換えないといけません。

こういう用途の場合の変更って面倒だなーと思っていたところ、Power Automate Web API を使ってフローの中身を一部書き換えながらコピーするというのができそうなので、やってみたいと思います。

参考

Power Automate Web API
https://learn.microsoft.com/en-us/power-automate/web-api

前提

本件では、Power Automate Web API を利用しますが、これは Dataverse Web API で特定のテーブルにデータを登録、更新するものと同じです。
そのため、Office 365 に付属するライセンスでは利用できないものですのでご注意ください。

事前準備

以下の SharePoint サイト、リスト、ソリューション、フローを作成しておきます。
また、フローはソリューションに追加しておかないと、APIで操作できないため追加しておきます。

SharePoint サイト

カスタムリスト1

カスタムリスト2 (カスタムリスト1と列定義は同じ)

ソリューション

※接続参照はクラウドフローを作成すると勝手に追加されます。

クラウドフローA


作業手順

今回はクラウドフローAのトリガーとなるカスタムリストをカスタムリスト2に書き換えながら、クラウドフローBを作成してみたいと思います。
Web API の実行方法はいろいろありますが、今回は楽したいので Power Automate クラウドフローを使って実行したいと思います。

フローコピー用のインスタントフローを作成する

  • 全体像は以下のような感じです。

クラウドフローAの情報を取得する

  • まずはコピー元となるフローの情報を取得するため、HTTP with Azure AD コネクタの Invoke an HTTP request を使って、Power Automate Web API を実行します。
    対象のアクションを設置すると、以下のようなサインイン情報の入力を求められます。

    ここでは、Power Apps の開発者リソースの画面から Web API エンドポイントの一部をコピーして入力します。
項目 設定値
Base Resource URL {Web API エンドポイントのdynamics.comまで}
Azure AD Resource URI (Application ID URI) {Web API エンドポイントのdynamics.comまで}
  • サインインが完了したら、以下の値を設定します。
項目 設定値
Method GET
Url of the request /api/data/v9.1/workflows?$filter=name eq '{クラウドフローAの名前}'
Headers Accept : application/json

これでフローの情報を取得できました。

フローそのものを構成している JSON を抽出しやすくする

  • 次に、取得したクラウドフローの情報から、JSON の解析を使って、フローそのものを構成している JSON を抽出しやすくします。
    対象のJSON は clientdata という key で取得できるのですが、それを取得しやすくするために、配列としての情報の削除も一緒に行います。
項目 設定値
コンテンツ first(outputs('Invoke_an_HTTP_request')?['body']?['value'])
スキーマ 以下に記載(取得結果から配列を取り除いたもの)
スキーマ
{
    "type": "object",
    "properties": {
        "@@odata.etag": {
            "type": "string"
        },
        "category": {
            "type": "integer"
        },
        "statecode": {
            "type": "integer"
        },
        "statuscode": {
            "type": "integer"
        },
        "istransacted": {
            "type": "boolean"
        },
        "workflowidunique": {
            "type": "string"
        },
        "createdon": {
            "type": "string"
        },
        "triggeroncreate": {
            "type": "boolean"
        },
        "runas": {
            "type": "integer"
        },
        "triggerondelete": {
            "type": "boolean"
        },
        "_ownerid_value": {
            "type": "string"
        },
        "versionnumber": {
            "type": "integer"
        },
        "asyncautodelete": {
            "type": "boolean"
        },
        "name": {
            "type": "string"
        },
        "solutionid": {
            "type": "string"
        },
        "ismanaged": {
            "type": "boolean"
        },
        "businessprocesstype": {
            "type": "integer"
        },
        "mode": {
            "type": "integer"
        },
        "introducedversion": {
            "type": "string"
        },
        "iscrmuiworkflow": {
            "type": "boolean"
        },
        "workflowid": {
            "type": "string"
        },
        "_modifiedby_value": {
            "type": "string"
        },
        "modifiedon": {
            "type": "string"
        },
        "trustedaccess": {
            "type": "boolean"
        },
        "subprocess": {
            "type": "boolean"
        },
        "clientdata": {
            "type": "string"
        },
        "scope": {
            "type": "integer"
        },
        "ondemand": {
            "type": "boolean"
        },
        "componentstate": {
            "type": "integer"
        },
        "_createdby_value": {
            "type": "string"
        },
        "_owningbusinessunit_value": {
            "type": "string"
        },
        "_owninguser_value": {
            "type": "string"
        },
        "syncworkflowlogonfailure": {
            "type": "boolean"
        },
        "overwritetime": {
            "type": "string"
        },
        "primaryentity": {
            "type": "string"
        },
        "type": {
            "type": "integer"
        },
        "createstage": {},
        "xaml": {},
        "suspensionreasondetails": {},
        "entityimage": {},
        "_plugintypeid_value": {},
        "entityimage_url": {},
        "processroleassignment": {},
        "languagecode": {},
        "dynamicssolutioncontext": {},
        "inputparameters": {},
        "_modifiedonbehalfby_value": {},
        "entityimageid": {},
        "rank": {},
        "description": {},
        "inputs": {},
        "rendererobjecttypecode": {},
        "processtriggerscope": {},
        "_createdonbehalfby_value": {},
        "processorder": {},
        "definition": {},
        "updatestage": {},
        "triggeronupdateattributelist": {},
        "metadata": {},
        "_owningteam_value": {},
        "deletestage": {},
        "_parentworkflowid_value": {},
        "dependencies": {},
        "_activeworkflowid_value": {},
        "uiflowtype": {},
        "schemaversion": {},
        "uidata": {},
        "processtriggerformid": {},
        "outputs": {},
        "formid": {},
        "uniquename": {},
        "connectionreferences": {},
        "entityimage_timestamp": {},
        "_sdkmessageid_value": {},
        "iscustomprocessingstepallowedforotherpublishers": {
            "type": "object",
            "properties": {
                "Value": {
                    "type": "boolean"
                },
                "CanBeChanged": {
                    "type": "boolean"
                },
                "ManagedPropertyLogicalName": {
                    "type": "string"
                }
            }
        },
        "iscustomizable": {
            "type": "object",
            "properties": {
                "Value": {
                    "type": "boolean"
                },
                "CanBeChanged": {
                    "type": "boolean"
                },
                "ManagedPropertyLogicalName": {
                    "type": "string"
                }
            }
        }
    }
}

抽出した JSON のカスタムリストを指定している部分を置換する

  • それから、関数を使って抽出した JSON の中のカスタムリストを指定している部分を置換します。
    clientdata で取得できる JSON 文字列の中の、カスタムリスト1のリストIDを、カスタムリスト2のリストIDに置換します。
    また、他にも差分があれば、ここで対応することができます。
項目 設定値
1つ目の作成の入力 replace(body('JSON_の解析')?['clientdata'],'{カスタムリスト1のリストID}','{カスタムリスト2のリストID}')

ちなみに取り出した JSON は以下のようなものになります。

clientdata の JSON
{
  "properties": {
    "connectionReferences": {
      "shared_office365": {
        "runtimeSource": "embedded",
        "connection": {
          "connectionReferenceLogicalName": "ex_sharedoffice365_c3897"
        },
        "api": { "name": "shared_office365" }
      },
      "shared_sharepointonline": {
        "runtimeSource": "embedded",
        "connection": {
          "connectionReferenceLogicalName": "ex_sharedsharepointonline_87b27"
        },
        "api": { "name": "shared_sharepointonline" }
      }
    },
    "definition": {
      "$schema": "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#",
      "contentVersion": "1.0.0.0",
      "parameters": {
        "$connections": { "defaultValue": {}, "type": "Object" },
        "$authentication": { "defaultValue": {}, "type": "SecureObject" }
      },
      "triggers": {
        "項目が作成されたとき": {
          "recurrence": { "interval": 1, "frequency": "Minute" },
          "splitOn": "@triggerOutputs()?['body/value']",
          "metadata": {
            "operationMetadataId": "761485fa-48aa-4b88-9c4f-2784f0151ab1"
          },
          "type": "OpenApiConnection",
          "inputs": {
            "host": {
              "connectionName": "shared_sharepointonline",
              "operationId": "GetOnNewItems",
              "apiId": "/providers/Microsoft.PowerApps/apis/shared_sharepointonline"
            },
            "parameters": {
              "dataset": "https://{自テナントのSharePointドメイン}/sites/SampleSite",
              "table": "{カスタムリスト1のID}"
            },
            "authentication": "@parameters('$authentication')"
          }
        }
      },
      "actions": {
        "メールの送信_(V2)": {
          "runAfter": {},
          "metadata": {
            "operationMetadataId": "aa322bf0-f94b-43eb-aaaa-c63ced85bd03"
          },
          "type": "OpenApiConnection",
          "inputs": {
            "host": {
              "connectionName": "shared_office365",
              "operationId": "SendEmailV2",
              "apiId": "/providers/Microsoft.PowerApps/apis/shared_office365"
            },
            "parameters": {
              "emailMessage/To": "HamanKarn@*****.com",
              "emailMessage/Subject": "クラウドフローからメールを送信します",
              "emailMessage/Body": "<p>クラウドフローからメールを送信します</p>",
              "emailMessage/Importance": "Normal"
            },
            "authentication": "@parameters('$authentication')"
          }
        },
        "項目の更新": {
          "runAfter": { "メールの送信_(V2)": ["Succeeded"] },
          "metadata": {
            "operationMetadataId": "9cb7cb89-7ae9-4dd6-8649-0db65e426718"
          },
          "type": "OpenApiConnection",
          "inputs": {
            "host": {
              "connectionName": "shared_sharepointonline",
              "operationId": "PatchItem",
              "apiId": "/providers/Microsoft.PowerApps/apis/shared_sharepointonline"
            },
            "parameters": {
              "dataset": "https://{自テナントのSharePointドメイン}/sites/SampleSite",
              "table": "{カスタムリスト1のID}",
              "id": "@triggerOutputs()?['body/ID']",
              "item/Title": "@triggerOutputs()?['body/Title']",
              "item/flow_completed": true
            },
            "authentication": "@parameters('$authentication')"
          }
        }
      }
    },
    "templateName": ""
  },
  "schemaVersion": "1.0.0.0"
}

抽出した JSON がその後の処理で利用できるように加工する

  • それから、再度関数を使って JSON の " が有効になるように、エスケープ文字を追加します。
    その後の処理にて、この JSON はただの文字列として扱われるためそのまんまの " があると、エラーになってしまうためです。
項目 設定値
2つ目の作成の入力 replace(outputs('作成'),'"','\"')

置換した JSON を使ってクラウドフローBとしてフローを作成する

  • 最後に、加工した JSON を使ってクラウドフローを作成するため、HTTP with Azure AD コネクタの Invoke an HTTP request を使って、Power Automate Web API を実行します。
    加工した clientdata 以外の設定値は、取得できた値を使用します。(ただし、作成するフローは必ず OFF の状態で作成されます)
項目 設定値
Method POST
Url of the request /api/data/v9.1/workflows
Headers Accept : application/json, Content-Type : application/json
Body 以下に記載
    {
      "category": @{body('JSON_の解析')?['category']},
      "statecode": @{body('JSON_の解析')?['statecode']},
      "name": "{クラウドフローBの名前}",
      "clientdata": "@{outputs('作成_2')}",
      "type": @{body('JSON_の解析')?['type']},
      "primaryentity": "@{body('JSON_の解析')?['primaryentity']}"
    }

※設定する値の意味に関しては以下に情報があります。
https://learn.microsoft.com/en-us/power-automate/web-api#list-flows

これで準備は完了です。

実行と結果の確認

実行結果を確認します。

  • 以下の通り実行すると

  • フローが作成されていて

  • 先ほど置換した通り、カスタムリスト1 を設定していた部分が カスタムリスト2 になっています。

あとはフローをONしてあげれば、手動で作成したものと同じように動作します。

おまけ

フローの更新

  • クラウドフローAに更新が発生したとして、その更新をクラウドフローBに同期したい場合は、作成時と同様に clientdata の JSON をコピーし、必要な部分だけ書き換えて、以下のように更新することでフローを上書きすることができます。
    また、フローのON/OFFも可能です。
項目 設定値
Method PATCH
Url of the request /api/data/v9.1/workflows({workflowid})
Headers Accept : application/json, Content-Type : application/json
Body 以下に記載
    {
      "clientdata": "@{outputs('作成_2')}"
    }

これでフローが更新されます。

フローにアクションを追加して作成

  • clientdata の JSON の書き方や、スキーマのファイルを参考にしつつ更新してあげることで、フローにアクションを追加しつつ作成や更新をすることができます。
    アクションを追加するときは、少なくともアクションの名前が重複がないように変更し、runAfter の値にどのアクションの後に実行するかを入力するのことが必要です。
    以下はメール送信をコピーして最後に追加したものです。
clientdata の JSON
{
  "properties": {
    "connectionReferences": {
      "shared_office365": {
        "runtimeSource": "embedded",
        "connection": {
          "connectionReferenceLogicalName": "ex_sharedoffice365_c3897"
        },
        "api": { "name": "shared_office365" }
      },
      "shared_sharepointonline": {
        "runtimeSource": "embedded",
        "connection": {
          "connectionReferenceLogicalName": "ex_sharedsharepointonline_87b27"
        },
        "api": { "name": "shared_sharepointonline" }
      }
    },
    "definition": {
      "$schema": "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#",
      "contentVersion": "1.0.0.0",
      "parameters": {
        "$connections": { "defaultValue": {}, "type": "Object" },
        "$authentication": { "defaultValue": {}, "type": "SecureObject" }
      },
      "triggers": {
        "項目が作成されたとき": {
          "recurrence": { "interval": 1, "frequency": "Minute" },
          "splitOn": "@triggerOutputs()?['body/value']",
          "metadata": {
            "operationMetadataId": "761485fa-48aa-4b88-9c4f-2784f0151ab1"
          },
          "type": "OpenApiConnection",
          "inputs": {
            "host": {
              "connectionName": "shared_sharepointonline",
              "operationId": "GetOnNewItems",
              "apiId": "/providers/Microsoft.PowerApps/apis/shared_sharepointonline"
            },
            "parameters": {
              "dataset": "https://{自テナントのSharePointドメイン}/sites/SampleSite",
              "table": "{カスタムリスト1のID}"
            },
            "authentication": "@parameters('$authentication')"
          }
        }
      },
      "actions": {
        "メールの送信_(V2)": {
          "runAfter": {},
          "metadata": {
            "operationMetadataId": "aa322bf0-f94b-43eb-aaaa-c63ced85bd03"
          },
          "type": "OpenApiConnection",
          "inputs": {
            "host": {
              "connectionName": "shared_office365",
              "operationId": "SendEmailV2",
              "apiId": "/providers/Microsoft.PowerApps/apis/shared_office365"
            },
            "parameters": {
              "emailMessage/To": "HamanKarn@*****.com",
              "emailMessage/Subject": "クラウドフローからメールを送信します",
              "emailMessage/Body": "<p>クラウドフローからメールを送信します</p>",
              "emailMessage/Importance": "Normal"
            },
            "authentication": "@parameters('$authentication')"
          }
        },
        "項目の更新": {
          "runAfter": { "メールの送信_(V2)": ["Succeeded"] },
          "metadata": {
            "operationMetadataId": "9cb7cb89-7ae9-4dd6-8649-0db65e426718"
          },
          "type": "OpenApiConnection",
          "inputs": {
            "host": {
              "connectionName": "shared_sharepointonline",
              "operationId": "PatchItem",
              "apiId": "/providers/Microsoft.PowerApps/apis/shared_sharepointonline"
            },
            "parameters": {
              "dataset": "https://{自テナントのSharePointドメイン}/sites/SampleSite",
              "table": "{カスタムリスト1のID}",
              "id": "@triggerOutputs()?['body/ID']",
              "item/Title": "@triggerOutputs()?['body/Title']",
              "item/flow_completed": true
            },
            "authentication": "@parameters('$authentication')"
          }
        },
        "メールの送信_(V2)_2": {
          "runAfter": { "項目の更新": ["Succeeded"] },
          "metadata": {
            "operationMetadataId": "aa322bf0-f94b-43eb-aaaa-c63ced85bd03"
          },
          "type": "OpenApiConnection",
          "inputs": {
            "host": {
              "connectionName": "shared_office365",
              "operationId": "SendEmailV2",
              "apiId": "/providers/Microsoft.PowerApps/apis/shared_office365"
            },
            "parameters": {
              "emailMessage/To": "HamanKarn@*****.com",
              "emailMessage/Subject": "クラウドフローからメールを送信します",
              "emailMessage/Body": "<p>クラウドフローからメールを送信します</p>",
              "emailMessage/Importance": "Normal"
            },
            "authentication": "@parameters('$authentication')"
          }
        }
      }
    },
    "templateName": ""
  },
  "schemaVersion": "1.0.0.0"
}

作成されたフローは既定のソリューションに紐づく(ことしかできない)

  • フローを作成すると同時に特定のソリューションに紐づけたいところですが、そういった設定値は見つかりませんでした。
    だからといって、ソリューション対応フローでないわけではなく、既定のソリューションに参照される形で作成されるため、意識しなくてもソリューション対応フローで作成されます。

Power Automate Web API は Dataverse Web API と使い方が同じ

  • Power Automate Web API は Dataverse Web API でプロセス(論理名:workflow)というテーブルに対して操作することをきっかけに動作するものに見えますので、API の使い方に関しては他の Dataverse のテーブルを操作するところとなんら変わりません。
    また、テーブルを使っているため、中身は Dataverse の画面からも確認することができるようになっています。
    ちなみに、このプロセスというテーブルは、デスクトップフローの情報なども記録されています。

※デスクトップフローの確認方法の参考
https://qiita.com/nanoka/items/a27b119efb253c38d8e9

感想

上記のようにしていけば、いわゆる有償ライセンスを購入している前提はありますが、フローの内容で一部の設定値相当を書き換えてコピーする、ということが実現できると思います。
が、もちろん同じ内容のフローで、変数の値が異なるだけとか、フロー中の分岐で対応するとか、子フローにしてるとかで大丈夫な場合は、無理にやる必要はないです。

仮にやるとしても、JSON をルールを守って一から作成するのは大変なので、今回紹介したような特定文字列を狙って置換程度がよいのではないかと思います。

Discussion