add-data-source
Guide the user to add a data source, connection, or API connector to a Canvas App via Power Apps Studio, then verify and…
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.
Claude Code
$ npx -y skills add microsoft/power-platform-skills --skill assign-offline-profile --agent claude-code
Codex
$ npx skills add microsoft/power-platform-skills --skill assign-offline-profile -a codex -y
Cursor
$ npx skills add microsoft/power-platform-skills --skill assign-offline-profile -a cursor -y
Gemini CLI
$ npx skills add microsoft/power-platform-skills --skill assign-offline-profile -a gemini-cli -y
GitHub Copilot
$ npx skills add microsoft/power-platform-skills --skill assign-offline-profile -a github-copilot -y
OpenCode
$ npx skills add microsoft/power-platform-skills --skill assign-offline-profile -a opencode -y
Windsurf
$ npx skills add microsoft/power-platform-skills --skill assign-offline-profile -a windsurf -y
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:**
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.
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
---
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-tenantCapture `<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.
`$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.
**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&Plugins from Microsoft that teach AI assistants, such as GitHub Copilot and Claude Code, to build on Power Platform.
Guide the user to add a data source, connection, or API connector to a Canvas App via Power Apps Studio, then verify and…
Creates or edits a Power Apps Canvas App through the Canvas Authoring MCP coauthoring session. Handles staged experience design,…
Configure the Canvas Authoring MCP server for the current coauthoring session. Use before starting to edit a canvas app with the…
Use this skill when the user wants to "report a bug", "file an issue", "report an issue", "submit a bug report", or report any…
Adds Azure DevOps connector to a Power Apps code app. Use when querying work items, creating bugs, managing pipelines, or making…
Adds any Power Platform connector to a Power Apps code app. Generic fallback for connectors not covered by a specific skill.
You MUST use this before any creative work - creating features, building components, adding functionality, modifying behavior, or…
Ask which skill or flow fits your situation. A router over the skills in this repo.