Skip to content

About

No description, website, or topics provided.

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

11 Commits

Folders and files

Repository files navigation

PPAC Environment Prototyper

An interactive and headless PowerShell wizard for bulk-provisioning Power Platform environments sized for a Dynamics 365 Finance & Operations (F&O) deployment.

Run the interactive wizard on your laptop for an 8-step flow, or drop a JSON config into CI and run the same code unattended with a service-principal credential — both paths share the same preflight, retry, structured-logging, and reporting pipeline.

At a glance

  • One script, many environments. Pick a complexity tier and get the full set (UAT, Prod, Golden, DM, Training, etc.) in one pass.
  • Interactive or headless. -ConfigPath my-run.json bypasses every prompt.
  • Enterprise auth. Service principal (secret or cert), managed identity, and Azure Key Vault secret lookup. Sovereign clouds supported via -Endpoint usgov|usgovhigh|dod|china.
  • Preflight guards. Name-collision, permission, and soft-quota checks fail fast before any environment is created.
  • Retry + resume. Exponential-backoff retry on transient errors; per-run state persisted so a partial run can be resumed with -ResumeRunId.
  • Structured logging. NDJSON log (logs/wizard-*.jsonl) alongside the classic transcript, with correlation ids and automatic redaction of known secret patterns.
  • Reporting & notifications. Emit JSON / CSV / HTML / Markdown reports; post to Teams or Slack webhooks; send SMTP email summaries.
  • Complexity tiers are XML. Standard, Medium, Complex, and a sample Custom are included; drop your own into complexities/.
  • Naming is enforced. {company}-{product}-{purposeCode}, lowercase, validated.
  • WhatIf supported. Dry-run the whole wizard without touching the Power Platform API.

Prerequisites

  • Windows PowerShell 5.1 or PowerShell 7+.
  • Network access to the Power Platform admin endpoints.
  • A Power Platform admin credential:
    • For interactive runs, a user account (the wizard prompts for sign-in).
    • For headless runs, a service principal registered with Power Platform via New-PowerAppManagementApp (one-time, by a tenant admin), plus a secret or certificate. The registration grants the SPN effective Power Platform Administrator treatment — no Entra directory role is required. See Microsoft docs and docs/ONBOARDING.md.
  • The Microsoft.PowerApps.Administration.PowerShell module — installed automatically if missing.
  • Optional: Microsoft.PowerShell.SecretManagement + a registered vault alias if you want -SecretVaultName / -SecretName for developer-workstation auth. See docs/RBAC.md → Developer-workstation auth for the Register-SecretVault recipe. Pipelines use workload identity federation instead — no SecretManagement needed there.
  • Optional: Pester 5+ and PSScriptAnalyzer for local test runs.

Two ways to use this

This project ships two entry points for two complementary jobs:

Use this When you want
Invoke-EnvironmentWizard.ps1 Interactive or one-shot headless provisioning. The classic 8-step flow plus -ConfigPath for CI. Best for first-time tenant setup.
Invoke-EpPortfolio.ps1 Declarative, ongoing reconciliation. You commit a manifest to git; the script computes diffs (-Plan), executes them (-Apply), and alerts on drift (-Drift). Best for steady-state enterprise management.

The two scripts share modules and authentication. Pick whichever matches the job; you don't have to migrate to use the new one.

When this solution fits

This tool is purpose-built for Power Platform environment provisioning for Dynamics 365 F&O deployments. It's the right pick when:

  • You're spinning up a new F&O project and need the full environment fleet (UAT, Prod, Gold, DM, Training, Hypercare, Perf, SIT, etc.) in one shot, with naming, region, template, language, currency, and SKU applied consistently — not seven portal click-paths.
  • A partner or ISV is onboarding a customer tenant and wants a repeatable, auditable scaffolding pass instead of ad-hoc portal work.
  • You're standing up a second/third project tenant that should mirror an existing one's topology but differ on company code, region, or complexity tier.
  • You need to manage a portfolio of environments over time — add a new workstream's Gold + DM, retire an old Training env, track what exists where — and want drift detection plus PR-gated change review instead of relying on admin hygiene.
  • You need audit evidence of who asked for what environment, when, and what actually got provisioned — the NDJSON log, plan artifact, state file history, and optional App Insights/OTLP telemetry cover that.
  • Your controls require separation of duties: the -Plan / PR review / -Apply pipeline lets developers propose changes without holding Power Platform admin rights directly.
  • You're doing brownfield cleanup — -Import queries the live tenant and scaffolds a manifest + state so you can bring an existing unmanaged estate under declarative control.
  • Sovereign cloud / regulated tenants. USGov, USGovHigh, DoD, and China endpoints are first-class via -Endpoint.

