mcpField
A
assign-offline-profile

assign-offline-profile

github.com/microsoftデータ・データベース1.1k127 回インストール今日更新
リポジトリ

assign-offline-profile について

assign-offline-profile は、作成済みの Mobile Offline Profile をユーザーやチームに割り当てるスキルです。既存のメンバーシップを調べて変更内容を確認し、追加または削除を実行した後、結果を検証します。

主な機能

  1. 01ID または名前でプロファイルを特定
  2. 02ユーザーやチームをメンバーとして選択
  3. 03既存のメンバーシップを調査し変更差分を算出
  4. 04メンバーシップを追加・削除して結果を検証

活用シーン

  1. 01現在のユーザーにオフラインプロファイルを割り当て
  2. 02指定ユーザーやチームにオフラインプロファイルを割り当て
  3. 03プロファイルから既存ユーザーやチームを削除

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/microsoft/power-platform-skills --skill assign-offline-profile

インストール後、Claude Code で/assign-offline-profileを入力して読み込まれたか確認します。

詳細

共有手順:shared-instructions.md — まずお読みください。

参照資料:

オフラインプロファイルを割り当てる

1 人以上のユーザーやチームを既存のモバイル オフライン プロファイルにバインドします。この手順を行わないと、プロファイルは Dataverse に存在していてもバインドされず、誰のアプリもオフライン同期に使用できません。

メーカー ポータルの UX(環境設定の「ユーザーにプロファイルを割り当てる」ダイアログ)では、これはプロファイル作成とは別の操作です。「プロファイルを作成したのにオフラインで動作しない」というユーザーは多くいます。足りないのはメンバーシップの設定です。

プロジェクトの読み取りやコマンドの実行前に、 app-working-directory.mdを適用してください。 直接呼び出す場合は絶対パスのworking_dirを1つ解決するか、子呼び出しのオーナールートを必須にしてください。 すべてのシェル/ファイル操作、参照する手順、および再試行は、そのルートと選択した環境/テナントで行ってください。コンテキストが不足または競合している場合はNEEDS_CONTEXTです。ファイルツールでは<working_dir>/...を使用してください。 計画フェーズの呼び出しでは、メンバーシップの差分を検出して提示できますが、確認ゲート、POST/DELETE、またはアーティファクトの更新に進む前に戻る必要があります。

ワークフロー

  1. プロジェクトを確認してプロファイルを特定 → 2. ユーザー/チームを選択 → 3. 既存のメンバーシップを検出 → 4. 差分を確認(単一ゲート)→ 5. メンバーシップを POST → 6. 検証 → 7. 概要

ステップ 1 — プロジェクトを確認してプロファイルを特定
cd -- '<working_dir>' || { echo "BLOCKED: cannot enter working_dir" >&2; exit 1; }
environment_id=$(node -p "require('./power.config.json').environmentId || ''") || {
  echo "BLOCKED: unreadable power.config.json" >&2; exit 1;
}
if [ -z "$environment_id" ]; then
  echo "NEEDS_CONTEXT: selected app has no environmentId" >&2; exit 1;
fi
node "${PLUGIN_ROOT}/scripts/resolve-environment.js" "$environment_id" --no-cache --require-tenant

<envUrl>と<tenantId>を取得し、指定されたオーナー ID と一致することを必須にしてください。プロファイルの検索、WhoAmI、メンバーシップの検出、変更、読み戻し、再試行を含むすべての Dataverse リクエストで、--tenant-id "<tenantId>"を指定してください。

プロファイル ID の解決順:

ソース使用条件
$ARGUMENTSに--profile-id <guid>が含まれている明示的な上書き
$ARGUMENTSに--profile-name <name>が含まれているGET /mobileofflineprofiles?$filter=name eq '<name>'&$select=mobileofflineprofileidで解決
<working_dir>/offline-profile.json最上位のprofileIdフィールドを読み取る
その他GET /mobileofflineprofilesを実行し、一覧(最大 4 件)を指定してAskUserQuestionを提示

プロファイルを解決できない場合は停止してください。次を表示します:Run /setup-offline-profile first, or pass --profile-id

