关于 ensure-pipelines-host
ensure-pipelines-host 是用于 Power Platform Pipelines 的主机检测与配置技能,供管理 Power Platform 部署环境的用户使用。它按 Power Apps 的解析顺序查找现有主机,并在需要时提供经确认的配置路径。
主要功能
- 01按解析顺序检测 Pipelines 主机
- 02识别 Platform Host 与 Custom Host
- 03支持复用现有主机
- 04提供经确认的主机配置路径
- 05缓存检查结果并验证可用性
适用场景
- 01为源环境查找已绑定的主机
- 02检测主机缺失时选择配置方式
- 03复核缓存主机是否仍可访问
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ microsoft/ power-platform-skills --skill ensure-pipelines-host安装后在 Claude Code 中输入/ensure-pipelines-host确认已加载。
附带文件scripts/validate-ensure-host.js
详细介绍
插件检查:运行
node "${PLUGIN_ROOT}/scripts/check-version.js"— 如果有消息输出,请先向用户显示,然后再继续。
ensure-pipelines-host
范围: 当源环境未绑定主机时,此技能会检测是否存在可复用的主机(Custom 或 PE);如果处于
NoHost状态,则提供三种预配方式:新建 Platform Host(推荐;幂等,约 3–5 分钟);新建 Custom Host(仅限管理员,约 5–10 分钟);或通过 PPAC 手动预配(备用方案)。实现细节——端点名称、模板名称、BAP audience——见下文 Phase 4.0 / 4.A / 4.C;面向用户的说明则聚焦于结果。
Power Platform Pipelines 需要一个主机环境——一个安装了 Power Platform Pipelines 托管解决方案的 Dataverse 环境,管道、阶段、运行历史记录和项目都存放在其中。现有的 setup-pipeline 和 deploy-pipeline 技能假定主机已配置完成。本技能用于填补这一空缺。
我们了解的内容(事实来源)
本计划以三个主要来源为依据,优先级如下:
useGetOrCreatePlatformEnvironment.v4.ts(Microsoft 内部客户端源代码 —power-platform-ux/packages/powerapps-appdeployment-ux/src/hooks/v4/)。定义了 Platform Environment 预配的准确 HTTP 契约:端点、请求正文、标头和轮询。ProjectHostProvider.tsx(同一仓库,src/components/ProjectHostProvider/)。定义了 Power Apps UI 用于确定哪个环境是源环境项目主机的确切解析顺序。我们会沿用该顺序,确保本技能与 UI 一致。- eng.ms
createcustompipelineshost(Microsoft 内部)。记录了 Custom Host 的快速路径:可通过标准环境创建 API 调用D365_ProjectHost组织模板,该模板会预装 Pipelines 应用。
公共 Microsoft Learn(learn.microsoft.com/power-platform/alm/{platform-host-pipelines, custom-host-pipelines, set-a-default-pipelines-host})以面向用户的方式描述了相同流程;我们引用它来说明用户熟悉的行为。PipelinesDeployScenario.har 和 Pipelines.har 中的 HAR 文件确认了读取端调用。
租户可能处于的三种主机状态
| 状态 | 成因 | 所在位置 | 组织模板 |
|---|---|---|---|
| Platform Host (PE) | 由 getOrCreate BAP 调用自动预配(或首次在 make.powerapps.com 中导航到 Pipelines 页面时作为副作用创建)。在环境选择器中隐藏。每个租户一个。 | 位于租户主地理区域、由 Microsoft 管理的 Dataverse 环境 | D365_1stPartyAdminApps |
| Custom Host | 管理员通过 PPAC Deployments → New custom host 创建,或通过使用 D365_ProjectHost 模板的标准环境创建 API 创建,或在现有 Dataverse 环境上安装 Power Platform Pipelines 应用。 | 租户中的常规 Dataverse 环境 | D365_ProjectHost(或在现有环境上安装应用) |
| 源环境未绑定主机 | 租户尚未在此环境中使用 Pipelines。 | — | — |
当前的 discover-pipelines-host.js 只检查租户级的 DefaultCustomPipelinesHostEnvForTenant 设置。这只是多个信号之一。本技能会实现完整的解析顺序。
解析顺序(与 ProjectHostProvider.tsx 一致)
这是决定性的决策树,也是 Power Apps UI 的处理方式。我们会复现这一顺序,确保本技能与 UI 一致。
┌─────────────────────────────────────────────────────────────────────┐
│ 1. GetOrgDbOrgSetting('ProjectHostEnvironmentId') on source env │
└──────────────────────────┬──────────────────────────────────────────┘
│
┌──────────────┴───────────────┐
│ value present │ value empty
▼ ▼
┌───────────────────────┐ ┌────────────────────────────┐
│ 2. Resolve env via │ │ 5a. Tenant-wide search: │
│ BAP GET │ │ list envs + per-env │
│ /environments/{id} │ │ /deploymentpipelines │
└───────┬───────────────┘ │ probe. │
│ │ │
environmentSku? │ - 1 Custom Host found → │
│ │ AvailableUnboundCustom │
┌────┴────────────┐ │ (3.C-pre) │
│ Platform │ │ - >1 Custom Hosts → │
│ │ │ MultipleUnboundCustom │
│ │ │ (3.C-pre') │
│ │ │ - PE only → │
│ │ │ PlatformHostExists- │
│ │ │ Unbound (3.C-pre'') │
│ │ │ - none → NoHost (3.C) │
│ │ │ │
│ │ │ 5b. Decision tree paths │
│ │ │ for create-new (3.C): │
│ │ │ - Platform getOrCreate │
│ │ │ (fast-path, no admin) │
│ │ │ - Custom D365_ProjectHost│
│ │ │ (fast-path, admin) │
│ │ │ - Manual app install │
│ │ │ - Manual PPAC create │
│ │ └────────────────────────────┘
▼ │
┌──────────────┐ │
│ 3. Check │ │ environmentSku ≠ Platform (Custom Host)
│ Default- │ ▼
│ Custom- │ ┌──────────────────────────────┐
│ Pipelines- │ │ 4. Use the Custom Host │
│ HostEnv- │ │ directly. Skip default- │
│ ForTenant │ │ custom check. │
└──────┬───────┘ └──────────────────────────────┘
│
┌───┴────────────────────────┐
│ admin set a custom default │
│ │
▼ ▼
┌─────────────────┐ ┌─────────────────────────┐
│ default == │ │ default != │
│ org setting? │ │ org setting │
│ │ │ │
│ → use default │ │ → CannotRedirect ERROR │
│ custom │ │ (user locked to PE │
└─────────────────┘ │ but admin overrode │
│ at tenant scope) │
└─────────────────────────┘
if no admin default → use PE
来源:ProjectHostProvider.tsx 第 100–213 行(获取 orgSetting → 获取 defaultCustomPipelinesHost → 解析 finalProjectHostEnvironmentId)。
此技能不会做什么
以下是刻意排除的目标(每项都基于硬性约束或破坏性影响范围,详见下文的 设计约束):
- 不会在不提示的情况下预配任何内容。 任何会创建环境或将源环境绑定到主机的操作,都必须先获得用户明确确认,并回显租户名称和租户 ID。PE 是租户单例且管理员不可删除,因此 Phase 4.0 的调用前确认关卡是防止为错误租户预配的主要缓解措施。
getOrCreate端点具有幂等性——如果在已有 PE 的租户上调用,它会返回现有 PE,而不是创建重复项。 - 不会调用
Force Link来将环境重新绑定到其他主机。Force Link 具有破坏性(创建者会失去对之前主机中现有管道的访问权限),并由单独的确认关卡控制,只有用户明确说“rebind”时才能触发。 - 不会更改租户级别的
DefaultCustomPipelinesHostEnvForTenant设置。 该设置近乎不可逆(之前默认主机中的现有管道将无法访问,详见learn.microsoft.com/power-platform/alm/set-a-default-pipelines-host)。不在范围内。 - 不会删除环境。
- 不会直接写入
ProjectHostEnvironmentId。 绑定通过有文档说明的 Pipelines 流程建立(在主机中创建deploymentenvironment记录);直接写入组织设置会绕过验证。
身份验证策略:优先使用 PAC,BAP 作为回退(--source auto)
读取侧检测(Phase 2 的解析顺序、环境列表、按 ID 查找环境)默认使用 --source auto:
- 如果提供了 BAP 令牌,先尝试使用 BAP 获取环境列表或读取环境(数据更丰富,包含
lastModifiedTime、permissions、tenantId)。 - 遇到 HTTP 401 或 403 时,通过
pac-bap-shim.js回退到pac admin list --json。PAC 在 BAP 上有自己的第一方客户端 ID 授权,Az CLI 不一定会继承这些授权(已于 2026-04-28 验证:即使受众声明正确,演示租户D365DemoTSCE53051106仍会拒绝用于 BAP 的 Az 令牌)。 - 如果完全没有提供 BAP 令牌,则直接使用 PAC。
PAC shim 会返回 BAP 格式的数据;下游代码(SKU 筛选、排序、分类)保持不变。PAC 不提供的字段(tenantId、lastModifiedTime、permissions、isManaged)会返回 null,这些字段对主机检测都不是关键字段。PAC 也不会显示 Platform Host(PE),因为 pac admin list 不包含 Platform SKU 环境;检测 PE 需要使用 --source bap 并提供有效的 BAP 令牌。
写入侧操作(provision-custom-host.js 中的环境创建 POST 请求、生命周期操作轮询)仍需要 BAP。即使环境列表调用失败,具有正确受众的 Az CLI 令牌通常仍可用于这些操作,因为 BAP RP 对操作和读取实施了不同的策略。如果 provision-custom-host.js 返回 401,用户必须在目标租户中注册服务主体(或使用 PPAC UI 回退路径 4.C)。
设计约束
- 选择 PE 时必须进行 JIT 预配——无论 PE 是现有的还是刚刚预配的。 来自
ProjectHostProvider.tsx(第 232–240 行)的注释:“在 Platform Environment 场景中,用户可能尚未在其中完成预配,因此 BAP 无法发现它。因此,我们将使用通过 getOrCreate 调用检索到的组织 URL 来发起此首个请求,以便触发用户 JIT。” 当第 2 阶段检测到现有 PE 且用户接受它(第 3.A 阶段)时,或者当第 4.0 阶段通过getOrCreate预配新的 PE 时,第 5 阶段针对instanceApiUrl发起的WhoAmI调用会在任何后续主机操作之前触发 JIT。(对于 Custom Host 路径,调用方按设计已拥有访问权限。) CannotRedirect是真实的终止状态,并非理论上的边缘情况。当ProjectHostEnvironmentId(源环境中的组织设置)指向 PE,而DefaultCustomPipelinesHostEnvForTenant(管理员租户设置)指向其他位置时,就会发生这种情况。技能必须检测到此情况并将其作为特定错误呈现——若静默跳过,就会将管道操作路由到错误的主机。- 仅限管理员的 Custom Host 快速路径。 PPAC 的
New custom host流程受DeploymentHubCreatePipelinesHostForAdminsOnly限制,仅向 Global / Power Platform / Dynamics 管理员显示(eng.ms 文档)。BAP 环境创建 API 也需要相应的权限。非管理员会收到 403;该技能会在预检中提示用户,并优雅地回退到手动路径。 - BAP 环境 GET 返回的 404 含义不明确。 对于已删除、已禁用、没有 PE 和无访问权限,都会返回 404,且不会区分(
PowerPipelines_PE_Knowledge.md§6.A)。我们绝不会将单个 404 视为“不存在主机”——采取操作前,我们会通过 list-environments 和组织设置进行交叉验证。 - 每个环境一次只能绑定一个主机。 重新绑定需要使用 Force Link,这会对先前的主机造成破坏性影响。超出范围(见非目标)。
- 该技能在用户 OAuth 上下文中运行——使用与 Power Apps UI 相同的范围和受众。BAP 调用使用
https://service.powerapps.com/受众。
getOrCreate的幂等性——BAPgetOrCreate端点具有幂等性(现有 PE 返回 200 +provisioningState === 'Succeeded';新 PE 返回 202 + 生命周期操作)。第 4.0 阶段会利用这一点——对已拥有 PE 的租户调用 getOrCreate 是安全的,只会返回现有 PE。provision-platform-host.js帮助程序通过返回值中的alreadyExisted: true | false标志体现这一差异(记录在docs/alm/last-host-check.json遥测块中,字段为platformHostAlreadyExisted)。
先决条件
- PAC CLI 已登录(
pac env who执行成功) - Azure CLI 已登录(
az account show执行成功) - 源 Dataverse 环境 URL(如果从 Power Pages 项目中调用,则从
powerpages.config.json读取;否则作为参数传入) - 对于第 4 阶段的管理员专属路径:调用方具有 Global / Power Platform / Dynamics 管理员权限(如果缺少权限,技能会检测到并清晰地呈现 403)
阶段
阶段 1 — 检测前置条件并收集租户上下文
在本阶段开始时预先创建所有任务。
要创建的任务:
- “检查本地缓存并检测前置条件”
- “按解析顺序查找主机”
- “向用户确认操作”
- “执行所选路径”
- “即时预配并验证主机”
- “写入主机检查产物”
步骤:
-
本地缓存快速路径。 如果
docs/alm/last-host-check.json存在,且Date.now() - Date.parse(checkedAt) < cacheMaxAgeMs(默认 24h;可通过--cacheMaxAgeHours配置):- 为缓存的
finalHostEnvUrl源获取HOST_TOKEN。 - 执行一次低成本探测:
GET {finalHostEnvUrl}/api/data/v9.0/solutions?$filter=uniquename eq 'msdyn_AppDeploymentAnchor'&$select=version&$top=1(一次往返即可证明 Pipelines 已安装并获取版本)- 200 → 缓存有效。根据缓存文件设置
RESOLUTION。设置ACTION_TAKEN = "none"。跳过阶段 2–5;跳转到阶段 6,并附上“已复用缓存的主机”摘要。 - 404 / 403 / 超时 / 网络错误 → 缓存已过期或不再可访问。继续执行步骤 1(完整解析)。不要失败——环境删除或权限变更后缓存过期是预期情况。
- 200 → 缓存有效。根据缓存文件设置
- 如果文件缺失、格式错误、早于
cacheMaxAgeMs,或包含ready: false→ 继续执行步骤 1。 - 如果传入
--no-cache,则完全跳过此步骤(用于 CI / 冒烟测试)。
- 为缓存的
-
运行
verify-alm-prerequisites.js:node "${PLUGIN_ROOT}/scripts/lib/verify-alm-prerequisites.js"获取
.envUrl(devEnvUrl)、.token(DEV_TOKEN)、.userId、.tenantId、.organizationId。如果身份验证失败,使用脚本的修复提示停止。 -
运行
detect-project-context.js(非致命错误——此技能在 Power Pages 项目之外也有效):node "${PLUGIN_ROOT}/scripts/lib/detect-project-context.js"获取
.siteName和.solutionManifest,用于消息提示。 -
获取 BAP 令牌(受众与 Dataverse 不同):
az account get-access-token --resource "https://service.powerapps.com/" --query accessToken -o tsv保存为
BAP_TOKEN。阶段 2 和阶段 4 中的所有 BAP/providers/Microsoft.BusinessAppPlatform/...调用都会使用此令牌。
3a. 解析租户显示名称(单次尝试,尽力而为)。阶段 1.4 和阶段 4.0 会同时显示易读的租户名称和租户 GUID,以便用户验证目标租户。从 Microsoft Graph 组织终结点获取:
az rest --method GET --url "https://graph.microsoft.com/v1.0/organization?$select=id,displayName" --resource "https://graph.microsoft.com/" --query "value[0].displayName" -o tsv
保存为 TENANT_DISPLAY_NAME。若发生任何失败(无 Graph 权限、网络错误、多租户歧义),则回退为 TENANT_DISPLAY_NAME = null 并继续——阶段 1.4 / 4.0 提示会在显示名称为 null 时仅显示租户 GUID。
🚦 关卡(同意 · ensure-pipelines-host:1.4.tenant-identity): 在检测任何主机之前,显示租户显示名称、租户 GUID 和开发环境 URL。这是第一道错误租户防护。取消后会正常退出,不会进行任何 BAP/Dataverse 调用。
-
租户身份确认关卡。 通过
AskUserQuestion显示以下信息:“即将检查租户 {TENANT_DISPLAY_NAME}(
{tenantId})、组织{organizationId}、开发环境{devEnvUrl}的 Pipelines 主机配置。是否继续?1. 是 / 2. 取消”(如果
TENANT_DISPLAY_NAME为 null,则省略加粗的租户名称部分,并以租户 GUID 开头。)这是用于防止对错误租户执行操作的同意关卡中的第一道。
阶段 1.5——以当前 Pipelines 主机文档为依据
参考:
${PLUGIN_ROOT}/references/alm-docs-grounding.md
此步骤最多用时约 30 秒。如果 MCP 搜索 / 获取出错,记录一行说明并继续——此技能必须支持离线运行。
- 使用查询
Power Platform Pipelines host environment Platform Host Custom Host运行microsoft_docs_search。 - 通过
microsoft_docs_fetch并行获取https://learn.microsoft.com/en-us/power-platform/alm/pipelines(以及最多一个关于主机设置、默认自定义主机配置或管理员角色要求的相关页面)。 - 提取一段摘要,说明 Microsoft Learn 当前对 Platform Host 与 Custom Host 权衡、解析顺序(组织数据库设置 → BAP 环境元数据 → 租户默认值)以及管理员角色要求的说明。与此技能自身的 Resolution order 部分和
${PLUGIN_ROOT}/references/cicd-pipeline-patterns.md进行比较;标记任何差异(例如新增 Platform-Host SKU、默认自定义主机设置名称变更、新的租户策略控件)。 - 使用该摘要为阶段 2 及后续阶段的决策提供依据。不要悄悄更改技能行为——在阶段 3(向用户确认操作)之前,将任何差异作为提示告知用户。
阶段 2——按解析顺序查找主机
此阶段为只读操作。它会生成一个 RESOLUTION 对象,供用户确认阶段进行分支判断。
此阶段与 ProjectHostProvider.tsx 完全一致。该钩子中的 useState 变量映射到 RESOLUTION 中的字段:
| TS 变量 | 我们的字段 |
|---|---|
orgSetting.orgDbOrgSettingValue | orgSettingHostEnvId |
initialProjectHostEnvironmentId | (相同) |
isInitialHostPlatformEnvironment | isPlatform |
defaultCustomPipelinesHost | tenantDefaultCustomHostEnvId |
finalProjectHostEnvironmentId | finalHostEnvId |
projectHostStatus | status |
步骤:
-
组织设置探测(对应 tsx 第 103 行的
useGetOrgDbOrgSetting('ProjectHostEnvironmentId'))。新建辅助程序check-env-host-binding.js:POST {devEnvUrl}/api/data/v9.0/GetOrgDbOrgSetting Authorization: Bearer {DEV_TOKEN} Body: { "SettingName": "ProjectHostEnvironmentId" }SettingValue为空 → 当前没有绑定。跳至步骤 4。- 不为空 → 保存为
orgSettingHostEnvId。继续步骤 2。
-
通过 BAP 解析环境(对应 tsx 第 483 行的
useGetEnvironmentByName(initialProjectHostEnvironmentId))。新建辅助程序resolve-env-by-id.js:GET https://api.bap.microsoft.com/providers/Microsoft.BusinessAppPlatform/environments/{envId}?api-version=2020-06-01&$expand=properties.linkedEnvironmentMetadata,properties.permissions Authorization: Bearer {BAP_TOKEN}- 200 → 获取
environmentSku、displayName、linkedEnvironmentMetadata.instanceApiUrl、linkedEnvironmentMetadata.instanceUrl。设置RESOLUTION.isPlatform = (environmentSku === 'Platform')。 - 404 → 先消除歧义再采取行动(约束 5)。运行
list-tenant-envs.js(步骤 5),并检查环境是否在列表中:- 如果在列表中 → 用户无权访问 → 设置
RESOLUTION.status = "PermissionDenied",告知用户并停止。 - 如果不在列表中 → 环境确实已删除 / 已禁用 → 设置
RESOLUTION.status = "OrgSettingStale",建议用户清除ProjectHostEnvironmentId并重新运行,然后停止。
- 如果在列表中 → 用户无权访问 → 设置
- 403 → 设置
RESOLUTION.status = "PermissionDenied",然后停止。
- 200 → 获取
-
如果
isPlatform === true,则对应默认自定义租户设置检查(tsx 第 148–213 行)。复用现有的discover-pipelines-host.js:node "${PLUGIN_ROOT}/scripts/lib/discover-pipelines-host.js" \ --envUrl "{devEnvUrl}" --token "{DEV_TOKEN}" --userId "{userId}"found: false→ 租户没有管理员默认自定义主机。finalHostEnvId = orgSettingHostEnvId(PE)。设置RESOLUTION.status = "AvailableUsingPlatformHost"。found: true且hostEnvUrl与orgSettingHostEnvId匹配 → 管理员默认值与组织设置一致。finalHostEnvId = orgSettingHostEnvId。设置RESOLUTION.status = "AvailableUsingCustomHostByAdminDefault"。found: true且hostEnvUrl与orgSettingHostEnvId不匹配 →CannotRedirect(约束 3)。设置RESOLUTION.status = "CannotRedirect",记录两个 URL。停止并显示具体错误消息——只有管理员才能解决此问题。
如果
isPlatform === false(Custom Host):直接使用。finalHostEnvId = orgSettingHostEnvId。设置RESOLUTION.status = "AvailableUsingCustomHost"。跳过步骤 4–5,转至步骤 6。 -
未配置组织设置 → 在宣布为 NoHost 前进行租户范围搜索。 源环境未绑定,但租户中可能已有可用的自定义主机(由管理员创建,或由本技能在其他项目中的先前运行创建)。提供创建选项前,始终先盘点。
-
租户环境盘点 + Pipelines 存在性探测(用于决策 — 为
RESOLUTION.status提供依据)。新增辅助工具list-tenant-envs.js:步骤 5a — 列出环境:
GET https://api.bap.microsoft.com/providers/Microsoft.BusinessAppPlatform/environments?api-version=2020-06-01&$expand=properties.linkedEnvironmentMetadata Authorization: Bearer {BAP_TOKEN}对每个环境,记录
{ envId, displayName, environmentSku, instanceApiUrl, isManaged, hasDataverse: !!instanceApiUrl }。步骤 5b — 探测每个环境是否存在 Pipelines(并行,最多 10 个并发;受 SKU 筛选器和 maxEnvsToProbe 上限约束):
预筛选(避免在大型租户中探测每个环境 — 侦察发现有些租户拥有 1000 多个环境):
- 跳过没有 Dataverse 的环境(
linkedEnvironmentMetadata.instanceApiUrl == null)。 - 跳过不在
--skus中的环境(默认值:Production,Sandbox— 两者均可通过 Phase 4.B 在现有环境上安装,作为 Pipelines 应用的主机)。PE 始终报告environmentSku === 'Platform',因此无论如何都会纳入。传入--skus Production,Sandbox,Trial可纳入 Trial 环境(可通过 4.B 安装应用,但不能通过 4.A 创建环境 — 拥有 Trial 许可证的租户在创建环境时会收到NotEnoughCapacity_HasTrialLicense)。 - 按
lastModifiedTime降序排序。 - 限制为
--maxEnvsToProbe(默认值为 50;使用 10 并发,可在 5 秒内覆盖典型租户的 80% 情况)。 - 如果达到上限仍未找到主机,则显示警告:
"已扫描 N 个环境,共 M 个(筛选条件:Production+Sandbox,按 lastModifiedTime 排序)。传入 --maxEnvsToProbe N+ 或 --skus Production,Sandbox,Trial 以扩大范围。"
探测查询(单次查询同时完成存在性检查和版本捕获):
GET {instanceApiUrl}/api/data/v9.0/solutions?$filter=uniquename eq 'msdyn_AppDeploymentAnchor'&$select=uniquename,version&$top=1 Authorization: Bearer {HOST_TOKEN-per-env} OData-Version: 4.0 OData-MaxVersion: 4.0- 200 且
value.length === 1→ 已安装 Pipelines。记录value[0].version作为pipelinesSolutionVersion。标记为自定义主机候选项(如果environmentSku === 'Platform',则标记为 PE)。 - 200 且
value.length === 0→ 未安装 Pipelines。如果有 Dataverse 且调用方有访问权限,则标记为eligible-for-app-install。 - 404 → 实体存在,但 Dataverse 无法访问 / URL 错误;将其视为非候选项。
- 403 → 调用方无访问权限;不要将其计为主机候选项。仅为警告添加到
inaccessibleEnvs[]。 - 超时 / 5xx → 记录到警告中,并将其视为非候选项;不要重试。
为什么不使用
deploymentpipelines?$top=0? 即使主机已正确安装,Dataverse 也会对deploymentpipelines上的$top=0返回 HTTP 400“Invalid value for $top query option”(已于 2026-04-28 在pascalepipelineshost.crm.dynamics.com上验证)。solutions?$filter=uniquename eq 'msdyn_AppDeploymentAnchor'查询才是正确且低开销的探测方式 — 只需一次往返;使用$top=1无需担心速率限制,而且还会返回我们需要的版本。步骤 5b 的令牌策略:针对每个不同的环境源,获取一个 HOST_TOKEN,使用
az account get-access-token --resource "{origin}",并在本次运行中缓存在内存里。除非资源不存在(环境已删除),否则令牌获取本身不应失败;若资源不存在,则跳过。步骤 5 的输出(
RESOLUTION.candidates):{ existingCustomHosts: [{ envId, instanceApiUrl, displayName, environmentSku, pipelinesSolutionVersion }, ...], existingPlatformHost: { envId, instanceApiUrl, displayName, ... } | null, // at most one eligibleForAppInstall: [{ envId, instanceApiUrl, displayName }, ...], inaccessibleEnvs: [{ envId, displayName, reason: "403" | "timeout" }, ...] }判定逻辑(设置
RESOLUTION.status):existingCustomHosts.length === 1→RESOLUTION.status = "AvailableUnboundCustomHost"。暂时将finalHostEnvId / finalHostEnvUrl设为该主机(Phase 3.C-pre 将确认)。existingCustomHosts.length > 1→RESOLUTION.status = "MultipleUnboundCustomHosts"。Phase 3.C-pre' 将询问用户选择。existingCustomHosts.length === 0ANDexistingPlatformHost !== null→RESOLUTION.status = "PlatformHostExistsUnbound"。Phase 3.C-pre'' 提供使用 PE 的选项(无需创建;租户中已有 PE)。注意:源环境实际绑定到 PE,是通过文档所述的 Pipelines 流程完成的,即setup-pipeline在deploymentenvironments中注册源环境,与 Custom Host 的方式相同。- 全部为零 →
RESOLUTION.status = "NoHost"。进入 Phase 3.C 决策树(新建)。
**自行检测说明:**此技能之前的运行创建的 Custom Host(Phase 4.A
D365_ProjectHost模板)会在模板中安装 Pipelines 解决方案。它们会以与管理员创建的主机完全相同的信号出现在existingCustomHosts中。我们不需要为自行创建的主机添加标记——检测到已安装 Pipelines 解决方案的信号就足够了。 - 跳过没有 Dataverse 的环境(
-
探测 Pipelines 解决方案版本(仅当
finalHostEnvId已知且步骤 5b 尚未填充该值时)。在已解析主机的 instanceUrl 上:GET {hostEnvUrl}/api/data/v9.0/solutions?$filter=uniquename eq '{PIPELINES_SOLUTION_UNIQUE_NAME}'&$select=version,installedon Authorization: Bearer {HOST_TOKEN}其中,
HOST_TOKEN是通过az account get-access-token --resource针对{hostEnvUrl origin}获取的。该解决方案的确切唯一名称尚待确定——请参阅 待解决事项。当前假设:
msdyn_AppDeploymentAnchor。记录PIPELINES_SOLUTION_VERSION。如果查询结果为空(非 PE 主机上缺少解决方案)→RESOLUTION.status = "HostWithoutPipelines"——进入 Phase 3.D 路径。
向用户报告结果:
"租户
{tenantId}的主机状态:{RESOLUTION.status}。{与状态对应的摘要行。}"
Phase 3 — 与用户确认操作
根据 RESOLUTION.status 分支。每个分支最终都会进入 "proceed to Phase 5"(主机已可用)或 "Phase 4 with chosen path"。
3.A — 状态 AvailableUsingCustomHost / AvailableUsingCustomHostByAdminDefault / AvailableUsingPlatformHost
主机已就绪。确认后跳过后续步骤。
"找到现有主机:
{finalHostEnvUrl}({RESOLUTION.status},Pipelines 解决方案版本 v{PIPELINES_SOLUTION_VERSION})。使用此主机?
- 是——继续验证
- 取消"
- 是 → 设置
ACTION_TAKEN = "none",跳转到 Phase 5。 - 取消 → 退出。
3.B — 状态 CannotRedirect
状态已锁定。无法继续。
"无法继续:
{devEnvUrl}上的ProjectHostEnvironmentId指向 Platform Host({orgSettingHostEnvId}),但租户管理员将DefaultCustomPipelinesHostEnvForTenant设为了其他环境({tenantDefaultCustomHostEnvId})。Pipelines UI 无法将此环境重定向到管理员选择的环境。需要 Power Platform 管理员执行以下任一操作才能解决:(a) 清除租户默认值;或 (b) 更新此环境上的组织设置。正在退出。"
停止。
3.C-pre — 状态 AvailableUnboundCustomHost(找到一个现有自定义主机)
租户中已恰好有一个安装了 Pipelines 的自定义主机。源环境尚未绑定到该主机,但在 setup-pipeline 将源环境注册到主机的 deploymentenvironments 表中时,会自动完成绑定。复用可避免创建重复主机。
“在租户
{tenantId}中找到一个现有自定义主机:
- 显示名称:
{displayName}- URL:
{instanceApiUrl}- Pipelines 解决方案: v
{pipelinesSolutionVersion}源环境
{devEnvUrl}尚未绑定到该主机——首次针对该主机运行setup-pipeline时会自动完成绑定。使用此主机吗?
- 是 — 使用现有主机(推荐;可避免重复)
- 否 — 显示创建新主机的决策树(阶段 3.C)
- 取消”
- 是 → 设置
finalHostEnvUrl/Id,ACTION_TAKEN = "reuse-existing-custom",跳转到阶段 5。 - 否 → 转到阶段 3.C 的
NoHost决策树(仍允许创建另一个主机)。 - 取消 → 退出。
3.C-pre' — 状态 MultipleUnboundCustomHosts(找到多个现有自定义主机)
“在租户
{tenantId}中找到 {N} 个已安装 Pipelines 的现有自定义主机。请选择一个使用,或创建新主机:
{host[0].displayName}({host[0].instanceApiUrl},Pipelines v{host[0].pipelinesSolutionVersion}){host[1].displayName}(...) ... N. ... N+1. 改为创建新的自定义主机 — 转到阶段 3.C 决策树 N+2. 取消”
- 选择 1..N → 根据所选主机设置
finalHostEnvUrl/Id,ACTION_TAKEN = "reuse-existing-custom",跳转到阶段 5。 - N+1 → 转到阶段 3.C 的
NoHost决策树。 - N+2 → 退出。
3.C-pre'' — 状态 PlatformHostExistsUnbound(PE 已存在,没有自定义主机)
租户中已存在一个 PE(任何人首次导航到 Pipelines 页面时,系统会自动预配一个)。根据范围决策,此次迭代不会自动预配 PE;但如果 PE 已存在,则会提供使用选项。
“租户
{tenantId}已有一个平台主机({instanceApiUrl},Pipelines v{pipelinesSolutionVersion})。源环境尚未绑定到该主机。使用此主机吗?
- 是 — 使用现有平台主机(幂等 — 已在此租户中预配)
- 否 — 改为创建自定义主机(阶段 3.C 决策树)
- 取消”
- 是 → 根据 PE 设置
finalHostEnvUrl/Id,ACTION_TAKEN = "reuse-existing-pe",跳转到阶段 5。(阶段 5 的 WhoAmI 调用会触发 JIT — 约束 1。) - 否 → 转到阶段 3.C 的
NoHost决策树(出于治理考虑,优先使用管理员创建的自定义主机)。 - 取消 → 退出。
3.C — 状态 NoHost(主机类型决策树)
🚦 关卡(计划 · ensure-pipelines-host:3.C.host-type): 顶层主机类型提示 — 选择平台主机 / 自定义主机 / PPAC 手动操作 / 手动导出-导入策略 / 取消。决定阶段 3 和阶段 4 后续流程的走向。
触发条件: 状态为
NoHost,且上游没有hostResolution.willProvision*标记提供答案。 询问原因: 自动选择会在未经同意的情况下预配环境(PE 或自定义主机);PE 在租户中为单例,且管理员无法删除。 取消后的状态: 无 — 尚未触发任何预配。
🚦 关卡(计划 · ensure-pipelines-host:3.C.env-pick): 阶段 3.C 顶层选项“自定义主机”下的子提示 — 显示符合条件的环境列表(最多 5 个),附带角色标签(
dev env、source env、staging env、production env),并提供“其他(粘贴 URL)”选项。仅在主机类型菜单中的自定义主机分支下触发。触发条件: 用户在阶段 3.C 顶层选择“自定义主机”。 询问原因: 自动选择错误的环境会导致通过用户无意使用的主机运行 Pipelines。 取消后的状态: 无 — 尚未触发应用安装。 提示会先要求用户选择主机类型(Platform Host、Custom Host、PPAC 手动操作或取消)。选择 Custom Host 后,会显示子提示,要求选择安装方式(使用现有环境或新建环境)。Platform-Host 路径是阻力最小的默认选项,并排在首位。
跳过规则——调用方已收集答案。 从 setup-pipeline 调用此技能时,如果 docs/alm/last-pipeline.json 包含由 plan-alm Phase 2 Q4 填充的 hostResolution 块,请先检查这些标志,再显示提示:
| 上游信号 | 操作 |
|---|---|
hostResolution.willProvisionPlatform === true | 完全跳过 Phase 3.C。直接转到 Phase 4.0(预配新的 Platform Host)。4.0 中的调用前确认门仍会执行——请参阅下文 4.0“调用前确认(不可跳过)”。 |
hostResolution.chosenEnvUrl 是非空 URL | 完全跳过 Phase 3.C。设置 CHOSEN_ENV_URL = hostResolution.chosenEnvUrl,并携带该环境直接转到 Phase 4.B(适用 4.B 步骤 1 中“已选择”的路径)。根据现有路由表设置 ACTION_TAKEN(如果来源与 devEnvUrl 匹配,则设为 "user-installed-app-on-dev",否则设为 "user-installed-app")。 |
hostResolution.willProvisionCustom === true 且 chosenEnvUrl 为空 | 跳过 Phase 3.C。直接转到 Phase 4.A(预配新的 Custom Host)。4.A 中的调用前确认门仍会执行。 |
hostResolution.willUsePpac === true 且 chosenEnvUrl 为空 | 跳过 Phase 3.C。直接转到 Phase 4.C(PPAC 手动操作)。 |
| 以上均不符合 | 按下文所述执行 Phase 3.C。 |
此跳过规则的原因。 plan-alm Phase 2 Q4 的 NoHost 分支会显示相同的主机类型菜单,让用户在规划时、查看已呈现的 ALM 计划时就做出选择。执行时在 3.C 再次询问,会迫使用户对同一问题回答两次,还可能导致 Agent 将其中一个答案视为权威答案而忽略另一个(这是 2026-05-05 出现的 trial-license-409 → wrong-env-fallback 链式问题的根源)。只要存在上游信号,就应予以信任。
步骤 1:显示顶层主机类型提示。
“没有与
{devEnvUrl}绑定的 Pipelines 主机。应使用哪个环境托管 Pipelines?每个租户的 Pipelines 都位于一个环境中;管道、阶段和运行历史记录均存储在该环境中。源环境通过它进行部署。
预配 Platform Host(推荐) — 由 Microsoft 管理的 Dataverse 环境,会在租户的主地理区域中自动预配。已预装 Pipelines 应用。幂等(可安全重复运行)。约 3–5 分钟。
设置 Custom Host — Pipelines 位于你控制的 Dataverse 环境中。我们会询问你是要使用现有环境,还是创建一个全新的专用环境。
打开 PPAC 并手动创建 — 如果选项 2 不适用,可使用此备用方案。
取消 — 退出。”
顶层路由:
| 选择 | 操作 |
|---|---|
| 选项 1 | Phase 4.0(包含调用前确认门)。ACTION_TAKEN = "fast-path-platform-getorcreate"。 |
| 选项 2 | 显示下方步骤 2 中的 Custom-Host 子提示。 |
| 选项 3 | Phase 4.C。ACTION_TAKEN = "user-created-custom-ppac"。 |
| 选项 4 | 退出。 |
步骤 2:构建符合条件的环境列表并应用角色标签(仅当用户选择选项 2 → 子选项 a 时需要;仅在选择选项 2 后再按需执行)。
从 Phase 2 获取 RESOLUTION.candidates.eligibleForAppInstall[](具有 Dataverse、调用方有权访问、尚未安装 Pipelines、sku ∈ 默认筛选条件 {Production, Sandbox} 的环境;对于试用许可证租户,通过 --skus 将范围扩展为 Production,Sandbox,Trial)。
在展示列表时,为每个条目添加项目上下文标签。按 URL 源匹配(转换为小写、去掉末尾斜杠、忽略路径和查询参数)。多个标签之间用 · 连接。
| 标签 | 来源信号 |
|---|---|
dev env | 技能正在使用的环境 URL(来自调用方的 devEnvUrl / pac env who) |
source env | docs/alm/last-pipeline.json 中的 sourceEnvironmentUrl(通常与 dev env 相同) |
staging env | docs/alm/last-pipeline.json 中任一阶段的 targetEnvironmentUrl,且阶段名称匹配 /stag|test|uat/i |
production env | 阶段名称匹配 /prod/i 的任一阶段的 targetEnvironmentUrl |
没有匹配角色的环境不显示标签。
步骤 2a:对符合条件的环境列表排序并限制数量。 超过约 7–8 个选项时,AskUserQuestion 将无法使用。采用最多展示 5 个环境的限制,并按角色进行排序:
- 始终显示带角色标签的环境(最高优先级):包括步骤 2 中添加了
dev env、source env、staging env或production env标签的任何符合条件的环境。按 URL 来源去重。 - 从其余符合条件的列表中补足剩余名额,最多 5 个,顺序沿用
list-tenant-envs.js的原生排序(名称提示模式 → 管理员权限 →lastModifiedTime)。 - 始终追加一项“Other (paste URL)”作为最后一项。
单独跟踪符合条件的环境总数——当 eligible.length > 5 时,在提示中显示数量差值。
步骤 2b:空列表时的处理。 如果筛选后符合条件的列表为空,先尝试扩大 SKU 筛选范围,将 Trial 也包含在内(使用 --skus Production,Sandbox,Trial 重新调用 ensure-pipelines-host-detect.js)。如果仍为空,从 Custom-Host 子提示中移除子选项 a——仅显示 b(新建)和 c(返回)。打印 SKU 筛选详情,以便用户覆盖筛选条件:
"没有环境符合 SKU 筛选条件(Production、Sandbox、Trial)。运行
--skus <comma-list>以进一步扩大范围,或选择b创建全新环境,或选择c返回。"
步骤 2c:显示 Custom-Host 子提示(用户选择选项 2 时):
"你希望如何设置 Custom Host?
a. 使用现有环境——在其中安装 Pipelines 应用。{eligibleCountSuffix} 从符合条件的环境中选择:
{env[0].displayName}({env[0].instanceApiUrl})—{environmentSku}— {如有标签则显示}{env[1].displayName}(...)- …(最多 5 项)
- Other (paste URL) — 可用于任何未列在此简表中的符合条件的环境
⚠ Sandbox SKU 环境在安装前会触发确认提示。
b. 创建专用全新环境——使用模板
D365_ProjectHost自动创建环境。预装 Pipelines 应用。需要 Global / Power Platform / Dynamics 管理员权限。约需 5–10 分钟。c. 返回——回到顶层主机类型菜单。"
{eligibleCountSuffix} 替换规则:
eligible.length <= 5→ 空字符串(不添加后缀;显示所有环境)。eligible.length > 5→显示前 5 个,共 {N} 个;其余 {N-5} 个符合条件的环境可通过 "Other (paste URL)" 选项访问。(开头有空格)
当用户选择“Other (paste URL)”时,预填充 URL 输入框,内容为以下命令获取的环境列表:node "${PLUGIN_ROOT}/scripts/lib/list-environments.js"(将 pac env list 解析为 JSON { displayName, environmentId, environmentUrl, uniqueName, active };当前 PAC CLI 不支持旧命令 pac env list --output json),以便用户从清单中复制粘贴或选择,而不必手动输入 URL。
更改此提示时要验证的测试场景:
- 符合条件的环境为 0 个 → 移除子选项
a(子提示仅显示b/c)。 - 符合条件的环境为 1–5 个 → 在提示中列出全部,不添加后缀。
- 符合条件的环境为 6 个或更多,且存在带角色标签的环境(dev/staging/prod)→ 首先显示所有带角色标签的环境;按排序补足其余名额;后缀显示数量差值。
- 符合条件的环境为 6 个或更多,且没有带角色标签的环境 → 按排序显示前 5 个;后缀显示数量差值。
步骤 3:根据子提示的回答进行路由。
| Selection | Phase 4 路径 | ACTION_TAKEN |
|---|---|---|
| Option 1(顶层 Platform Host) | 4.0(调用前确认,然后运行 provision-platform-host.js) | "fast-path-platform-getorcreate" |
Option 2 → 子选项 a — 从列表中选择环境,URL 与 devEnvUrl 匹配(源相同) | 4.B(跳过“选择哪个环境”子提示;将所选环境 URL 作为 CHOSEN_ENV_URL 传入) | "user-installed-app-on-dev" |
Option 2 → 子选项 a — 选择列表中的其他环境 | 4.B(跳过子提示;传入 CHOSEN_ENV_URL) | "user-installed-app" |
Option 2 → 子选项 a — “Other (paste URL)” | 4.B,将用户提供的 URL 作为 CHOSEN_ENV_URL | "user-installed-app" |
Option 2 → 子选项 a — 选择的环境满足 environmentSku === "Sandbox" | Step 4 Sandbox 确认关卡,确认后进入 4.B | (确认前暂不设置) |
Option 2 → 子选项 b | 4.A(子提示:名称、区域、管理员确认) | "fast-path-custom-d365projecthost" |
Option 2 → 子选项 c(返回) | 重新显示顶层菜单 | n/a |
| Option 3 | 4.C | "user-created-custom-ppac" |
| Option 4 | 退出 | n/a |
对于 Option 1 和子选项 b,触发调用前会经过不可跳过的调用前确认关卡,向用户显示租户身份。Option 1 → 参见 Phase 4.0“调用前确认”。子选项 b → 参见 Phase 4.A“调用前确认(不可跳过)”。
Step 4(条件性):Sandbox 确认关卡。
如果在子选项 a 中选择的环境满足 environmentSku === "Sandbox",则显示:
"⚠
{displayName}是一个 Sandbox 环境(environmentSku: Sandbox)。文档说明,Power Platform Pipelines 在 Production 环境中运行。Sandbox 应该也能正常运行,但不在受支持的环境矩阵中。
是否继续?
- 是,继续并自行承担风险
- 选择其他环境
- 取消"
- 是 → 使用 Sandbox 环境继续执行 4.B。根据上述开发环境匹配规则设置
ACTION_TAKEN。 - 选择其他环境 → 重新显示子选项
a的环境列表(保留选取器的其余状态)。 - 取消 → 返回顶层 Phase 3.C 提示。
遥测说明。 将 "user-installed-app" 和 "user-installed-app-on-dev" 分开后,我们便能在推出后了解用户将 Pipelines 与开发环境放在一起,还是使用独立环境。新的 "fast-path-platform-getorcreate" 值可以帮助我们衡量用户对全新、摩擦最低路径的采用情况;工件中的 telemetry.platformHostAlreadyExisted 标志可区分幂等的已存在结果(200)和新预配结果(202)——如果我们发现 Phase 4.0 经常返回 200,这将有助于调整 Phase 2.5 的 --maxEnvsToProbe 默认值。
3.D — 状态 HostWithoutPipelines(罕见)
Host 环境已存在,但缺少 Pipelines 解决方案。
"找到 Host 环境
{finalHostEnvUrl},但尚未安装 Pipelines 解决方案。是否立即通过 PPAC 安装?
- 是 — 打开 PPAC 并安装(手动引导)
- 否 — 退出"
- 是 → 使用预选环境进入 Phase 4.C。
- 否 → 退出。
3.E — 状态 OrgSettingStale / PermissionDenied
向用户显示具体故障。此问题不在自动修复范围内。建议手动清理。
Phase 4 — 执行所选路径
4.0 — 快速路径:通过 getOrCreate 创建平台主机
🚦 关卡(确认 · ensure-pipelines-host:4.0.pre-call): 调用 BAP
getOrCreate平台主机接口之前,先在请求正文中回显租户身份。即使上游hostResolution.willProvisionPlatform === true,也不可跳过。PE 是每个租户唯一且租户管理员无法删除的环境——此关卡是防止操作错误租户的主要措施。触发条件: 进入阶段 4.0(从 3.C 或上游路由而来)。 询问原因: PE 创建在了错误的租户中;租户管理员无法删除。 取消后的状态: 无——尚未开始创建。
这是创建主机所需操作最少的路径。使用 D365_1stPartyAdminApps + Platform 请求正文调用幂等的 BAP getOrCreate 接口。与用户在 make.powerapps.com → Pipelines 页面点击“Get started”时触发的调用相同——我们直接调用即可。规范见 useGetOrCreatePlatformEnvironment.v4.ts。新建的辅助程序为 provision-platform-host.js。
无子提示词。 BAP 会选择租户的主区域和默认显示名称;无需管理员角色。
通过 AskUserQuestion 进行调用前确认——不可跳过的单一确认关卡:
“即将为租户 {TENANT_DISPLAY_NAME}(
{tenantId})创建平台主机。平台主机是 Microsoft 在租户主区域中托管的 Dataverse 环境。每个租户只有一个,且操作幂等——如果已有平台主机,将返回现有主机。创建新的平台主机大约需要 3–5 分钟。
是否继续?1. 是 / 2. 取消”
(当 TENANT_DISPLAY_NAME 为 null 时,省略加粗的租户名称部分,直接显示租户 GUID。)
即使上游 hostResolution.willProvisionPlatform === true,也仍需运行此关卡的原因——与 4.A 的调用前关卡理由相同:此处会回显确切的租户身份,这是用户发现操作租户错误的最后机会。PE 是每个租户唯一且租户管理员无法删除的环境;此关卡是主要防范措施。实现细节(BAP 接口、请求正文格式)有意不放在面向用户的提示中,而是保存在此 SKILL.md 和 provision-platform-host.js 源码中。
调用:
node "${PLUGIN_ROOT}/scripts/lib/provision-platform-host.js" \
--bapToken "{BAP_TOKEN}" \
--correlationId "{uuid v4}"
响应处理(由辅助程序负责,但路由决策在此处进行):
200+properties.provisioningState === 'Succeeded'→ 租户已有 PE(幂等路径)。辅助程序返回{ status: 'Succeeded', alreadyExisted: true, instanceApiUrl, ... }。设置RESOLUTION.finalHostEnvUrl、finalHostEnvId、RESOLUTION.isPlatform = true、actionTaken = "fast-path-platform-getorcreate"、telemetry.platformHostAlreadyExisted = true。继续进入阶段 5。*防御性说明:*这表示阶段 2.5 的枚举未发现 PE(可能是因为--maxEnvsToProbe达到上限后停止,尚未查询到 PE,也可能是阶段 2 和 4.0 之间发生了竞态)。记录一条调试级说明;不要报错。202+ Location → 辅助程序轮询,直到provisioningState === 'Succeeded',然后返回{ status: 'Succeeded', alreadyExisted: false, instanceApiUrl, ... }。设置相同字段,并将telemetry.platformHostAlreadyExisted设为false。403→ 辅助程序抛出异常,错误内容原样保留 BAP 响应正文;建议运行az logout && az login。提供切换到选项 2/3/4 的备用方案。(不要复用 4.A 中要求管理员权限的说明——getOrCreate不要求管理员权限。)- 其他
4xx/5xx→ 辅助程序抛出异常;显示错误,并询问用户是重试还是切换路径。
成功后: 设置 RESOLUTION.isPlatform = true(这样阶段 5 会采用 JIT 分支——约束 1)。继续进入阶段 5。
4.A — 快速路径:通过 D365_ProjectHost 模板创建自定义主机
🚦 门禁(同意 · ensure-pipelines-host:4.A.pre-call): 在发起 Custom Host 创建调用前,回显 BAP 环境创建请求正文(区域 + 显示名称 + 模板)。即使上游
hostResolution.willProvisionCustom === true,也不得跳过。此为发现错误租户或错误区域预配的最后机会。2026-05-05 修复的 Bug 正是因跳过此门禁导致的。触发条件: 进入阶段 4.A;已收集子提示词(显示名称、区域、管理员确认)。 询问原因: Custom Host 可能在错误的租户或区域中预配;会消耗 Azure 容量配额;还可能将环境归属到错误的组织。 取消后状态: 无事发生——尚未启动预配。
🚦 门禁(同意 · ensure-pipelines-host:4.sandbox-confirm): 如果选定的安装目标环境具有
environmentSku=Sandbox,则显示 Sandbox 限制警告,并要求在安装前明确再次确认。每次选中 Sandbox SKU 环境时触发一次。触发条件: 阶段 4.B / 阶段 3.C 子选项 a——选定的环境为 Sandbox SKU。 询问原因: 未经同意就在 Sandbox 环境中安装 Pipelines——Sandbox 环境可能会因不活跃而被删除,且容量较低;Pipelines 可能会意外中断。 取消后状态: 无事发生——尚未启动应用安装。
使用 D365_ProjectHost 模板的标准环境创建 API(eng.ms-documented;PPAC 的 New custom host 内部使用相同模板)。新增辅助程序 provision-custom-host.js。
通过 AskUserQuestion 收集 API 调用前的子提示:
- 显示名称(默认建议:
"{tenant displayName} Pipelines Host") - 区域(默认使用 BAP
tenant端点提供的租户主区域;可选择覆盖) - 确认调用者是管理员——单个 AskUserQuestion:"您是此租户中的全局管理员 / Power Platform 管理员 / Dynamics 管理员吗?是 / 否 / 不确定"。如果选择“否”或“不确定”,建议使用路径 4.B/4.C 并回退。
通过 AskUserQuestion 进行调用前确认——不可跳过的第二道同意门禁:
"即将为租户
{tenantId}调用POST https://api.bap.microsoft.com/providers/Microsoft.BusinessAppPlatform/environments?api-version=2021-04-01,请求正文如下:{ "location": "{region}", "properties": { "displayName": "{display name}", "environmentSku": "Production", "databaseType": "CommonDataService", "linkedEnvironmentMetadata": { "templates": ["D365_ProjectHost"] } } }将预配一个已预装 Pipelines 应用的 Custom Host(约需 5–10 分钟)。是否继续?1. 是 / 2. 取消"
此门禁必须始终执行。 不得因为 plan-alm Q4 已收到“继续使用 PP Pipelines”的确认,或因为上游
hostResolution.willProvisionCustom === true,或因为用户刚刚在管理员确认中回答“是”,就跳过此门禁。plan-alm Q4 和管理员确认针对的是策略;此门禁则会回显确切的 API 调用正文(URL、区域、租户、模板),是用户发现错误租户或错误区域预配的最后机会。2026-05-05 修复的 Bug 正是因为 Agent 将 plan-alm 预确认视为已涵盖此门禁而出现的。
调用:
POST https://api.bap.microsoft.com/providers/Microsoft.BusinessAppPlatform/environments?api-version=2021-04-01
Authorization: Bearer {BAP_TOKEN}
Content-Type: application/json
x-ms-correlation-id: {uuid v4}
{
"location": "{region}",
"properties": {
"displayName": "{display name}",
"environmentSku": "Production",
"databaseType": "CommonDataService",
"linkedEnvironmentMetadata": { "templates": ["D365_ProjectHost"] }
}
}
响应处理:
202+Location标头 +Retry-After标头 → 轮询 Location URL,直到生命周期操作完成。200+ 立即返回的正文(环境创建时很少见)→ 捕获 URL。- 初始 POST 返回
403→ 停止,并提示 “Custom Host 快速通道需要 Global / Power Platform / Dynamics 管理员权限。如果你在 Dataverse 环境中拥有 system-admin 权限,建议使用路径 2(在现有环境中安装 Pipelines 应用);如果你可以请求管理员协助,建议使用路径 3(PPAC UI)。” 无缝回退到 4.B / 4.C。 409且错误代码与容量相关(例如NotEnoughCapacity_HasTrialLicense_ProvisionEnvironment、NotEnoughCapacity、NotEnoughCapacity_OrganizationDisabled、EnvironmentCapacityExceeded)→ 回退到路径 4.B 前,先提示用户选择回退 SKU。许可证 / 容量限制通常仅适用于所请求的 SKU;较小的 SKU(Sandbox、Developer、Trial)通常可在同一租户中成功创建,并生成功能完全相同的 Pipelines 主机(Pipelines 应用可安装在任何 SKU 上,区别仅在许可证分配)。请参阅下面的子步骤“4.A — SKU 回退提示”。只有在用户拒绝回退 SKU,或回退尝试也失败后,才重新进入阶段 3.C / 路径 4.B。- 其他 4xx / 5xx → 显示错误,并询问用户是重试还是切换路径。切换到 4.B 时,遵循上文相同的“使用原始符合条件的环境列表”规则。
轮询:
GET {Location}
Authorization: Bearer {BAP_TOKEN}
间隔 = Retry-After 秒数(默认 10s)。超时时间 = 15min(可配置)。收到每个响应时,读取 provisioningState(和/或操作的 state 字段——执行期间确认):
Creating/InProgress→ 继续轮询Succeeded→ 完成;捕获instanceApiUrl、name(环境 GUID)、displayNameFailed/Canceled→ 显示错误并停止
**成功后:**设置 RESOLUTION.finalHostEnvUrl、finalHostEnvId、instanceApiUrl、actionTaken = "fast-path-custom-d365projecthost"。继续阶段 5。
4.A — SKU 回退提示词(容量错误补救)
当 env-create 返回与容量相关的 409 错误时,用户的租户没有足够的可用许可证/容量来满足所请求的 SKU,但可能有足够的容量来使用较小的 SKU。请清楚说明此限制,并在建议采用 Path 4.B 之前提供可选的回退 SKU:
-
读取错误正文。提取:
error.code(例如NotEnoughCapacity_HasTrialLicense_ProvisionEnvironment)error.message(BAP 提供的面向用户的说明,例如 "Trial licenses are limited to creating Trial environments only.")
-
构建回退 SKU 列表。默认顺序为 Production(推荐)→ Sandbox → Developer → Trial,并移除刚刚失败的 SKU。每个 SKU 都有不同的注意事项:
SKU 建议使用的情况 需要说明的注意事项 Production默认首选 无 — 文档中指定的 Pipelines 主机 SKU Sandbox租户有订阅,但没有多余的 Production 容量 "Sandbox SKU 可用于 Pipelines,但文档中将其标记为非生产环境。Microsoft 可能会对托管在 Sandbox 上的应用采用不同的 SLA。" Developer个人开发者租户 "Developer SKU 为单用户 SKU。其他团队成员无法通过此主机进行部署。仅当这是个人/仅供开发使用的 ALM 设置时才使用。" Trial试用许可证租户(没有其他可行选项时) "Trial 环境将在 30 天后过期,除非进行转换。到期时需要重新预配 Pipelines 主机。" -
告知用户此限制并显示提示:
"自定义主机预配失败,错误代码为
{error.code}:{error.message}Pipelines 应用在任何 SKU 上的安装方式都相同 — 只有许可证分配不同。你可以使用较小的 SKU 重试 env-create,也可以改为在现有 env 上安装 Pipelines 应用(Path 4.B)。"
🚦 关卡(计划 · ensure-pipelines-host:4.A.sku-fallback): env-create 遇到容量错误 — 使用其他 SKU 重试、回退到 Path 4.B(在现有 env 上安装),或取消。
AskUserQuestion(动态构建选项列表 — 移除刚刚失败的 SKU,并在说明中附上注意事项):问题 标题 选项 使用不同的 SKU 重试 env-create? SKU 回退 (每个剩余 SKU 对应一个选项,并在说明中注明注意事项)、回退到 Path 4.B(在现有 env 上安装 Pipelines 应用)、取消 -
根据回答进行分支处理:
- 选择了 SKU:使用新 SKU 替换正文中的原 SKU,再次发出 4.A 调用前确认关卡(不可跳过 — 请参见上文“调用前确认”),然后使用
--environmentSku <picked>再次调用provision-custom-host.js。如果仍然遇到容量错误,则再次显示提示,并移除下一个 SKU。连续两次遇到容量错误后,停止提供 SKU 回退选项,并转到 Path 4.B。 - Path 4.B:根据下文的“原始符合条件的 env 列表”规则转到 4.B。
- 取消:正常退出。
始终丢弃 409 响应正文中返回的任何 env GUID — 预配失败,因此该 GUID 要么不代表可用的 env,要么只是附带产生的结果。Path 4.B 必须仅使用 Phase 2 符合条件的 env 清单。
- 选择了 SKU:使用新 SKU 替换正文中的原 SKU,再次发出 4.A 调用前确认关卡(不可跳过 — 请参见上文“调用前确认”),然后使用
4.B — 在现有 env 上安装 Pipelines 应用(自动化)
此前,此路径需要手动点击进入 PPAC。截至 2026-05-08,此流程已完全自动化:install-pipelines-app.js 会调用 BAP applicationPackages/install endpoint(与 PPAC“安装应用”按钮背后的 API 相同);当 BAP 返回 401/403/5xx 时,则会回退到 pac application install。
子提示:选择目标 env(已选择 env 时跳过):
-
如果环境已被选定(已设置
CHOSEN_ENV_URL,无论是来自阶段 3.C 选项 2 → 子选项a,还是来自针对hostResolution.chosenEnvUrl的上游跳过规则):跳过子提示 — 直接带着所选环境进入安装步骤。按阶段 3.C 步骤 3 的路由表设置ACTION_TAKEN(当CHOSEN_ENV_URL的来源与devEnvUrl匹配时设为"user-installed-app-on-dev",否则设为"user-installed-app")。否则(旧入口路径 — 调用方未经过 3.C 直接调用了 4.B,或者经由 4.A 的失败回退到达此处):展示子提示 “哪个环境将托管 Pipelines?(来自阶段 2 清单的自动检测环境):”,选项为来自
RESOLUTION.candidates.eligibleForAppInstall[]的符合应用安装条件列表,外加“其他(粘贴 URL)”。当从 4.A 的 409 试用许可证回退到达时,符合条件列表仍然适用 — 丢弃 409 响应体中出现的任何环境 GUID。
环境 GUID 健全性检查。 传给安装助手的 GUID 必须来自阶段 2 中枚举的所选环境的
name字段(或者对于用户粘贴的 URL,来自pac env list)。切勿替换为来自 4.A 失败响应、来自不同运行批次的docs/alm/last-host-check.json,或来自任何其他状态路径的 GUID。如果你无法有把握地确定 GUID,请在调用助手之前,通过AskUserQuestion展示环境显示名称 + URL + GUID,请用户确认。
调用前确认门(不可跳过 — 与阶段 4.0 / 4.A 相同的理由):
“即将在租户 {TENANT_DISPLAY_NAME}(
{tenantId})的{displayName}({instanceApiUrl})上安装 Power Platform Pipelines 应用。这与 PPAC 的 Install app 按钮执行的是同一次安装 — Agent 直接调用 BAP API,因此无需手动点击。耗时约 2–5 分钟。是否继续? 1. 是 / 2. 取消”
调用助手:
node "${PLUGIN_ROOT}/scripts/lib/install-pipelines-app.js" \
--bapToken "{BAP_TOKEN}" \
--envId "{envId}" \
--instanceApiUrl "{instanceApiUrl}" \
--hostToken "{HOST_TOKEN}" \
--correlationId "{uuid}"
HOST_TOKEN 是针对所选环境的来源获取的(az account get-access-token --resource "{instanceApiUrl origin}")并传递进去,以便助手的安装后验证探测(solutions?$filter=uniquename eq 'msdyn_AppDeploymentAnchor')能够端到端运行,而无需该技能串联单独的验证步骤。
响应处理(委托给助手,但路由决策在此处):
{ status: 'Succeeded', alreadyInstalled: true, installPath: 'cached' }— 该包已安装在此环境上(幂等路径;在 4.B 中罕见,因为阶段 2.5 本应已将该环境归类为主机,但这是防御性处理)。按上述 dev-env-match 规则设置ACTION_TAKEN,并继续到阶段 5。{ status: 'Succeeded', alreadyInstalled: false, installPath: 'bap' }— BAP 的applicationPackages/installPOST 成功(200 同步或 202 + Location 轮询)。按上述 dev-env-match 规则设置ACTION_TAKEN,并继续到阶段 5。{ status: 'Succeeded', alreadyInstalled: false, installPath: 'pac', pacFallbackReason: '...' }— BAP 返回 401/403/5xx(通常是租户中 Az → BAP 被拒绝时的令牌受众不匹配,与pac-bap-shim.js为环境枚举覆盖的场景相同);助手回退到pac application install。结果相同,在docs/alm/last-host-check.json遥测中记录回退原因。继续到阶段 5。- 助手抛出异常 — BAP 和 PAC 均失败。向用户展示合并后的错误消息。最后手段回退(手动):打印 PPAC URL
https://admin.powerplatform.microsoft.com/manage/environments/{envId}/dynamics365apps以及四个手动步骤(Install app → Power Platform Pipelines → Next → 接受条款 → Install),并附带一个后续的 “已完成 — 继续” AskUserQuestion。确认后,针对该环境 URL 运行verify-host-readiness.js,仅当检测到 Pipelines 解决方案时才继续到阶段 5。此分支应很少出现;如果频繁触发,请使用助手的合并错误提交 issue,以便我们调整 BAP/PAC 路径。 成功时: 记录 helper 的pipelinesSolutionVersion(在传入instanceApiUrl+hostToken时填充)。设置RESOLUTION.finalHostEnvUrl/Id。继续执行第 5 阶段。
4.C — 引导式手动操作:PPAC New custom host
🚦 关卡(进度 · ensure-pipelines-host:4.C.ppac-done): 外部系统等待关卡——用户必须手动完成 PPAC“New custom host”流程,然后返回确认。之后,Skill 会轮询 BAP,查找新环境。
触发条件: 进入第 4.C 阶段;已打印 PPAC URL。 要求确认的原因: Skill 会在未确认新环境是否存在的情况下继续执行;随后
setup-pipeline会因没有主机而失败。更糟的是,如果 PPAC 创建流程进行到一半时用户取消了关卡,主机绑定就会处于部分建立状态(租户中会存在一个以D365_ProjectHost为模板的环境)。 取消后的状态:host-binding——租户中可能会留下手动创建的环境;用户必须通过 PPAC 清理。
-
打印:
https://admin.powerplatform.microsoft.com/deployments和以下说明:“单击‘New custom host’ → 填写名称(建议:‘{tenant} Pipelines Host’)→ 选择租户主区域中的 Production 环境 → 创建。预配需要 5–10 分钟。”根据 eng.ms 文档:“面板默认会选择 Production 环境类型。还必须添加 Dataverse……此处会隐藏模板和示例应用选项,因为此场景使用特定的组织模板。”(该模板为
D365_ProjectHost,与路径 4.A 自动执行时使用的模板相同。) -
使用双选项
AskUserQuestion:“已完成——已启动预配” / “取消”。 -
用户确认后,每隔 15 秒轮询一次 BAP
list-tenant-envs.js,查找带有 Pipelines 标记的新环境。检测到后,记录 URL,设置actionTaken = "user-created-custom-ppac"。继续执行第 5 阶段。
通用:超时处理
每条路径默认超时为 15 分钟(可配置)。超时后,询问用户要延长(再延长 15 分钟)、切换路径还是退出。
第 5 阶段 — JIT 预配(仅限检测到 PE 时)并验证主机
无论 finalHostEnvUrl 是如何获取的,始终运行。
JIT 步骤(仅当检测并选择了现有 PE 时运行——RESOLUTION.isPlatform === true): 根据约束 2,调用用户可能很久以前就在 PE 中完成了 JIT 预配,也可能从未完成。为了确保交接前主机上的身份验证正常,我们会针对 instanceApiUrl 发出一次 WhoAmI 请求。(Custom Host 路径也需要执行相同步骤,但下方 verify-host-readiness.js 的第 1 步会自然满足此要求——创建环境的管理员必然拥有访问权限;对于 user-installed-app,用户已拥有环境访问权限。)
GET {instanceApiUrl}/api/data/v9.0/WhoAmI
Authorization: Bearer {HOST_TOKEN}
其中,HOST_TOKEN = az account get-access-token --resource "{instanceApiUrl origin}"。
预期结果:返回 200 和 UserId。如果首次调用返回 404 / 403,则每隔 5 秒重试一次,最多 60 秒——JIT 传播有时是异步的。
验证(verify-host-readiness.js)——按顺序检查:
WhoAmI返回UserId(证明身份验证正常,并在检测到 PE 的情况下触发 JIT)。GET {hostEnvUrl}/api/data/v9.0/solutions?$filter=uniquename eq 'msdyn_AppDeploymentAnchor'&$select=version&$top=1返回一行 → 记录PIPELINES_SOLUTION_VERSION。(一次查询即可同时检查是否已安装 Pipelines 并记录版本;Dataverse 会以 400 拒绝deploymentpipelines?$top=0。)
与 MIN_PIPELINES_VERSION(scripts/lib/alm-thresholds.js 中的常量)进行比较。
- 所有检查均通过 →
READY = true。 - 解决方案版本低于最低要求 → 发出警告(非致命)。
- 任意检查失败 → 停止并提供针对该项检查的修复方法。
第 6 阶段 — 写入主机检查产物
写入 docs/alm/last-host-check.json(如果 docs/alm/ 目录不存在,请先创建 — node -e "require('fs').mkdirSync('docs/alm',{recursive:true})";或者在项目外调用时使用 --outputPath):
{
"schemaVersion": 2,
"checkedAt": "2026-04-28T...",
"tenantId": "...",
"sourceEnvUrl": "{devEnvUrl}",
"sourceEnvId": "...",
"resolutionStatus": "AvailableUsingPlatformHost" | "AvailableUsingCustomHost" | "AvailableUsingCustomHostByAdminDefault" | "AvailableUnboundCustomHost" | "MultipleUnboundCustomHosts" | "PlatformHostExistsUnbound" | "CannotRedirect" | "NoHost" | "OrgSettingStale" | "PermissionDenied" | "HostWithoutPipelines",
"finalHostEnvUrl": "...",
"finalHostEnvId": "...",
"finalHostInstanceApiUrl": "...",
"isPlatformHost": true | false,
"tenantDefaultCustomHostEnvId": "...",
"actionTaken": "none" | "reuse-existing-custom" | "reuse-existing-pe" | "fast-path-platform-getorcreate" | "fast-path-custom-d365projecthost" | "user-installed-app" | "user-installed-app-on-dev" | "user-created-custom-ppac",
"pipelinesSolutionVersion": "9.x.y.z",
"ready": true,
"warnings": [
"Pipelines solution version 9.0.0.1 is below recommended 9.1.0.0 — RetrieveDeploymentPipelineInfo may not be available."
],
"candidates": {
"existingCustomHosts": [
{ "envId": "...", "instanceApiUrl": "...", "displayName": "...", "pipelinesSolutionVersion": "..." }
],
"existingPlatformHost": null,
"eligibleForAppInstall": [
{ "envId": "...", "instanceApiUrl": "...", "displayName": "..." }
],
"inaccessibleEnvs": [
{ "envId": "...", "displayName": "...", "reason": "403" }
]
},
"telemetry": {
"correlationId": "{uuid passed to env-create, if applicable}",
"platformHostAlreadyExisted": true | false
}
}
telemetry.platformHostAlreadyExisted— 仅当actionTaken === "fast-path-platform-getorcreate"时存在。当 BAPgetOrCreate返回 200 +Succeeded时为true(幂等 — 租户已有 PE);当调用返回 202 并且我们轮询至完成时为false。此字段可用于衡量第 2.5 阶段的枚举漏掉现有 PE 的频率,从而调整--maxEnvsToProbe的默认值。
架构版本升级(1 → 2): 新增
candidates.*块,用于记录租户范围内的枚举结果。缓存快速路径(第 1 阶段步骤 0)无论schemaVersion为何都会读取finalHostEnvUrl;candidates块仅供参考,有助于调试/重新运行决策。旧版 v1 文件仍可读取 — 缺失的字段均视为“尚未枚举”。
actionTaken枚举值定义:
"none"— 本次运行前已确定主机;未执行安装/预配(第 3.A 阶段路径或缓存快速路径)。"reuse-existing-custom"— 用户选择了租户中已有的未绑定 Custom Host(第 3.C-pre 或 3.C-pre')。"reuse-existing-pe"— 用户选择了现有的 Platform Host(第 3.C-pre'')。"fast-path-platform-getorcreate"— 第 4.0 阶段调用了 BAPgetOrCreate端点。telemetry.platformHostAlreadyExisted标志可区分幂等复用现有主机(200)与新预配主机(202)。"fast-path-custom-d365projecthost"— 第 4.A 阶段通过环境创建 API 预配了新的 Custom Host。"user-installed-app"— 第 4.B 阶段在现有环境中安装了 Pipelines,且该环境不是开发环境。"user-installed-app-on-dev"— 第 4.B 阶段在开发环境本身安装了 Pipelines(URL 源与devEnvUrl匹配)。与"user-installed-app"的遥测数据分开记录,以便了解用户将 Pipelines 与开发环境共置的频率。"user-created-custom-ppac"— 第 4.C 阶段 — 用户通过 PPAC UI 创建了环境;流程在创建后检测到了该环境。
此文件由 setup-pipeline 和 deploy-pipeline 使用。
记录技能使用情况:
参考:
${PLUGIN_ROOT}/references/skill-tracking-reference.md
按照参考中的技能跟踪说明记录此技能的使用情况。使用 --skillName "EnsurePipelinesHost"。
刷新 ALM 计划(如果存在):
node "${PLUGIN_ROOT}/scripts/lib/refresh-alm-plan-data.js" \
--projectRoot "." \
--phase ensure-pipelines-host \
--render
这会根据刚写入的 docs/alm/last-host-check.json 更新 planData.hostResolution(仅主机 — 尚无管道),并移除运行前的 NoHost 风险,然后重新渲染 docs/alm-plan.html。请在此处执行,而不只是放在 setup-pipeline 第 7 阶段 — 主机安装可能耗时 18 分钟以上并跨越会话边界,因此延迟刷新可能导致计划始终无法反映主机。当 docs/.alm-plan-data.json 不存在时(独立运行,不属于 ALM 计划),该辅助程序会返回 ok:false,并作为软性无操作处理。集中式 PostToolUse 钩子也会作为后备机制协调计划,但在源头刷新可立即确保渲染后的计划保持最新。
展示摘要表:
| 字段 | 值 |
|---|---|
| 租户 | {tenantId} |
| 源环境 | {devEnvUrl} |
| 解析状态 | {resolutionStatus} |
| 最终主机 | {finalHostEnvUrl} |
| 主机类型 | Platform / Custom |
| 执行的操作 | {actionTaken} |
| Pipelines 版本 | {pipelinesSolutionVersion} |
| 警告 | {warnings} |
如果 actionTaken !== "none":
"下一步: 运行
/power-pages:setup-pipeline,以此主机创建第一个管道。"
与现有技能集成
setup-pipeline(✅ 已接入)
setup-pipeline/SKILL.md Phase 1 第 4 步调用 ensure-pipelines-host-detect.js(编排器包装程序),并根据 resolutionStatus 分支:
AvailableUsing*→ 直接使用finalHostEnvUrl,继续执行。*Unbound*/NoHost→ 委派给/power-pages:ensure-pipelines-host(此技能)进行复用或预配;当docs/alm/last-host-check.json显示ready: true后恢复执行。CannotRedirect/OrgSettingStale/PermissionDenied→ 停止,并显示相应的管理员处理消息。
Phase 3 中原有的“手动向用户询问主机 URL”后备方案已移除 — HOST_ENV_URL 始终由 Phase 1 填充,或者技能会在进入 Phase 3 前停止。
deploy-pipeline
无变化。deploy-pipeline 从 setup-pipeline 写入的 docs/alm/last-pipeline.json 中读取 hostEnvUrl。
plan-alm(✅ 已接入)
plan-alm Phase 1 第 12 步调用 ensure-pipelines-host-detect.js,并将结果存储为 HOST_RESOLUTION(当 PIPELINE_DONE = true 时跳过)。Phase 2 Q4 根据 HOST_RESOLUTION.status 分支。生成的 docs/alm-plan.html 包含一张“Pipelines 主机”卡片,并且(当 willEnsureDuringExecution: true 时)在“设置管道”检查清单步骤下添加一个子项。请参阅 references/cicd-pipeline-patterns.md 和 plan-alm-update-PLAN.md 规范。
威胁模型 — 内置缓解措施
| 风险 | 此技能中的缓解措施 |
|---|---|
| 混淆代理 / 静默预配 | Phase 1.4 租户身份验证关卡 + Phase 3 明确选择 + Phase 4.A 调用前确认,回显确切的请求正文 |
| 重复创建主机 | Phase 2.5 在 Phase 3 提供创建选项前,对整个租户进行枚举,查找现有的 Custom Host。Phase 3.C-pre / 3.C-pre' 会显示现有主机以供复用。用户必须明确拒绝复用(选择“否”)才能进入创建新主机流程。 |
| 本地缓存过期 → 使用已删除的主机 | Phase 1.0 缓存快速通道会先通过实时 solutions?$filter=uniquename eq 'msdyn_AppDeploymentAnchor'&$top=1 探测进行验证,然后才复用 — 404/403/超时会回退到完整的 Phase 2 |
| 404 含义不明确 → 意外操作 | Phase 2.2 消歧规则 — 绝不根据单个 404 采取操作;需通过 list-tenant-envs 进行交叉验证 |
| 在错误的租户中预配 | Phase 1.4 回显 tenantId、organizationId 和开发环境 URL;Phase 4.A 在调用前的正文中回显 tenantId |
CannotRedirect 被掩盖 | Phase 2.3 会明确检测此情况并显示特定错误后停止,而不是继续向错误主机写入 |
| JIT 预配未命中 → 静默的 404 链 | Phase 5 会先针对 instanceApiUrl 调用 WhoAmI,然后才进行任何其他主机操作(检测到现有 PE 时适用) |
| 解决方案过期 → 静默失败 | Phase 5 读取 PIPELINES_SOLUTION_VERSION,如果低于 MIN_PIPELINES_VERSION 则发出警告 |
| 非管理员尝试使用 Custom Host 快速通道 | Phase 4.A 会预先提示需要管理员角色;遇到 403 时会平稳回退到 Phase 4.B / 4.C |
| 意外创建租户单例 PE | Phase 1.4 租户身份验证关卡(回显租户显示名称 + 租户 ID + 开发环境 URL)+ Phase 4.0 调用前确认关卡(在调用前立即再次回显租户显示名称 + 租户 ID)。getOrCreate 端点具有幂等性 — 对已有 PE 的租户调用时,会返回现有 PE(200 + alreadyExisted = true),而不是创建重复项。 |
| 遥测数据泄漏 | 所有探测结果都保留在 docs/alm/last-host-check.json 中;关联 ID 是我们生成的标准 x-ms-correlation-id UUID;update-skill-tracking.js 只写入计数器 + 编写工具名称 |
| 权限边界 | 所有路径都在用户 OAuth 上下文中运行;403/401 会触发停止,并显示“此操作需要 X 管理员角色”的消息;不会尝试提升权限 |
| 速率限制 | 每条路径的总超时时间为 15 分钟;遵循 BAP 返回的 Retry-After;最小轮询间隔为 10 秒 |
| 强制链接不可逆 | 不在范围内 — 请参阅此技能不会执行的操作 |
关键决策点(等待用户确认)
- Phase 1.4 — 确认租户身份(只读意图)
- Phase 3.A/B/C-pre/C-pre'/C-pre''/C/D/E — 根据
RESOLUTION.status作出分支决策 - Phase 3.C-pre — 是否复用唯一一个现有 Custom Host(是 / 否 / 取消)
- Phase 3.C-pre' — 从多个现有 Custom Host 中选择,或创建新的
- Phase 3.C-pre'' — 使用现有 PE,或改为创建 Custom Host
- Phase 3.C — 选择新建路径(4 个选项:Custom-fast、app-install、PPAC-UI、取消)
- Phase 4.A — 自我确认是否具有管理员角色(否 / 不确定 → 回退到 4.B / 4.C)
- Phase 4.A — 调用前确认,回显准确的 API 请求正文
- Phase 4.B/C — 用户执行 UI 步骤 → 通过“Done — proceed”确认完成
- Phase 5 — 如果 Pipelines 解决方案版本低于最低要求,则确认已知悉警告
错误处理
verify-alm-prerequisites.js失败 → 停止并提供修复步骤(az login、pac auth create)- BAP 令牌获取失败 → 停止;建议执行
az logout && az login - BAP env GET 返回 404 → 执行消歧流程(Phase 2.2 回退方案)
- BAP env GET 返回 403 → 设置
RESOLUTION.status = "PermissionDenied",显示租户 ID 和环境 ID,然后停止 - Custom Host 环境创建返回 403 → 无缝回退到 4.B(app install)或 4.C(PPAC UI)
- Custom Host 环境创建返回其他 4xx → 记录状态和正文,询问用户是重试还是切换路径
- 生命周期操作轮询超时(默认 15 分钟)→ 询问用户:延长(再等 15 分钟)/ 切换路径 / 退出
RetrieveSetting返回 404 → 视为“没有管理员默认 Custom Host”(当前discover-pipelines-host.js的行为)GetOrgDbOrgSetting返回 404 → 视为“未绑定”(与 UI 行为一致)- 对主机执行 WhoAmI,在 JIT 重试后仍返回 403 → 可能是
CannotRedirect竞争条件或确实存在权限问题;显示两条错误消息并停止 - 用户声称已安装(4.C)后,
verify-host-readiness.js报告Pipelines tables not found→ 请用户重新检查 PPAC,或延长轮询时间
进度跟踪表
| 任务主题 | activeForm | 描述 |
|---|---|---|
| 检查本地缓存并检测前置条件 | 正在检查缓存并检测前置条件 | Phase 1.0:读取 docs/alm/last-host-check.json;如果缓存仍有效,则通过 solutions?$filter=uniquename eq 'msdyn_AppDeploymentAnchor'&$top=1 探测 finalHostEnvUrl——返回 200 时复用并跳转到 Phase 6。否则运行 verify-alm-prerequisites.js + detect-project-context.js;获取 BAP_TOKEN;进入租户身份确认关卡 |
| 按解析顺序查找主机 | 正在按解析顺序查找 | 获取 GetOrgDbOrgSetting('ProjectHostEnvironmentId');执行 BAP env GET;如果是 Platform,则检查租户默认 Custom Host;检测 CannotRedirect;如果没有组织绑定,则通过 list-tenant-envs.js 并行列出并探测租户范围内的环境(最多 10 个),查找现有 Custom Host 和 PE;分类为 AvailableUnboundCustomHost / MultipleUnboundCustomHosts / PlatformHostExistsUnbound / NoHost |
| 与用户确认操作 | 正在与用户确认操作 | 按 resolutionStatus 分支;对于 AvailableUnboundCustomHost / MultipleUnboundCustomHosts / PlatformHostExistsUnbound,先显示复用提示;只有用户拒绝复用时,才进入 NoHost 新建流程;收集 Phase 4.A 的明确同意,并在调用前回显请求正文 |
| 执行所选路径 | 正在执行所选路径 | 运行路径 A(Custom D365_ProjectHost 环境创建)/B(手动安装应用)/C(PPAC 新建 Custom Host);按 Retry-After 间隔轮询生命周期操作;遵守 15 分钟超时限制 |
| JIT 配置并验证主机 | 正在验证主机 | 对 instanceApiUrl 执行 WhoAmI(仅在检测到现有 PE 时触发 JIT);探测 deploymentpipelines 表;探测 Pipelines 解决方案版本;设置 READY 标志 |
| 写入主机检查记录 | 正在写入主机检查记录 | 将完整的 RESOLUTION + actionTaken + correlationId 写入 docs/alm/last-host-check.json;更新技能跟踪记录;显示摘要;建议接下来运行 /power-pages:setup-pipeline |
待处理事项(执行阶段解决)
这些事项需要在真实环境中验证:
Pipelines 解决方案的✅ 已于 2026-04-28 解决:通过对 SIP 主机(uniquename。pascalepipelineshost.crm.dynamics.com)进行实时查询,确认值为msdyn_AppDeploymentAnchorv9.1.2026034.260325188。已存储为PIPELINES_SOLUTION_UNIQUE_NAME常量。MIN_PIPELINES_VERSION。 在测试最低实际使用的解决方案版本中哪些 Pipelines 功能会失效后设置。初步保守估值:"9.0.0.0"。✅ 已于 2026-04-28 解决:通过 BAP 环境列表实时查询(测试租户中有 1000 个环境)确认:即使使用list-tenant-envs.js中的 Custom Host 检测标记。$expand=properties.linkedEnvironmentMetadata,也不会返回linkedEnvironmentMetadata.templates。必须逐个环境进行 Dataverse 探测。探测查询已修正为solutions?$filter=uniquename eq 'msdyn_AppDeploymentAnchor'&$select=version&$top=1(一次调用即可检查是否存在并获取版本)。PE 检测仍然很简单,可通过environmentSku === 'Platform'判断。- 创建环境时的
BapApiVersion值。 已为 Pegasus / BAP-RP / Neptune 接入D365_ProjectHost模板(根据 eng.ms PR 列表)。PPAC UI 在环境操作中使用2021-04-01。执行期间通过从New custom host捕获新的 HAR 来确认。 - 生命周期操作的响应结构。 需要确认 Location URL 返回的是
{ properties: { provisioningState } }、{ state },还是两者都有。需通过 HAR 捕获确认。 - 租户主区域的发现方式。 4.A 的默认区域。选项包括:
BAP_TOKEN声明(tid、xms_tcdt?)、BAP/tenant?api-version=2021-04-01端点,或现有环境中的properties.azureRegionHint。执行期间选择一种。 - 冷启动租户测试环境。 需要一个此前未使用过 Pipelines 的租户,以验证端到端流程。个人 MSDN 租户即可。
- BAP 环境列表是否支持对
linkedEnvironmentMetadata.templates进行筛选? 如果支持,我们可以预先筛选出使用D365_ProjectHost创建的环境,并在阶段 2.5b 中跳过逐个环境的 Dataverse 探测。待测试。如果不支持,则使用并发数受限的逐个环境探测。 - 环境列表响应是否实际包含
linkedEnvironmentMetadata.templates? 如果包含,即使不支持服务器端筛选,我们也可以在客户端轻松筛选。如果不包含,逐个环境的 Dataverse 探测就是唯一的判断依据。通过捕获新的 BAP 环境列表 HAR 来验证,并确保包含$expand=properties.linkedEnvironmentMetadata查询参数(resolve-env-by-id.js已使用该参数)。 逐个环境探测的速率限制预算。✅ 已于 2026-04-28 部分解决:Microsoft 内部测试租户有 1000 个环境(526 个 Production sku,453 个 Sandbox)。即使并发数为 10,逐个探测全部环境也太慢。已采用多层筛选:- 预先筛除没有 Dataverse 的环境(在测试租户中约筛除 3 个环境,影响较小)
- 按
--skus筛选(默认Production;始终包含 PE) - 按
lastModifiedTime降序排序 - 限制为
--maxEnvsToProbe(默认 50;并发数为 10 时,耗时约 5 秒) - 达到上限且未找到主机时,显示“已扫描 N 个,共 M 个(筛选条件:...)”警告 待处理:针对典型客户租户(5–50 个环境)验证上限默认值——在这些租户中应该不会产生额外开销。
cacheMaxAgeMs默认值。 24 小时是初步估值。如果开发租户中的主机频繁变化,可能需要缩短。使其可通过--cacheMaxAgeHours配置,并添加文档说明。
脚本
所有脚本均位于 plugins/power-pages/scripts/lib/ 下(或按说明存放)。每个脚本都只负责一项功能,使用 Node,解析 argv,使用 validation-helpers.js 处理 HTTPS,并将 JSON 输出到 stdout。
| 脚本 | 用途 | 参数 | 输出 |
|---|---|---|---|
check-env-host-binding.js | 在源环境上执行 POST GetOrgDbOrgSetting('ProjectHostEnvironmentId') | --envUrl、--token | { bound, hostEnvId } |
resolve-env-by-id.js | BAP 环境 GET,使用 $expand=properties.linkedEnvironmentMetadata,properties.permissions;遇到 401/403 时回退到 PAC shim。 | --source(auto|bap|pac;默认 auto)、--bapToken(bap 模式必需)、--envId | { found, envId, instanceUrl, instanceApiUrl, displayName, environmentSku, isManaged, permissions, sourceUsed, fallbackReason, ... };遇到 404 时返回 { found: false, reason: "404-ambiguous" };PAC 列表中找不到时返回 { found: false, reason: "not-in-pac-list" } |
pac-bap-shim.js | 将 pac admin list --json 封装成 BAP 格式的环境列表。当 Az→BAP 返回 401 时用作回退方案(某些租户会拒绝 Az CLI 的第一方客户端 ID 访问 BAP)。从 EnvironmentUrl 推导 instanceApiUrl;将 PAC 的 Type 映射到 BAP 的 environmentSku。无法显示 PE(PAC 不会列出 Platform SKU 环境);仅 BAP 提供的字段(tenantId、lastModifiedTime、permissions)返回 null。 | 不适用(CLI 会打印所有环境) | BAP 格式的环境数组 |
list-tenant-envs.js | 列出环境并逐个探测是否存在 Pipelines(solutions?$filter=uniquename eq 'msdyn_AppDeploymentAnchor'&$select=version&$top=1),最多并行 10 个。预筛选:SKU、是否具有 Dataverse,以及可选的 --includeName。排序:名称提示模式 + 管理员权限 + 最近活动时间。上限:--maxEnvsToProbe(默认 30)。 | --source(auto|bap|pac;默认 auto)、--bapToken(bap 模式必需)、--skus(默认 Production;始终包含 PE)、--maxEnvsToProbe、--maxConcurrency、--probeTimeoutMs、--includeName、--firstHitWins | { existingCustomHosts[], existingPlatformHost, eligibleForAppInstall[], inaccessibleEnvs[], inaccessibilityBreakdown, totalEnvsInTenant, envsAfterFilter, envsProbed, hitProbeCap, earlyExitOnFirstHit, probeDurationMs, sourceUsed, fallbackReason } |
verify-host-readiness.js | WhoAmI(证明身份验证有效,并在 PE 检测路径中触发 JIT)→ 按条件筛选解决方案以查找 msdyn_AppDeploymentAnchor(一次调用即可同时检查是否存在及其版本) | --hostEnvUrl、--hostToken、--skipWhoAmI(可选)、--minPipelinesVersion(可选) | { ready, pipelinesSolutionVersion, checks: { whoami, solutions }, warnings[] } |
provision-custom-host.js | 使用 D365_ProjectHost 模板、Production SKU 和 CommonDataService databaseType 执行 BAP 环境创建 POST。按 Retry-After 间隔通过 Location 标头轮询生命周期操作。处理 properties.provisioningState / state / status.code 格式。对暂时性 5xx 错误重试。遇到 401/403 时提供明确指引。 | --bapToken、--displayName、--region、--correlationId(可选)、--timeoutSec(可选,默认 900)、--apiVersion(可选,默认 2021-04-01) | { status, envId, instanceUrl, instanceApiUrl, displayName, environmentSku, provisioningState, durationSec, correlationId, pollAttempts, locationHeader } |
provision-platform-host.js | 使用 D365_1stPartyAdminApps 模板和 Platform SKU 执行 BAP getOrCreate。在 200 幂等路径(返回现有 PE)时返回 alreadyExisted: true,在 202 + Location 轮询路径(新建环境)时返回 alreadyExisted: false。供 Phase 4.0 使用。 | --bapToken、--correlationId(可选)、--timeoutSec(可选,默认 600)、--apiVersion(可选,默认 2021-04-01)、--bapBase(可选) | { status, alreadyExisted, envId, instanceUrl, instanceApiUrl, displayName, environmentSku, provisioningState, durationSec, correlationId, pollAttempts, locationHeader } |
install-pipelines-app.js | 在现有环境中发现并安装 Power Platform Pipelines 应用程序包。解析方式:BAP applicationPackages LIST + /install POST → 200 同步完成 / 202 + Location 轮询;遇到 401/403/5xx 时回退到 PAC CLI(pac application install --environment-id ... --application-list msdyn_AppDeploymentAnchor)。将安装 POST 返回的 409 视为幂等(已安装)。可选的安装后 Dataverse 验证探测。供 Phase 4.B 使用(于 2026-05-08 替代手动点击 PPAC 完成安装)。 | --bapToken、--envId、--instanceApiUrl(可选,用于验证)、--hostToken(可选,用于验证)、--no-pac-fallback(可选;默认启用 PAC 回退)、--correlationId(可选)、--timeoutSec(可选,默认 600)、--apiVersion(可选,默认 2022-03-01-preview)、--bapBase(可选) | { status, alreadyInstalled, installPath: 'bap'|'pac'|'cached', packageUniqueName, pipelinesSolutionVersion, durationSec, correlationId, pollAttempts, locationHeader, pacFallbackReason } |
ensure-pipelines-host-detect.js | 仅用于检测的编排包装器。运行 Phase 1.0(缓存快速路径)+ Phase 2(包含租户范围枚举的解析顺序)+ Phase 5(若找到主机则验证)。始终输出 actionTaken: "none"。供 plan-alm Phase 1 和 setup-pipeline Phase 1 使用。--source auto(默认)会先尝试 BAP;遇到 401/403 时回退到 PAC CLI shim——可用于 Az CLI 令牌被 BAP 拒绝的租户。 | --envUrl、--token、--userId、--bapToken(与 --source auto 或 pac 搭配时可选)、--source(auto|bap|pac)、--projectRoot、--cacheMaxAgeHours(可选)、--no-cache、--includeName、--maxEnvsToProbe、--skus、--minPipelinesVersion | docs/alm/last-host-check.json 架构(包含 sourceUsed、fallbackReason) |
validate-ensure-host.js(技能 scripts/) | PostToolUse Stop 钩子验证器。兼容架构 v1+v2。将 CannotRedirect / OrgSettingStale / PermissionDenied 视为已记录的终止错误状态(即使主机不可用,技能仍视为运行成功)。 | 不适用(从 stdin JSON {cwd} 读取) | 退出码 0(批准)或 退出码 2(阻止) |
| 已复用的现有辅助脚本(未作更改): |
verify-alm-prerequisites.jsdetect-project-context.jsdiscover-pipelines-host.js(租户默认自定义主机探测;由封装脚本在阶段 2 的步骤 3 中调用)update-skill-tracking.js
验证脚本
skills/ensure-pipelines-host/scripts/validate-ensure-host.js(由集中式 PostToolUse hook 在 Skill 工具上运行;由于 skills/ensure-pipelines-host/SKILL.md 存在,该技能会自动被跟踪——请参阅 scripts/lib/powerpages-hook-utils.js):
- 如果当前工作目录中没有
docs/alm/last-host-check.json→ 退出码为 0(不是 ensure-host 会话)。 - 如果存在:验证
schemaVersion === 1或2(向前兼容);必填字段均已填充(tenantId、sourceEnvUrl、resolutionStatus);对于非终端错误状态,ready === true;当ready === true时,finalHostEnvUrl已填充。 - 终端错误状态(
CannotRedirect/OrgSettingStale/PermissionDenied)在ready: false时可接受——技能已成功运行,并指出了需要手动处理或由管理员解决的状态。 candidates区块(v2)为可选项——缺少该区块不会导致验证失败。
配套的提示词 hook 会检查:
- 是否满足以下任一条件:(a) 阶段 1.0 的缓存快速路径成功,并复用了缓存的主机;或 (b) 完整流程已运行:
- 已确认租户身份门控。
- 已通过完整解析顺序确定
RESOLUTION.status,其中包括在源环境未绑定时枚举整个租户。 - 如果状态为
AvailableUnbound*、MultipleUnboundCustomHosts或PlatformHostExistsUnbound,用户已明确选择复用或新建。 - 如果状态表明需要预配(
NoHost),则已完成用户明确选择的路径(actionTaken为fast-path-*、user-installed-*或user-created-*之一——即不是"none",也不是"reuse-existing-*")。
verify-host-readiness.js报告ready: true(或已达到有记录的终端错误状态)。- 已写入
docs/alm/last-host-check.json,且其schemaVersion为2;如果运行了整个租户枚举,则已填充candidates区块。 - 已呈现摘要。