It is not the right pick for:

  • Deploying apps, flows, solutions, or Dataverse data into environments — that's ALM Accelerator, pac CLI solution deployment, or Power Platform Pipelines territory. This tool stops at the environment boundary.
  • Provisioning non-Power-Platform Azure resources (storage, key vault, AKS, networking). Use Terraform/Bicep for those; call this tool alongside them, not instead.
  • A general-purpose IaC replacement. It knows one resource kind well.

How this differs from Terraform

Terraform could manage Power Platform environments — there's a community provider — so the fair question is: why this and not that?

Dimension PPAC Environment Prototyper Terraform
Scope One resource kind (PP environments) modeled deeply, with F&O-specific template/SKU/complexity-tier opinions baked in. Any resource a provider exposes, modeled generically.
Language YAML/JSON manifest with group inheritance + complexity tiers; PowerShell underneath. HCL.
State Git-committed JSON (state/<tenant>.state.json). Branch policy + environment exclusive-lock is the lock. Remote state (S3/Blob/Terraform Cloud) with distributed locking.
Immutable-field changes Explicit RecreateRequired op, blocked by default, requires -AllowRecreate to opt into destroy-then-create. Implicit via replace_triggered_by / plan-time replace markers.
Delete posture Removing from the manifest = Unmanage (stop tracking, leave the live env). Destroy needs lifecycle: retired + delete: true + -AllowDelete. Removing from config = destroy, gated by prevent_destroy lifecycle.
F&O-shaped defaults Complexity tiers (Standard/Medium/Complex/Custom) encode the canonical env fleet; naming convention enforced. You write the fleet and naming yourself each time.
Brownfield onboarding -Import scans the tenant and scaffolds a manifest for you. terraform import one resource at a time.
Power Platform auth Service principal + secret/cert, managed identity, Key Vault lookup, workload identity federation in ADO — wired in, including sovereign clouds. Depends on provider; you wire it yourself.
Post-provision verification On by default: checks the env actually reached a terminal healthy state before declaring success. Not standard; you add a null_resource or external check.
Observability NDJSON log, App Insights / OTLP telemetry exporter, Teams/Slack webhooks, plan artifacts — built in. Plan/state is the artifact; observability is whatever you bolt on.
Runtime PowerShell 5.1 / 7+, no provider binary, no HCL to learn. Terraform binary + provider plugins.