ここではpower.config.jsonを意図的に参照しません。 このファイルはpa app initが管理します。プロファイル ID はoffline-profile.jsonにのみ格納されています。

ステップ 2 — ユーザー/チームを選択

$ARGUMENTSの解析:

フラグ効果
--user <upn>(繰り返し可)UPN([email protected])で特定のユーザーを追加
--team <name>(繰り返し可)名前で特定のチームを追加
--meWhoAmI / systemusers(<UserId>)から現在の Dataverse ユーザーを追加 — 個人開発のデモに便利
--all-app-users現在の環境でシステムユーザーロールを持つすべてのユーザーを追加(範囲が広いため、本番展開を想定 — ゲートで確認)
--unassign-user <upn> / --unassign-team <name>追加ではなく、既存のメンバーシップを削除

フラグが渡されなかった場合は、AskUserQuestionを提示します:

質問:「このオフラインプロファイルを誰に割り当てますか?」

選択肢(最大 4 件):

  • 自分のみ(現在のユーザー) — --meと同等
  • UPN で特定のユーザーを選択 — 次のメッセージでカンマ区切りのメールアドレスを返信
  • チームを選択 — 環境のチーム一覧を表示して 1 つ選択
  • システムユーザーロールを持つすべてのユーザー — --all-app-usersと同等。範囲が広いため、ゲートで確認

ユーザー選択フローでは、選択後に次を表示します:

「カンマ区切りの UPN を返信してください(例:[email protected], [email protected])」

次にユーザーが送信するメッセージを読み取り、解析します。

ステップ 3 — 既存のメンバーシップを確認する

テレメトリチェックポイント:discover_offline_profile_memberships

冪等性を確保するため:

cd -- '<working_dir>' || { echo "BLOCKED: cannot enter working_dir" >&2; exit 1; }
# Existing user memberships for this profile
node "${PLUGIN_ROOT}/scripts/dataverse-request.js" <envUrl> GET \
  "usermobileofflineprofilememberships?\$filter=_mobileofflineprofileid_value eq <profileId>&\$select=usermobileofflineprofilemembershipid,_systemuserid_value&\$expand=systemuserid_systemuser(\$select=domainname)" \
  --tenant-id "<tenantId>"

# Existing team memberships for this profile
node "${PLUGIN_ROOT}/scripts/dataverse-request.js" <envUrl> GET \
  "teammobileofflineprofilememberships?\$filter=_mobileofflineprofileid_value eq <profileId>&\$select=teammobileofflineprofilemembershipid,_teamid_value&\$expand=teamid_team(\$select=name)" \
  --tenant-id "<tenantId>"

すでにバインドされている UPN とチーム名のセットを作成します。

ステップ 2 の各候補ユーザー/チームについて、systemuserid/teamid を検索します(already-bound に含まれている場合はスキップします)。

cd -- '<working_dir>' || { echo "BLOCKED: cannot enter working_dir" >&2; exit 1; }
node "${PLUGIN_ROOT}/scripts/dataverse-request.js" <envUrl> GET \
  "systemusers?\$filter=domainname eq '<upn>'&\$select=systemuserid,fullname,domainname&\$top=1" \
  --tenant-id "<tenantId>"

node "${PLUGIN_ROOT}/scripts/dataverse-request.js" <envUrl> GET \
  "teams?\$filter=name eq '<team-name>' and teamtype eq 0&\$select=teamid,name&\$top=1" --tenant-id "<tenantId>"

(teamtype eq 0 は Access Teams と Owner Teams を除外します。プロファイルを割り当てるのは Manage Teams のみです。)

次の 3 つのリストを作成します。

  • to_add — POST する解決済み ID
  • to_remove — DELETE する解決済み ID(--unassign-* フラグで指定)
  • not_found — 解決できなかった UPN/チーム名(警告)
  • already_bound — スキップした変更なしの項目
ステップ 4 — 差分を確認する(単一ゲート)

テレメトリチェックポイント:confirm_offline_profile_assignment_diff

AskUserQuestion:

質問のヘッダー:Confirm membership changes

質問の本文:

Profile: <name> (<profileId>)

Will ADD:
  - User: [email protected] (Rahul Bansal)
  - User: charanma@... (Charan Mahankali)
  - Team: Field Service RMs (12 members)

