functions/Build-HydrationDeploymentPlans.ps1
<#
.SYNOPSIS Builds the deployment plans for the Policy as Code (PAC) environment. .PARAMETER PacEnvironmentSelector Defines which Policy as Code (PAC) environment we are using, if omitted, the script prompts for a value. The values are read from `$DefinitionsRootFolder/global-settings.jsonc. .PARAMETER DefinitionsRootFolder Definitions folder path. Defaults to environment variable `$env:PAC_DEFINITIONS_FOLDER or './Definitions'. .PARAMETER OutputFolder Output folder path for plan files. Defaults to environment variable `$env:PAC_OUTPUT_FOLDER or './Output'. .PARAMETER Interactive Script is used interactively. Script can prompt the interactive user for input. .PARAMETER DevOpsType If set, outputs variables consumable by conditions in a DevOps pipeline. Valid values are '', 'ado' and 'gitlab'. .Parameter FullExportForDocumentationFile Causes the script to output a full list of policySets in use for consumption by documentation generation automation. This will not output build plancs for pipeline deployment. .Parameter ExtendedReporting Output a report with specific data used for evaluation on each object, useful for debugging as well as large sets of changes. .EXAMPLE .\Build-HydrationDeploymentPlans.ps1 -PacEnvironmentSelector "dev" Builds the deployment plans for the Policy as Code (PAC) environment 'dev'. .EXAMPLE .\Build-HydrationDeploymentPlans.ps1 -PacEnvironmentSelector "dev" -DevOpsType "ado" Builds the deployment plans for the Policy as Code (PAC) environment 'dev' and outputs variables consumable by conditions in an Azure DevOps pipeline. .LINK https://azure.github.io/enterprise-azure-policy-as-code/#deployment-scripts #> function Build-HydrationDeploymentPlans { [CmdletBinding()] param ( [parameter(HelpMessage = "Defines which Policy as Code (PAC) environment we are using, if omitted, the script prompts for a value. The values are read from `$DefinitionsRootFolder/global-settings.jsonc.", Position = 0)] [string] $PacEnvironmentSelector = "", [Parameter(HelpMessage = "Definitions folder path. Defaults to environment variable `$env:PAC_DEFINITIONS_FOLDER or './Definitions'.")] [string]$DefinitionsRootFolder, [Parameter(HelpMessage = "Output folder path for plan files. Defaults to environment variable `$env:PAC_OUTPUT_FOLDER or './Output'.")] [string]$OutputFolder, [Parameter(HelpMessage = "If set, only build the exemptions plan.")] [switch] $BuildExemptionsOnly, [Parameter(HelpMessage = "Script is used interactively. Script can prompt the interactive user for input.")] [switch] $Interactive, [Parameter(HelpMessage = "If set, outputs variables consumable by conditions in a DevOps pipeline.")] [ValidateSet("ado", "gitlab", "")] [string] $DevOpsType = "", [switch]$SkipNotScopedExemptions, [parameter(Mandatory = $false, HelpMessage = "Causes the script to output a full list of policySets in use for consumption by documentation generation automation. This will not output build plancs for pipeline deployment.")] [switch] $FullExportForDocumentationFile, [parameter(Mandatory = $false, HelpMessage = "Output a report with specific data used for evaluation on each object, useful for debugging as well as large sets of changes.")] [switch] $ExtendedReporting ) $PSDefaultParameterValues = @{ "Write-Information:InformationVariable" = "+global:epacInfoStream" } Clear-Variable -Name epacInfoStream -Scope global -Force -ErrorAction SilentlyContinue # Dot Source Helper Scripts # TODO: Not necessary as a function, reinstate when returned to Deploy folder # . "$PSScriptRoot/../Helpers/Add-HelperScripts.ps1" # Initialize $InformationPreference = "Continue" $pacEnvironment = Select-PacEnvironment $PacEnvironmentSelector -DefinitionsRootFolder $DefinitionsRootFolder -OutputFolder $OutputFolder -Interactive $Interactive $null = Set-AzCloudTenantSubscription -Cloud $pacEnvironment.cloud -TenantId $pacEnvironment.tenantId -Interactive $pacEnvironment.interactive -DeploymentDefaultContext $pacEnvironment.defaultContext # Telemetry if ($pacEnvironment.telemetryEnabled) { Write-Information "Telemetry is enabled" Submit-EPACTelemetry -Cuapid "pid-3c88f740-55a8-4a96-9fba-30a81b52151a" -DeploymentRootScope $pacEnvironment.deploymentRootScope } else { Write-Information "Telemetry is disabled" } Write-Information "" #region plan data structures $buildSelections = @{ buildAny = $false buildPolicyDefinitions = $false buildPolicySetDefinitions = $false buildPolicyAssignments = $false buildPolicyExemptions = $false } $policyDefinitions = @{ new = @{} update = @{} replace = @{} delete = @{} numberOfChanges = 0 numberUnchanged = 0 } $policyRoleIds = @{} $allDefinitions = @{ policydefinitions = @{} policysetdefinitions = @{} } $replaceDefinitions = @{} $policySetDefinitions = @{ new = @{} update = @{} replace = @{} delete = @{} numberOfChanges = 0 numberUnchanged = 0 } $assignments = @{ new = @{} update = @{} replace = @{} delete = @{} numberOfChanges = 0 numberUnchanged = 0 } $roleAssignments = @{ numberOfChanges = 0 added = [System.Collections.ArrayList]::new() updated = [System.Collections.ArrayList]::new() removed = [System.Collections.ArrayList]::new() } $allAssignments = @{} $exemptions = @{ new = @{} update = @{} replace = @{} delete = @{} numberOfOrphans = 0 numberOfExpired = 0 numberOfChanges = 0 numberUnchanged = 0 } $pacOwnerId = $pacEnvironment.pacOwnerId $timestamp = Get-Date -AsUTC -Format "u" $policyPlan = @{ createdOn = $timestamp pacOwnerId = $pacOwnerId policyDefinitions = $policyDefinitions policySetDefinitions = $policySetDefinitions assignments = $assignments exemptions = $exemptions } $rolesPlan = @{ createdOn = $timestamp pacOwnerId = $pacOwnerId roleAssignments = $roleAssignments } if ($ExtendedReporting) { $detailedRecordList = [ordered]@{} $detailedRecord = [ordered]@{ name = "" definitionType = "" evaluationResult = "" fileRelativePath = "" id = "" changes = "" changeList = @() identityReplaced = $false oldIdentity = "" newIdentity = "" changedIdentityStrings = "" changedMemberPolicyDefinitions = $false changedMemberPolicyDefinitionList = @() scopeChangedOnly = $false oldDefinitionId = "" newDefinitionId = "" replacedReferencedDefinition = $false newReferencedDefinition = "" oldReferencedDefinition = "" requiresRoleChanges = $false roleAdded = @() roleUpdated = @() roleRemoved = @() displayNameChanged = $false oldDisplayName = "" newDisplayName = "" descriptionChanged = $false oldDescription = "" newDescription = "" oldOwner = "" newOwner = "" metadataChanged = $false oldMetadata = @{} newMetadata = @{} definitionVersionChanged = $false oldDefinitionVersion = "" newDefinitionVersion = "" parametersChanged = $false oldParameters = @{} newParameters = @{} enforcementModeChanged = $false oldEnforcementMode = "" newEnforcementMode = "" modeChanged = $false oldMode = "" newMode = "" notScopesChanged = $false oldNotScopes = @{} newNotScopes = @{} nonComplianceMessagesChanged = $false oldNonComplianceMessages = @{} newNonComplianceMessages = @{} overridesChanged = $false oldOverrides = @{} newOverrides = @{} resourceSelectorsChanged = $false oldResourceSelectors = @{} newResourceSelectors = @{} policyRuleChanged = $false oldPolicyRule = @{} newPolicyRule = @{} replacedPolicy = $false replacedPolicyList = @() policyDefinitionsChanged = $false oldPolicyDefinitions = @() newPolicyDefinitions = @() policyDefinitionGroupsChanged = $false deletedPolicyDefinitionGroups = $false oldPolicyDefinitionGroups = @() newPolicyDefinitionGroups = @() } } $policyDefinitionsFolder = $pacEnvironment.policyDefinitionsFolder $policySetDefinitionsFolder = $pacEnvironment.policySetDefinitionsFolder $policyAssignmentsFolder = $pacEnvironment.policyAssignmentsFolder $policyExemptionsFolder = $pacEnvironment.policyExemptionsFolder $policyExemptionsFolderForPacEnvironment = "$($policyExemptionsFolder)/$($pacEnvironment.pacSelector)" #endregion plan data structures #region calculate which plans need to be built $warningMessages = [System.Collections.ArrayList]::new() $exemptionsAreNotManagedMessage = $null $exemptionsAreManaged = $true if (!(Test-Path $policyExemptionsFolder -PathType Container)) { $exemptionsAreNotManagedMessage = "Policy Exemptions folder '$policyExemptionsFolder not found. Exemptions not managed by this EPAC instance." $exemptionsAreManaged = $false } elseif (!(Test-Path $policyExemptionsFolderForPacEnvironment -PathType Container)) { $exemptionsAreNotManagedMessage = "Policy Exemptions folder '$policyExemptionsFolderForPacEnvironment' for PaC environment $($pacEnvironment.pacSelector) not found. Exemptions not managed by this EPAC instance." $exemptionsAreManaged = $false } $localBuildExemptionsOnly = $BuildExemptionsOnly # $localBuildExemptionsOnly = $true # $VerbosePreference = "Continue" if ($localBuildExemptionsOnly) { $null = $warningMessages.Add("Building only the Exemptions plan. Policy, Policy Set, and Assignment plans will not be built.") if ($exemptionsAreManaged) { $buildSelections.buildPolicyExemptions = $true $buildSelections.buildAny = $true } else { $null = $warningMessages.Add($exemptionsAreNotManagedMessage) $null = $warningMessages.Add("Policy Exemptions plan will not be built. Exiting...") } $buildSelections.buildPolicyDefinitions = $false $buildSelections.buildPolicySetDefinitions = $false $buildSelections.buildPolicyAssignments = $false } else { if (!(Test-Path $policyDefinitionsFolder -PathType Container)) { $null = $warningMessages.Add("Policy definitions '$policyDefinitionsFolder' folder not found. Policy definitions not managed by this EPAC instance.") } else { $buildSelections.buildPolicyDefinitions = $true $buildSelections.buildAny = $true } if (!(Test-Path $policySetDefinitionsFolder -PathType Container)) { $null = $warningMessages.Add("Policy Set definitions '$policySetDefinitionsFolder' folder not found. Policy Set definitions not managed by this EPAC instance.") } else { $buildSelections.buildPolicySetDefinitions = $true $buildSelections.buildAny = $true } if (!(Test-Path $policyAssignmentsFolder -PathType Container)) { $null = $warningMessages.Add("Policy Assignments '$policyAssignmentsFolder' folder not found. Policy Assignments not managed by this EPAC instance.") } else { $buildSelections.buildPolicyAssignments = $true $buildSelections.buildAny = $true } if ($exemptionsAreManaged) { $buildSelections.buildPolicyExemptions = $true $buildSelections.buildAny = $true } else { $null = $warningMessages.Add($exemptionsAreNotManagedMessage) } if (-not $buildSelections.buildAny) { $null = $warningMessages.Add("No Policies, Policy Set, Assignment, or Exemptions managed by this EPAC instance found. No plans will be built. Exiting...") } } if ($warningMessages.Count -gt 0) { foreach ($warningMessage in $warningMessages) { Write-Warning $warningMessage } } #endregion calculate which plans need to be built if ($FullExportForDocumentationFile) { # Force items that are required for documentation $buildSelections.buildPolicyAssignments = $true $buildSelections.buildAny = $true } if ($buildSelections.buildAny) { # get the scope table for the deployment root scope amd the resources $scopeTable = Build-ScopeTableForDeploymentRootScope -PacEnvironment $pacEnvironment $skipExemptions = -not $buildSelections.buildPolicyExemptions $skipRoleAssignments = -not $buildSelections.buildPolicyAssignments $deployedPolicyResources = Get-AzPolicyResources ` -PacEnvironment $pacEnvironment ` -ScopeTable $scopeTable ` -SkipExemptions:$skipExemptions ` -SkipRoleAssignments:$skipRoleAssignments if ($FullExportForDocumentationFile) { # Clear values that will be used to confirm existing deployments in order to ensure that all policySets are in the list to be documented in the policyDocumentation file $deployedPolicyResources.policyassignments.readOnly = @{} $deployedPolicyResources.policyassignments.managed = @{} $deployedPolicyResources.policyassignments.all = @{} } # Calculate roleDefinitionIds for built-in and inherited Policies $readOnlyPolicyDefinitions = $deployedPolicyResources.policydefinitions.readOnly foreach ($id in $readOnlyPolicyDefinitions.Keys) { $deployedDefinitionProperties = Get-PolicyResourceProperties -PolicyResource $readOnlyPolicyDefinitions.$id if ($deployedDefinitionProperties.policyRule.then.details -and $deployedDefinitionProperties.policyRule.then.details.roleDefinitionIds) { $roleIds = $deployedDefinitionProperties.policyRule.then.details.roleDefinitionIds $null = $policyRoleIds.Add($id, $roleIds) } } # Populate allDefinitions.policydefinitions with all deployed definitions $allDeployedDefinitions = $deployedPolicyResources.policydefinitions.all foreach ($id in $allDeployedDefinitions.Keys) { $allDefinitions.policydefinitions[$id] = $allDeployedDefinitions.$id } if ($buildSelections.buildPolicyDefinitions) { # Process Policies Build-HydrationPolicyPlan ` -DefinitionsRootFolder $policyDefinitionsFolder ` -PacEnvironment $pacEnvironment ` -DeployedDefinitions $deployedPolicyResources.policydefinitions ` -Definitions $policyDefinitions ` -AllDefinitions $allDefinitions ` -ReplaceDefinitions $replaceDefinitions ` -PolicyRoleIds $policyRoleIds ` -DetailedRecord $detailedRecord ` -ExtendedReporting:$ExtendedReporting } # Calculate roleDefinitionIds for built-in and inherited PolicySets $readOnlyPolicySetDefinitions = $deployedPolicyResources.policysetdefinitions.readOnly foreach ($id in $readOnlyPolicySetDefinitions.Keys) { $policySetProperties = Get-PolicyResourceProperties -PolicyResource $readOnlyPolicySetDefinitions.$id $roleIds = @{} foreach ($policyDefinition in $policySetProperties.policyDefinitions) { $policyId = $policyDefinition.policyDefinitionId if ($policyRoleIds.ContainsKey($policyId)) { $addRoleDefinitionIds = $PolicyRoleIds.$policyId foreach ($roleDefinitionId in $addRoleDefinitionIds) { $roleIds[$roleDefinitionId] = "added" } } } if ($roleIds.psbase.Count -gt 0) { $null = $policyRoleIds.Add($id, $roleIds.Keys) } } # Populate allDefinitions.policysetdefinitions with deployed definitions $allDeployedDefinitions = $deployedPolicyResources.policysetdefinitions.all foreach ($id in $allDeployedDefinitions.Keys) { $allDefinitions.policysetdefinitions[$id] = $allDeployedDefinitions.$id } if ($buildSelections.buildPolicySetDefinitions) { # Process Policy Sets Build-HydrationPolicySetPlan ` -DefinitionsRootFolder $policySetDefinitionsFolder ` -PacEnvironment $pacEnvironment ` -DeployedDefinitions $deployedPolicyResources.policysetdefinitions ` -Definitions $policySetDefinitions ` -AllDefinitions $allDefinitions ` -ReplaceDefinitions $replaceDefinitions ` -PolicyRoleIds $policyRoleIds ` -DetailedRecord $detailedRecord ` -ExtendedReporting:$ExtendedReporting } # Convert Policy and PolicySetDefinition to detailed Info $combinedPolicyDetails = Convert-PolicyResourcesToDetails ` -AllPolicyDefinitions $allDefinitions.policydefinitions ` -AllPolicySetDefinitions $allDefinitions.policysetdefinitions # Populate allAssignments $deployedPolicyAssignments = $deployedPolicyResources.policyassignments.managed foreach ($id in $deployedPolicyAssignments.Keys) { $allAssignments[$id] = $deployedPolicyAssignments.$id } #region Process Deprecated $deprecatedHash = @{} foreach ($key in $combinedPolicyDetails.policies.keys) { if ($combinedPolicyDetails.policies.$key.isDeprecated) { $deprecatedHash[$combinedPolicyDetails.policies.$key.name] = $combinedPolicyDetails.policies.$key } } if ($buildSelections.buildPolicyAssignments) { # Process Assignment JSON files Build-HydrationAssignmentPlan ` -AssignmentsRootFolder $policyAssignmentsFolder ` -PacEnvironment $pacEnvironment ` -ScopeTable $scopeTable ` -DeployedPolicyResources $deployedPolicyResources ` -Assignments $assignments ` -RoleAssignments $roleAssignments ` -AllAssignments $allAssignments ` -ReplaceDefinitions $replaceDefinitions ` -PolicyRoleIds $policyRoleIds ` -CombinedPolicyDetails $combinedPolicyDetails ` -DeprecatedHash $deprecatedHash ` -DetailedRecord $detailedRecord ` -ExtendedReporting:$ExtendedReporting } if ($buildSelections.buildPolicyExemptions) { # Process Exemption JSON files if ($SkipNotScopedExemptions) { Build-ExemptionsPlan ` -ExemptionsRootFolder $policyExemptionsFolderForPacEnvironment ` -ExemptionsAreNotManagedMessage $exemptionsAreNotManagedMessage ` -PacEnvironment $pacEnvironment ` -ScopeTable $scopeTable ` -AllDefinitions $allDefinitions ` -AllAssignments $allAssignments ` -CombinedPolicyDetails $combinedPolicyDetails ` -Assignments $assignments ` -DeployedExemptions $deployedPolicyResources.policyExemptions ` -Exemptions $exemptions ` -SkipNotScopedExemptions # TODO: Add ExtendedReporting to Exemptions } else { Build-ExemptionsPlan ` -ExemptionsRootFolder $policyExemptionsFolderForPacEnvironment ` -ExemptionsAreNotManagedMessage $exemptionsAreNotManagedMessage ` -PacEnvironment $pacEnvironment ` -ScopeTable $scopeTable ` -AllDefinitions $allDefinitions ` -AllAssignments $allAssignments ` -CombinedPolicyDetails $combinedPolicyDetails ` -Assignments $assignments ` -DeployedExemptions $deployedPolicyResources.policyExemptions ` -Exemptions $exemptions # TODO: Add ExtendedReporting to Exemptions } } Write-Information "===================================================================================================" Write-Information "Summary" Write-Information "===================================================================================================" if ($buildSelections.buildPolicyDefinitions) { Write-Information "Policy counts:" Write-Information " $($policyDefinitions.numberUnchanged) unchanged" if ($policyDefinitions.numberOfChanges -eq 0) { Write-Information " $($policyDefinitions.numberOfChanges) changes" } else { Write-Information " $($policyDefinitions.numberOfChanges) changes:" Write-Information " new = $($policyDefinitions.new.psbase.Count)" Write-Information " update = $($policyDefinitions.update.psbase.Count)" Write-Information " replace = $($policyDefinitions.replace.psbase.Count)" Write-Information " delete = $($policyDefinitions.delete.psbase.Count)" } } if ($buildSelections.buildPolicySetDefinitions) { Write-Information "Policy Set counts:" Write-Information " $($policySetDefinitions.numberUnchanged) unchanged" if ($policySetDefinitions.numberOfChanges -eq 0) { Write-Information " $($policySetDefinitions.numberOfChanges) changes" } else { Write-Information " $($policySetDefinitions.numberOfChanges) changes:" Write-Information " new = $($policySetDefinitions.new.psbase.Count)" Write-Information " update = $($policySetDefinitions.update.psbase.Count)" Write-Information " replace = $($policySetDefinitions.replace.psbase.Count)" Write-Information " delete = $($policySetDefinitions.delete.psbase.Count)" } } if ($buildSelections.buildPolicyAssignments) { Write-Information "Policy Assignment counts:" Write-Information " $($assignments.numberUnchanged) unchanged" if ($assignments.numberOfChanges -eq 0) { Write-Information " $($assignments.numberOfChanges) changes" } else { Write-Information " $($assignments.numberOfChanges) changes:" Write-Information " new = $($assignments.new.psbase.Count)" Write-Information " update = $($assignments.update.psbase.Count)" Write-Information " replace = $($assignments.replace.psbase.Count)" Write-Information " delete = $($assignments.delete.psbase.Count)" } Write-Information "Role Assignment counts:" if ($roleAssignments.numberOfChanges -eq 0) { Write-Information " $($roleAssignments.numberOfChanges) changes" } else { Write-Information " $($roleAssignments.numberOfChanges) changes:" Write-Information " add = $($roleAssignments.added.psbase.Count)" Write-Information " update = $($roleAssignments.updated.psbase.Count)" Write-Information " remove = $($roleAssignments.removed.psbase.Count)" } } if ($buildSelections.buildPolicyExemptions) { Write-Information "Policy Exemption counts:" Write-Information " $($exemptions.numberUnchanged) unchanged" Write-Information " $($exemptions.numberOfOrphans) orphaned" Write-Information " $($exemptions.numberOfExpired) expired" if ($exemptions.numberOfChanges -eq 0) { Write-Information " $($exemptions.numberOfChanges) changes" } else { Write-Information " $($exemptions.numberOfChanges) changes:" Write-Information " new = $($exemptions.new.psbase.Count)" Write-Information " update = $($exemptions.update.psbase.Count)" Write-Information " replace = $($exemptions.replace.psbase.Count)" Write-Information " delete = $($exemptions.delete.psbase.Count)" } } } Write-Information "---------------------------------------------------------------------------------------------------" Write-Information "Output plan(s); if any, will be written to the following file(s):" $policyResourceChanges = $policyDefinitions.numberOfChanges $policyResourceChanges += $policySetDefinitions.numberOfChanges $policyResourceChanges += $assignments.numberOfChanges $policyResourceChanges += $exemptions.numberOfChanges $policyStage = "no" $planFile = $pacEnvironment.policyPlanOutputFile if ($policyResourceChanges -gt 0) { Write-Information " Policy resource deployment required; writing Policy plan file '$planFile'" if (-not (Test-Path $planFile)) { $null = (New-Item $planFile -Force) } $null = $policyPlan | ConvertTo-Json -Depth 100 | Out-File -FilePath $planFile -Force $policyStage = "yes" } else { if (Test-Path $planFile) { $null = (Remove-Item $planFile) } Write-Information " Skipping Policy deployment stage/step - no changes" } $roleStage = "no" $planFile = $pacEnvironment.rolesPlanOutputFile if ($roleAssignments.numberOfChanges -gt 0) { Write-Information " Role assignment changes required; writing Policy plan file '$planFile'" if (-not (Test-Path $planFile)) { $null = (New-Item $planFile -Force) } $null = $rolesPlan | ConvertTo-Json -Depth 100 | Out-File -FilePath $planFile -Force $roleStage = "yes" } else { if (Test-Path $planFile) { $null = (Remove-Item $planFile) } Write-Information " Skipping Role Assignment stage/step - no changes" } if ($ExtendedReporting -and ($policyStage -eq "yes" -or $roleStage -eq "yes")) { # Output json, csv, and md $detailedOutJson = Join-Path $OutputFolder ( -join ('plans-', $pacEnvironment.pacSelector)) 'detailed-deployment-report.json' $detailedOutCsv = Join-Path $OutputFolder ( -join ('plans-', $pacEnvironment.pacSelector)) 'detailed-deployment-report.csv' Write-Information " Detailed policy resource deployment documented; writing detailed change information json file '$detailedOutJson'" Write-Information " Detailed policy resource deployment documented; writing detailed change information csv file '$detailedOutCsv'" $detailedRecordList | Select-Object -Unique | ConvertTo-Json -Depth 100 ` | Out-File -FilePath $detailedOutJson -Force $detailedRecordHashtable = Get-DeepCloneAsOrderedHashtable -InputObject $detailedRecordList Convert-HashtableToFlatPsObject -Hashtable $detailedRecordHashtable.values ` | Select-Object @{Name="CombinedKey"; Expression={ "$($_.id)-$($_.name)-$($_.fileRelativePath)" }}, * ` | Sort-Object CombinedKey ` | Select-Object -Unique * ` | Select-Object -ExcludeProperty CombinedKey ` | Export-Csv -Path $detailedOutCsv -NoTypeInformation -Force # Build md file ## TODO: Confirming base format to generate template for output, will be in next release of this cmdlet } Write-Information "---------------------------------------------------------------------------------------------------" Write-Information "" switch ($DevOpsType) { ado { Write-Host "##vso[task.setvariable variable=deployPolicyChanges;isOutput=true]$($policyStage)" Write-Host "##vso[task.setvariable variable=deployRoleChanges;isOutput=true]$($roleStage)" break } gitlab { Add-Content "build.env" "deployPolicyChanges=$($policyStage)" Add-Content "build.env" "deployRoleChanges=$($roleStage)" } default { } } } |