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.
npx skills add https://github.com/microsoft/power-platform-skills --skill assign-offline-profile
Shared instructions: shared-instructions.md — read first.
References:
usermobileofflineprofilemembership / teammobileofflineprofilemembership entity field mapBind 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.
test -f power.config.json
node "${CLAUDE_SKILL_DIR}/../../scripts/resolve-environment.js" "$(node -e \"console.log(require('./power.config.json').environmentId)\")"
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 |
| offline-profile.json in cwd | 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 npx power-apps init. The profile ID lives in offline-profile.json only.
$ARGUMENTS parsing:
| Flag | Effect |
|---|---|
| --user <upn> (repeatable) | Add specific user(s) by UPN ([email protected]) |
| --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. [email protected], [email protected])"
Then read the next user message and parse.
For idempotency:
# Existing user memberships for this profile
node "${CLAUDE_SKILL_DIR}/../../scripts/dataverse-request.js" <envUrl> GET \
"usermobileofflineprofilememberships?\$filter=_mobileofflineprofileid_value eq <profileId>&\$select=usermobileofflineprofilemembershipid,_systemuserid_value&\$expand=systemuserid_systemuser(\$select=domainname)"
# Existing team memberships for this profile
node "${CLAUDE_SKILL_DIR}/../../scripts/dataverse-request.js" <envUrl> GET \
"teammobileofflineprofilememberships?\$filter=_mobileofflineprofileid_value eq <profileId>&\$select=teammobileofflineprofilemembershipid,_teamid_value&\$expand=teamid_team(\$select=name)"
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):
node "${CLAUDE_SKILL_DIR}/../../scripts/dataverse-request.js" <envUrl> GET \
"systemusers?\$filter=domainname eq '<upn>'&\$select=systemuserid,fullname,domainname&\$top=1"
node "${CLAUDE_SKILL_DIR}/../../scripts/dataverse-request.js" <envUrl> GET \
"teams?\$filter=name eq '<team-name>' and teamtype eq 0&\$select=teamid,name&\$top=1"
(teamtype eq 0 excludes Access Teams and Owner Teams — only Manage Teams get profile assignments.)
Construct three lists:
to_add — resolved IDs to POSTto_remove — resolved IDs to DELETE (from --unassign-* flags)not_found — UPNs/team-names that didn't resolve (warn)already_bound — skipped no-opsAskUserQuestion:
> Question header: Confirm membership changes
>
> Question body:
>
> `
> 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?
> `
>
> Options:
> - Proceed
> - Cancel
For each in to_add, POST sequentially (parallel POSTs occasionally return 429):
User membership:
node "${CLAUDE_SKILL_DIR}/../../scripts/dataverse-request.js" <envUrl> POST \
"usermobileofflineprofilememberships" \
--body '{
"[email protected]": "/mobileofflineprofiles(<profileId>)",
"[email protected]": "/systemusers(<systemuserid>)"
}' \
--include-headers
Expected 204 with OData-EntityId → capture membership GUID.
Team membership:
node "${CLAUDE_SKILL_DIR}/../../scripts/dataverse-request.js" <envUrl> POST \
"teammobileofflineprofilememberships" \
--body '{
"[email protected]": "/mobileofflineprofiles(<profileId>)",
"[email protected]": "/teams(<teamid>)"
}' \
--include-headers
For each in to_remove, DELETE:
node "${CLAUDE_SKILL_DIR}/../../scripts/dataverse-request.js" <envUrl> DELETE \
"usermobileofflineprofilememberships(<membershipid>)"
> ⚠️ Duplicate handling: POSTing a membership that already exists returns 409 Conflict. The dataverse-request.js wrapper's looksLikeDuplicate rescue treats this as silent success (the Step 3 dedup should catch most cases first). Re-runs are safe.
Re-query memberships from Step 3 and assert the diff applied:
to_add now appears in the GET responseto_remove no longer appearsIf the verification disagrees, return BLOCKED: membership writes did not commit and print the discrepancy.
Print:
✓ 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.
Update memory-bank.md ## Offline profile block:
membership:
users: [rahul@..., charanma@...]
teams: [Field Service RMs]
lastAssignedAt: 2026-05-19T...
DONE — every requested add/remove applied; verify confirmedDONE_WITH_CONCERNS: <list> — some UPNs/teams could not be resolved, or --all-app-users matched 0 users (env may not have the role granted yet)NEEDS_CONTEXT: <missing> — couldn't determine profileId (no offline-profile.json, no --profile flags, no profiles in env)BLOCKED: <reason> — auth failure, profile not found in env, or verification disagreementMemberships are individually committed (no transaction). If Step 5 fails mid-loop:
--unassign-* to undo specific bindings if neededIntegration with protocols.io API for managing scientific protocols. This skill should be used when working with protocols.io to search, create, update, or publish protocols; manage protocol steps and materials; handle discussions and comments; organize workspaces; upload and manage files; or integrate protocols.io functionality into workflows. Applicable for protocol discovery, collaborative protocol development, experiment tracking, lab protocol management, and scientific documentation.
Analyzes job descriptions and generates tailored resumes that highlight relevant experience, skills, and achievements to maximize interview chances
Generate Excalidraw diagrams from natural language descriptions. Use when asked to "create a diagram", "make a flowchart", "visualize a process", "draw a system architecture", "create a mind map", or "generate an Excalidraw file". Supports flowcharts, relationship diagrams, mind maps, and system architecture diagrams. Outputs .excalidraw JSON files that can be opened directly in Excalidraw.
Build and distribute Expo development clients locally or via TestFlight
Use when you have a written implementation plan to execute in a separate session with review checkpoints
Data structure for annotated matrices in single-cell analysis. Use when working with .h5ad files or integrating with the scverse ecosystem. This is the data format skill—for analysis workflows use scanpy; for probabilistic models use scvi-tools; for population-scale queries use cellxgene-census.
Benchling R&D platform integration. Access registry (DNA, proteins), inventory, ELN entries, workflows via API, build Benchling Apps, query Data Warehouse, for lab data management automation.
Comprehensive molecular biology toolkit. Use for sequence manipulation, file parsing (FASTA/GenBank/PDB), phylogenetics, and programmatic NCBI/PubMed access (Bio.Entrez). Best for batch processing, custom bioinformatics pipelines, BLAST automation. For quick lookups use gget; for multi-service integration use bioservices.
Take microsoft/assign-offline-profile from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.
The instructions reference npx.
Without those the skill loads but fails at the first command.