Install
$ agentstack add skill-microsoft-power-platform-skills-configure-env-variables ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
> Plugin check: Run node "${PLUGIN_ROOT}/scripts/check-version.js" — if it outputs a message, show it to the user before proceeding.
configure-env-variables
Creates and links Dataverse environment variables to Power Pages site settings, enabling different configuration values per deployment environment (dev vs staging vs prod). Generates deployment-settings.json for use by deploy-pipeline.
Background
Power Pages site settings can be backed by environment variables (GA March 2025, enhanced data model only). When linked:
- The site setting's
mspp_sourcechanges from0(static) to1(environment variable) - The runtime reads the env var value for the current environment instead of the static
mspp_value - During pipeline deployment, target-environment values are injected via
deploymentsettingsjson
API note: The site setting → env var link is set via a HAR-confirmed OData PATCH pattern (v9.0, EnvironmentValue nav property, if-match: * and clienthost: Browser headers required). This is handled by scripts/lib/link-site-setting-to-env-var.js. All steps are fully automated.
Prerequisites
- PAC CLI authenticated:
pac auth who - Azure CLI token available:
az account get-access-token .solution-manifest.jsonexists in the project root (runsetup-solutionfirst)- Power Pages site deployed to dev environment (
.powerpages-site/folder exists)
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.json — PLAN_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 "${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), classifies which site settings should become environment variables, and orchestrates the right skills (including this one) in the right order. Want me to run plan-alm now?"
> 🚦 Gate (intent · configure-env-variables: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 |
- Yes (Recommended) → invoke
/power-pages:plan-alm. It builds the plan and returns —plan-almis a planner and does not deploy. This skill then re-runs the Phase 0 check (nowexists:true) and proceeds to Phase 1, picking up the pre-classifiedsiteSettingsfromdocs/alm/alm-plan-context.json. - Continue without a plan → set
BYPASSED_PLAN_GATE = trueand proceed to Phase 1. - Cancel → exit cleanly.
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 · configure-env-variables:0.stale-plan): Fail-closed entry gate when check-alm-plan.js returns stale:true (solution-modified-since-plan). 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 |
- Refresh (Recommended) → invoke
/power-pages:plan-alm. After completion, re-run the Phase 0 helper once to confirm freshness; if still stale, surface the detail and proceed to Phase 1 anyway (don't infinite-loop). - Continue → set
STALE_PLAN_ACK = trueand proceed to Phase 1. - Cancel → exit cleanly.
Why this gate exists. Direct invocation of configure-env-variables creates env var definitions and a deployment-settings.json without the orchestrator's per-stage value gathering, site-setting classification (keepAsIs / promoteToEnvVar / authNoValue / excluded), and pipeline-strategy alignment. Users running this skill standalone often pick env var schema names that don't align with the plan's solution split, miss authNoValue settings that the plan classified for inclusion, or generate stage names that don't match the pipeline configured later by setup-pipeline. The gate ensures plan-alm either ran (so env var decisions are coherent with the rest of the deployment plan) or the user explicitly chose to bypass it.
Phase 1 — Discover Existing State
Read project context and query Dataverse to understand what's already configured.
1.1 Read project files:
cat .solution-manifest.json # get solutionUniqueName, environmentUrl, publisher.prefix
cat docs/alm/last-pipeline.json # get hostEnvUrl, stages[].name
ls .powerpages-site/site-settings/ # list all site setting YAML files
1.2 Acquire token and verify prerequisites:
node "${PLUGIN_ROOT}/scripts/lib/verify-alm-prerequisites.js" \
--envUrl "{devEnvUrl}" \
--require-manifest
Capture output as JSON; extract .envUrl (store as devEnvUrl) and .token (store as TOKEN).
1.3 Query existing env vars in the environment:
GET {devEnvUrl}/api/data/v9.2/environmentvariabledefinitions?$select=schemaname,displayname,type,defaultvalue,environmentvariabledefinitionid&$orderby=schemaname
1.4 Query site settings that already have env vars linked (mspp_source = 1):
GET {devEnvUrl}/api/data/v9.2/mspp_sitesettings?$filter=mspp_source eq 1 and _mspp_websiteid_value eq {WEBSITE_ID}&$select=mspp_name,mspp_source,_mspp_environmentvariable_value,mspp_envvar_schema
Get WEBSITE_ID from .powerpages-site/website.yml → id field.
1.5 Parse site setting YAML files to list all settings and their current source:
- Files with
source: 1are already env-var-backed - Files with
source: 0or no source field are static
Present a summary table to the user:
Current site settings (static): 48
Already env-var-backed: 3
Existing env var definitions: 2
Phase 2 — Select Site Settings and Plan Env Vars
Ask the user which site settings should be backed by environment variables. Present the list of static site settings as candidates. Recommend settings that are likely to vary per environment:
Common candidates:
Authentication/OpenIdConnect/AzureAD/ClientId— Entra ID app registration differs per envAuthentication/OpenAuth/Microsoft/ClientId— OAuth app IDAuthentication/OpenAuth/Microsoft/ClientSecret— OAuth secret (use Secret type)Authentication/Registration/LocalLoginEnabled— may differ in dev vs prod- Any
Authentication/Registration/OpenRegistrationEnabled— open sign-up policy - Custom site settings the user has added
> 🚦 Gate (plan · configure-env-variables:2.selection): User picks which site settings get promoted to env vars. Multi-select. Cancel exits before any env var definitions are created.
Ask via AskUserQuestion: > "Which site settings should be backed by environment variables? I'll create an env var for each and guide you through linking them. > > Here are the candidates (enter numbers, comma-separated): > 1. Authentication/Registration/LocalLoginEnabled (currently: true) > 2. Authentication/OpenIdConnect/AzureAD/ClientId (currently: empty) > 3. [other settings...] > N. I'll type my own setting names"
For each selected setting, ask for:
- Env var schema name — generate via
${PLUGIN_ROOT}/scripts/lib/generate-env-var-schema-name.js(single source of truth shared withsetup-solution):
``bash node "${PLUGIN_ROOT}/scripts/lib/generate-env-var-schema-name.js" \ --publisherPrefix "{publisherPrefix}" --settingName "{settingName}" ` Output: { schemaName, sanitized }. The canonical rule is {prefix}{settingName.replace(/[^A-Za-z0-9]+/g,'').toLowerCase()} — e.g. Authentication/Registration/LocalLoginEnabled becomes idsauthenticationregistration_localloginenabled`. Do NOT inline a custom rule here: setup-solution emits schema names from this helper, and configure-env-variables MUST match what setup-solution already created (otherwise the link to the existing site setting fails). The user can override the suggestion if they have a reason, but the default must come from the helper.
- Display name (human-readable)
- Type: String (default), Boolean, Number, Secret
- Dev/source value (default = current
mspp_valuefrom YAML) - Per-stage values — for each stage in
docs/alm/last-pipeline.json, what should the value be?
Example:
Setting: Authentication/Registration/LocalLoginEnabled
Schema name: ids_authentication_registration_localloginenabled
Display name: IdeaSphere Local Login Enabled
Type: String (site settings always resolve as strings)
Dev value: true
Staging value: false
Production value: false
Phase 3 — Create Environment Variable Definitions
For each planned env var, branch on typeCode:
> {displayName} substitution rule — pick a human-readable label, not the schema name. The Dataverse displayname column on environmentvariabledefinition is what shows up in PPAC, in the rendered ALM plan's Env Variables tab, and in last-env-vars.json. If you substitute the schema name (e.g. c311_api_secret) into --displayName, the plan and PPAC both read like raw tokens. Pick the friendly label from the source site setting (e.g. API Secret from c311/api_secret's Description or its human-readable title in the planData). When no friendly label exists, derive one from the schema name by title-casing the prefix-stripped tail (e.g. c311_api_secret → Api Secret → manually clean up to API Secret). Keep displayName and schemaName deliberately different — they have different audiences.
3.A — String env vars (typeCode = 100000000)
Definition-only flow — per-stage values come later from deployment-settings.json via deploymentsettingsjson PATCH at deploy time.
3.A.1 Check and create if needed using create-env-var-definition.js (the script checks for an existing definition by schemaName before posting):
node "${PLUGIN_ROOT}/scripts/lib/create-env-var-definition.js" \
--envUrl "{devEnvUrl}" \
--token "{TOKEN}" \
--schemaName "{schemaName}" \
--displayName "{displayName}" \
--type 100000000 \
--defaultValue "{devValue}"
Capture output as JSON; extract .definitionId (store as envVarDefId) and check .created (true = newly created, false = already existed). If already existed, confirm the existing definition matches expectations before proceeding.
3.A.2 Create the current-environment value (the live dev value, separate from defaultvalue):
POST {devEnvUrl}/api/data/v9.2/environmentvariablevalues
Content-Type: application/json
{
"EnvironmentVariableDefinitionId@odata.bind": "/environmentvariabledefinitions({envVarDefId})",
"value": "true"
}
Response: HTTP 204.
3.B — Secret env vars (typeCode = 100000005) when a Key Vault Secret URI is available
Acceptable Secret reference formats
Dataverse / the Power Platform Pipelines handler accept exactly three formats for a Secret-type env var value. Anything else is rejected at import time with "ImportAsHolding failed: The value provided as a secret reference does not match a valid secret reference format" — and the rejection can come hours after the deploy queues, since the host serializes imports. The pre-deploy validator (deploy-pipeline Phase 5.1b, helper at scripts/lib/validate-deployment-settings.js) catches these formats upfront, but they're documented here so SKILL.md authors writing to deployment-settings.json use the right shape from the start.
Accepted:
- Key Vault Secret Identifier URI — what
store-keyvault-secret.jsemits in itssecretUrioutput:
`` https://.vault.azure.net/secrets/ https://.vault.azure.net/secrets// ` Vault name must be 3–24 chars, lowercase alphanumeric + hyphens, start with a letter, end with a letter or digit. This is the canonical form and the one add-server-logic` Phase 7.2a hands back via the user-visible "share the secretUri output" step.
- Azure resource ID — the full ARM-style path, when the maker doesn't have the URI form handy:
`` /subscriptions//resourceGroups//providers/Microsoft.KeyVault/vaults//secrets/ ` Both resourceGroups and resourcegroups` casings are accepted by Dataverse.
- Empty string
""— legitimate when the env var has a sensible default-value baked into the definition. Per-stageValue: ""indeployment-settings.jsonmeans "use the definition default in this stage."
Rejected (validator flags these):
@KeyVault(vaultName=;secretName=)— a templating-style placeholder. NOT recognized by Dataverse. Real-world failure case from a live session: this exact pattern caused a 4h41m queue wait + ImportAsHolding failure. Replace with form (1) or (2).- `
,,,${ENV_VAR}` — any angle-bracketed or shell-style placeholder. Same story; these are maker conventions Dataverse does not parse. - Plain-text secret values — both insecure (the file is committed to git) AND rejected by Dataverse when the env var type is Secret. If the maker intended plain text, the env var type should be String (
100000000), not Secret (100000005). - HTTPS URLs that look like Key Vault URIs but miss the canonical shape (
.comhost suffix instead of.net, missing/secrets/segment, short version suffix). The validator flags these specifically asinvalid-uriso the maker can fix the typo rather than re-coining the value.
Implement
…
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: microsoft
- Source: microsoft/power-platform-skills
- License: MIT
- Homepage: https://aka.ms/ppskills
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.