Repository navigation
Configuration Repository Layout
A configuration is a Datum repository: a tree of YAML
files plus a Datum.yml that says how a value is resolved when more than one file supplies it.
The runner compiles that tree into one flat YAML file per node, then executes each compiled
file independently.
config/
├── Datum.yml # resolution precedence, lookup options, PipelineRunnerSettings
├── Projects/
│ ├── Present/
│ │ └── Magenta.yml # node-specific: the most specific layer
│ └── Absent/
│ └── Blue.yml
├── ProjectPolicies/
│ ├── Project.yml # role/policy layer
│ ├── ProjectGroups.yml
│ └── ProjectGitRepositories.yml
└── OrganizationPolicies/
├── Organization.yml # baseline layer: applies to everything
└── OrganizationGroups.yml
That is the shape of the repository's own Example Configuration/ directory, and it is the
shape the rest of this wiki's examples assume.
# Lower entries in ResolutionPrecedence lose to higher entries.
ResolutionPrecedence:
- Projects\$($Node.ProjectPresence)\$($Node.Project)
- ProjectPolicies\ProjectGitRepositories
- ProjectPolicies\ProjectGroups
- ProjectPolicies\Project
- OrganizationPolicies\OrganizationGroups
- OrganizationPolicies\Organization
DatumHandlersThrowOnError: true
default_lookup_options: MostSpecific
PipelineRunnerSettings:
ConfigurationVersion: 0.5
PipelineRunnerVersion: 1.1.0
Engine: DscV2
AllowExecutionScripts: true
Reboot: Fail
Target: Local
DatumHandlers:
Datum.InvokeCommand::InvokeCommand:
SkipDuringLoad: true
lookup_options:
variables:
merge_hash_array: deep
resources:
merge_hash_array: UniqueKeyValTuples
merge_options:
tuple_keys:
- nameTwo lookup options carry most of the weight:
-
variables: merge_hash_array: deep— variables from every layer are merged, so a baseline can set a default and a node can override one key without restating the rest. -
resources: merge_hash_array: UniqueKeyValTupleskeyed onname— resource lists from every layer are concatenated, and two entries with the samenamemerge into one instead of producing a duplicate. This is what lets a policy layer declare a resource and a node layer adjust one of its properties.
PipelineRunnerSettings is documented in full on PipelineRunnerSettings.
It is read before compilation, because a compiled per-node file does not carry it.
Every YAML file that contributes to a node has the same shape:
parameters: {} # run-scoped inputs, read with parameters('Name'); missing name throws
variables: {} # resolved values, read with variables('Name')
resources: [] # the ordered list of things to evaluateparameters and variables are dictionaries. resources is a list; each entry is documented
on Resource Properties.
OrganizationPolicies/Organization.yml:
parameters: {}
variables:
OrganizationName: Contoso
Project_Service_GitRepositories: enabled
resources: []ProjectPolicies/Project.yml:
parameters: {}
variables:
ProjectName: '[x={ $Node.Project }=]'
Project_Ensure: '[x={ $Node.ProjectPresence }=]'
resources:
- name: Project
type: AzureDevOpsDscNative/AzDoProject
properties:
projectName: $(variables('ProjectName'))
visibility: private
SourceControlType: Git
ProcessTemplate: Agile
Ensure: $(variables('Project_Ensure'))[x={ ... }=] is Datum's own InvokeCommand handler — it runs at compile time and puts a
literal value into the compiled file. $(...) is the runner's own expansion and runs at
execution time. They are two different stages; see Function Language.
Projects/Present/Magenta.yml:
parameters: {}
variables:
ProjectDescription: The Magenta project.
resources:
- name: Project
properties:
visibility: publicBecause resources merges on name, this does not declare a second Project resource — it
overrides visibility on the one the policy layer already declared.
Build-DatumConfiguration writes one *.yml per node into the cache directory. That file is
flat: precedence has been applied, [x={ }=] handlers have run, and there is no
PipelineRunnerSettings block left. Invoke-DscRunner then enumerates those files and calls
the runner once per file.
Build-DatumConfiguration -OutputPath .\cache -ConfigurationPath 'C:\config' -AllowedRoot .\cache
Get-ChildItem .\cache -Filter *.ymlInspect the compiled output whenever a value is not what you expected — it is the exact input the runner sees.
The compiled file is the unit of execution and the unit of scope:
-
dependsOnandnotifymay only name resources in the same compiled file. -
using()may only read a resource in the same compiled file. -
Stop-TaskProcessing/stopProcessing()skip the rest of this file, not the run.
A relationship that needs to cross node boundaries is not expressible today. See notify and using() for why the boundary exists and what widening it would require.
Build-DatumConfiguration calls Test-DatumConfiguration on the Datum structure it builds,
and Resolve-DscDatumProject once per node, so a compile is itself the validation pass:
New-Item -ItemType Directory -Path .\cache -Force | Out-Null
Build-DatumConfiguration -OutputPath .\cache -ConfigurationPath 'C:\config' -AllowedRoot .\cache -VerboseTest-DatumConfiguration throws when Datum.yml has no PipelineRunnerSettings block, and
checks the declared versions against the module's own. Compiling in CI against the
configuration repository makes a version drift fail there rather than mid-deployment.
OutputPath must already exist and must sit inside AllowedRoot (the system temp directory
when -AllowedRoot is omitted) — a path-traversal guard, so an ad-hoc call cannot be steered
into writing outside a trusted scratch root.
Dsc.PipelineRunner 1.1.0 — generated from
the repository on 2026-09-17.
Conceptual pages are authored in WikiSource/; command pages are generated from
comment-based help. Edit those, not the wiki: the wiki is overwritten on every release.