https://github.com/raandree/datum.invokecommand
Datum handler to invoke a script block
https://github.com/raandree/datum.invokecommand
configdata datum dsc
Last synced: 3 months ago
JSON representation
Datum handler to invoke a script block
- Host: GitHub
- URL: https://github.com/raandree/datum.invokecommand
- Owner: raandree
- License: mit
- Created: 2020-07-14T14:00:36.000Z (almost 6 years ago)
- Default Branch: master
- Last Pushed: 2023-03-20T13:01:42.000Z (about 3 years ago)
- Last Synced: 2024-10-12T13:09:08.339Z (over 1 year ago)
- Topics: configdata, datum, dsc
- Language: PowerShell
- Homepage:
- Size: 134 KB
- Stars: 2
- Watchers: 4
- Forks: 4
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- Contributing: CONTRIBUTING.md
- License: LICENSE
- Code of conduct: CODE_OF_CONDUCT.md
Awesome Lists containing this project
README
# Datum.InvokeCommand
[](https://dev.azure.com/RaijinCluster/Datum.InvokeCommand/_build/latest?definitionId=7&branchName=main)
[](https://www.powershellgallery.com/packages/Datum.InvokeCommand)
[](https://github.com/raandree/Datum.InvokeCommand/blob/main/LICENSE)
A [Datum](https://github.com/gaelcolas/datum/) handler module that enables dynamic command execution and string expansion within Datum configuration data for PowerShell Desired State Configuration (DSC).
## Overview
**Datum.InvokeCommand** extends the [Datum](https://github.com/gaelcolas/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 `ResolutionPrecedence` paths
For the Datum framework documentation, architecture overview, and handler concepts, refer to the [Datum project](https://github.com/gaelcolas/datum/).
## Requirements
- **PowerShell** 4.0 or later (Windows PowerShell or PowerShell 7+)
- **[Datum](https://github.com/gaelcolas/datum/)** module (0.40.0 or later recommended)
- **[DscResource.Common](https://github.com/dsccommunity/DscResource.Common)** module (bundled)
## Installation
Install from the PowerShell Gallery:
```powershell
Install-Module -Name Datum.InvokeCommand
```
Or with PowerShellGet v3+:
```powershell
Install-PSResource -Name Datum.InvokeCommand
```
## Quick Start
### 1. Register the Handler in Datum.yml
Add the `Datum.InvokeCommand` handler to your `Datum.yml` configuration file:
```yaml
DatumHandlers:
Datum.InvokeCommand::InvokeCommand:
SkipDuringLoad: true
```
> **Important**: The `SkipDuringLoad: true` flag 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).
### 2. Use Embedded Commands in YAML Files
Wrap your commands with the configurable delimiters (default: `[x=` and `=]`):
**Script blocks** (curly braces):
```yaml
NodeName: '[x={ $Node.Name }=]'
Environment: '[x={ $File.Directory.Name }=]'
```
**Expandable strings** (double quotes):
```yaml
Description: '[x= "$($Node.Role) in $($Node.Environment)" =]'
IpAddress: '[x= "$($Datum.Global.Network.IP.Subnet1).$($Node.IpNumber)" =]'
```
### 3. Resolve the Configuration
```powershell
Import-Module -Name datum
Import-Module -Name Datum.InvokeCommand
$datum = New-DatumStructure -DefinitionFile .\DscConfigData\Datum.yml
$rsop = Get-DatumRsop -Datum $datum -AllNodes $allNodes
```
Embedded commands are automatically evaluated during RSOP resolution.
## Embedded Command Syntax
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`).
### Script Blocks
Use curly braces `{ }` inside the delimiters to define a PowerShell script block. The script block is invoked and its output becomes the configuration value.
```yaml
# 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.
### Expandable Strings
Use double quotes `" "` inside the delimiters for PowerShell string expansion. Variables and sub-expressions are expanded at resolution time.
```yaml
# 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)"=]'
```
### Literal Strings
Single-quoted strings `' '` are returned as-is and cannot be expanded. A warning is emitted when a literal string is encountered.
```yaml
# This will return 'Get-Date' as a string, not a date - a warning is emitted
Value: "[x='Get-Date'=]"
```
## Available Variables in Embedded Commands
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. |
### Using $Node
The `$Node` variable provides access to the current node's properties:
```yaml
# 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'
```
### Using $Datum
The `$Datum` variable provides access to the entire configuration hierarchy:
```yaml
# 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" =]'
```
### Using $File
The `$File` variable represents the current YAML file being processed:
```yaml
# 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 }=]'
```
## Dynamic Resolution Precedence
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:
```yaml
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
```
The 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.
## Nested References
Datum.InvokeCommand automatically resolves nested embedded references. If the result of an embedded command itself contains another embedded command pattern, it is recursively evaluated:
```yaml
# If $Datum.Global.Template returns '[x={ Get-Date }=]',
# the inner command is automatically resolved
Value: '[x={ $Datum.Global.Template }=]'
```
## Configuration
### Customizing Delimiters
The header and footer delimiters are configured in [source/Config/Datum.InvokeCommand.Config.psd1](source/Config/Datum.InvokeCommand.Config.psd1):
```powershell
@{
Header = '[x='
Footer = '=]'
}
```
Change these values to use different delimiters, for example:
```powershell
@{
Header = '[Command='
Footer = ']'
}
```
### Error Handling
The module respects the `DatumHandlersThrowOnError` property in the Datum definition:
```yaml
# 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. |
## Exported Functions
### Invoke-InvokeCommandAction
The primary action handler. Evaluates embedded commands within Datum configuration data.
```powershell
Invoke-InvokeCommandAction
-InputObject
[-Datum ]
[-Node ]
[-ProjectPath ]
```
### Test-InvokeCommandFilter
The filter function that determines whether a value contains an embedded command.
```powershell
Test-InvokeCommandFilter
[-InputObject ]
[-ReturnValue]
```
For detailed parameter documentation, use `Get-Help`:
```powershell
Get-Help Invoke-InvokeCommandAction -Full
Get-Help Test-InvokeCommandFilter -Full
```
## Complete Example
Below is a complete example of a DSC configuration data project structure using Datum.InvokeCommand.
### Datum.yml
```yaml
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: Unique
```
### AllNodes/Dev/DSCFile01.yml
```yaml
NodeName: '[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 }=]'
```
### Multi-Role Node (AllNodes/Dev/DSCFileWeb01.yml)
```yaml
NodeName: '[x={ $Node.Name }=]'
Environment: '[x={ $File.Directory.Name }=]'
Role:
- FileServer
- WebServer
Description: '[x= "$($Node.Role) in $($Node.Environment)" =]'
Location: Frankfurt
```
When 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.
### Resolving Configuration
```powershell
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'
```
## Relationship to Datum
This module is a **handler module** for the [Datum](https://github.com/gaelcolas/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](https://github.com/gaelcolas/datum/)**: Extensible modules that process specific patterns in configuration values. Each handler provides a `Test-*Filter` function and an `Invoke-*Action` function.
- **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 `ResolutionPrecedence` list 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/](https://github.com/gaelcolas/datum/).
## Related Projects
- [Datum](https://github.com/gaelcolas/datum/) - The main Datum framework for hierarchical configuration data
- [Datum.ProtectedData](https://github.com/gaelcolas/Datum.ProtectedData) - Datum handler for encrypting/decrypting secrets
- [DscWorkshop](https://github.com/dsccommunity/DscWorkshop) - Full DSC CI/CD pipeline example using Datum
## Contributing
Please check out common DSC Community [contributing guidelines](https://dsccommunity.org/guidelines/contributing).
For test information, see the [Testing Guidelines](https://dsccommunity.org/guidelines/testing-guidelines/#running-tests).
## License
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.