3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Power Automateでユーザーにライセンス付与したり、サービスプランを制御したり

Last updated at Posted at 2025-12-07

こんな人向けの記事です

  • Microsoft365のアカウント・ライセンス付与を自動化したいシステム担当者の方
  • 特定のサービスプランを有効化・無効化させたい方
  • Graph APIのPower Automateでの利用方法を知りたい方

概要

Microsoft 365にて、ユーザーへのライセンス付与はMicrosoft365管理センターや、Entra IDから手動操作で行うことができます。

また、ライセンス付与はPowerShellを使って自動化することもできるのでこちらを使っていらっしゃる管理者の方も多いかもしれません。

今回の記事では、ライセンスやサービスプランの制御をPower Automateのクラウドフローから行ってみます。Power Automateを使う利点はTeamsやFormsとの連携が容易になることです。

例えば、Formsでライセンスの申請フォームを作り、ユーザーの上司が承認したら自動でライセンスを付与し、本人と上司にライセンス付与結果をメールやチャットで送信する、ということもローコードで簡単に行うことができます。まさに市民開発です。

Forms以外にもCopilot Studioで作成したエージェントと組み合わせても良いですし、ライセンス付与用のメールアドレス宛に、ユーザーから届いたメール内容をAI Builderで解析して必要なライセンスを提案して自動付与を……。やりすぎか?

ともかく、いろいろ組み合わせることができる処理の基本部分を作る方法を紹介します。

必要な権限

ライセンスの付与を行うには、Entra IDで「アプリ登録」を行ってサービスプリンシパルを作成する必要があります。今回のフローを作成しようという方は企業のシステム担当者の方だと思いますので、しかるべき社内の手続きに従って登録してください。

勉強目的でチャレンジしたい場合には、Microsoft 365 開発者プログラムでテスト用のテナントを作ることをおすすめします。

「HTTP with Microsoft Entra ID (事前承認)」でユーザーのライセンス状況を確認する

image.png

ライセンスの付与は当然ですが権限を持ったユーザーしか行えません。(誰でも付与できたらいけませんよね?)
Power Automateのアクションを実行する権限は接続参照として設定されますが、今回はライセンス付与権限を持ったユーザーでフローを作製し、 「HTTP with Microsoft Entra ID (事前承認)」アクションを使います。

手始めに、指定したユーザーが今なんのライセンスを持っているのかをこのアクションを使って確認してみましょう。

https://graph.microsoft.com/v1.0/users/@{outputs('UPN')}/licenseDetails

image.png

実行結果としてこのようなBodyが返ってきました。
image.png

JSON
{
  "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users('AdeleV%400phny.onmicrosoft.com')/licenseDetails",
  "value": [
    {
      "id": "tbIa-QWNN0KRqxk50X0FzKlUa2DYeJhCrYvfbvRIHIA",
      "skuId": "606b54a9-78d8-4298-ad8b-df6ef4481c80",
      "skuPartNumber": "CCIBOTS_PRIVPREV_VIRAL",
      "servicePlans": [
        {
          "servicePlanId": "5d798708-6473-48ad-9776-3acc301c40af",
          "servicePlanName": "FLOW_CCI_BOTS",
          "provisioningStatus": "Success",
          "appliesTo": "User"
        },
        {
          "servicePlanId": "ce312d15-8fdf-44c0-9974-a25a177125ee",
          "servicePlanName": "CCIBOTS_PRIVPREV_VIRAL",
          "provisioningStatus": "Success",
          "appliesTo": "User"
        },
        {
          "servicePlanId": "cf7034ed-348f-42eb-8bbd-dddeea43ee81",
          "servicePlanName": "DYN365_CDS_CCI_BOTS",
          "provisioningStatus": "Success",
          "appliesTo": "User"
        }
      ]
    }
  ]
}

とても長いので省略していますが、指定したユーザーが持っているライセンスと、その下の階層にライセンスのなかで有効となっているサービスプランが並んでいるのがわかります。

今回テストケースで取得したアデルさんのライセンスは"CCIBOTS_PRIVPREV_VIRAL"という名前のものだけで、そのskuIdは"606b54a9-78d8-4298-ad8b-df6ef4481c80"であることがわかります。

ライセンスに紐づく状態でサービスプランにも名前とIdがついていることがわかります。

ちなみに、skuIdはライセンスごとに決められているIDですが、Microsoft365 管理センター>課金情報>ライセンス>サブスクリプション から、確認したいライセンスをクリックして開いた際のURLに表示されているのと同じものです。ここから与えたいライセンスのskuIdを調べればよいですね。image.png