Will REMOVE:
  (none)

Already bound (skipping):
  - User: admin@... (no-op)

Could not resolve:
  - [email protected] — not in this env's system users

Proceed?

選択肢:

  • Proceed
  • Cancel
ステップ 5 — メンバーシップを POST する

テレメトリチェックポイント:assign_offline_profile_memberships

to_add の各項目を順番に POST します(並列 POST は 429 を返す場合があります)。

ユーザーメンバーシップ:

cd -- '<working_dir>' || { echo "BLOCKED: cannot enter working_dir" >&2; exit 1; }
node "${PLUGIN_ROOT}/scripts/dataverse-request.js" <envUrl> POST \
  "usermobileofflineprofilememberships" \
  --tenant-id "<tenantId>" \
  --body '{
    "[email protected]": "/mobileofflineprofiles(<profileId>)",
    "[email protected]": "/systemusers(<systemuserid>)"
  }' \
  --include-headers

OData-EntityId を含む 204 が返される想定です。メンバーシップ GUID を取得します。

チームメンバーシップ:

cd -- '<working_dir>' || { echo "BLOCKED: cannot enter working_dir" >&2; exit 1; }
node "${PLUGIN_ROOT}/scripts/dataverse-request.js" <envUrl> POST \
  "teammobileofflineprofilememberships" \
  --tenant-id "<tenantId>" \
  --body '{
    "[email protected]": "/mobileofflineprofiles(<profileId>)",
    "[email protected]": "/teams(<teamid>)"
  }' \
  --include-headers

to_remove の各項目に対して DELETE します。

cd -- '<working_dir>' || { echo "BLOCKED: cannot enter working_dir" >&2; exit 1; }
node "${PLUGIN_ROOT}/scripts/dataverse-request.js" <envUrl> DELETE \
  "usermobileofflineprofilememberships(<membershipid>)" --tenant-id "<tenantId>"

⚠️ 重複の処理: すでに存在するメンバーシップを POST すると、409 Conflict が返されます。dataverse-request.js ラッパーの looksLikeDuplicate による救済処理は、これを成功として扱い、通知しません(ステップ 3 の重複排除でほとんどのケースは事前に検出されます)。再実行しても安全です。

ステップ 6 — 検証する

テレメトリチェックポイント:verify_offline_profile_memberships

ステップ 3 のメンバーシップを再取得し、差分が適用されたことを確認します。

  • to_add の各項目が GET レスポンスに含まれている
  • to_remove の各項目が含まれていない

検証結果が一致しない場合は、BLOCKED: membership writes did not commit を返し、不一致の内容を出力します。

ステップ 7 — 要約

次を出力します。

✓ Membership updates applied.

  Profile      : <name>
  Total members: <N users + M teams>
  Added        : <list>
  Removed      : <list>
  Skipped      : <list> (already bound)

Users will receive the profile on their next mobile app sign-in. Existing
sessions need to sign out + sign in to trigger the profile pull.

memory-bank.md の ## Offline profile ブロックを更新します。

membership:
  users: [rahul@..., charanma@...]
  teams: [Field Service RMs]
  lastAssignedAt: 2026-05-19T...

ステータスコード(最終行)

  • DONE — 要求された追加/削除がすべて適用され、検証で確認できた
  • DONE_WITH_CONCERNS: <list> — 解決できない UPN/チームがある、または --all-app-users に一致するユーザーが 0 人だった(環境にまだロールが付与されていない可能性があります)
  • NEEDS_CONTEXT: <missing> — profileId を特定できなかった(offline-profile.json がない、--profile フラグがない、または環境にプロファイルがない)
  • BLOCKED: <reason> — 認証失敗、環境内にプロファイルが見つからない、または検証結果の不一致

失敗からの復旧

メンバーシップは個別にコミットされます(トランザクションはありません)。ステップ 5 のループ中に失敗した場合:

  • 追加済みのメンバーシップは残り、環境内で確認できます
  • 同じ引数で再実行しても冪等です(ステップ 3 の重複排除で、すでにバインドされている項目を検出します)
  • 必要に応じて --unassign-* を使い、特定のバインドを取り消します

コメント