Skip to content
Development
Skill

/assign-offline-profile

Use when the user needs to bind users or teams to a Mobile Offline Profile so they actually receive offline sync on their devices. Without this, the profile exists in Dataverse but no one's app uses it.

From plugin
power-platform-skills
1.1k110 skills25 agents5 MCP

Claude Code

$ npx -y skills add microsoft/power-platform-skills --skill assign-offline-profile --agent claude-code

SKILL.md

assign-offline-profile.SKILL.md
name: assign-offline-profile
description: Use when the user needs to bind users or teams to a Mobile Offline Profile so they actually receive offline sync on their devices. Without this, the profile exists in Dataverse but no one's app uses it.
user-invocable: false
allowed-tools: Read, Edit, Write, Grep, Glob, Bash, AskUserQuestion
model: sonnet

**Shared instructions: [shared-instructions.md](${PLUGIN_ROOT}/shared/shared-instructions.md)** — read first.

**References:**

  • [offline-profile-schema.md](${PLUGIN_ROOT}/shared/references/offline-profile-schema.md) — `usermobileofflineprofilemembership` / `teammobileofflineprofilemembership` entity field map
  • [dataverse-offline-api.md](${PLUGIN_ROOT}/shared/references/dataverse-offline-api.md) — Web API recipe (§12 — membership POSTs)

Assign Offline Profile

Bind one or more users and/or teams to an existing Mobile Offline Profile. Without this step, the profile exists in Dataverse but is unbound — no one's app actually uses it for offline sync.

Per the maker portal's UX (the "Assign profile to user" dialog under env settings), this is a separate operation from profile creation. Many users hit "I created the profile but offline still doesn't work" — the missing piece is membership.

Before project reads or commands, apply [app-working-directory.md](${PLUGIN_ROOT}/shared/references/app-working-directory.md). Resolve one absolute `working_dir` for a direct call or require the child invocation's owner root. Keep every shell/file operation, referenced recipe, and retry on that root and the selected environment/tenant; missing or conflicting context is `NEEDS_CONTEXT`. File tools use `<working_dir>/...`. Planning-phase calls may discover and propose the membership diff but must return before the confirmation gate, POST/DELETE, or artifact updates.

Workflow

1. Verify project + locate profile → 2. Pick users/teams → 3. Discover existing memberships → 4. Confirm diff (single gate) → 5. POST memberships → 6. Verify → 7. Summary

---

Step 1 — Verify project + locate profile

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

Capture `<envUrl>` and `<tenantId>` and require them to match any supplied owner identity. Pass `--tenant-id "<tenantId>"` on every Dataverse request, including profile lookup, `WhoAmI`, membership discovery, mutation, readback, and retries.

Profile ID resolution (in order):

| Source | Used when | |---|---| | `$ARGUMENTS` contains `--profile-id <guid>` | Explicit override | | `$ARGUMENTS` contains `--profile-name <name>` | Resolve via `GET /mobileofflineprofiles?$filter=name eq '<name>'&$select=mobileofflineprofileid` | | `<working_dir>/offline-profile.json` | Read top-level `profileId` field | | Otherwise | `GET /mobileofflineprofiles` and present `AskUserQuestion` with the list (max 4 options) |

STOP if no profile can be resolved. Print: `Run /setup-offline-profile first, or pass --profile-id`.

> **`power.config.json` is intentionally NOT consulted here.** That file is owned by `pa app init`. The profile ID lives in `offline-profile.json` only.

Step 2 — Pick users/teams

`$ARGUMENTS` parsing:

| Flag | Effect | |---|---| | `--user <upn>` (repeatable) | Add specific user(s) by UPN (`user@domain.com`) | | `--team <name>` (repeatable) | Add specific team(s) by name | | `--me` | Add the current Dataverse user from `WhoAmI` / `systemusers(<UserId>)` — useful for solo dev demos | | `--all-app-users` | Add every user with **System User** role in the current env (broad; intended for prod rollout — confirm at gate) | | `--unassign-user <upn>` / `--unassign-team <name>` | Remove an existing membership rather than add |

If no flags passed, present `AskUserQuestion`:

> **Question**: "Who should receive this offline profile?" > > **Options** (max 4): > - `Just me (the current user)` — equivalent to `--me` > - `Pick specific users by UPN` — you reply with comma-separated emails in the next message > - `Pick a team` — list env's teams and pick one > - `All users with System User role` — equivalent to `--all-app-users`; broad scope, confirm at gate

For pick-users flow: after the choice, print:

> "Reply with comma-separated UPNs (e.g. `rm1@contoso.com, rm2@contoso.com`)"

Then read the next user message and parse.

Step 3 — Discover existing memberships

**Telemetry checkpoint: `discover_offline_profile_memberships`**

For idempotency:

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>"

Build the set of `already-bound` UPNs and team names.

For each candidate user/team from Step 2, look up their `systemuserid` / `teamid` (skip if already in `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&
Read more
@microsoft
Ships withpower-platform-skills

Plugins from Microsoft that teach AI assistants, such as GitHub Copilot and Claude Code, to build on Power Platform.

1.1k110 skills25 agents5 MCP
Get the whole plugin

Stars on GitHub

Momentum

stars in total
1.1k
Star history of microsoft/power-platform-skills on GitHub, a chart by star-history.com

Other skills on power-platform-skills.

More skills from other plugins.

brainstorming
Auto-invoked

brainstorming

You MUST use this before any creative work - creating features, building components, adding functionality, modifying behavior, or…

@obra@obraInstall Skill
ask-matt

ask-matt

Ask which skill or flow fits your situation. A router over the skills in this repo.

skills10d309k
@mattpocock@mattpocockInstall Skill
accessibility
Auto-invoked

accessibility

Design, implement, and audit accessible UI to WCAG 2.2 Level AA across Web, iOS, and Android — semantic ARIA roles and labels,…

ecc12d293k
@affaan-m@affaan-mInstall Skill
compiler-commit

compiler-commit

Use when you want to verify compiler changes and commit with the correct convention. Runs tests, lint, and format, then commits…

react1d252k
@facebook@facebookInstall Skill

Questions about assign-offline-profile.

Which coding agents can use assign-offline-profile?
assign-offline-profile installs in Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot, OpenCode and Windsurf with the skills CLI, using each agent's flag shown on this page. Flowy's auto-invocation, which fires a skill when your prompt matches, runs in Claude Code only.