サービスプリンシパル作成と権限付与

事前承認の要求ではテナントのライセンス在庫確認と付与はできないようなので、Entra IDからアプリ登録の操作を行い、権限をつkたサービスプリンシパルを作成します。

ライセンス付与に必要な権限は以下の2つです。

  • User.ReadWrite.All
  • Directory.ReadWrite.All

image.png

image.png

image.png

Directory.Read.All
→ テナント全体のディレクトリ情報を読み取る権限(ライセンス一覧やユーザー情報の取得のみ)。
User.ReadWrite.All
→ ユーザーオブジェクトのプロパティを更新する権限(ライセンス付与、パスワードリセット、属性変更など)。
Directory.ReadWrite.All
→ テナント全体のディレクトリ情報を更新する権限(より広範囲な操作が可能)。

image.png

同様にUser.ReadWrite.Allにもアプリケーションの権限を与えた後、
「管理者の同意を与えます」をクリックしておきます。
image.png

シークレットの作成

「証明書とシークレット」の項目を開き、「+新しいクライアントシークレット」をクリックして適当な名前と期限を設定してシークレットの「値」をコピーしてメモにの押しておきます。必要なのはシークレットIDではない点に注意が必要です。必要なのは「値」です。
image.png

「概要」から「ディレクトリ(テナント)ID」と「アプリケーション(クライアント)ID」の値も同じくコピーしてメモに残します。この3点セットでサービスプリンシパルを使うことができます。
image.png

image.png

HTTP要求

Graph APIをつかってライセンスを付与するには、assignLicenseを使います。

こちらに書かれているエンドポイントに対して付与するユーザーをURIに。付与するライセンスをBodyに以下のように書いて与えます。

URI
https://graph.microsoft.com/v1.0/users/@{outputs('UPN')}/assignLicense
本文
{
  "addLicenses": [https://graph.microsoft.com/v1.0/users/<UPN>/licenseDetails

    {
      "skuId": "@{outputs('skuId')}",
      "disabledPlans": []
    }
  ],
  "removeLicenses": []
}

image.png

上記の可変部分は「作成」を使って変数のように与えています。全体感はこんな感じです。シークレットなどの情報には鍵をかけてます。
image.png

こうしておくと、実行結果から隠されるので、クラウドフローの編集権を持っている人にしか見えないので少し安心です。
image.png

あらかじめ、アデルさんから全てのライセンスを外しておいた状態で、フローを実行してみます。

image.png

指定したskuIdのライセンスが付与されていることが確認できます。また、ライセンスに含まれる3つのサービスプランは全て有効担っています。
image.png

ちなみに、今回与えたMicrosoft Copilot Studio バイラル試験版は、Copilot Studioの試用版を誰かが試すと付与されるものです。

一部のサービスプランのみ有効にするには?

さてここで、ユーザーに対してライセンスを有効化すると、すべてのサービスプランが有効の状態で付与されることがわかりました。
実は、さきほど POST users//assignLicense を送信した際の本文に「"disabledPlans": []」という項目がありました。こちらに不要なサービスプランのIDを配列形式で与えてやることができます。こんな感じ。

本文
{
  "addLicenses": [https://graph.microsoft.com/v1.0/users/<UPN>/licenseDetails

    {
      "skuId": "@{outputs('skuId')}",
      "disabledPlans": [
          '5d798708-6473-48ad-9776-3acc301c40af',
          'cf7034ed-348f-42eb-8bbd-dddeea43ee81'
      ]
    }
  ],
  "removeLicenses": []
}

そこで、配列を作って、それをHTTP要求の本文のなかのdisabledPlansのコロンの後ろに配置してやりました。
image.png

サービスプランの、IDを指定していなかったサービスプランだけが付与された状態になりました。
image.png

取り除くプランではなく付与したいプランを指定したい場合は?

実際、サービスプランは増えたり減ったりします。そのなかで、指定したサービスプランだけをピンポイントに指定したい場合にはどうすればよいでしょう?

集合の図を書くとこんな感じに表現できそうです。

ライセンスが持つ全てのサービスプラン - 有効にしたいサービスプラン = 取り除くサービスプラン

という関係になります。

付与したいサービスプランの配列を指定する。

今回は下のサービスプランだけを有効化したいとしましょう。

有効化したいサービスプラン
        {
          "servicePlanId": "cf7034ed-348f-42eb-8bbd-dddeea43ee81",
          "servicePlanName": "DYN365_CDS_CCI_BOTS",
          "provisioningStatus": "Success",
          "appliesTo": "User"
        }

