Mastering App Control for Business | Part 7: Maintaining your policies with Azure DevOps (or PowerShell)

Hello everyone, in this last post in this series, I will describe how you can maintain your App Control for Business Policies with Azure DevOps Pipeline (or interactively via PowerShell 7) as code. Over the past few month, since I started the blog post series about App Control for Business, I did some research and developed a couple of PowerShell scripts, which helps me to maintain all these policies files as configurations with the advantages of git.

Before we can maintain the configurations with git, we have to connect an Azure DevOps organization with our tenant.

Azure DevOps

First of all, we need an Azure DevOps organization (https://aex.dev.azure.com/) with at least one project and git-repository. Then, to prevent storage of secrets in Azure DevOps, we need to add a Workload Identity Federation and associated it to our Azure DevOps pipeline, for authentication against Microsoft Graph endpoints. Thanks to federated workload identities sensitive credentials no longer need to be stored in repositories, when OpenID Connect (OIDC) is used. When a pipeline executes, Azure DevOps requests authentication from Microsoft Entra ID via service principal. Instead of using stored credentials, Entra ID verifies the request and issues a short-lived token. This token is valid only for the duration of the pipeline run and grants access only to the resources specified in its permissions.

oAuth/OIDC authentication flow for federated identities

Create a service connection point for Workload Identity Federation

Follow these steps to create a service Connection point:  Project settings –>  Service connections –>  Create service connection –> Azure Resource Manager –> Workload Identity federation (automatic)

Now, you have to specify to what resource you will grant this newly created managed identity permissions to. For this use case, I decide to use Subscription (Resource Group) scope, which means that this managed identity will be granted a Contributor role to the selected Resource Group.

Note: The Contributor role more permissions as we needed. You can assign a less privileged role for this use case, such as Reader. However, it is important that workload identity must be assigned some role, otherwise all authentication attempts will fail! These permissions are scoped to the resource group we have assigned. 

Once the federated identity has been created, we need to configure the service principal (toggle of some defaults) and assign the necessary graph permission for the service principal that belongs to the federated identity. You shoud rename the app registration to be compliant with your naming convention.

Grant API permission to the service principal

Next, we need to assign the right app permission to this service principal to perform the required ms graph calls.

Azure DevOps Repository

We used Azure DevOps Repository for version control and all the other benefits of git. The repository consist of the following structure:

repo-root/
├─ Publish-ACFBPolicy.ps1
├─ ACFB-Build-Pipeline.yml
├─ README.md
├─ Certs/
│ ├─ org-code-sign.cer #example
├─ Policies/
│ ├─ unsigned_original/
│ │ ├─ Base_MyBigBusinessFromWizard.xml
│ │ └─ Supplemental_Allow_Vendors.xml
│ └─ signed/ # target directory

Create Azure DevOps Pipeline

Thereby the pipeline works properly, we have to assign the build service contribute rights to our repository

Now, all required steps are done, we can upload the powershell script and related signer certificate to our repository. You can download the files from my github repo: PatrickSeltmann/AppControlForBusiness_DevOps

Afterwards, we create a new pipeline:

Next, we select “Existing Azure Pipelines XAML file” an chose the yaml pipline configuration file.

The pipeline consist of the following lines:

# Pipeline: Build & Publish ACfB Policies
# One-script approach: sign the XML and upload it
# Service connection to Entra ID / Graph: 'AC4B' (OIDC / workload identity)
# Script file lives in the repo: Publish-ACFBPolicy.ps1
# Certificates must be stored under Certs\*.cer or *.crt

trigger:
  branches:
    include:
      - main                  # Run this pipeline only for changes on the 'main' branch
  paths:
    include:
      - Policies/unsigned_original/**   # Run only if files under this folder (or subfolders) changed

pool:
  vmImage: windows-2022       # Use Microsoft-hosted Windows agent

steps:
- checkout: self
  persistCredentials: true
  clean: true                 # Start with a clean workspace
  fetchDepth: 0               # Full history (needed for rebase/push)

# 1) Get a Microsoft Graph access token using OIDC (from the service connection)
#    Then save it into a secret pipeline variable named 'secret'
- task: AzureCLI@2
  displayName: Get Microsoft Graph access token (OIDC)
  inputs:
    azureSubscription: AC4B   # Name of your service connection
    scriptType: pscore        # Use PowerShell Core inside the AzureCLI task
    scriptLocation: inlineScript
    inlineScript: |
      # Ask Azure CLI for an access token for Microsoft Graph
      $json = az account get-access-token --resource-type ms-graph -o json
      $accessToken = ($json | ConvertFrom-Json).accessToken

      # Basic check: fail early if token is empty
      if ([string]::IsNullOrWhiteSpace($accessToken)) { throw "Failed to obtain MS Graph token." }

      # Store the token as a secret variable named 'secret' (masked in logs)
      Write-Host "##vso[task.setvariable variable=secret;issecret=true]$accessToken"

# 2) Run your one script that signs and uploads the policies
#    We pass absolute paths and inject the token via -AccessToken
- task: PowerShell@2
  displayName: Build & publish ACfB policies
  inputs:
    targetType: filePath                     # Run a script file from the repo
    filePath: Publish-ACFBPolicy.ps1         # Script path (relative to repo root)
    arguments: >                             # Script parameters (multi-line for readability)
      -PolicyRootDir "$(Build.SourcesDirectory)\Policies\unsigned_original"
      -OutputPolicyDir "$(Build.SourcesDirectory)\Policies\signed"
      -CertFolder "$(Build.SourcesDirectory)\Certs"
      -AccessToken "$(secret)"               # Use the token from step 1 (non-interactive Graph auth)
    pwsh: true                               # Use PowerShell 7
    workingDirectory: '$(Build.SourcesDirectory)'  # Run from repo root

# 3) Commit signed output back to the repo — only if this is not a PR
#    (So main gets the new signed files, and [skip ci] prevents infinite pipeline loops)
- task: PowerShell@2
  displayName: Commit signed policies (only if changed)
  condition: and(succeeded(), ne(variables['Build.Reason'], 'PullRequest'))  # Skip for PRs
  inputs:
    targetType: inline
    pwsh: true
    script: |
      $ErrorActionPreference = 'Stop'

      # Configure Git author identity for this agent session
      git config --global user.email "pipeline@domain.tbd"
      git config --global user.name  "Pipeline ACfB Build"

      # Mark the workspace as safe (avoids Git security warnings on hosted agents)
      git config --global --add safe.directory "$(Build.SourcesDirectory)"

      # Pick the current source branch (fallback to main if not set)
      $branch = "$(Build.SourceBranchName)"
      if (-not $branch) { $branch = "main" }

      # Make sure we are on that branch and up to date
      git checkout $branch
      git pull --rebase origin $branch

      # Stage signed files for commit
      git add "Policies/signed"

      # Create a commit; [skip ci] prevents triggering the pipeline again from this commit

When you run the pipeline for the first time, you have to grant it permissions to the service connection.

If the pipeline was triggered, the powershell script will be executed.

Note: The powershell script can also be used without interactively with PowerShell 7

#Requires -Version 7.0   # Make sure we run on PowerShell 7 or newer

<#
.SYNOPSIS
Publishes App Control for Business (ACfB) policies to Intune:
- Signs local policy XML files (Base_*.xml / Supplemental_*.xml) with a certificate,
- Restores the original VersionEx after signing and compares local and remote version on it (signer tool reset it),
- Creates or updates the matching Intune configuration policy via Microsoft Graph.

.DESCRIPTION
This script automates the full ACfB policy publishing flow. It scans a source folder for
policy XMLs with the naming pattern Base_*.xml or Supplemental_*.xml, copies them to an output folder,
signs them using a .cer/.crt certificate (stored in the 'certs' folder), and uploads the signed XML to Intune using the Microsoft Graph API.

Key behaviors:
- Dual auth mode:
  • CI/CD (non-interactive): Use -AccessToken (e.g., from Azure DevOps OIDC service connection).
  • Manual (interactive): Prompt using Microsoft.Graph.Authentication with the required scope.
- update logic:
  The script reads the currently deployed XML from Intune and compares versions/content:
    • Update if local VersionEx > remote VersionEx,
- Safe writes:
  The script writes the signed XML to the output folder and uses that file for upload, so you
  can audit, commit, or archive the exact payload that Intune receives.

Requirements:
- PowerShell 7+ (see #Requires -Version 7.0).
- Microsoft.Graph PowerShell (module Microsoft.Graph.Authentication; auto-install on demand).
- A signing certificate file (.cer or .crt) accessible on the machine/repo.
- Intune / Graph permissions:
  • Scope DeviceManagementConfiguration.ReadWrite.All (for manual interactive login)
  • Or an app/workload identity with equivalent permissions and consent (for CI/CD).

Folders:
- $PolicyRootDir: unsigned XML templates (Base_*.xml / Supplemental_*.xml)
- $OutputPolicyDir: signed XML copies (final upload payloads)
- $CertFolder: contains .cer/.crt to sign with (first match is used)

API:
- Uses Microsoft Graph beta endpoints for ACfB configuration policies.
- Template families:
    Base:          endpointSecurityApplicationControl
    Supplemental:  endpointSecurityApplicationControlSupplementalPolicy
- The script targets the Intune setting definition “..._xml” to embed the full policy XML string.

Error handling & logging:
- The script stops on errors (ErrorActionPreference = 'Stop').
- If Graph returns an error with a JSON body, it is printed for troubleshooting.
- Logs source/local/remote VersionEx values to help diagnose version logic.

.PARAMETER PolicyRootDir
Path to the folder containing unsigned policy XML files (Base_*.xml / Supplemental_*.xml).
Default: .\Policies\unsigned_original

.PARAMETER OutputPolicyDir
Path to the folder where signed policy XML copies will be written (and uploaded from).
Default: .\Policies\signed

.PARAMETER CertFolder
Path to the folder containing .cer or .crt files used for signing.
The first found match is used.
Default: .\Certs

.PARAMETER TenantId
Optional tenant ID hint for interactive (manual) Connect-MgGraph.
Ignored when -AccessToken is provided (CI/CD).

.PARAMETER AccessToken
Optional bearer token for non-interactive (CI/CD) authentication with Connect-MgGraph -AccessToken.
If provided, the script will not prompt for interactive login.

.PARAMETER DryRun
If set, the script only prints what it would do (CREATE/UPDATE policy) without uploading to Intune.

.EXAMPLE
# Manual (interactive) run with default folders and interactive Graph login.
pwsh .\Publish-ACFBPolicy.ps1 `
  -PolicyRootDir .\Policies\unsigned_original `
  -OutputPolicyDir .\Policies\signed `
  -CertFolder .\Certs

.EXAMPLE
# Manual run for a specific tenant (interactive login).
pwsh .\Publish-ACFBPolicy.ps1 `
  -TenantId "00000000-0000-0000-0000-000000000000"

.EXAMPLE
# CI/CD (Azure DevOps): pass the access token obtained in a prior AzureCLI@2 step.
pwsh .\Publish-ACFBPolicy.ps1 `
  -PolicyRootDir "$(Build.SourcesDirectory)\Policies\unsigned_original" `
  -OutputPolicyDir "$(Build.SourcesDirectory)\Policies\signed" `
  -CertFolder "$(Build.SourcesDirectory)\Certs" `
  -AccessToken "$(secret)"

.EXAMPLE
# Dry run (no upload) to see what would be created/updated.
pwsh .\Publish-ACFBPolicy.ps1 -DryRun

.NOTES
- PowerShell 7+ is required. On Windows, run with “pwsh” (not “powershell”).
- If your signing helper resets VersionEx, this script restores it from the unsigned source.
- Make sure Add-SignerRule is available on the PATH (or dot-source your custom implementation).
- If you want to commit the signed outputs back to your repo, do so after the script finishes.

.LINK
Microsoft Graph docs (Intune configuration policies):
https://learn.microsoft.com/mem/intune/configuration/device-profile-create
#>

param (
  [string]$PolicyRootDir   = ".\Policies\unsigned_original",
  [string]$OutputPolicyDir = ".\Policies\signed",
  [string]$CertFolder      = ".\Certs",
  [string]$TenantId        = $null,
  [string]$AccessToken,
  [switch]$DryRun
)

begin {
  $ErrorActionPreference = 'Stop'

  function Ensure-Module {
    param([string]$Name)
    if (-not (Get-Module -ListAvailable -Name $Name)) {
      Install-Module $Name -Scope CurrentUser -Force -ErrorAction Stop
    }
    Import-Module $Name -ErrorAction Stop
  }

  function Connect-MSGraphSmart {
    param([string]$Token, [string]$Tenant)
    Ensure-Module -Name Microsoft.Graph.Authentication

    if ($Token) {
      $secureToken = ConvertTo-SecureString -String $Token -AsPlainText -Force
      Connect-MgGraph -AccessToken $secureToken -NoWelcome | Out-Null
      return
    }

    $scopes = @('DeviceManagementConfiguration.ReadWrite.All')
    if ($Tenant) {
      Connect-MgGraph -Scopes $scopes -TenantId $Tenant -NoWelcome | Out-Null
    } else {
      Connect-MgGraph -Scopes $scopes -NoWelcome | Out-Null
    }
  }

  function Load-PolicyXml {
    param([string]$Path)
    try {
      $rawXml = Get-Content -Path $Path -Raw -Encoding UTF8
      [xml]$xml = $rawXml
      return @{ Xml = $xml; Raw = $rawXml }
    } catch {
      Write-Error "Failed to load XML from '$Path': $($_.Exception.Message)"
      return $null
    }
  }

  function Get-VersionText {
    param([string]$RawXml)
    $m = [regex]::Match($RawXml, 'VersionEx\s*=\s*"([^"]+)"', 'IgnoreCase')
    if ($m.Success) { return $m.Groups[1].Value }
    $m = [regex]::Match($RawXml, '<VersionEx>\s*([^<]+)\s*</VersionEx>', 'IgnoreCase')
    if ($m.Success) { return $m.Groups[1].Value }
    return $null
  }

  # Connect to Microsoft Graph (token preferred)
  Connect-MSGraphSmart -Token $AccessToken -Tenant $TenantId
  $MgContext = Get-MgContext
  if (-not $MgContext) { throw "Failed to connect to Microsoft Graph." }
  Write-Host "Connected to Graph. TenantId=$($MgContext.TenantId)"

  # Paths
  if (-not (Test-Path $PolicyRootDir))  { throw "Directory '$PolicyRootDir' does not exist." }
  if (-not (Test-Path $OutputPolicyDir)) { New-Item -Path $OutputPolicyDir -ItemType Directory | Out-Null }
  if (-not (Test-Path $CertFolder))     { throw "Cert folder '$CertFolder' does not exist." }

  # Ensure signer helper exists
  if (-not (Get-Command Add-SignerRule -ErrorAction SilentlyContinue)) {
    throw "Add-SignerRule not found on this machine/agent."
  }

  $NamePattern = [regex]'(?i)^(Base|Supplemental)_(.+)\.xml$'
  $PolicyFiles = Get-ChildItem -Path $PolicyRootDir -Recurse -Filter *.xml |
                 Where-Object { $NamePattern.IsMatch($_.Name) }

  if (-not $PolicyFiles) {
    Write-Warning "No policies found in $PolicyRootDir."
    return
  }
}

process {
  foreach ($PolicyFile in $PolicyFiles) {
    $match = $NamePattern.Match($PolicyFile.Name)
    $PolicyType = $match.Groups[1].Value
    $PolicyKey  = $match.Groups[2].Value
    $PolicyName = "($PolicyType) - $PolicyKey"

    switch ($PolicyType) {
      "Base" {
        $TemplateId = "4321b946-b76b-4450-8afd-769c08b16ffc_1"
        $TemplateFamily = "endpointSecurityApplicationControl"
        $TemplateDisplayName = "App Control for Business"
        $SignSupplemental = $true
      }
      "Supplemental" {
        $TemplateId = "08441ae9-e0c0-4e57-8e8b-6e72405cd64f_1"
        $TemplateFamily = "endpointSecurityApplicationControlSupplementalPolicy"
        $TemplateDisplayName = "App Control for Business - Supplemental"
        $SignSupplemental = $false
      }
      default {
        Write-Warning "Unknown policy type: $PolicyType skipping."
        continue
      }
    }

    Write-Host ""
    Write-Host "Processing policy: $PolicyName" -ForegroundColor Cyan

    # Read source version (before signing)
    $SourceXmlRaw = Get-Content -Path $PolicyFile.FullName -Raw -Encoding UTF8
    $SourceVersionText = Get-VersionText $SourceXmlRaw
    if (-not $SourceVersionText) { $SourceVersionText = '0.0.0.0' }

    # Sign
    $Cert = Get-ChildItem -Path $CertFolder -Recurse -Include *.cer, *.crt -File | Select-Object -First 1
    if (-not $Cert) { throw "No .cer/.crt found in '$CertFolder'." }

    $SignedPath = Join-Path $OutputPolicyDir $PolicyFile.Name
    Copy-Item -Path $PolicyFile.FullName -Destination $SignedPath -Force

    $beforeHash = (Get-FileHash -Algorithm SHA256 -Path $SignedPath).Hash
    Add-SignerRule -FilePath $SignedPath -CertificatePath $Cert.FullName -Update:$true -Supplemental:$SignSupplemental
    $afterHash  = (Get-FileHash -Algorithm SHA256 -Path $SignedPath).Hash
    if ($beforeHash -eq $afterHash) { throw "Signing did not change '$SignedPath'." }

    # Restore VersionEx after signing
    [xml]$SignedXml = Get-Content -Path $SignedPath -Raw -Encoding UTF8
    if ($SignedXml.SiPolicy.VersionEx) {
      $SignedXml.SiPolicy.VersionEx = $SourceVersionText
    } else {
      $newNode = $SignedXml.CreateElement("VersionEx", $SignedXml.SiPolicy.NamespaceURI)
      $newNode.InnerText = $SourceVersionText
      [void]$SignedXml.SiPolicy.InsertAfter($newNode, $SignedXml.SiPolicy.FirstChild)
    }
    # PS7: utf8 is UTF-8 without BOM
    $SignedXml.OuterXml | Set-Content -Path $SignedPath -Encoding utf8

    # Local version from signed XML
    $LocalPolicy = Load-PolicyXml -Path $SignedPath
    if (-not $LocalPolicy) { continue }
    $LocalXmlRaw = $LocalPolicy.Raw
    $LocalVersionText = Get-VersionText $LocalXmlRaw
    if (-not $LocalVersionText) { $LocalVersionText = '0.0.0.0' }
    try { [version]$LocalVersion = $LocalVersionText } catch { $LocalVersion = [version]'0.0.0.0' }

    # Query existing policy
    $UriGET = "https://graph.microsoft.com/beta/deviceManagement/configurationPolicies?`$filter=templateReference/TemplateFamily eq '$TemplateFamily' and name eq '$PolicyName'"
    $Existing = Invoke-MgGraphRequest -Method GET -Uri $UriGET
    $ExistingPolicy = $Existing.value | Select-Object -First 1

    $DoCreate = -not $ExistingPolicy
    $DoUpdate = $false
    $RemoteVersion = [version]"0.0.0.0"
    $RemoteXmlRaw = $null

    if ($ExistingPolicy) {
      $PolicyId = $ExistingPolicy.id
      $UriSettings = "https://graph.microsoft.com/beta/deviceManagement/configurationPolicies/$PolicyId/settings"
      $Settings = Invoke-MgGraphRequest -Method GET -Uri $UriSettings

      foreach ($v in $Settings.value) {
        $choice = $v.settingInstance.choiceSettingValue
        if ($choice -and $choice.children) {
          $xmlChild = $choice.children | Where-Object {
            $_.settingDefinitionId -eq "device_vendor_msft_policy_config_applicationcontrol_policies_{policyguid}_xml"
          } | Select-Object -First 1
          if ($xmlChild -and $xmlChild.simpleSettingValue.value) {
            $RemoteXmlRaw = [string]$xmlChild.simpleSettingValue.value
            break
          }
        }
        if (-not $RemoteXmlRaw -and $v.settingInstance.simpleSettingValue.value) {
          $candidate = [string]$v.settingInstance.simpleSettingValue.value
          if ($candidate -match '<SiPolicy') { $RemoteXmlRaw = $candidate; break }
        }
      }

      if ($RemoteXmlRaw) {
        $RemoteVersionText = Get-VersionText $RemoteXmlRaw
        if (-not $RemoteVersionText) { $RemoteVersionText = '0.0.0.0' }
        try { [version]$RemoteVersion = $RemoteVersionText } catch { }
      }

      if ($LocalVersion -gt $RemoteVersion) { $DoUpdate = $true }
            else {
        Write-Host "Versions match or remote is newer - no update required." -ForegroundColor Yellow
        continue
      }
    }

    if ($DryRun) {
      Write-Host -NoNewline "[DRY RUN] Would "
      if ($DoCreate) { Write-Host "CREATE $PolicyName" -ForegroundColor Cyan }
      elseif ($DoUpdate) { Write-Host "UPDATE $PolicyName" -ForegroundColor Cyan }
      continue
    }

    # Payload
    $SettingsPayload = @(
      @{
        "@odata.type"   = "#microsoft.graph.deviceManagementConfigurationSetting"
        settingInstance = @{
          "@odata.type"                    = "#microsoft.graph.deviceManagementConfigurationChoiceSettingInstance"
          settingDefinitionId              = "device_vendor_msft_policy_config_applicationcontrol_policies_{policyguid}_policiesoptions"
          choiceSettingValue               = @{
            "@odata.type"                 = "#microsoft.graph.deviceManagementConfigurationChoiceSettingValue"
            value                         = "device_vendor_msft_policy_config_applicationcontrol_configure_xml_selected"
            children                      = @(
              @{
                "@odata.type"                    = "#microsoft.graph.deviceManagementConfigurationSimpleSettingInstance"
                settingDefinitionId              = "device_vendor_msft_policy_config_applicationcontrol_policies_{policyguid}_xml"
                simpleSettingValue               = @{
                  "@odata.type"                 = "#microsoft.graph.deviceManagementConfigurationStringSettingValue"
                  value                         = [string]$LocalXmlRaw
                  settingValueTemplateReference = @{
                    settingValueTemplateId = "88f6f096-dedb-4cf1-ac2f-4b41e303adb5"
                  }
                }
                settingInstanceTemplateReference = @{
                  settingInstanceTemplateId = "4d709667-63d7-42f2-8e1b-b780f6c3c9c7"
                }
              }
            )
            settingValueTemplateReference = @{
              settingValueTemplateId = "b28c7dc4-c7b2-4ce2-8f51-6ebfd3ea69d3"
            }
          }
          settingInstanceTemplateReference = @{
            settingInstanceTemplateId = "1de98212-6949-42dc-a89c-e0ff6e5da04b"
          }
        }
      }
    )

    $Payload = @{
      name              = $PolicyName
      description       = "Uploaded at $(Get-Date -Format 'yyyy-MM-dd HH:mm') via Azure DevOps Pipeline"
      platforms         = "windows10"
      technologies      = "mdm"
      roleScopeTagIds   = @("0")
      templateReference = @{
        "@odata.type"          = "microsoft.graph.deviceManagementConfigurationPolicyTemplateReference"
        templateId             = $TemplateId
        templateFamily         = $TemplateFamily
        templateDisplayName    = $TemplateDisplayName
        templateDisplayVersion = "Version 1"
      }
      settings          = $SettingsPayload
    }

    try {
      if ($DoUpdate) {
        $UriUpdate = "https://graph.microsoft.com/beta/deviceManagement/configurationPolicies('$PolicyId')"
        Invoke-MgGraphRequest -Method PUT -Uri $UriUpdate -Body $Payload -ContentType "application/json"
        Write-Host "Updated: $PolicyName" -ForegroundColor Green
      } elseif ($DoCreate) {
        $UriCreate = "https://graph.microsoft.com/beta/deviceManagement/configurationPolicies"
        Invoke-MgGraphRequest -Method POST -Uri $UriCreate -Body $Payload -ContentType "application/json"
        Write-Host "Created: $PolicyName" -ForegroundColor Green
      }
    } catch {
      Write-Error "Failed to process '$PolicyName': $($_.Exception.Message)"
      if ($_.Exception.Response -and $_.Exception.Response.Content) {
        try {
          $errorContent = $_.Exception.Response.Content.ReadAsStringAsync().Result
          Write-Host ("Graph API Error Content:`n" + $errorContent) -ForegroundColor Red
        } catch { }
      }
    }
  }
}

end {
  Disconnect-MgGraph | Out-Null
  Write-Host ""
  Write-Host "Script execution completed." -ForegroundColor DarkGray
}


Script logic

The logic in the powershell script finds existing policy via filter query

and compares the VersionEx version of the local policy file with the VersionEx of the remote policy file (Intune). If no policy exists, it will be created.

If the local version is higher then the remote one, the script will update the remote policy (intune).

When you modified the local policy, you have to increase the VersionEx manually!

Commit changes in git & trigger pipeline

Lets have a look at how commitments trigger the pipeline. First, we made a change and perform a commit:

The commit and the following sync triggers the pipeline and start executing the corresponding tasks:

Once the pipeline has been successfully completed, the application control for business policy has been update.

run it interactively

The powershell script is developed to use it in both ways – either via Azure DevOps Pipeline or run it interactively via Powershell.

If you run it interactively, you can use the -DryRun parameter to simulate, what would be done.

pwsh .\Publish-ACFBPolicy.ps1 `
  -PolicyRootDir .\Policies\unsigned_original `
  -OutputPolicyDir .\Policies\signed `
  -CertFolder .\Certs `
  -DryRun

Total
0
Shares
Leave a Reply

Your email address will not be published. Required fields are marked *

Previous Post

Mastering App Control for Business | Part 6: Sign, apply and remove signed policies

Related Posts
Total
0
Share