assign-offline-profile について
assign-offline-profile は、作成済みの Mobile Offline Profile をユーザーやチームに割り当てるスキルです。既存のメンバーシップを調べて変更内容を確認し、追加または削除を実行した後、結果を検証します。
主な機能
- 01ID または名前でプロファイルを特定
- 02ユーザーやチームをメンバーとして選択
- 03既存のメンバーシップを調査し変更差分を算出
- 04メンバーシップを追加・削除して結果を検証
活用シーン
- 01現在のユーザーにオフラインプロファイルを割り当て
- 02指定ユーザーやチームにオフラインプロファイルを割り当て
- 03プロファイルから既存ユーザーやチームを削除
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ microsoft/ power-platform-skills --skill assign-offline-profileインストール後、Claude Code で/assign-offline-profileを入力して読み込まれたか確認します。
詳細
共有手順:shared-instructions.md — まずお読みください。
参照資料:
- offline-profile-schema.md —
usermobileofflineprofilemembership/teammobileofflineprofilemembershipエンティティのフィールドマップ - dataverse-offline-api.md — Web API の手順(§12 — メンバーシップの POST)
オフラインプロファイルを割り当てる
1 人以上のユーザーやチームを既存のモバイル オフライン プロファイルにバインドします。この手順を行わないと、プロファイルは Dataverse に存在していてもバインドされず、誰のアプリもオフライン同期に使用できません。
メーカー ポータルの UX(環境設定の「ユーザーにプロファイルを割り当てる」ダイアログ)では、これはプロファイル作成とは別の操作です。「プロファイルを作成したのにオフラインで動作しない」というユーザーは多くいます。足りないのはメンバーシップの設定です。
プロジェクトの読み取りやコマンドの実行前に、
app-working-directory.mdを適用してください。
直接呼び出す場合は絶対パスのworking_dirを1つ解決するか、子呼び出しのオーナールートを必須にしてください。
すべてのシェル/ファイル操作、参照する手順、および再試行は、そのルートと選択した環境/テナントで行ってください。コンテキストが不足または競合している場合はNEEDS_CONTEXTです。ファイルツールでは<working_dir>/...を使用してください。
計画フェーズの呼び出しでは、メンバーシップの差分を検出して提示できますが、確認ゲート、POST/DELETE、またはアーティファクトの更新に進む前に戻る必要があります。
ワークフロー
- プロジェクトを確認してプロファイルを特定 → 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>(繰り返し可) | 名前で特定のチームを追加 |
--me | WhoAmI / 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 する解決済み IDto_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?選択肢:
ProceedCancel
ステップ 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-*を使い、特定のバインドを取り消します