This document covers deployment options beyond the standard azd up workflow. Use these paths when you need fine-grained control, CI/CD integration, or environment-specific configurations.
- Direct run.ps1 Execution
- Tag Reference
- Microsoft 365 Desktop Deployment
- Foundry-Only Configuration
- CI/CD Integration
- GitHub Actions with Federated Identity
- Local Quick Start
- Spec File Management
- Environment Variables
- Run Plan Breakdown
- Undo and Rollback
Use run.ps1 directly instead of azd up when you need to:
- Run only specific modules - Deploy just Defender plans without touching Purview or M365
- Resume a failed deployment - Pick up where you left off without re-running completed steps
- Split work across teams - Azure admin runs infrastructure tags; compliance admin runs M365 tags separately
- Debug a single script - Isolate and troubleshoot one automation step
- Skip the Bicep layer - Already have infrastructure and just need governance configuration
- Use different spec files - Point to environment-specific configs (dev, staging, prod)
# Run specific governance modules
pwsh ./run.ps1 -Tags foundation,dspm,defender,foundry -SpecPath ./spec.local.jsonBefore running, authenticate in the same terminal:
az login
azd auth login
Connect-AzAccount -Tenant <tenantId> -Subscription <subscriptionId>
Set-AzContext -Subscription <subscriptionId>
Get-AzContext # Verify tenant/subscription match your spec| Tag | What it runs | Example command |
|---|---|---|
foundation |
Baseline Purview/DSPM bootstrap: resource group, Purview account, data-source registration + first scans | ./run.ps1 -Tags foundation -SpecPath ./spec.local.json |
m365 |
Exchange Online / Compliance Center steps requiring interactive auth: Unified Audit, Know Your Data policy, DLP/label/retention settings | ./run.ps1 -Tags m365 -SpecPath ./spec.local.json |
dspm |
Broader Purview governance: scan registration, audit subscriptions/exports, Azure policy assignments, tagging, posture validation | ./run.ps1 -Tags foundation,dspm -SpecPath ./spec.local.json |
defender |
Defender for AI enablement: plans, diagnostics, content-safety wiring | ./run.ps1 -Tags defender -SpecPath ./spec.local.json |
foundry |
Microsoft Foundry integration: resource registration, tagging, diagnostics, content safety | ./run.ps1 -Tags foundry -SpecPath ./spec.local.json |
audit |
Replay audit export scripts only | ./run.ps1 -Tags audit -SpecPath ./spec.local.json |
all |
Runs everything end-to-end | ./run.ps1 -Tags all -SpecPath ./spec.local.json |
| Goal | Run these tags | Prerequisites | Key spec sections |
|---|---|---|---|
| Secure Microsoft Foundry only | defender,foundry |
Azure Contributor on subscription with Foundry projects | aiFoundry.*, foundry.resources[], defenderForAI.enableDefenderForCloudPlans |
| Full Purview DSPM for AI (no M365) | foundation,dspm,defender,foundry |
Azure Contributor + Purview Data Source Admin | All Azure sections (skip dlpPolicy, labels, retentionPolicies) |
| Enable M365 Copilot governance | m365 (run separately from desktop) |
Desktop + MFA + Exchange Online admin + E5 license | dlpPolicy, labels, retentionPolicies |
| Everything | all |
All of the above (may require multiple operators) | All spec sections |
The m365 tag requires a desktop workstation with browser-based MFA capability. This cannot run in containers or Codespaces.
Exchange Online and Security & Compliance PowerShell require interactive authentication with MFA. Containerized environments lack the browser integration needed for this flow.
-
Open PowerShell 7 on your workstation
-
Install/update the Exchange Online Management module:
Install-Module ExchangeOnlineManagement -Scope CurrentUser -Force
-
Run the M365 tag:
./run.ps1 -Tags m365 -ConnectM365 -M365UserPrincipalName <your-upn> -SpecPath ./spec.local.json
-
Complete the MFA prompts in your browser when prompted
-
Verify that Unified Audit reports "Enabled" and the Secure interactions (KYD) policy appears in the Purview portal
If your team splits responsibilities:
- Azure admin runs
foundation,dspm,defender,foundryfrom containers/CI - Compliance admin runs
m365from their workstation independently
For organizations that only need to govern Microsoft Foundry projects without full Purview DSPM or M365 setup:
- Azure Contributor RBAC on the subscription containing your Foundry projects
- PowerShell 7.x and Az modules installed
- Azure Developer CLI (azd) 1.9.0+ and Azure CLI 2.58.0+
Populate only these sections in spec.local.json:
{
"tenantId": "<your-tenant-id>",
"subscriptionId": "<subscription-with-foundry>",
"aiResourceGroup": "<foundry-resource-group>",
"aiSubscriptionId": "<subscription-with-foundry>",
"aiFoundry": {
"name": "<foundry-project-name>",
"resourceId": "/subscriptions/<subscription-guid>/resourceGroups/<foundry-resource-group>/providers/Microsoft.CognitiveServices/accounts/<account-name>/projects/<foundry-project-name>"
},
"foundry": {
"resources": [
{
"name": "<friendly-resource-name>",
"resourceId": "/subscriptions/<subscription-guid>/resourceGroups/<foundry-resource-group>/providers/Microsoft.CognitiveServices/accounts/<account-name>",
"diagnostics": true
}
]
},
"defenderForAI": {
"enableDefenderForCloudPlans": ["CognitiveServices", "Storage"]
}
}Use full Azure resource IDs for aiFoundry.resourceId and each foundry.resources[].resourceId. This matches the schema described in spec-local-reference.md.
# Sign in
az login
azd auth login
Connect-AzAccount -Tenant <tenantId> -Subscription <subscriptionId>
Set-AzContext -Subscription <subscriptionId>
# Run only Foundry + Defender tags
./run.ps1 -Tags defender,foundry -SpecPath ./spec.local.json- Defender for AI plans on Cognitive Services resources
- Diagnostic settings streaming to Log Analytics
- Content Safety blocklists (if
foundry.contentSafetyconfigured in spec) - Resource tagging for governance lineage
- No Purview DSPM scanning of data sources
- No M365 KYD/DLP policies for Copilot
- No audit exports to external storage
- No Purview registration of Foundry projects (use
foundationtag to add this)
The azure.yaml and infra/main.bicepparam let you run azd up to trigger the post-provision hook using the repo's parameterized defaults.
The hook (hooks/postprovision.ps1) reuses the current azd auth login context by importing Azure CLI tokens into Az PowerShell before invoking run.ps1.
azd auth login(oraz login) to seed the CLI context- Populate
spec.local.json - Run
azd up- the Bicep deployment no-ops, then the hook runsrun.ps1
Edit infra/main.bicepparam to control:
dagaSpecPath- spec file pathdagaTags- tags to run after provisioningdagaConnectM365- enable M365 stepsdagaM365UserPrincipalName- UPN for interactive M365 auth when an operator can complete MFAdagaM365AppId- app registration client ID for app-only M365 authdagaM365Organization- Microsoft 365 organization or tenant domain, such ascontoso.onmicrosoft.comdagaM365CertificateThumbprint- certificate thumbprint for app-only auth using a certificate installed on the machinedagaM365CertificatePath- path to a.pfxcertificate file for app-only authdagaM365CertificatePassword- password for the.pfxcertificate file
These M365 parameters are not used for Azure deployment itself. They are only used when the m365 workflow needs to authenticate to Exchange Online and Security & Compliance cmdlets.
Use the parameters in one of these two ways:
- Interactive auth:
dagaM365UserPrincipalName - App-only auth:
dagaM365AppId+dagaM365Organizationplus eitherdagaM365CertificateThumbprintordagaM365CertificatePath+dagaM365CertificatePassword
App-only auth is mainly useful for unattended runs, GitHub Actions, or other CI/CD scenarios where browser-based sign-in is not practical.
The workflow in .github/workflows/daga-automation-oidc.yml runs ./run.ps1 -Tags all -ConnectM365 on ubuntu-latest with OIDC-based Azure login.
| Secret | Purpose |
|---|---|
AZURE_FEDERATED_CLIENT_ID |
Federated SPN client ID |
AZURE_FEDERATED_TENANT_ID |
Tenant ID |
AZURE_FEDERATED_SUBSCRIPTION_ID |
Subscription ID |
DAGA_SPEC_JSON |
Entire spec.local.json contents as JSON |
DAGA_M365_APP_ID |
App registration for M365 |
DAGA_M365_ORGANIZATION |
M365 organization |
DAGA_M365_CERT_PFX |
Base64-encoded PFX certificate |
DAGA_M365_CERT_PASSWORD |
Certificate password |
run.ps1 supports certificate-based automation:
./run.ps1 -Tags m365 -ConnectM365 `
-M365AppId "<app-id>" `
-M365Organization "<org>.onmicrosoft.com" `
-M365CertificatePath "./cert.pfx" `
-M365CertificatePassword "<password>"Or via environment variables:
DAGA_M365_APP_IDDAGA_M365_ORGANIZATIONDAGA_M365_CERT_PATH/DAGA_M365_CERT_PASSWORDDAGA_M365_CERT_THUMBPRINT(alternative to path)
# PowerShell 7
Install-Module Az -Scope CurrentUser -Repository PSGallery -Force -AllowClobber
Install-Module Az.Security -Scope CurrentUser -ForceConnect-AzAccount -Tenant '<tenant-guid>' -Subscription '<subscription-guid>'For automation, use a service principal:
Connect-AzAccount -ServicePrincipal -Tenant '<tenant-guid>' -ApplicationId '<appId>' -Credential (Get-Credential)pwsh ./scripts/governance/00-New-DspmSpec.ps1 -OutFile ./spec.dspm.template.json
Copy-Item ./spec.dspm.template.json ./spec.local.json# Bash command
cp ./spec.dspm.template.json ./spec.local.jsonPopulate tenant/subscription IDs, Purview settings, Foundry resources, etc.
Run individual scripts or use tags:
Foundation (Purview landing zone):
pwsh ./scripts/governance/01-Ensure-ResourceGroup.ps1 -SpecPath ./spec.local.json
pwsh ./scripts/governance/dspmPurview/02-Ensure-PurviewAccount.ps1 -SpecPath ./spec.local.jsonDefender for AI posture:
pwsh ./scripts/defender/06-Enable-DefenderPlans.ps1 -SpecPath ./spec.local.json
pwsh ./scripts/defender/07-Enable-Diagnostics.ps1 -SpecPath ./spec.local.jsonFoundry registration + Content Safety:
pwsh ./scripts/governance/dspmPurview/30-Foundry-RegisterResources.ps1 -SpecPath ./spec.local.json
pwsh ./scripts/governance/dspmPurview/31-Foundry-ConfigureContentSafety.ps1 -SpecPath ./spec.local.jsonOr use the orchestrator:
./run.ps1 -Tags dspm,defender,foundry -SpecPath ./spec.local.jsonpwsh ./scripts/governance/dspmPurview/17-Export-ComplianceInventory.ps1 -SpecPath ./spec.local.json
pwsh ./scripts/governance/dspmPurview/21-Export-Audit.ps1 -SpecPath ./spec.local.jsonThe repo tracks a sanitized contract in spec.dspm.template.json. Create your working copy:
Copy-Item ./spec.dspm.template.json ./spec.local.json# Bash command
cp ./spec.dspm.template.json ./spec.local.jsonThis file is listed in .gitignore so it stays on your machine.
Create additional local files for different environments:
spec.dev.jsonspec.staging.jsonspec.prod.json
Point run.ps1 -SpecPath to the one you need.
| Section | Purpose | Consumed by tags |
|---|---|---|
tenantId, subscriptionId |
Target tenant and subscription | All |
purview.* |
Purview account and data source settings | foundation, dspm |
aiResourceGroup, aiSubscriptionId, aiFoundry.* |
Foundry resource scope and primary project reference | foundry, defender |
foundry.resources[] |
List of Foundry projects to govern | foundry |
defenderForAI.* |
Defender plans to enable | defender |
dlpPolicy, labels, retentionPolicies |
M365 compliance settings | m365 |
activityExport.* |
Audit export configuration | audit |
See spec-local-reference.md for field-by-field documentation.
Keep secrets in Azure Key Vault rather than the spec file. Reference them using Key Vault URIs where scripts support it.
Environment variables override main.bicepparam values:
| Variable | Purpose |
|---|---|
DAGA_SPEC_PATH |
Spec file path (default: ./spec.local.json) |
DAGA_POSTPROVISION_TAGS |
Comma-separated tags to run |
DAGA_POSTPROVISION_CONNECT_M365 |
Enable M365 steps (true/false) |
DAGA_POSTPROVISION_M365_UPN |
UPN for interactive M365 auth |
DAGA_M365_APP_ID |
App ID for certificate-based M365 auth |
DAGA_M365_ORGANIZATION |
M365 organization for app auth |
DAGA_M365_CERT_THUMBPRINT |
Certificate thumbprint |
DAGA_M365_CERT_PATH |
Path to PFX certificate |
DAGA_M365_CERT_PASSWORD |
Certificate password |
run.ps1 executes scripts in a fixed order. Here's the complete sequence:
| Order | Script | What happens |
|---|---|---|
| 5 | 00-New-DspmSpec.ps1 |
Generates/refreshes the spec contract |
| 10 | 01-Ensure-ResourceGroup.ps1 |
Creates landing-zone resource group |
| 20 | 02-Ensure-PurviewAccount.ps1 |
Ensures Purview account exists |
| 30 | 10-Connect-Compliance.ps1 |
Establishes Exchange Online sessions |
| 40 | 11-Enable-UnifiedAudit.ps1 |
Turns on Unified Audit |
| 50 | 12-Create-DlpPolicy.ps1 |
Creates DLP/KYD policies |
| 60 | 13-Create-SensitivityLabel.ps1 |
Publishes sensitivity labels |
| 70 | 14-Create-RetentionPolicy.ps1 |
Creates retention policies |
| 80 | 03-Register-DataSource.ps1 |
Registers data sources in Purview |
| 90 | 04-Run-Scan.ps1 |
Triggers Purview scans |
| 100 | 20-Subscribe-ManagementActivity.ps1 |
Sets up audit exports |
| 110 | 21-Export-Audit.ps1 |
Executes audit export |
| 120 | 05-Assign-AzurePolicies.ps1 |
Applies Azure Policy assignments |
| 130 | 06-Enable-DefenderPlans.ps1 |
Enables Defender for Cloud plans |
| 140 | 07-Enable-Diagnostics.ps1 |
Configures diagnostic settings |
| 150 | 25-Tag-ResourcesFromSpec.ps1 |
Applies governance tags |
| 200 | 30-Foundry-RegisterResources.ps1 |
Registers Foundry projects in Purview |
| 210 | 31-Foundry-ConfigureContentSafety.ps1 |
Deploys Content Safety settings |
| 220 | 17-Export-ComplianceInventory.ps1 |
Captures compliance inventory |
| 280 | 24-Create-BudgetAlert-Stub.ps1 |
Budget alerts (placeholder) |
To remove all resources deployed by this accelerator:
azd downThis deletes the resource group and all contained resources. It does NOT:
- Remove Purview data source registrations
- Delete M365 policies (DLP, labels, retention)
- Disable Defender for Cloud plans
- Remove audit subscriptions
If azd down fails or you need selective cleanup:
1. Delete the resource group:
Remove-AzResourceGroup -Name "<resource-group-name>" -Force2. Remove Purview data sources (if needed): Sign into the Purview portal and manually delete registered data sources.
3. Remove M365 policies:
- Navigate to Microsoft Purview Compliance Portal
- Delete DLP policies under Data loss prevention > Policies
- Delete sensitivity labels under Information protection > Labels
- Delete retention policies under Data lifecycle management > Retention policies
4. Disable Defender plans:
# List current plans
Get-AzSecurityPricing
# Disable a specific plan
Set-AzSecurityPricing -Name "CognitiveServices" -PricingTier "Free"5. Remove diagnostic settings:
$resources = Get-AzResource -ResourceGroupName "<rg-name>"
foreach ($r in $resources) {
$diag = Get-AzDiagnosticSetting -ResourceId $r.ResourceId -ErrorAction SilentlyContinue
if ($diag) {
Remove-AzDiagnosticSetting -ResourceId $r.ResourceId -Name $diag.Name
}
}If a deployment fails partway through:
- Check the error - Review the terminal output to identify which script failed
- Fix the issue - Usually a missing permission, invalid spec value, or transient error
- Resume from where you left off - Run only the remaining tags:
# If foundation succeeded but dspm failed ./run.ps1 -Tags dspm,defender,foundry -SpecPath ./spec.local.json
These scripts are idempotent and safe to re-run:
- All
Ensure-*scripts (check before creating) Register-DataSource.ps1(skips existing registrations)Enable-DefenderPlans.ps1(skips already-enabled plans)Tag-ResourcesFromSpec.ps1(updates existing tags)
Create-DlpPolicy.ps1- May create duplicate policies if names differExport-Audit.ps1- Creates new export files each run- Any script that modifies M365 settings - Changes take effect immediately
| Folder | Purpose | Key scripts |
|---|---|---|
scripts/governance |
Spec management, Purview bootstrap, policy creation, audit exports, Foundry integration | 00-New-DspmSpec.ps1, 02-Ensure-PurviewAccount.ps1, 12-Create-DlpPolicy.ps1, 30-Foundry-RegisterResources.ps1 |
scripts/defender |
Defender for Cloud AI plans, diagnostics | 06-Enable-DefenderPlans.ps1, 07-Enable-Diagnostics.ps1 |
scripts/exchangeOnline |
Security and Compliance PowerShell (behind m365 tag) |
10-Connect-Compliance.ps1, 11-Enable-UnifiedAudit.ps1 |
Each script is idempotent and checks for prerequisites before applying changes.