The short version: If the only thing you're managing is Power Platform environments for an F&O project — and you want the F&O conventions, the import-the-existing-tenant story, the PR-plus-environment-approver governance loop, and the verification/observability wiring ready out of the box — this is a smaller, more opinionated tool that meets you where the job already is. If you're standing up a multi-resource Azure estate and PP is one small piece, keep using Terraform and call this tool alongside it for the PP slice (or don't — the community provider is fine for simple fleets).

We don't try to be Terraform. We borrow its semantics (plan/apply, hash-bound state, explicit destroy gates) because they're the right mental model for infra-as-code, but we narrow the scope so the manifest can be three lines where the HCL equivalent is thirty.

Quick start — interactive wizard

# Classic interactive dry-run
.\Invoke-EnvironmentWizard.ps1 -WhatIf

# Classic interactive real run
.\Invoke-EnvironmentWizard.ps1

# Headless run from a config file using SPN + client secret
$sec = Read-Host -AsSecureString "client secret"
.\Invoke-EnvironmentWizard.ps1 `
    -ConfigPath .\examples\config.example.json `
    -TenantId '<tenant-guid>' -ApplicationId '<app-guid>' -ClientSecret $sec

# Headless run using Key Vault-sourced secret + HTML report + Teams webhook.
# Copy examples\config.example.json to your own config first, then edit it.
# $env:TEAMS_WEBHOOK is NOT set by this project — it's whatever variable
# you populate with the Incoming Webhook URL from your Teams channel
# (Channel -> ... -> Connectors -> Incoming Webhook). You can also pass
# the URL inline. See the "Notification webhooks" section below.
.\Invoke-EnvironmentWizard.ps1 `
    -ConfigPath .\examples\config.example.json `
    -TenantId '<tenant>' -ApplicationId '<app>' `
    -SecretVaultName CorpVault -SecretName PpacSpnSecret `
    -ReportPath .\logs\report.html -ReportFormat html `
    -NotifyWebhook $env:TEAMS_WEBHOOK -NotifyWebhookFlavor teams

# Resume a partial run
.\Invoke-EnvironmentWizard.ps1 -ResumeRunId 8f2d91a...

Quick start — portfolio reconciler

First time setting this up for a new tenant? Stop here and read docs/ONBOARDING.md — it walks through every Azure AD / Power Platform / Key Vault resource you need to create before the commands below will work, and tells you where to capture each GUID. The Quick Start assumes those are already in place.

The reconciler treats a YAML/JSON manifest as the source of truth for what environments should exist, then converges the live tenant to match. State is a JSON file checked into git alongside the manifest; the apply pipeline commits updates back.

0. Scaffold a tenant manifest from the worked example

The tenants/ directory ships with tenants/contoso-prod.example.yaml — a fully-commented reference manifest. Copy it and edit:

Copy-Item .\tenants\contoso-prod.example.yaml .\tenants\your-tenant.yaml

Set metadata.tenantId to your Azure AD tenant GUID, pick a defaults.companyCode / productCode / location, and adjust the environments: list (or pick a complexity: tier — see docs/COMPLEXITIES.md). Schema: schemas/manifest.schema.json.

state/ and plans/ also ship with *.example.* reference files showing the expected shape. The pipelines skip anything matching *.example.*, so these won't ever be treated as a managed tenant. Don't use the .example. infix on your real files.

1. Plan — diff the manifest against the live tenant

.\Invoke-EpPortfolio.ps1 -Plan `
    -Manifest tenants\your-tenant.yaml `
    -OutPlan  plans\your-tenant.plan.json `
    -TenantId '<tenant-guid>' -ApplicationId '<spn-app-guid>' `
    -SecretVaultName CorpVault -SecretName PpacSpn

State path defaults to state\<manifest-basename>.state.json; omit -State unless you want it elsewhere. The first -Plan against a greenfield tenant shows N × Create.

2. Apply — execute the saved plan

.\Invoke-EpPortfolio.ps1 -Apply `
    -ApplyPlan plans\your-tenant.plan.json `
    -Manifest  tenants\your-tenant.yaml `
    -TenantId '<tenant-guid>' -ApplicationId '<spn-app-guid>' `
    -SecretVaultName CorpVault -SecretName PpacSpn

Apply refuses to run if either the manifest or the state has shifted since the plan was generated. That's the hash-binding safety net.

3. Drift — read-only plan, exit 2 on divergence

.\Invoke-EpPortfolio.ps1 -Drift `
    -Manifest tenants\your-tenant.yaml `
    -NotifyWebhook $env:TEAMS_WEBHOOK `
    -TenantId '<tenant-guid>' -ApplicationId '<spn-app-guid>' `
    -SecretVaultName CorpVault -SecretName PpacSpn

Good shape for a scheduled drift check; exit code 2 lets a pipeline surface divergence without failing the whole run.

4. Import — brownfield onboarding

.\Invoke-EpPortfolio.ps1 -Import `
    -Manifest tenants\your-tenant.yaml `
    -TenantId '<tenant-guid>' -ApplicationId '<spn-app-guid>' `
    -SecretVaultName CorpVault -SecretName PpacSpn

Queries the live tenant, writes an initial manifest to the -Manifest path and a matching state file into state/. Edit the manifest to match intent, then run -Plan to see what will change.

Scheduled rollout waves

Environments can declare when they should exist, not just that they should. Give an entry activeFrom / activeUntil (inclusive yyyy-MM-dd) in the manifest, or <ActiveFrom> / <ActiveUntil> in a complexity tier, and the reconciler works out what today makes due:

environments:
  - purposeCode: uat
    activeFrom: "2026-08-24"    # skipped until this date, then created

  - purposeCode: hypercare
    activeFrom:  "2027-04-26"   # stands up with production
    activeUntil: "2027-06-21"   # window closes 8 weeks later
    delete:      true           # and it is torn down, not just unmanaged
# Preview a future wave without waiting for it
.\Invoke-EpPortfolio.ps1 -Plan -Manifest tenants\your-tenant.yaml -AsOf 2026-10-26

Before the date it is a Skip (and does not register as drift, so a wave three months out doesn't page anyone nightly); inside the window it reconciles normally; after activeUntil it behaves exactly like lifecycle: retired. A date never destroys an environment on its own — that still needs delete: true and -AllowDelete.

.azure-pipelines/portfolio-schedule.yml runs this daily and only asks for approval on days something actually moved. Full semantics: docs/SCHEDULING.md.

Key safety gates

  • Delete ops require both lifecycle: retired (or a passed activeUntil) + delete: true in the manifest and -AllowDelete on the apply CLI.
  • RecreateRequired ops (immutable-field changes) are blocked by default; pass -AllowRecreate to opt into the destroy-then-create flow.
  • The plan stores manifestHash + stateHash; apply refuses if either has drifted between plan and apply.

Where every referenced file lives

Path Ships with? Role
tenants/contoso-prod.example.yaml ✓ example manifest Worked portfolio manifest. Copy to tenants/your-tenant.yaml.
tenants/contoso-rollout.example.yaml ✓ example manifest Same, but dated — a three-wave rollout with activeFrom / activeUntil.
complexities/wave-rollout.xml ✓ example tier Complexity tier that carries the wave calendar the manifest above inherits.
state/contoso-prod.example.state.json ✓ example state Illustrative state file showing the schema in use.
plans/contoso-prod.example.plan.json ✓ example plan Real plan artifact generated from the example manifest.
tenants/ README + example Your manifests land here.
state/ README + example Machine-managed state; created on first apply.
plans/ README + example Saved plan artifacts + optional plans/queued/ for change-window mode.
policies/ 3 example JSONs Region / SKU / demo-data policies enforced at plan time.
schemas/ ✓ JSON Schemas for config, manifest, state, plan, policy.
complexities/ ✓ XML tier definitions (Standard / Medium / Complex / Custom).
examples/config.example.json ✓ Worked headless config for Invoke-EnvironmentWizard.ps1.
examples/azure-pipelines.yml ✓ Example ADO pipeline driving the interactive wizard headlessly.

All files matching *.example.* under tenants/, state/, and plans/ are skipped by the pipelines — they're reference material, not managed tenants.

For the full runtime layout see docs/ARCHITECTURE.md; for the manifest design rationale see docs/ADR/0004-portfolio-manifest.md.

8-step interactive flow

Step What it collects
1 Company code (e.g. cto)
2 Product code (default fo, or - to omit)
3 Optional pre-sets: location, template, language, currency
4 Project complexity tier
5 Which environments to create from that tier
6 Authentication to Power Platform (+ preflight at 6.5)
7 Shared defaults validated against the live PP catalog
8 Fast path or individual review, then provision

Naming convention

{company}-{product}-{purposeCode} — all lowercase, hyphens only, no leading/trailing hyphens.

Example with company = cto, product = fo:

cto-fo-uat
cto-fo-prod
cto-fo-gold
cto-fo-dm
cto-fo-train

The product segment is dropped if you enter - in Step 2 (or set productCode: "" in the config).

Built-in complexity tiers

Tier Environments Intended for
Standard UAT, Prod Low-risk deployments
Medium UAT, Prod, Gold, Data migration Phased rollouts / migrations
Complex UAT, Prod, Gold, DM, Training, Demo, Staging (+ optional Perf, SIT) Large multi-workstream projects
Custom Deployment SIT, UAT, Gold, DM, Training, Hypercare, Prod (+ optional Perf) Partner- or ISV-led deployments with full promotion pipeline

See docs/COMPLEXITIES.md to author your own.

Config file schema

Headless runs are driven by a JSON file validated against schemas/config.schema.json. See examples/config.example.json.

Minimal shape:

{
  "companyCode": "cto",
  "productCode": "fo",
  "complexity":  "Complex",
  "includeCodes": ["uat","prod","gold"],
  "endpoint": "prod",
  "shared": {
    "location": "unitedstates",
    "template": "D365_FinOps_Finance",
    "language": "1033",
    "currency": "USD",
    "demoData": false
  },
  "overrides": { "prod": { "sku": "Production" } },
  "runtime": { "maxRetries": 3, "pollUntilFinished": false, "fastPath": true }
}

Observability

Every run produces:

  • logs/wizard-<timestamp>-<runShort>.log — classic PowerShell transcript (pass -NoTranscript to skip, -ScrubTranscript to post-process for secret redaction).
  • logs/wizard-<timestamp>-<runShort>.jsonl — structured NDJSON log. Parse with jq, ship to Log Analytics, etc.
  • logs/run-<runId>.json — run state (per-environment status, attempts, timestamps). Used by -ResumeRunId.
  • Optional report at -ReportPath in csv / json / html / markdown.

The correlation id threaded through the JSON log matches the run id; grep either file to follow a run end-to-end.

Testing

Two tiers, documented in docs/TESTING.md:

  • Tier A — cassette replay — pwsh ./tests/Run-All.ps1 runs the full Pester suite against recorded PP API fixtures. No tenant, no network. Runs in CI on every PR.
  • Tier B — lab tenant integration — tests/lab/ drives a licensed lab tenant through known-good apply → NoOp → induced drift → remediate. Opt-in via .env.local (gitignored); never runs in CI.

Install and configure in Azure DevOps

The tool's automation surface is Azure DevOps. Pipelines live under .azure-pipelines/:

File Trigger Purpose
ci.yml PR + push to main Pester + analyzer + schema + cassette smoke. No auth.
portfolio-plan.yml PR touching tenants / policies / engine Computes -Plan per changed tenant, posts plan as PR comment, fails check on any policy: block op.
portfolio-apply.yml Merge to main (default apply mode) Runs -Apply, commits state back. Gated by ppac-apply environment approvers.
portfolio-queue.yml Merge to main (change-window mode) Queues plans under plans/queued/ for a scheduled drain.
portfolio-apply-scheduled.yml Cron (default Thu 20:00 UTC) + manual Drains plans/queued/ inside the change window.

Install

  1. Import this repo into Azure Repos (Repos → Files → Import a repository).
  2. Register each pipeline you want active: Pipelines → New pipeline → Azure Repos Git → (this repo) → Existing Azure Pipelines YAML file, pointed at the YAML under .azure-pipelines/.
  3. Delete the pipeline YAML files you're not using. Running both portfolio-apply.yml and the queue + scheduled pair will race on state commits.

Configure

Five moving parts, all step-by-step in docs/RBAC.md:

  1. Service connections (Project Settings) — ppac-plan and ppac-apply, both Azure Resource Manager / Workload Identity Federation against the PP admin SPN.
  2. Library variable group ppac — PPAC_TENANT_ID and PPAC_APPLICATION_ID; link to each pipeline.
  3. Environment ppac-apply — approvers listed under Approvals and checks → Approvals.
  4. Branch policy on main — required reviewers, build-validation against portfolio-plan.yml, no direct pushes.
  5. AAD federated credentials on the SPN — one per service connection; issuer + subject values come from the service-connection wizard.

Permissions

The quick read — see docs/RBAC.md for the full matrix including grant paths:

  • Pipeline build service (<Project> Build Service (<Org>)) needs Contribute + Contribute to pull requests on the repo (to commit state back), Use on both service connections, User on the ppac variable group, and User on the ppac-apply environment.
  • The SPN must be registered with Power Platform via New-PowerAppManagementApp (one-time, run by a tenant admin), and needs a federated credential for each service connection in Entra ID. No Azure RBAC required.
  • Humans fall into two roles: code reviewers (branch-policy approvers) and environment approvers (the apply gate). They can be the same people or different sets.

Choose your apply mode

  • Apply-on-merge (default): keep portfolio-apply.yml, delete portfolio-queue.yml and portfolio-apply-scheduled.yml.
  • Change-window: keep the queue + scheduled pair, delete portfolio-apply.yml. Edit the cron in portfolio-apply-scheduled.yml to match your window.

First run

  1. Copy tenants/contoso-prod.example.yaml to tenants/<your-tenant>.yaml; commit on a branch.
  2. Open a PR. portfolio-plan.yml posts the plan as a PR comment — greenfield tenants show N × Create.
  3. Reviewers approve; merge.
  4. portfolio-apply.yml triggers; ppac-apply approvers approve; the pipeline provisions the environments and commits state/<your-tenant>.state.json back to main.

Further reading

Repository layout

Invoke-EnvironmentWizard.ps1            Entry: interactive / legacy headless wizard
Invoke-EpPortfolio.ps1                  Entry: declarative reconciler (-Plan/-Apply/-Drift/-Import)
PPACEnvironmentPrototyper.psd1/.psm1    Top-level module manifest + loader
Modules/
  EpWizard.Logging.psm1                 NDJSON logging + redaction
  EpWizard.Secrets.psm1                 Secret hygiene + transcript scrubber
  EpWizard.Helpers.psm1                 UI helpers, culture/currency/location utilities
  EpWizard.Naming.psm1                  Naming convention enforcement
  EpWizard.Complexity.psm1              XML complexity loader
  EpWizard.Config.psm1                  Legacy headless config loader/exporter
  EpWizard.Auth.psm1                    Interactive / SPN / MI / Key Vault auth
  EpWizard.Policy.psm1                  Declarative policy engine
  EpWizard.Manifest.psm1                Portfolio manifest loader + inheritance + hash
  EpWizard.State.psm1                   Portfolio state persistence + canonical SHA-256
  EpWizard.Plan.psm1                    Reconciler diff + plan artifact
  EpWizard.Apply.psm1                   Reconciler executor (resume-safe state writes)
  EpWizard.Preflight.psm1               Name collision / quota / permissions checks
  EpWizard.Retry.psm1                   Retry with backoff + run-state persistence
  EpWizard.Reporting.psm1               Report export + webhook/email + drift webhook
  EpWizard.Platform.psm1                Power Platform API + provisioning + live query
  EpWizard.Verify.psm1                  Post-provision verification hooks
  EpWizard.Telemetry.psm1               App Insights + OTLP exporters
complexities/                           Tier definitions (*.xml)
schemas/                                JSON schemas: config, manifest, state, plan, policy
examples/                               Example config + worked portfolio manifest + example ADO pipeline
tenants/                                Per-tenant portfolio manifests (yours, version-controlled)
state/                                  Per-tenant state files (machine-managed, in git)
plans/                                  Plan artifacts + plans/queued/ for change-window mode
policies/                               Policy JSONs enforced at plan time
tests/                                  Pester 5 tests + Run-All.ps1
  cassettes/                            Recorded PP-API fixtures for cassette tests
  snapshots/                            Plan-output snapshots
  lab/                                  Opt-in lab-tenant integration tests (Tier B)
scripts/
  New-EpCassette.ps1                    Record a fresh cassette from a live tenant
logs/                                   Run artifacts (gitignored)
docs/                                   Further documentation + ADRs
.azure-pipelines/                       Azure DevOps pipelines (CI + plan + apply + scheduled)
.env.local.example                      Template for lab-tenant auth env vars (.env.local is gitignored)

Parameters

Interactive / legacy

Parameter Effect
-WhatIf Walk all steps, print what would be provisioned, call nothing.
-NoTranscript Skip writing a transcript to logs/.

Headless

Parameter Effect
-ConfigPath <file.json> Drive the run from a config file; skip all prompts.
-ExportConfig <file> Dump the resolved config after a run for later replay.
-NonInteractive Fail fast on any missing value (without requiring a ConfigPath).

Authentication

Before any of these flags will work, the SPN must be registered with Power Platform via New-PowerAppManagementApp (one-time, per tenant, run by a tenant admin user). The registration grants the SPN effective Power Platform Administrator treatment — no separate Entra directory role is required, and per the Microsoft docs none gets assigned. A SecureString token from AAD is necessary but not sufficient — without the management-app registration the admin APIs return 401/403 even though Connect-AzAccount / direct AAD calls succeed. Full walkthrough in docs/ONBOARDING.md (end-to-end first-run setup) and docs/RBAC.md (pipeline + ADO setup).

Parameter Effect
-Endpoint prod (default), preview, tip1, tip2, usgov, usgovhigh, dod, china.
-TenantId, -ApplicationId SPN identifiers.
-ClientSecret Client secret for SPN+secret auth. Accepts a SecureString (e.g. Read-Host -AsSecureString) or a plain String (e.g. an Azure DevOps secret library variable, which the agent passes to scripts as a string). Strings are converted to a SecureString in-process before use.
-CertificateThumbprint For SPN+cert auth.
-UseManagedIdentity Use the runtime MI (requires -TenantId).
-SecretVaultName, -SecretName Resolve -ClientSecret from a SecretManagement vault.
-DiagnoseSecret Print non-revealing diagnostics about the resolved client secret (length, hash, first/last 2 chars, GUID-pattern check, whitespace/control-char flags) before calling AAD. Use this when you keep getting AADSTS7000215 and want to confirm the secret reaching the SDK is the one you intended. The secret itself is never printed.

Preflight, retry, resume

Parameter Effect
-SkipPreflight Skip name / quota / permission checks.
-SoftQuotaCeiling <int> Projected-count threshold for quota warnings (default 100).
-MaxRetries <int> Attempts per environment (default 3).
-ResumeRunId <id> Resume a prior run using its run-state file.
-PollUntilFinished After initiating, poll until each environment reaches a terminal state.

Reporting + notifications

Parameter Effect
-ReportPath <file> Write a run report.
-ReportFormat <fmt> csv | json (default) | html | markdown.
-NotifyWebhook <url> POST summary to an incoming webhook.
-NotifyWebhookFlavor teams (default) or slack.
-NotifyEmail <addr[]> Send SMTP summary (requires -SmtpServer, -NotifyEmailFrom).

Notification webhooks

-NotifyWebhook takes the Incoming Webhook URL from your chat platform. The project does not create or store this URL — you supply it.

Microsoft Teams

  1. In Teams, right-click the target channel → Connectors → search for Incoming Webhook → Configure.
  2. Name the connector (e.g. PPAC provisioning), upload an icon if you like, then click Create.
  3. Copy the generated URL — it looks like https://<tenant>.webhook.office.com/webhookb2/....
  4. Use -NotifyWebhookFlavor teams (default).

Slack

  1. Create or pick a Slack app at https://api.slack.com/apps → Incoming Webhooks → enable, then Add New Webhook to Workspace and pick a channel.
  2. Copy the URL — it looks like https://hooks.slack.com/services/T.../B.../....
  3. Use -NotifyWebhookFlavor slack.

Passing the URL to the wizard

Treat the URL as a secret — anyone with it can post to the channel. Don't commit it. Pick one of:

# Per-shell environment variable (recommended for ad-hoc runs)
$env:TEAMS_WEBHOOK = 'https://<tenant>.webhook.office.com/webhookb2/...'
.\Invoke-EnvironmentWizard.ps1 ... -NotifyWebhook $env:TEAMS_WEBHOOK -NotifyWebhookFlavor teams

# Persist for the current user (Windows)
[Environment]::SetEnvironmentVariable('TEAMS_WEBHOOK','https://...','User')

# From a SecretManagement vault
$hook = (Get-Secret -Vault CorpVault -Name TeamsProvisioningWebhook -AsPlainText)
.\Invoke-EnvironmentWizard.ps1 ... -NotifyWebhook $hook -NotifyWebhookFlavor teams

In Azure DevOps, store the URL as a secret pipeline variable (e.g. TEAMS_WEBHOOK) and map it into the task as an env var, then reference $env:TEAMS_WEBHOOK in the script step.

Logging

Parameter Effect
-LogLevel <lvl> Debug | Info (default) | Warn | Error.
-ScrubTranscript Post-process the transcript through the secret scrubber.

Development

# Run the Pester suite
pwsh -File tests/Run-All.ps1

# Run the analyzer
Invoke-ScriptAnalyzer -Path . -Recurse -Settings ./PSScriptAnalyzerSettings.psd1

CI runs both on every PR via .azure-pipelines/ci.yml.

About

No description, website, or topics provided.

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages