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.
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.
baseline/groups-config.json
config/tenant-groups.json (per tenant)
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.
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.
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.
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
- 1Reads groups-config.json from the checked-out baseline repo.
- 2Reads tenant-groups.json from the checked-out tenant repo for direct group membership.
- 3Evaluates membership.dynamic rules from groups-config.json against backups/licenses/subscribed-skus.json in the tenant repo. Merges matching groups with the direct list.
- 4Collects folders, filePatterns, and files from every group the tenant is NOT a member of.
- 5Filters the $Files list — any file matching an excluded folder, pattern, or direct path is removed.
- 6Returns 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.