A Datum handler module that enables dynamic command execution and string expansion within Datum configuration data for PowerShell Desired State Configuration (DSC).
Datum.InvokeCommand extends the Datum configuration management framework by allowing you to embed PowerShell script blocks and expandable strings directly in your YAML configuration files. Instead of hard-coding values, you can dynamically compute configuration data at resolution time.
This is particularly useful for:
- Avoiding data duplication by referencing other parts of the Datum hierarchy (e.g.,
$Datum.Global.Adds.DomainName) - Computing values dynamically using PowerShell expressions (e.g.,
{ Get-Date }) - Building strings from node context using variable expansion (e.g.,
"$($Node.Name) in $($Node.Environment)") - Cross-referencing nodes and generating data from the full configuration tree
- Supporting multi-role assignments by dynamically resolving
ResolutionPrecedencepaths
For the Datum framework documentation, architecture overview, and handler concepts, refer to the Datum project.
- PowerShell 4.0 or later (Windows PowerShell or PowerShell 7+)
- Datum module (0.40.0 or later recommended)
- DscResource.Common module (bundled)
Install from the PowerShell Gallery:
Install-Module -Name Datum.InvokeCommandOr with PowerShellGet v3+:
Install-PSResource -Name Datum.InvokeCommandAdd the Datum.InvokeCommand handler to your Datum.yml configuration file:
DatumHandlers:
Datum.InvokeCommand::InvokeCommand:
SkipDuringLoad: trueImportant: The
SkipDuringLoad: trueflag is required. It prevents the handler from being invoked during the initial Datum structure loading. Commands are only evaluated during value resolution (e.g., when computing RSOP - Resultant Set of Policy).
Wrap your commands with the configurable delimiters (default: [x= and =]):
Script blocks (curly braces):
NodeName: '[x={ $Node.Name }=]'
Environment: '[x={ $File.Directory.Name }=]'Expandable strings (double quotes):
Description: '[x= "$($Node.Role) in $($Node.Environment)" =]'
IpAddress: '[x= "$($Datum.Global.Network.IP.Subnet1).$($Node.IpNumber)" =]'Import-Module -Name datum
Import-Module -Name Datum.InvokeCommand
$datum = New-DatumStructure -DefinitionFile .\DscConfigData\Datum.yml
$rsop = Get-DatumRsop -Datum $datum -AllNodes $allNodesEmbedded commands are automatically evaluated during RSOP resolution.
All embedded commands are wrapped with header and footer delimiters. The default delimiters are [x= (header) and =] (footer). These can be customized in the module configuration file (Config\Datum.InvokeCommand.Config.psd1).
Use curly braces { } inside the delimiters to define a PowerShell script block. The script block is invoked and its output becomes the configuration value.
# Simple command
NodeName: '[x={ $Node.Name }=]'
# Complex expression
Computers: '[x={ Get-DatumNodesRecursive -Nodes $Datum.AllNodes -Depth 4 |
Where-Object { $_.Name -like "*Web*" } |
ForEach-Object { @{ ComputerName = $_.Name } } }=]'
# Return a hashtable
Credential: '[x={ $Datum.Global.Adds.DomainAdminCredential }=]'
# Multi-line script block
DomainDn: |
[x={
$parts = $Datum.Global.Adds.DomainFqdn -split '\.'
$parts | ForEach-Object { "DC=$_" } | Join-String -Separator ','
}=]Script blocks can return any PowerShell object type: strings, integers, dates, hashtables, arrays, PSCredential objects, and more.
Use double quotes " " inside the delimiters for PowerShell string expansion. Variables and sub-expressions are expanded at resolution time.
# Variable expansion
Description: '[x= "$($Node.Role) in $($Node.Environment)" =]'
# Sub-expression with Datum lookup
IpAddress: '[x= "$($Datum.Global.Network.IP.Subnet1).$($Node.IpNumber)" =]'
Gateway: '[x= "$($Datum.Global.Network.IP.Subnet1).50" =]'
# Multi-line expandable string
Message: '[x="Server $($Node.Name) is
located in $($Node.Location)"=]'Single-quoted strings ' ' are returned as-is and cannot be expanded. A warning is emitted when a literal string is encountered.
# This will return 'Get-Date' as a string, not a date - a warning is emitted
Value: "[x='Get-Date'=]"Inside script blocks and expandable strings, the following variables are automatically available:
| Variable | Description |
|---|---|
$Node |
The current node's configuration data (resolved from the YAML file or RSOP). |
$Datum |
The full Datum configuration hierarchy, enabling lookups like $Datum.Global.Adds.DomainName. |
$File |
The System.IO.FileInfo object representing the current YAML file being processed. |
The $Node variable provides access to the current node's properties:
# In AllNodes/Dev/DSCFile01.yml
NodeName: '[x={ $Node.Name }=]' # Returns 'DSCFile01'
Environment: '[x={ $File.Directory.Name }=]' # Returns 'Dev'
Role: FileServer
Description: '[x= "$($Node.Role) in $($Node.Environment)" =]' # Returns 'FileServer in Dev'The $Datum variable provides access to the entire configuration hierarchy:
# Reference global configuration
DomainName: '[x={ $Datum.Global.Adds.DomainFqdn }=]'
DomainDn: '[x="$($Datum.Global.Adds.DomainDn)"=]'
# Use Datum lookups in complex expressions
DnsServer:
- '[x= "$($Datum.Global.Network.IP.Subnet1).10" =]'The $File variable represents the current YAML file being processed:
# Get the directory name (often used for environment)
Environment: '[x={ $File.Directory.Name }=]'
# Get the source file path for tagging
DscTagging:
Layers:
- '[x={ Get-DatumSourceFile -Path $File }=]'One of the most powerful features of Datum.InvokeCommand is the ability to use embedded commands in the ResolutionPrecedence section of Datum.yml. This enables dynamic lookup paths:
ResolutionPrecedence:
- AllNodes\$($Node.Environment)\$($Node.NodeName)
- '[x= "Environment\$($Node.Environment)" =]'
- Locations\$($Node.Location)
- '[x={ $Node.Role | ForEach-Object { "Roles\$_" } } =]'
- Baselines\Security
- Baselines\$($Node.Baseline)
- Baselines\DscLcmThe handler for ResolutionPrecedence in the example above enables multi-role support: when a node has multiple roles (e.g., Role: [FileServer, WebServer]), the script block dynamically generates multiple role paths, and configuration from all roles is merged according to the Datum merge strategy.
Datum.InvokeCommand automatically resolves nested embedded references. If the result of an embedded command itself contains another embedded command pattern, it is recursively evaluated:
# If $Datum.Global.Template returns '[x={ Get-Date }=]',
# the inner command is automatically resolved
Value: '[x={ $Datum.Global.Template }=]'The header and footer delimiters are configured in source/Config/Datum.InvokeCommand.Config.psd1:
@{
Header = '[x='
Footer = '=]'
}Change these values to use different delimiters, for example:
@{
Header = '[Command='
Footer = ']'
}The module respects the DatumHandlersThrowOnError property in the Datum definition:
# In Datum.yml
DatumHandlersThrowOnError: true| Setting | Behavior |
|---|---|
$false (default) |
Errors emit warnings and return the original input value. |
$true |
Errors are terminating and stop processing immediately. |
The primary action handler. Evaluates embedded commands within Datum configuration data.
Invoke-InvokeCommandAction
-InputObject <Object>
[-Datum <Hashtable>]
[-Node <Object>]
[-ProjectPath <String>]The filter function that determines whether a value contains an embedded command.
Test-InvokeCommandFilter
[-InputObject <Object>]
[-ReturnValue]For detailed parameter documentation, use Get-Help:
Get-Help Invoke-InvokeCommandAction -Full
Get-Help Test-InvokeCommandFilter -FullBelow is a complete example of a DSC configuration data project structure using Datum.InvokeCommand.
ResolutionPrecedence:
- AllNodes\$($Node.Environment)\$($Node.NodeName)
- '[x= "Environment\$($Node.Environment)" =]'
- Locations\$($Node.Location)
- '[x={ $Node.Role | ForEach-Object { "Roles\$_" } } =]'
- Baselines\Security
- Baselines\$($Node.Baseline)
- Baselines\DscLcm
DatumHandlers:
Datum.InvokeCommand::InvokeCommand:
SkipDuringLoad: true
default_lookup_options: MostSpecific
lookup_options:
Configurations:
merge_basetype_array: UniqueNodeName: '[x={ $Node.Name }=]'
Environment: '[x={ $File.Directory.Name }=]'
Role: FileServer
Description: '[x= "$($Node.Role) in $($Node.Environment)" =]'
Location: Frankfurt
Baseline: Server
IpNumber: 100
NetworkIpConfiguration:
Interfaces:
- InterfaceAlias: Ethernet
IpAddress: '[x= "$($Datum.Global.Network.IP.Subnet1).$($Node.IpNumber)" =]'
Prefix: 24
Gateway: '[x= "$($Datum.Global.Network.IP.Subnet1).50" =]'
DnsServer:
- '[x= "$($Datum.Global.Network.IP.Subnet1).10" =]'
DisableNetbios: true
DscTagging:
Layers:
- '[x={ Get-DatumSourceFile -Path $File }=]'NodeName: '[x={ $Node.Name }=]'
Environment: '[x={ $File.Directory.Name }=]'
Role:
- FileServer
- WebServer
Description: '[x= "$($Node.Role) in $($Node.Environment)" =]'
Location: FrankfurtWhen Datum resolves this node, the ResolutionPrecedence script block '[x={ $Node.Role | ForEach-Object { "Roles\$_" } } =]' expands to both Roles\FileServer and Roles\WebServer, merging configuration from both role definitions.
Import-Module datum
Import-Module Datum.InvokeCommand
# Load the Datum structure
$datum = New-DatumStructure -DefinitionFile .\DscConfigData\Datum.yml
# Get all nodes
$allNodes = Get-DatumNodesRecursive -Nodes $datum.AllNodes -Depth 4
# Compute RSOP for a specific node
$rsop = Get-DatumRsop -Datum $datum -AllNodes $allNodes -Filter { $_.Name -eq 'DSCFile01' }
# Access resolved values
$rsop.NodeName # 'DSCFile01'
$rsop.Description # 'FileServer in Dev'
$rsop.NetworkIpConfiguration.Interfaces[0].IpAddress # e.g., '192.168.1.100'This module is a handler module for the Datum framework. Datum is a configuration management module for PowerShell DSC that provides a hierarchical data store with merge capabilities similar to Hiera in Puppet.
Key Datum concepts relevant to this module:
- Datum Handlers: Extensible modules that process specific patterns in configuration values. Each handler provides a
Test-*Filterfunction and anInvoke-*Actionfunction. - ResolutionPrecedence: The ordered list of paths Datum searches to resolve a value, supporting hierarchical overrides.
- RSOP (Resultant Set of Policy): The fully merged configuration for a node, computed by walking the
ResolutionPrecedencelist and merging values. - Merge Strategies: Datum supports various merge strategies (
MostSpecific,deep,Unique, etc.) that control how values from different levels are combined.
For the complete Datum documentation, visit https://github.com/gaelcolas/datum/.
- Datum - The main Datum framework for hierarchical configuration data
- Datum.ProtectedData - Datum handler for encrypting/decrypting secrets
- DscWorkshop - Full DSC CI/CD pipeline example using Datum
Please check out common DSC Community contributing guidelines.
For test information, see the Testing Guidelines.
This project is licensed under the MIT License - see the LICENSE file for details.