Skip to content

PowerShell Setup: Control 1.4 — Advanced Connector Policies (ACP)

Read the FSI PowerShell baseline first

Before running any command in this playbook, read the PowerShell Authoring Baseline for FSI Implementations. It is the canonical source for module version pinning, sovereign-cloud (GCC / GCC High / DoD) endpoints, mutation safety (-WhatIf / SupportsShouldProcess), Dataverse compatibility, and SHA-256 evidence emission. Snippets below show abbreviated patterns; the baseline is authoritative.

Automation guide for Control 1.4 — Advanced Connector Policies (ACP). ACP rule authoring is currently a portal + Power Platform REST API operation — the Microsoft.PowerApps.Administration.PowerShell and Microsoft.PowerApps.PowerShell modules expose the prerequisite Managed Environment, environment group, and classic DLP surface plus evidence-collection read paths. Use REST for the ACP rule body itself.


1. Modules

# REQUIRED: pin to versions approved by your CAB. See the FSI PowerShell baseline §1.
Install-Module -Name Microsoft.PowerApps.Administration.PowerShell `
    -RequiredVersion '<version>' -Repository PSGallery -Scope CurrentUser -AllowClobber -AcceptLicense
Install-Module -Name Microsoft.PowerApps.PowerShell `
    -RequiredVersion '<version>' -Repository PSGallery -Scope CurrentUser -AllowClobber -AcceptLicense

Both modules are Windows PowerShell 5.1 (Desktop) only. Add the edition guard from baseline §2 to every script:

if ($PSVersionTable.PSEdition -ne 'Desktop') {
    throw "Microsoft.PowerApps.Administration.PowerShell requires Windows PowerShell 5.1 (Desktop edition). Detected: $($PSVersionTable.PSEdition) $($PSVersionTable.PSVersion)."
}

2. Authenticate (sovereign-cloud aware)

param(
    [ValidateSet('prod','usgov','usgovhigh','dod')] [string]$Endpoint = 'prod'
)

# Interactive (recommended for change windows under PIM elevation)
Add-PowerAppsAccount -Endpoint $Endpoint

# Unattended (service principal). Store secret in Key Vault, never in source.
# Add-PowerAppsAccount -Endpoint $Endpoint -ApplicationId $appId -ClientSecret $secret -TenantID $tenantId

Wrong -Endpoint against a sovereign tenant returns zero environments and produces false-clean evidence. See baseline §3.


3. Verify Prerequisites Before Authoring ACP

[CmdletBinding()]
param(
    [Parameter(Mandatory)] [string]$EnvironmentId,
    [Parameter(Mandatory)] [string]$EnvironmentGroupId
)
$ErrorActionPreference = 'Stop'

# 3a. Confirm environment exists and is Managed
$env = Get-AdminPowerAppEnvironment -EnvironmentName $EnvironmentId
if (-not $env) { throw "Environment $EnvironmentId not found (or wrong cloud endpoint)." }

$isManaged = $env.Internal.properties.governanceConfiguration.protectionLevel -eq 'Standard'
if (-not $isManaged) {
    throw "Environment $EnvironmentId is NOT a Managed Environment. ACP requires Managed Environments to block nonblockable connectors. Enable Control 2.1 first."
}

# 3b. Confirm region is United States
if ($env.Location -notmatch '^(unitedstates|usgov|usgovhigh|dod)') {
    Write-Warning "Environment region '$($env.Location)' is not US. Confirm zone classification before proceeding."
}

# 3c. Confirm classic DLP policy exists (mixed-mode coverage for custom + HTTP connectors)
$dlp = Get-DlpPolicy
if (-not $dlp) {
    Write-Warning "No classic DLP policies found. ACP does not cover custom or HTTP connectors — at least one classic DLP policy is required for FSI mixed mode."
}

4. Author the ACP Rule via Power Platform REST API

The PowerShell admin module does not yet expose first-class cmdlets for ACP rule CRUD. Use Invoke-PowerAppsRequest (ships in Microsoft.PowerApps.Administration.PowerShell) which signs requests with the current Add-PowerAppsAccount token.