「作成」でこのように有効化した配列を作っておきます。
image.png

全てのサービスプランを配列にする

各ライセンスが持つ現在のサービスプランの一覧を取得するにはこちらのエンドポイントを使います。

GET https://graph.microsoft.com/v1.0/subscribedSkus

認証設定を追加済みのHTTP要求アクションに以下のように設定します。
image.png

結果は長大なのでリファレンスページの結果サンプルを参考にしましょう。
image.png

valueというキーの配列1つずつが1つのライセンスです。skuIdが見えます。また、servicePlansの値は配列になっているので、この配列部分を取得すればライセンスごとの全手のサービスプランが取得できそうです。

テナントのライセンス一覧には、その他多くのライセンスが配列状態でひしめき合っているので、この中から今回の対象となるskuIdだけに絞る必要があります。「アレイのフィルター」アクションを使います。

image.png

差出人
body('テナントのライセンス一覧')?['value']
左辺
item()?['skuId']
右辺
outputs('skuId')

これで、両辺が一致した、つまり指定のskuIdの情報だけが配列として取得できます。

結果
[
  {
    "accountName": "0phny",
    "accountId": "f91ab2b5-8d05-4237-91ab-1939d17d05cc",
    "appliesTo": "User",
    "capabilityStatus": "Enabled",
    "consumedUnits": 2,
    "id": "f91ab2b5-8d05-4237-91ab-1939d17d05cc_606b54a9-78d8-4298-ad8b-df6ef4481c80",
    "skuId": "606b54a9-78d8-4298-ad8b-df6ef4481c80",
    "skuPartNumber": "CCIBOTS_PRIVPREV_VIRAL",
    "subscriptionIds": [
      "0481b988-1410-4854-bd98-f26807a49975"
    ],
    "prepaidUnits": {
      "enabled": 10000,
      "suspended": 0,
      "warning": 0,
      "lockedOut": 0
    },
    "servicePlans": [
      {
        "servicePlanId": "5d798708-6473-48ad-9776-3acc301c40af",
        "servicePlanName": "FLOW_CCI_BOTS",
        "servicePlanType": "ProcessSimple",
        "provisioningStatus": "Success",
        "appliesTo": "User"
      },
      {
        "servicePlanId": "ce312d15-8fdf-44c0-9974-a25a177125ee",
        "servicePlanName": "CCIBOTS_PRIVPREV_VIRAL",
        "servicePlanType": "ccibotsprod",
        "provisioningStatus": "Success",
        "appliesTo": "User"
      },
      {
        "servicePlanId": "cf7034ed-348f-42eb-8bbd-dddeea43ee81",
        "servicePlanName": "DYN365_CDS_CCI_BOTS",
        "servicePlanType": "CRM",
        "provisioningStatus": "Success",
        "appliesTo": "User"
      }
    ]
  }
]

こんどは、この中から"servicePlans"の中身だけ、しかも"servicePlanId"の値だけを取り出してIDのみの配列にします。これには「選択」アクションを使います。

選択に渡す前に先程のアレイのフィルターは名前をわかりやすいものに変えておきました。

「選択」の「開始」に与える際のポイントは、skuIdのフィルタで得られた結果は要素1つの配列であることです。
取得したいのは、その中の"servicePlanId"であるので、「開始」には1つ目の配列のなかの"servicePlans"を指定します。
image.png

開始
first(body('skuIdでフィルタ'))?['servicePlans']
マップ
item()?['servicePlanId']

結果はこのとおり。サービスプランのIDだけが配列になりました。「選択」は実行前にわかりやすい名前に変えておきました。
image.png

配列の引き算を実行するにはアレイのフィルター処理をつかう

サービスプランごとの全IDから指定したいIDを引くには配列の引き算をします。これには「アレイのフィルター処理」アクションを使います。
引かれる側を「差出人」にして、その1要素ごとに引く側の配列に存在するかどうかを確認していくイメージです。配列に対する存在のチェックにはcontains関数を使います。

image.png

