Agent Skill · Microsoft Power Apps

import-solution

Imports a Dataverse solution zip into a target environment, with optional staged import for dependency checking before committing. Use when asked to: "import solution", "install solution", "deploy solution zip", "push solution to environment", "deploy to staging", "deploy to production", or "install site in new environment".

Provider: Microsoft Power Apps Path in repo: plugins/power-pages/skills/import-solution/SKILL.md

Skill body

Plugin check: Run node "${CLAUDE_PLUGIN_ROOT}/scripts/check-version.js" — if it outputs a message, show it to the user before proceeding.

import-solution

Imports a solution zip into a target Dataverse environment via ImportSolutionAsync. Supports optional staged import via StageSolution to check for missing dependencies before committing.

Prerequisites

Phases

Phase 0 — ALM plan gate

plan-alm is the front door. When the user expresses an ALM intent (promote / ship / deploy / set up CI-CD / move to staging / push to prod), the orchestrator (/power-pages:plan-alm) should run first. This Phase 0 enforces that and is meant to fail closed when there’s no plan, not to be a one-time check the user can dismiss forever.

Skip rule. If this skill was invoked as part of an active plan-alm orchestration, skip Phase 0 entirely and proceed to Phase 1. The gate helper exposes this via its inExecution block — pass through silently to Phase 1 when:

inExecution.status === "active"

The helper computes this from docs/.alm-plan-data.jsonPLAN_STATUS === "In Execution" AND LAST_INVOCATION_AT within the last 60 minutes. check-alm-plan.js refreshes LAST_INVOCATION_AT automatically on every invocation that finds the plan in execution, so each in-chain skill keeps the chain alive for the next one — even multi-hour deploys (deploy-pipeline alone can take 60 min per stage) survive the window without the chain incorrectly de-classifying. Stalled chains (no heartbeat for > 60 min) reclassify as stale-heartbeat and Phase 0 gates fire normally so an abandoned plan doesn’t silently bypass user confirmation.

When inExecution.status is anything other than "active" ("not-running", "stale-heartbeat", "no-plan"), run the Phase 0 gate flow below. Branch on the remaining helper fields:

Step 1 — Run the gate helper.

node "${CLAUDE_PLUGIN_ROOT}/scripts/lib/check-alm-plan.js" --projectRoot "."

The helper returns JSON with { exists, deferred, stale, staleness: { reason, detail }, generatedAt, planStatus, ... }. Pass --envUrl, --token, --solutionId once Phase 1 has acquired them if you also want a freshness check; otherwise the helper does an existence-only check, which is sufficient for the gate decision below.

Step 2 — Branch on the result.

Result Behavior
deferred: true The user has explicitly deferred ALM for this project (.alm-deferred marker present). Pass through silently to Phase 1 — do not nag.
exists: false The user hasn’t run plan-alm yet. See Step 3.
exists: true, stale: false Plan is current. Pass through silently to Phase 1.
exists: true, stale: true (reason: solution-modified) The solution changed after the plan was generated. See Step 4.

Step 3 — No plan. Tell the user:

“No ALM plan exists for this project. /power-pages:plan-alm builds one — it detects the project state, asks about your promotion strategy (PP Pipelines vs Manual export/import), and orchestrates the right skills (including this one) in the right order. Want me to run plan-alm now?”

🚦 Gate (intent · import-solution:0.no-plan): Fail-closed entry gate when check-alm-plan.js returns exists:false. Helper-script-backed.

AskUserQuestion:

Question Header Options
Run /power-pages:plan-alm first? ALM plan gate Yes — run /power-pages:plan-alm now (Recommended), Continue without a plan (advanced — I know what I’m doing), Cancel

Step 4 — Stale plan. Tell the user:

“ALM plan exists from {generatedAt} but the source solution has been modified since (at {solution.modifiedon}). Components may have changed. Re-running plan-alm will refresh the analysis and the rendered HTML.”

🚦 Gate (intent · import-solution:0.stale-plan): Fail-closed entry gate when check-alm-plan.js returns stale:true. Helper-script-backed.

AskUserQuestion:

Question Header Options
Refresh the plan first? ALM plan freshness Refresh — re-run /power-pages:plan-alm (Recommended), Continue with the existing plan, Cancel