[CmdletBinding(SupportsShouldProcess, ConfirmImpact='High')]
param(
    [Parameter(Mandatory)] [string]$EnvironmentGroupId,
    [Parameter(Mandatory)] [string]$AllowlistJsonPath,   # path to allowlist file in source control
    [string]$ApiVersion  = '2022-03-01-preview',
    [string]$EvidencePath = ".\evidence\1.4"
)
$ErrorActionPreference = 'Stop'
New-Item -ItemType Directory -Force -Path $EvidencePath | Out-Null
$ts = Get-Date -Format 'yyyyMMddTHHmmssZ'
Start-Transcript -Path "$EvidencePath\acp-publish-$ts.log" -IncludeInvocationHeader

if (-not (Test-Path $AllowlistJsonPath)) { throw "Allowlist file not found: $AllowlistJsonPath" }
$body = Get-Content -Raw -Path $AllowlistJsonPath

# 4a. Snapshot the current rule (BEFORE) for rollback evidence
$beforeUri = "/providers/Microsoft.BusinessAppPlatform/scopes/admin/environmentGroups/$EnvironmentGroupId/ruleSets/connector?api-version=$ApiVersion"
try {
    $before = Invoke-PowerAppsRequest -Method GET -Route $beforeUri
    $before | ConvertTo-Json -Depth 30 | Set-Content "$EvidencePath\acp-before-$ts.json" -Encoding UTF8
} catch {
    Write-Warning "No existing ACP rule on group $EnvironmentGroupId (first publish): $($_.Exception.Message)"
}

# 4b. Apply the new allowlist
if ($PSCmdlet.ShouldProcess($EnvironmentGroupId, "PUT advanced connector policy ruleset")) {
    $applyUri = "/providers/Microsoft.BusinessAppPlatform/scopes/admin/environmentGroups/$EnvironmentGroupId/ruleSets/connector?api-version=$ApiVersion"
    $resp = Invoke-PowerAppsRequest -Method PUT -Route $applyUri -Body $body
    $resp | ConvertTo-Json -Depth 30 | Set-Content "$EvidencePath\acp-applied-$ts.json" -Encoding UTF8
}

# 4c. Trigger Publish rules (cascades the rule to design-time + runtime infra of every member env)
if ($PSCmdlet.ShouldProcess($EnvironmentGroupId, "POST publishRules")) {
    $publishUri = "/providers/Microsoft.BusinessAppPlatform/scopes/admin/environmentGroups/$EnvironmentGroupId/publishRules?api-version=$ApiVersion"
    $publishResp = Invoke-PowerAppsRequest -Method POST -Route $publishUri
    $publishResp | ConvertTo-Json -Depth 30 | Set-Content "$EvidencePath\acp-publish-result-$ts.json" -Encoding UTF8
}

Stop-Transcript

Idempotency: PUT to the ruleSet endpoint is the canonical idempotent operation — re-running with the same body produces no functional change. The publish step should also be re-runnable but emits a new lifecycle event each time.

API version: 2022-03-01-preview is the public preview surface as of April 2026. Confirm against the Power Platform REST API reference for environment groups before each change window — preview API versions can change.


5. Verify Publish Succeeded and Status = Applied

$statusUri = "/providers/Microsoft.BusinessAppPlatform/scopes/admin/environmentGroups/$EnvironmentGroupId/ruleSets/connector?api-version=$ApiVersion"
$status = Invoke-PowerAppsRequest -Method GET -Route $statusUri
$applied = $status.properties.status -eq 'Applied'

if (-not $applied) {
    Write-Error "ACP rule status is '$($status.properties.status)' — expected 'Applied'. Investigate via PPAC environment History (look for 'Update Managed Environment Settings' lifecycle event)."
} else {
    Write-Host "[PASS] ACP status = Applied on group $EnvironmentGroupId" -ForegroundColor Green
}

6. Evidence Collection (Read-Only Inventory)

[CmdletBinding()]
param(
    [Parameter(Mandatory)] [string]$EnvironmentGroupId,
    [string]$EvidencePath = ".\evidence\1.4"
)
$ErrorActionPreference = 'Stop'
New-Item -ItemType Directory -Force -Path $EvidencePath | Out-Null
$ts = Get-Date -Format 'yyyyMMddTHHmmssZ'

