Operations

Baseline Policy Groups

Segment your shared baseline into named groups and control exactly which tenants receive specific folders, file patterns, or individual policy files. Non-member tenants are automatically excluded at deploy time across every Configure-*.ps1 script — no YAML changes required.

5 min read Updated April 15, 2026

What are Baseline Policy Groups?

A single baseline repository is shared across all tenants. Baseline Policy Groups let you carve out portions of that baseline — by folder, file pattern, or individual file — and restrict deployment to only the tenants that are members of that group.

Dynamic rules

baseline/groups-config.json

Direct membership

config/tenant-groups.json (per tenant)

Configure via

Baseline Viewer → Groups tab

Key principle: Groups are additive for members (they receive extra content) and exclusive for non-members (group content is automatically skipped during deployment). No environment variables, no YAML edits — the pipeline scripts read the config files directly.

groups-config.json Schema

The config file lives at baseline/groups-config.json. It contains a single top-level groups object whose keys are the group display names.

{
  "groups": {
    "MSP Clients": {
      "membership": {
        "dynamic": []
      },
      "content": {
        "folders": ["intune/msp-apps"],
        "filePatterns": ["*.MSP.*"],
        "files": [
          "intune/compliance/MSP - Compliance Policy.json"
        ]
      }
    },
    "Enterprise": {
      "membership": {
        "dynamic": [
          { "type": "license", "skuPartNumbers": ["SPE_E3", "SPE_E5", "ENTERPRISEPREMIUM"] }
        ]
      },
      "content": {
        "folders": ["conditional-access/enterprise"],
        "filePatterns": [],
        "files": []
      }
    }
  }
}
Field Type
groups object
groups[name].membership.dynamic object[]
groups[name].membership.dynamic[].type "license"
groups[name].membership.dynamic[].skuPartNumbers string[]
groups[name].content object
groups[name].content.folders string[]
groups[name].content.filePatterns string[]
groups[name].content.files string[]

Content Rules

The three rule types are evaluated against files in both the baseline repository (deploy) and the baseline-remove repository (remove). A file matching any rule is considered part of the group.

folders string[]

Folder path prefixes relative to the repository root. Any file whose path starts with one of these prefixes is considered part of the group.

"folders": ["intune/msp-apps", "intune/msp-compliance"]

Match behavior

StartsWith — path must begin with the given string (case-insensitive).

Applies to

baseline and baseline-remove

filePatterns string[]

Glob patterns matched against file paths. Patterns without a slash are tested against the filename only (basename match); patterns with a slash are tested against the full relative path.

"filePatterns": ["*.MSP.*", "intune/apps/**/*.HC.json"]

Match behavior

* matches any character except /. ** matches across path separators. ? matches a single character.

Applies to

baseline and baseline-remove

files string[]

Explicit relative paths to individual files. Matched exactly against paths in both baseline and baseline-remove. Ideal for one-off assignments that do not fit a pattern or folder.

"files": ["intune/compliance/MSP - Compliance Policy.json"]

Match behavior

Exact path match (case-insensitive).

Applies to

baseline and baseline-remove

  • – Glob patterns without a / are matched against the filename only (basename), consistent with .gitignore semantics. E.g. *.MSP.* matches intune/apps/Company.MSP.json.
  • – Patterns with a / are matched against the full relative path. E.g. intune/apps/**/*.HC.json matches intune/apps/wave1/Policy.HC.json.
  • – All three rule types are additive — a file matching any rule is included.

Tenant Membership

Membership is resolved at deploy time from two independent sources that are merged together:

Direct membership

Stored in the tenant repo at config/tenant-groups.json. This is the source of truth — edited per-tenant, not derived from the baseline.

Tenant-contoso/config/tenant-groups.json

{
  "groups": ["MSP Clients"]
}

Dynamic membership

Defined in the baseline repo via membership.dynamic rules in groups-config.json. Evaluated against tenant data at deploy time — no manual sync needed.

License rule example

{ "type": "license",
  "skuPartNumbers": ["SPE_E3"] }

How it works: The deploy pipeline runs Resolve-TenantGroups.ps1 before configuration scripts. It reads direct membership from config/tenant-groups.json in the tenant repo, then evaluates membership.dynamic rules from groups-config.json against the tenant's backed-up license data (backups/licenses/subscribed-skus.json). The merged group list determines which content.folders, content.filePatterns, and content.files are applied.

Web Portal

The Baseline Viewer's Groups panel provides a full group management UI. Changes are committed directly to baseline/groups-config.json via the Gitea API.

Group Editor

Create groups and configure dynamic membership rules (license-based) and content rules (folders, file patterns). Direct membership is managed per-tenant in each tenant's config/tenant-groups.json. Each group has a collapsible "Files matched by rules" preview that resolves matching files from both baseline and baseline-remove in real time.

groups-config.jsonDynamic rulesLive preview

Manual File Assignment

The "Manual file assignments" section lets you pin individual files to a group using a searchable autocomplete input. Files from both the baseline and baseline-remove repositories are available for selection.

content.filesAutocomplete

Assign to Group (file action)

In the Deploy/Remove file browser, every file has an "Assign to group" button that expands on hover. It shows all defined groups with a checkmark for groups the file is already assigned to. Clicking a group immediately persists the change.

Hover dropdownInline assignment

Deployment Enforcement

Exclusion is enforced by the shared PowerShell helper Get-GroupExcludedFiles in Common-IgnoreHelpers.ps1. It is called by every Configure-*.ps1 script immediately after Get-FilteredPolicyFiles.

Pattern used in every Configure-*.ps1

$policyFiles = @(Get-FilteredPolicyFiles -PolicyFiles $policyFiles -BaselineRoot $baselineRoot)
$policyFiles = @(Get-GroupExcludedFiles -Files $policyFiles `
                    -TenantBaselinePath $TenantBaselinePath `
                    -TenantRepoPath $TenantRepoPath)

How Get-GroupExcludedFiles works

  1. 1
    Reads groups-config.json from the checked-out baseline repo.
  2. 2
    Reads tenant-groups.json from the checked-out tenant repo for direct group membership.
  3. 3
    Evaluates membership.dynamic rules from groups-config.json against backups/licenses/subscribed-skus.json in the tenant repo. Merges matching groups with the direct list.
  4. 4
    Collects folders, filePatterns, and files from every group the tenant is NOT a member of.
  5. 5
    Filters the $Files list — any file matching an excluded folder, pattern, or direct path is removed.
  6. 6
    Returns the filtered list; the calling script deploys only what remains.

Scripts that enforce group exclusions

Script
Configure-Intune.ps1
Configure-ConditionalAccess.ps1
Configure-BaselineGroups.ps1
Configure-AuthenticationMethods.ps1
Configure-ConsentPermissions.ps1
Configure-SharePointSettings.ps1
Configure-Exchange.ps1
Configure-CustomAttributes.ps1
Configure-EnterpriseApps.ps1
  • – Exclusion is read from the checked-out repo files — no environment variables or YAML changes are needed.
  • – A tenant with no tenant-groups.json (not a member of any group) receives only the baseline content that is not claimed by any group.
  • – A tenant that is a member of a group receives all baseline content plus the group's extra content (folders, patterns, files).
  • – Dynamic license rules are evaluated against backups/licenses/subscribed-skus.json in the tenant repo, written by the Backup-Licenses.ps1 step. If the file is absent, dynamic rules are silently skipped.
  • – The glob pattern matching in PowerShell follows the same basename/path rules as the web portal: patterns without a slash match the filename only.