Why this gate exists. Direct invocation of import-solution deploys a zip into a target environment without the orchestrator’s deployment-strategy selection or post-import validation steps. Users running this skill standalone often skip the staged-import dependency check, miss env var override values for the target environment, and have no plan-tracked record of which environment received which artifact version. The gate ensures plan-alm either ran (so the strategy was selected, per-stage values were captured in deployment-settings.json, and the deployment is reproducible) or the user explicitly chose to bypass it.

Phase 1 — Verify Prerequisites

Create all tasks upfront at the start of this phase.

Tasks to create:

  1. “Verify prerequisites”
  2. “Locate solution file”
  3. “Configure import”
  4. “Stage solution (dependency check)”
  5. “Import solution”
  6. “Verify import”
  7. “Detect cloud flows”
  8. “Present summary”

Note: If the import fails with an AttachmentBlocked error, a Phase 5b remediation flow runs inline — no additional task is needed (it continues within the “Import solution” task). The “Detect cloud flows” task is skipped automatically if no Workflows/*.json files are present in the solution zip.

Steps:

  1. Run pac env who — extract environmentUrl (verify this is the target environment)
  2. Run az account get-access-token --resource "{environmentUrl}" --query accessToken -o tsv — capture token
  3. Verify API access: GET {environmentUrl}/api/data/v9.2/WhoAmI
  4. Present target environment URL and ask user to confirm this is correct before proceeding.

Important: Confirm the target environment with the user — importing to the wrong environment can be disruptive.

If any check fails, stop (reference ${CLAUDE_PLUGIN_ROOT}/references/dataverse-prerequisites.md).

Phase 1.5 — Ground in current ALM documentation

Reference: ${CLAUDE_PLUGIN_ROOT}/references/alm-docs-grounding.md

Cap this step at ~30 seconds. If MCP search / fetch errors out, log a one-line note and continue — this skill must remain runnable offline.

  1. Run microsoft_docs_search with the query: Power Pages solution import staging missing dependencies ImportSolutionAsync ALM.
  2. Fetch https://learn.microsoft.com/en-us/power-platform/alm/solution-concepts-alm (and at most one sister page on staged imports or dependency handling) in parallel via microsoft_docs_fetch.
  3. Extract a one-paragraph summary of what Microsoft Learn currently says about staging vs direct import, dependency resolution, and component-level error handling. Compare against ${CLAUDE_PLUGIN_ROOT}/references/solution-api-patterns.md and flag any divergence in ImportSolutionAsync / StageSolution signatures.
  4. Use the summary to inform Phase 2+ decisions. Do not silently change skill behavior — surface any divergence to the user as a soft warning before Phase 4 (the actual import).

Phase 2 — Locate Solution File

  1. If a zip path was provided as an argument, use it directly
  2. Otherwise, search for solution zips: glob('**/*.zip', { ignore: ['**/node_modules/**'] })
  3. For each found zip, verify it contains solution.xml:
    • Use Bash: unzip -l "{zipPath}" 2>/dev/null | grep -qi solution.xml
  4. If multiple valid zips found, ask user to choose:

    🚦 Gate (plan · import-solution:2.multiple-zips): More than one valid solution zip was discovered under the project root. User picks which one to import. Cancel exits before any target-env interaction.

    Use AskUserQuestion with one option per valid zip — show filename + size + modified date so the user can identify the right one (most-recent export is usually the intended target).

  5. If no valid zip found: stop and explain — run export-solution first or provide the zip path

Step 5a — Pre-import content inspection (run after zip is confirmed, before presenting to user):

Inspect the zip to surface post-import manual requirements. Run all checks in sequence:

# 1. Connection references (require user-binding post-import)
unzip -p "{zipPath}" customizations.xml 2>/dev/null | grep -c '<connectionreference '

# 2. Bots (require republishing in target environment)
unzip -l "{zipPath}" 2>/dev/null | grep -qi "bots/" && echo "found" || echo "none"

# 3. Cloud flows with hardcoded environment URLs (will silently fail in target)
unzip -p "{zipPath}" "Workflows/*.json" 2>/dev/null | grep -o '"organization":\s*"https://[^"]*"' | head -5

Build a postImportWarnings list from the results:

Finding Warning to surface
connectionreference count > 0 “⚠️ Connection references: This solution includes N connection(s) (e.g. Dataverse connector). After import, you must bind each connection reference to a live connection in the target environment, or cloud flows that depend on them will be disabled.”
bots/ folder present “⚠️ Copilot Studio bot: This solution includes a bot. After import, the bot must be republished in the target environment to complete provisioning.”
Hardcoded org URL found “⚠️ Hardcoded environment URL: The cloud flow contains a hardcoded environment URL ({foundUrl}). This will cause the flow to call the source environment after import. Edit the flow in the target environment to update the URL.”

If postImportWarnings is non-empty, display all warnings inline (not via AskUserQuestion) before presenting the zip details. The user should see these before confirming.

Present the selected zip file details (name, size, path), any pre-import warnings, and confirm with user.

Phase 3 — Configure Import

Step 3.0 — Version-skew advisory (read-only check before any prompt).

Before asking the user about staged/direct import, query the target environment for the solution unique name carried in the zip and compare the installed version against the zip’s version. The goal is to surface “you’re about to import the same version that’s already installed” before the user clicks through — this is the most common silent-failure pattern for the manual export/import path, because the source-side bump is what produces an unambiguously promotable artifact.

  1. Extract the zip’s uniqueName + version from solution.xml inside the zip:
    unzip -p "{zipPath}" solution.xml 2>/dev/null | head -50
    

    Parse <UniqueName> and <Version> from the XML. Store as ZIP_SOLUTION_NAME and ZIP_SOLUTION_VERSION.

  2. Query the target for the installed solution:
    GET {envUrl}/api/data/v9.2/solutions?$filter=uniquename eq '{ZIP_SOLUTION_NAME}'&$select=solutionid,uniquename,version,ismanaged
    

    Store the result as INSTALLED (or null if the filter returns an empty value array).

  3. Compare versions via the shared helper, then branch on the result.

    Precondition — skip this entire step when INSTALLED is null. That’s the first-time-install case: there’s nothing on the target to compare against. Do NOT call the helper with null substituted into '{INSTALLED.version}' — it would throw “version is required” and leave the agent without a branch. Jump straight to the staged/direct prompt below and treat the import as a fresh install.

    Otherwise (INSTALLED is non-null), compare versions:

    Critical: do not compare version strings with raw > / < / === — Dataverse versions are 4-segment integer tuples (1.0.0.9 vs 1.0.0.10) and lexical comparison reports 1.0.0.10 as lower than 1.0.0.9, flipping the skew gate on the 10th deploy of the day. Use the canonical helper instead:

    node -e "console.log(require('${CLAUDE_PLUGIN_ROOT}/scripts/lib/bump-solution-version').compareVersions('{ZIP_SOLUTION_VERSION}', '{INSTALLED.version}'))"
    

    The helper returns -1 when ZIP < INSTALLED, 0 when equal, 1 when ZIP > INSTALLED. Same segment-wise integer rules as bumpPatchSegment (pad-with-zero, max-4-segments, reject non-integer). Capture stdout, trim, and store the integer as VERSION_CMP. If the helper throws (malformed version on either side), surface the stderr to the user and stop — the version comparison is a precondition for safe import.

    INSTALLED VERSION_CMP Behavior
    null (helper not called — see precondition above) First-time install. Continue silently to the staged/direct prompt below.
    not null 1 (zip is strictly greater) Normal upgrade. Report: “Target has v{INSTALLED.version} installed; this zip is v{ZIP_SOLUTION_VERSION}. Importing will upgrade.”
    not null 0 (zip equals installed) Surface the warning below (same-version skew — applies to both managed and unmanaged).
    not null -1 (zip is strictly less) Surface the warning below (downgrade — applies to both managed and unmanaged).

    🚦 Gate (consent · import-solution:3.0.version-skew): Zip version is equal-to or lower-than the installed solution’s version on the target. Importing produces unpredictable upgrade semantics; the source export-solution is supposed to bump the version on every export. Re-export with bumped version, force the import anyway, or cancel.

    Warning promptAskUserQuestion:

    “The target environment already has {ZIP_SOLUTION_NAME} v{INSTALLED.version} installed ({INSTALLED.ismanaged ? ‘managed’ : ‘unmanaged’}). The zip you are about to import carries version {ZIP_SOLUTION_VERSION}.

    Importing the same or a lower version is unreliable:

    • Managed: no upgrade lineage; the platform may apply or reject the import depending on internal heuristics.
    • Unmanaged: behavior depends entirely on OverwriteUnmanagedCustomizations: true, and any in-target edits that happen to match the zip’s component IDs get silently overwritten without a version change to point to.

    /power-pages:export-solution always bumps the source version before producing a zip (since 2026-05-25). If this zip was produced before that change, re-exporting will give you a clean, strictly-greater version.

    How would you like to proceed?”

    Question Header Options
    What to do? Version skew Re-export with a bumped version (Recommended — invokes /power-pages:export-solution), Import anyway (proceed at your own risk), Cancel
    • Re-export: invoke /power-pages:export-solution. After it completes, restart this skill from Phase 2 with the freshly-produced zip.
    • Import anyway: set SKEW_ACK = true and proceed to the staged/direct prompt below. Record the acknowledged skew in docs/alm/last-import.json under versionSkew: { zipVersion, installedVersion, isManaged, acknowledged: true } for the audit trail.
    • Cancel: stop the skill cleanly. No target-env writes happened in Step 3.0 — it was read-only.
  4. If the comparison is the normal upgrade case (or the zip’s solution isn’t installed yet), continue silently — do not present a prompt, just record the version delta in the eventual docs/alm/last-import.json for the summary.

🚦 Gate (plan · import-solution:3.config): Staged vs Direct import, overwrite options. Cancel exits before any target-env mutation.

Ask user (via AskUserQuestion):

Key Decision Point: Staged vs Direct import

  • Staged (Recommended for managed solutions): Runs StageSolution first to check for missing dependencies. Shows issues before committing the import. Safer — the stage step is fully reversible.
  • Direct: Skips staging and imports immediately. Faster but may fail mid-import if dependencies are missing.

Also ask:

Phase 4 — Stage Solution (Conditional)

Only run this phase if the user chose staged import in Phase 3.

Refer to ${CLAUDE_PLUGIN_ROOT}/references/solution-api-patterns.md Section 5a.

  1. Base64-encode the zip file:
    node "${CLAUDE_PLUGIN_ROOT}/scripts/encode-solution-file.js" --zipPath "{zipPath}"
    
  2. POST {envUrl}/api/data/v9.2/StageSolution with CustomizationFile: {base64}
  3. Parse StageSolutionResults:
    • Extract StageSolutionUploadId (used in Phase 5 instead of re-encoding the file)
    • Check MissingDependencies array
  4. If MissingDependencies is non-empty:
    • List each missing dependency with its type and name
    • Ask user: “These dependencies are missing in the target environment. Proceed anyway (may fail) or cancel to install dependencies first?”
    • If cancel: stop and advise installing missing dependencies
  5. If MissingDependencies is empty: report “No missing dependencies found. Ready to import.”

Phase 5 — Import Solution

Refer to ${CLAUDE_PLUGIN_ROOT}/references/solution-api-patterns.md Section 5b.

  1. Prepare request body (always use CustomizationFileImportSolutionAsync does not accept StageSolutionUploadId):
    • Encode the zip: node "${CLAUDE_PLUGIN_ROOT}/scripts/encode-solution-file.js" --zipPath "{zipPath}"
    • Use { CustomizationFile: "{base64}", OverwriteUnmanagedCustomizations: {choice}, PublishWorkflows: {choice} }
  2. POST {envUrl}/api/data/v9.2/ImportSolutionAsync
  3. Extract AsyncOperationId and ImportJobKey (note: field is ImportJobKey, not ImportJobId)
  4. Report: “Import job started: {AsyncOperationId}. Polling for completion…”

Run scripts/poll-async-operation.js:

node "${CLAUDE_PLUGIN_ROOT}/scripts/poll-async-operation.js" \
  --asyncJobId "{AsyncOperationId}" \
  --envUrl "{envUrl}" \
  --token "{token}" \
  --intervalMs 8000 \
  --maxAttempts 75

Handle poll result:

Phase 5b — Resolve Attachment Restrictions (conditional)

Only run this phase if Phase 5 poll failed with AttachmentBlocked (-2147188706 or message contains AttachmentBlocked or not a valid type).

5b.1 Identify Blocked Extensions in the Solution Zip

List all files in the zip and extract unique extensions:

unzip -l "{zipPath}" | awk '{print $4}' | grep '\.' | sed 's/.*\.//' | sort -u

Get the current blocked attachments list from the environment:

pac env list-settings

Find the blockedattachments row in the output — it contains a semicolon-separated list (e.g., ade;adp;js;zip;...).

Compute the intersection: which extensions from the solution zip appear in the blocked list. These are the types that need to be unblocked.

5b.2 Explain the Issue

Tell the user:

“The solution import failed because the target environment blocks certain file types that are included in this solution. The following file extensions in the solution are currently blocked: {comma-separated list}. This is an environment-level security setting. To import this solution, these restrictions need to be temporarily relaxed.”

5b.3 Ask for Permission

🚦 Gate (consent · import-solution:5b.blocked-attachments): Reactive AttachmentBlocked remediation — modify env-level blockedattachments setting (tenant-wide impact). Reversible from PPAC. Fires fresh on every skill invocation that hits the failure. When plan-alm Manual path orchestrates multi-target imports (Staging then Production), it invokes import-solution once per target — if both targets block the same extensions, the gate fires once per target (each is a separate skill invocation against a separate env). Consent for Staging does NOT cover Production.

Invoke AskUserQuestion immediately — do NOT present this as a chat message. The user must answer live before the skill proceeds.

Question Header Options
The solution contains file types ({list}) that are blocked by this environment’s attachment security settings. Would you like to remove the block for these specific types so the solution can be imported? Unblock Attachment Types Yes, unblock {list} for this import (Recommended), No, do not change environment settings

If “No”: Stop and tell the user: “The import cannot proceed while these file types are blocked. To unblock manually: Power Platform Admin Center → Environments → {env} → Settings → Product → Features → Blocked Attachments.”

If “Yes”: Proceed to 5b.4.

5b.4 Update Blocked Attachments

  1. Parse the blockedattachments value (semicolon-separated)
  2. Remove only the extensions identified in 5b.1 — preserve all others
  3. Update the setting:
    pac env update-settings --name blockedattachments --value "{updated-list-with-types-removed}"
    
  4. Confirm the update succeeded.

5b.5 Retry Import

Re-encode the zip and retry ImportSolutionAsync (repeat Phase 5 steps 1–4 and poll again).

Phase 6 — Verify Import

  1. Query solution to confirm it exists and version matches:
    GET {envUrl}/api/data/v9.2/solutions?$filter=uniquename eq '{solutionName}'&$select=solutionid,uniquename,version,ismanaged
    
  2. Query import job for component results (use ImportJobKey from the import response):
    GET {envUrl}/api/data/v9.2/importjobs({ImportJobKey})?$select=solutionname,completedon,progress,data
    
    • Parse the data XML field for per-component results (look for result="failure" entries)
    • Count: imported successfully / warnings / failures
  3. Ensure docs/alm/ exists, then write docs/alm/last-import.json marker (node -e "require('fs').mkdirSync('docs/alm',{recursive:true})"):
    {
      "importedAt": "<ISO timestamp>",
      "solutionName": "<name>",
      "version": "<version>",
      "targetEnvironment": "<envUrl>",
      "asyncOperationId": "<id>",
      "importJobId": "<ImportJobKey value>",
      "status": "<Succeeded|Failed|Partial>",
      "componentResults": { "success": N, "warning": N, "failure": N },
      "versionSkew": null
    }
    

    The status field drives refresh-alm-plan-data.js’s step-sync (completed vs failed) for the rendered ALM plan’s per-stage checklist. Set it to:

    • Succeeded when the import job’s statecode is 3 (Succeeded) AND component-results show failure === 0.
    • Partial when statecode is 3 but componentResults.failure > 0 (the solution landed but some components didn’t import — usually managed-property conflicts or dependency gaps).
    • Failed when statecode is 4 (Failed) OR the import never reached terminal state OR all components failed. Without this field, every import shows completed in the rendered plan regardless of actual outcome.

    If the user acknowledged a same-version or downgrade import in Step 3.0 (SKEW_ACK = true), set versionSkew to:

    { "zipVersion": "<ZIP_SOLUTION_VERSION>", "installedVersion": "<INSTALLED.version>", "isManaged": <bool>, "acknowledged": true }
    

    Otherwise leave versionSkew: null.

Phase 6b — Set Environment Variable Values (if any)

After a successful import, env var definitions travel in the solution but their values do not. The target environment will have the definition records but blank values until explicitly set.

Query the target environment for any env var definitions from this solution:

GET {envUrl}/api/data/v9.2/environmentvariabledefinitions?$filter=introducedversion ne null&$select=schemaname,displayname,type,defaultvalue,environmentvariabledefinitionid

Filter to only those whose schemaname starts with the publisher prefix (from .solution-manifest.json), or cross-reference with solutioncomponents if available.

For each definition found, check if a value already exists in the target:

GET {envUrl}/api/data/v9.2/environmentvariablevalues?$filter=_environmentvariabledefinitionid_value eq '{id}'&$select=value

🚦 Gate (plan · import-solution:6b.env-vars): Imported env var definitions need per-target values. User supplies values or skips (uses default). Without values, runtime reads default which may be dev-only.

If any definitions have no existing value, present them to the user via AskUserQuestion:

“The imported solution contains {N} environment variable(s) with no value set in this environment. Enter the target value for each (leave blank to skip and use the default):

  1. {schemaname} ({displayname}) — default: {defaultvalue ?? 'none'}
  2. …”

For each value the user provides, POST an environmentvariablevalue record:

POST {envUrl}/api/data/v9.2/environmentvariablevalues
{
  "schemaname": "{schemaname}",
  "value": "{userValue}",
  "[email protected]": "/environmentvariabledefinitions({id})"
}

Note on Secret type (type 100000005): Secret values are stored encrypted. The POST behaves the same but the value will be masked in the UI. The user should provide the actual secret value for the target environment (e.g. the OAuth client secret for the production tenant’s app registration — different from the dev value).

If the user skips all values: inform them the site may not function correctly until values are set, and provide the direct Power Platform URL to set them manually: https://{targetEnvHost}/main.aspx?appid=...&etn=environmentvariabledefinition

6b.verify — Confirm values landed. After the per-variable POSTs complete, verify each environmentvariablevalues record actually exists on the target. The shared helper scripts/lib/verify-env-var-values.js does this read-only check and returns a structured JSON result per schema (landed / missing-value-record / missing-definition / value-mismatch / query-error):

node "${CLAUDE_PLUGIN_ROOT}/scripts/lib/verify-env-var-values.js" \
  --envUrl "{targetEnvUrl}" \
  --schemaNames "{comma-separated schema names that the user supplied values for}"

Capture stdout as JSON. If summary.missing > 0 or summary.error > 0, surface a single warning to the user with the affected schema names — but do not block the import summary, the import itself succeeded. The same helper is used at deploy time (deploy-pipeline Phase 7.6.5) and at configure time (configure-env-variables Phase 7); centralizing the check keeps the user-visible message consistent across skills.

Phase 6c — Detect Cloud Flows (if any)

After import succeeds, check whether the solution contains cloud flow JSON files. Use the zip path located in Phase 2.

unzip -l "{zipPath}" | grep -i "^.*Workflows/.*\.json"

If no matching files are found, skip this phase entirely.

If cloud flow files are found:

  1. Extract the flow name from each path (pattern: Workflows/FlowName-GUID.json — strip the path prefix and GUID suffix to get the display name)
  2. Inform the user:

    “This solution contains {N} cloud flow(s). Cloud flows must be registered with the Power Pages site in the target environment after import.”

    Flows detected:

    • {FlowName1}
    • {FlowName2}

    To register: Power Pages Management → target environment → Edit site → Set upCloud flows → register each flow listed above.

    Direct link: https://make.powerpages.microsoft.com/

    🚦 Gate (plan · import-solution:6c.cloud-flow-register): Cloud flows in imported solution need manual registration in target env. Acknowledge / defer.

  3. Invoke AskUserQuestion:

    Question Header Options
    Have you registered the cloud flow(s) listed above with the Power Pages site in {targetEnvUrl}? Cloud Flow Registration Flows registered — continue, I’ll register them later
  4. Record the user’s response as cloudFlowStatus: "Registered" or "Pending registration". This status is shown in the Phase 7 summary row — either answer allows the skill to continue.

Phase 6d — Check Site Activation (if Power Pages solution)

Only run this phase if the solution contains Power Pages website components (componentType 10374):

GET {envUrl}/api/data/v9.2/solutioncomponents?$filter=_solutionid_value eq '{solutionId}' and componenttype eq 10374&$select=objectid

If no componentType 10374 records found, skip this phase entirely.

If found, run the shared activation status check (PAC CLI is already authenticated to the target environment):

node "${CLAUDE_PLUGIN_ROOT}/scripts/check-activation-status.js" --projectRoot "."

Evaluate the result:

🚦 Gate (plan · import-solution:6d.activate): Site imported but not activated in target env. Offer to invoke activate-site now or defer.

Phase 7 — Present Summary

Display a summary table:

Item Value
Solution {solutionName} v{version}
Target environment {envUrl}
Managed Yes / No
Components imported N success, N warning, N failure
Env var values set N of N
Cloud flows {cloudFlowStatus} (Registered / Pending registration / Not applicable)
Site activation Activated at {siteUrl} / Pending / Not applicable
Import job {importJobId}

Record Skill Usage

Reference: ${CLAUDE_PLUGIN_ROOT}/references/skill-tracking-reference.md

Follow the skill tracking instructions in the reference to record this skill’s usage. Use --skillName "ImportSolution".

Refresh the ALM plan (if one exists)

node "${CLAUDE_PLUGIN_ROOT}/scripts/lib/refresh-alm-plan-data.js" \
  --projectRoot "." \
  --phase import-solution \
  --stageName "{targetLabel}" \
  --render

{targetLabel} is the Manual-path target stage (e.g. Staging, Production) the just-completed import was for — usually carried in by the orchestrator (plan-alm Phase 7) or derivable from the target env URL. The helper reads docs/alm/last-import.json, captures the import outcome (status, version, component count, component failures) into planData.manualImports[targetLabel], and re-renders docs/alm-plan.html so the matching Import to {targetLabel} checklist step shows an IMPORTED / FAILED badge with version + component count inline. Subsequent imports to OTHER targets each get their own entry — the renderer surfaces a per-target history rather than overwriting on each call.

If --stageName is omitted the helper falls back to matching docs/alm/last-import.json’s targetEnvironment URL against planData.stages[].envUrl. When the match fails (rare — usually a stage-label/env-URL mismatch in planData), the import is captured under a synthetic key so it isn’t silently lost; pass --stageName explicitly to keep the rendered plan clean. When docs/.alm-plan-data.json is absent, the helper returns ok:false as a soft no-op.

Key Decision Points (Wait for User)

  1. Phase 1: Confirm target environment — import is not easily undoable for managed solutions
  2. Phase 2: Select zip file if multiple found
  3. Phase 3 Step 3.0: Version-skew advisory — only fires when the target already has the solution installed AND the zip’s version is the installed version. Offers re-export with bumped version (Recommended), import-anyway (acknowledged in last-import.json), or cancel. Read-only check; cancelling here makes no target-env writes.
  4. Phase 3: Staged vs direct import; overwrite customizations
  5. Phase 4: Proceed despite missing dependencies
  6. Phase 5b: Consent to unblock attachment types — never modify environment settings without explicit approval
  7. Phase 6b: Env var values — always prompted if solution contains env var definitions with no existing value in the target; Secret type definitions require the user’s target-environment-specific secret value
  8. Phase 6c: Cloud flow registration — non-blocking; user may register later; status recorded in summary
  9. Phase 6d: Site activation — only if Power Pages website components present and site not yet activated

Error Handling

Progress Tracking Table

Task subject activeForm Description
Verify prerequisites Verifying prerequisites Confirm PAC CLI auth, acquire token, verify target environment with user
Locate solution file Locating solution file Find and validate solution zip, confirm Solution.xml present
Configure import Configuring import Step 3.0: extract zip uniqueName+version from solution.xml, query target’s installed version, surface a version-skew advisory if zip version ≤ installed (offer re-export / import-anyway / cancel); then ask: staged vs direct, overwrite customizations, publish workflows
Stage solution (dependency check) Staging solution Run StageSolution to check for missing dependencies before committing
Import solution Importing solution POST ImportSolutionAsync, poll until complete; if AttachmentBlocked: identify blocked types, get user consent, unblock via pac env update-settings, retry
Verify import Verifying import Confirm solution version in target, parse component results, write docs/alm/last-import.json
Detect cloud flows Detecting cloud flows List Workflows/*.json entries in zip; if found, prompt user to register flows with Power Pages site; record status (Registered / Pending registration)
Check site activation Checking site activation If solution has componentType 10374: run check-activation-status.js; if not activated, ask user and invoke /power-pages:activate-site
Present summary Presenting summary Show component counts (success/warning/failure), cloud flow registration status, site activation status, env var values set

Skill frontmatter

user-invocable: true argument-hint: Optional: path to solution zip file allowed-tools: Read, Write, Edit, Bash, Glob, Grep, TaskCreate, TaskUpdate, TaskList, AskUserQuestion, mcp__plugin_power-pages_microsoft-learn__microsoft_docs_search, mcp__plugin_power-pages_microsoft-learn__microsoft_docs_fetch model: opus