差出人
body('テナントの全サービスプラン配列'
左辺
contains(outputs('有効化したいサービスプランID配列'),item())

「次の値に等しい」を選択して

右辺
false

HTTP要求によるライセンスの追加の本文に取り除く対象として指定するだけ。初期には手で与えていた部分を差し替えましょう。

実行してみると、ちゃんと配列の引き算ができました。ここで取れたのは取り除くサービスプランIDの配列です。
image.png

ライセンスの追加には上記のID配列が取り除かれる対象ですので、結果的に有効化したいサービスプランIDの対象だけが有効化されました。(というよりは、消されたあとに有効として残った状態になりました。)
image.png

現在ユーザーが持つライセンスに指定サービスプランだけを加えるには?

ここまでで、付与したいサービスプランを指定できるようになったので、おおよそ問題ないのですが。
テナントによっては、ユーザーによってすでに付与されているサービスプランがまちまちだったりします。

その上で、指定したサービスプランを付与したいというニーズもあるでしょう。これを実現するにはどうすれば良いでしょうか?

こんがらがってきたので、図にして考えてみました。

image.png

ここまで見てきたとおり、ライセンスを追加する際には取り除くサービスプランID配列を指定します。
ユーザーがすでに持っているサービスプランを維持したいならば、その逆(対偶)のID配列を用意すれば良いことはここまでで確認できました。

持っているか持っていないか判定するのも良いですが、上記のような対偶を求める前に、ユーザーが持っているサービスプランID配列に与えたいIDを加えてしまってはどうでしょうか?

現在ユーザーが持つサービスプラン配列を取得

ユーザーがすでに持っている指定したライセンスのなかの"servicePlanId"を取りに行きます。これも手順は先程のテナント全体の場合と全く同じです。skuIdで絞ります。そのなかで、provisioningStatusがSuccessのものだけをに絞ります。選択を使ってservicePlanIdだけの配列にします。
最後にunion関数を使って、与えたいサービスプランとすでにユーザーが持っているサービスプランのID配列を重複なく統合します。

image.png

■skuIdでユーザのライセンスをフィルタ「アレイのフィルター処理」

差出人
body('ユーザーのライセンスを取得')?['value']
左辺
item()?['skuId'
右辺
outputs('skuId')

■ユーザの有効なライセンスをフィルタ「アレイのフィルター処理」

差出人
first(body('skuIdでユーザのライセンスをフィルタ'))?['servicePlans']
左辺
item()?['provisioningStatus']
右辺
Success

■ユーザのサービスプランID配列「アレイのフィルター処理」

開始
body('ユーザの有効なライセンスをフィルタ')
マップ
item()?['servicePlanId']

■ユーザのサービスプランIDに有効化するIDを加える「作成」

入力
union(body('ユーザのサービスプランID配列'),outputs('有効化したいサービスプランID配列'))

配列に要素を加えるには、union関数が使えます。以前にブログに書いたことがあるので参考にしてください。

テストする

現在、アデルさんは以下のような状態なので、これに加えて、 「CCI BotsのFlow」というサービスプランを有効にしたいとしましょう。

image.png

「CCI BotsのFlow」のIDを有効化したいサービスプランID配列に加えておきます。

image.png

最終的にHTTP要求で与えるのは、「取り除くサービスプラン配列」ですから、「ユーザのサービスプランID配列に有効化するIDを加える」の対偶を取得するために差出人の部分を入れ替えるのを忘れずに。

image.png

実行してみると、すでに有効化だったプランに、有効化させたいプランが加わりました!
image.png

まとめ

ずいぶん長くなりましたが、ライセンスとサービスプランの付与制御について丁寧に検証してみました。
まとめるとこういう感じになります。

  • ライセンスを付与するには、サービスプリンシパルが必要
  • ライセンスを有効にするとサービスプランは全て有効に。無効化したいプランIDを指定する
  • 有効化したいIDを指定するには、テナント全体から対偶を取る
  • ユーザーがすでに持っているサービスプランに加えたければ、union関数で加えてから対偶を取れば良い

検証できていない部分

今回はサービスプリンシパルの権限を「アプリ」種別で取得して使いました。
この場合、クラウドフローのなかのアプリIDとシークレットが流出すると、誰でもライセンスの有効化・無効化ができてしまいます。
セキュリティ的によろしくないので、本来は「委任」種別で権限を付与して、ロールを持つユーザーが実行した場合にだけ動作するようにしたいところです。

ここのところをどうやって実現したら良いのか、試行錯誤したのですがうまく行かず。ぜひアドバイスがほしいです。

こんな人が書きました。

職場でPower Platformの管理者さんをしています。ブログではPower AutomateのTipsのようなものを書いています。QiitaではPower BIとクラウドフローのちょこ技を記事にしています。Twitterをフォローしていただいたり、「いいね」などしていただけるととっても喜びます。

大阪開催Power Platform系のイベントやオンラインの集まりには時々参加していますので、こちらのアイコンを見かけたら声をかけていただけると嬉しいです。

3
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?