# Helper: emit JSON + SHA-256 + manifest entry (see baseline §5)
function Write-FsiEvidence {
    param([Parameter(Mandatory)] $Object, [Parameter(Mandatory)] [string]$Name, [Parameter(Mandatory)] [string]$EvidencePath)
    $stamp = Get-Date -Format 'yyyyMMddTHHmmssZ'
    $jsonPath = Join-Path $EvidencePath "$Name-$stamp.json"
    $Object | ConvertTo-Json -Depth 30 | Set-Content -Path $jsonPath -Encoding UTF8
    $hash = (Get-FileHash -Path $jsonPath -Algorithm SHA256).Hash
    $manifestPath = Join-Path $EvidencePath "manifest.json"
    $manifest = @()
    if (Test-Path $manifestPath) { $manifest = @(Get-Content $manifestPath | ConvertFrom-Json) }
    $manifest += [PSCustomObject]@{
        file = (Split-Path $jsonPath -Leaf); sha256 = $hash; bytes = (Get-Item $jsonPath).Length; generated_utc = $stamp; control = '1.4'
    }
    $manifest | ConvertTo-Json -Depth 5 | Set-Content -Path $manifestPath -Encoding UTF8
    return $jsonPath
}

# 6a. ACP rule body (current allowlist)
$acp = Invoke-PowerAppsRequest -Method GET `
    -Route "/providers/Microsoft.BusinessAppPlatform/scopes/admin/environmentGroups/$EnvironmentGroupId/ruleSets/connector?api-version=2022-03-01-preview"
Write-FsiEvidence -Object $acp -Name 'acp-ruleset' -EvidencePath $EvidencePath

# 6b. Environment group membership
$envs = Get-AdminPowerAppEnvironment | Where-Object { $_.Internal.properties.parentEnvironmentGroup.id -match $EnvironmentGroupId }
Write-FsiEvidence -Object ($envs | Select-Object DisplayName, EnvironmentName, Location, EnvironmentType) -Name 'envgroup-members' -EvidencePath $EvidencePath

# 6c. Classic DLP policies (for mixed-mode coverage proof)
$dlpPolicies = Get-DlpPolicy
Write-FsiEvidence -Object $dlpPolicies -Name 'classic-dlp-policies' -EvidencePath $EvidencePath

# 6d. Connection inventory per member environment (proof allowlist matches reality)
foreach ($e in $envs) {
    $conns = Get-AdminPowerAppConnection -EnvironmentName $e.EnvironmentName
    Write-FsiEvidence -Object ($conns | Select-Object DisplayName, ConnectorName, CreatedBy, CreatedTime) `
        -Name "connections-$($e.EnvironmentName)" -EvidencePath $EvidencePath
}

Write-Host "[DONE] Evidence written to $EvidencePath with manifest.json" -ForegroundColor Green

WORM landing: copy $EvidencePath to a Microsoft Purview Data Lifecycle retention-locked location (or Azure Storage immutability container) to satisfy SEC 17a-4(f) preservation expectations. See baseline §5.


7. Rollback (Restore Previous ACP Body)

[CmdletBinding(SupportsShouldProcess, ConfirmImpact='High')]
param(
    [Parameter(Mandatory)] [string]$EnvironmentGroupId,
    [Parameter(Mandatory)] [string]$BeforeJsonPath
)
$ErrorActionPreference = 'Stop'
$body = Get-Content -Raw -Path $BeforeJsonPath
if ($PSCmdlet.ShouldProcess($EnvironmentGroupId, "Rollback ACP ruleset to $BeforeJsonPath")) {
    Invoke-PowerAppsRequest -Method PUT `
        -Route "/providers/Microsoft.BusinessAppPlatform/scopes/admin/environmentGroups/$EnvironmentGroupId/ruleSets/connector?api-version=2022-03-01-preview" `
        -Body $body
    Invoke-PowerAppsRequest -Method POST `
        -Route "/providers/Microsoft.BusinessAppPlatform/scopes/admin/environmentGroups/$EnvironmentGroupId/publishRules?api-version=2022-03-01-preview"
}

Always invoke first with -WhatIf.


8. What This Script Does Not Do

  • Custom connectors / HTTP connectors: not yet in ACP scope; govern via classic DLP groups and Set-DlpPolicyConnectorConfigurations for endpoint filtering.
  • Copilot Studio virtual connectors: not in ACP scope and not planned. Continue using classic DLP data policies.
  • MCP server tool-level blocking: ACP supports server-level only. Tool-level toggles are configured in Copilot Studio per agent.
  • Service-principal-bypass safety net: classic DLP scoped at the environment level (not security-group level) is required to cover SP-authenticated connections — see the warning in the control doc.

Back to Control 1.4 | Portal Walkthrough | Verification & Testing | Troubleshooting


Updated: April 2026 | Version: v1.4.0