mcpField
A
assign-offline-profile

assign-offline-profile

github.com/microsoft数据与数据库1.1k127 次安装今天更新
代码仓库

关于 assign-offline-profile

assign-offline-profile 用于将现有 Mobile Offline Profile 分配给用户和/或团队,适合配置文件已创建但尚未绑定成员的情况。它先发现现有成员关系,再确认变更并执行添加或移除,最后验证结果。

主要功能

  1. 01按 ID 或名称定位离线配置文件
  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 — 请先阅读。

参考资料:

分配脱机配置文件

将一个或多个用户和/或团队绑定到现有的 Mobile Offline Profile。若不执行此步骤,该配置文件虽然存在于 Dataverse 中,但仍未绑定——实际上没有任何人的应用会将其用于脱机同步。

按照 maker portal 的 UX(环境设置下的“Assign profile to user”对话框),这是与创建配置文件分开的操作。很多用户会遇到“我创建了配置文件,但脱机功能仍无法使用”的问题——缺少的正是成员关系。

读取项目或执行命令前,请应用 app-working-directory.md。 为直接调用解析出一个绝对路径的 working_dir,或要求子调用指定其所属根目录。 确保所有 shell/文件操作、引用的操作指南 和重试都使用该根目录以及选定的环境/租户;缺少或冲突的上下文均为 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>,并要求它们与任何提供的所属身份信息匹配。每次 Dataverse 请求都传入 --tenant-id "<tenantId>",包括配置文件查找、WhoAmI、成员关系发现、变更、回读和重试。

配置文件 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 并使用列表呈现 AskUserQuestion(最多 4 个选项)

若无法解析配置文件,立即停止。打印: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添加当前环境中所有具有 System User 角色的用户(范围较广;适用于生产环境发布——在确认关卡确认)
--unassign-user <upn> / --unassign-team <name>移除现有成员关系,而不是添加

如果未传入任何标志,请呈现 AskUserQuestion:

问题:“谁应该使用此脱机配置文件?”

选项(最多 4 个):

  • Just me (the current user) — 等同于 --me
  • Pick specific users by UPN — 请在下一条消息中用逗号分隔填写电子邮件地址
  • Pick a team — 列出环境中的团队并选择一个
  • All users with System User role — 等同于 --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>"

构建 already-bound 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 会获得配置文件分配。)

构建三个列表:

  • 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

预期返回 204 和 OData-EntityId → 捕获成员关系 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-* 撤销特定绑定

评论

支持 Markdown