{"id":16481926,"url":"https://github.com/raandree/datum.invokecommand","last_synced_at":"2026-02-23T21:01:09.651Z","repository":{"id":42035699,"uuid":"279600688","full_name":"raandree/Datum.InvokeCommand","owner":"raandree","description":"Datum handler to invoke a script block","archived":false,"fork":false,"pushed_at":"2023-03-20T13:01:42.000Z","size":137,"stargazers_count":2,"open_issues_count":1,"forks_count":4,"subscribers_count":4,"default_branch":"master","last_synced_at":"2024-10-12T13:09:08.339Z","etag":null,"topics":["configdata","datum","dsc"],"latest_commit_sha":null,"homepage":"","language":"PowerShell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/raandree.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-07-14T14:00:36.000Z","updated_at":"2023-09-13T07:07:50.000Z","dependencies_parsed_at":"2022-08-12T03:00:58.182Z","dependency_job_id":null,"html_url":"https://github.com/raandree/Datum.InvokeCommand","commit_stats":null,"previous_names":[],"tags_count":16,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/raandree%2FDatum.InvokeCommand","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/raandree%2FDatum.InvokeCommand/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/raandree%2FDatum.InvokeCommand/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/raandree%2FDatum.InvokeCommand/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/raandree","download_url":"https://codeload.github.com/raandree/Datum.InvokeCommand/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":224270342,"owners_count":17283649,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["configdata","datum","dsc"],"created_at":"2024-10-11T13:09:05.879Z","updated_at":"2026-02-23T21:01:09.643Z","avatar_url":"https://github.com/raandree.png","language":"PowerShell","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Datum.InvokeCommand\r\n\r\n[![Build Status](https://dev.azure.com/RaijinCluster/Datum.InvokeCommand/_apis/build/status/raandree.Datum.InvokeCommand?branchName=main)](https://dev.azure.com/RaijinCluster/Datum.InvokeCommand/_build/latest?definitionId=7\u0026branchName=main)\r\n[![PowerShell Gallery](https://img.shields.io/powershellgallery/v/Datum.InvokeCommand)](https://www.powershellgallery.com/packages/Datum.InvokeCommand)\r\n[![License](https://img.shields.io/github/license/raandree/Datum.InvokeCommand)](https://github.com/raandree/Datum.InvokeCommand/blob/main/LICENSE)\r\n\r\nA [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).\r\n\r\n## Overview\r\n\r\n**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.\r\n\r\nThis is particularly useful for:\r\n\r\n- **Avoiding data duplication** by referencing other parts of the Datum hierarchy (e.g., `$Datum.Global.Adds.DomainName`)\r\n- **Computing values dynamically** using PowerShell expressions (e.g., `{ Get-Date }`)\r\n- **Building strings from node context** using variable expansion (e.g., `\"$($Node.Name) in $($Node.Environment)\"`)\r\n- **Cross-referencing nodes** and generating data from the full configuration tree\r\n- **Supporting multi-role assignments** by dynamically resolving `ResolutionPrecedence` paths\r\n\r\nFor the Datum framework documentation, architecture overview, and handler concepts, refer to the [Datum project](https://github.com/gaelcolas/datum/).\r\n\r\n## Requirements\r\n\r\n- **PowerShell** 4.0 or later (Windows PowerShell or PowerShell 7+)\r\n- **[Datum](https://github.com/gaelcolas/datum/)** module (0.40.0 or later recommended)\r\n- **[DscResource.Common](https://github.com/dsccommunity/DscResource.Common)** module (bundled)\r\n\r\n## Installation\r\n\r\nInstall from the PowerShell Gallery:\r\n\r\n```powershell\r\nInstall-Module -Name Datum.InvokeCommand\r\n```\r\n\r\nOr with PowerShellGet v3+:\r\n\r\n```powershell\r\nInstall-PSResource -Name Datum.InvokeCommand\r\n```\r\n\r\n## Quick Start\r\n\r\n### 1. Register the Handler in Datum.yml\r\n\r\nAdd the `Datum.InvokeCommand` handler to your `Datum.yml` configuration file:\r\n\r\n```yaml\r\nDatumHandlers:\r\n  Datum.InvokeCommand::InvokeCommand:\r\n    SkipDuringLoad: true\r\n```\r\n\r\n\u003e **Important**: The `SkipDuringLoad: true` flag is required. It prevents the handler from\r\n\u003e being invoked during the initial Datum structure loading. Commands are only evaluated during\r\n\u003e value resolution (e.g., when computing RSOP - Resultant Set of Policy).\r\n\r\n### 2. Use Embedded Commands in YAML Files\r\n\r\nWrap your commands with the configurable delimiters (default: `[x=` and `=]`):\r\n\r\n**Script blocks** (curly braces):\r\n\r\n```yaml\r\nNodeName: '[x={ $Node.Name }=]'\r\nEnvironment: '[x={ $File.Directory.Name }=]'\r\n```\r\n\r\n**Expandable strings** (double quotes):\r\n\r\n```yaml\r\nDescription: '[x= \"$($Node.Role) in $($Node.Environment)\" =]'\r\nIpAddress: '[x= \"$($Datum.Global.Network.IP.Subnet1).$($Node.IpNumber)\" =]'\r\n```\r\n\r\n### 3. Resolve the Configuration\r\n\r\n```powershell\r\nImport-Module -Name datum\r\nImport-Module -Name Datum.InvokeCommand\r\n\r\n$datum = New-DatumStructure -DefinitionFile .\\DscConfigData\\Datum.yml\r\n$rsop = Get-DatumRsop -Datum $datum -AllNodes $allNodes\r\n```\r\n\r\nEmbedded commands are automatically evaluated during RSOP resolution.\r\n\r\n## Embedded Command Syntax\r\n\r\nAll 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`).\r\n\r\n### Script Blocks\r\n\r\nUse curly braces `{ }` inside the delimiters to define a PowerShell script block. The script block is invoked and its output becomes the configuration value.\r\n\r\n```yaml\r\n# Simple command\r\nNodeName: '[x={ $Node.Name }=]'\r\n\r\n# Complex expression\r\nComputers: '[x={ Get-DatumNodesRecursive -Nodes $Datum.AllNodes -Depth 4 |\r\n    Where-Object { $_.Name -like \"*Web*\" } |\r\n    ForEach-Object { @{ ComputerName = $_.Name } } }=]'\r\n\r\n# Return a hashtable\r\nCredential: '[x={ $Datum.Global.Adds.DomainAdminCredential }=]'\r\n\r\n# Multi-line script block\r\nDomainDn: |\r\n  [x={\r\n    $parts = $Datum.Global.Adds.DomainFqdn -split '\\.'\r\n    $parts | ForEach-Object { \"DC=$_\" } | Join-String -Separator ','\r\n  }=]\r\n```\r\n\r\nScript blocks can return **any PowerShell object type**: strings, integers, dates, hashtables, arrays, PSCredential objects, and more.\r\n\r\n### Expandable Strings\r\n\r\nUse double quotes `\" \"` inside the delimiters for PowerShell string expansion. Variables and sub-expressions are expanded at resolution time.\r\n\r\n```yaml\r\n# Variable expansion\r\nDescription: '[x= \"$($Node.Role) in $($Node.Environment)\" =]'\r\n\r\n# Sub-expression with Datum lookup\r\nIpAddress: '[x= \"$($Datum.Global.Network.IP.Subnet1).$($Node.IpNumber)\" =]'\r\nGateway: '[x= \"$($Datum.Global.Network.IP.Subnet1).50\" =]'\r\n\r\n# Multi-line expandable string\r\nMessage: '[x=\"Server $($Node.Name) is\r\n  located in $($Node.Location)\"=]'\r\n```\r\n\r\n### Literal Strings\r\n\r\nSingle-quoted strings `' '` are returned as-is and cannot be expanded. A warning is emitted when a literal string is encountered.\r\n\r\n```yaml\r\n# This will return 'Get-Date' as a string, not a date - a warning is emitted\r\nValue: \"[x='Get-Date'=]\"\r\n```\r\n\r\n## Available Variables in Embedded Commands\r\n\r\nInside script blocks and expandable strings, the following variables are automatically available:\r\n\r\n| Variable | Description |\r\n|----------|-------------|\r\n| `$Node` | The current node's configuration data (resolved from the YAML file or RSOP). |\r\n| `$Datum` | The full Datum configuration hierarchy, enabling lookups like `$Datum.Global.Adds.DomainName`. |\r\n| `$File` | The `System.IO.FileInfo` object representing the current YAML file being processed. |\r\n\r\n### Using $Node\r\n\r\nThe `$Node` variable provides access to the current node's properties:\r\n\r\n```yaml\r\n# In AllNodes/Dev/DSCFile01.yml\r\nNodeName: '[x={ $Node.Name }=]'              # Returns 'DSCFile01'\r\nEnvironment: '[x={ $File.Directory.Name }=]'  # Returns 'Dev'\r\nRole: FileServer\r\nDescription: '[x= \"$($Node.Role) in $($Node.Environment)\" =]'  # Returns 'FileServer in Dev'\r\n```\r\n\r\n### Using $Datum\r\n\r\nThe `$Datum` variable provides access to the entire configuration hierarchy:\r\n\r\n```yaml\r\n# Reference global configuration\r\nDomainName: '[x={ $Datum.Global.Adds.DomainFqdn }=]'\r\nDomainDn: '[x=\"$($Datum.Global.Adds.DomainDn)\"=]'\r\n\r\n# Use Datum lookups in complex expressions\r\nDnsServer:\r\n  - '[x= \"$($Datum.Global.Network.IP.Subnet1).10\" =]'\r\n```\r\n\r\n### Using $File\r\n\r\nThe `$File` variable represents the current YAML file being processed:\r\n\r\n```yaml\r\n# Get the directory name (often used for environment)\r\nEnvironment: '[x={ $File.Directory.Name }=]'\r\n\r\n# Get the source file path for tagging\r\nDscTagging:\r\n  Layers:\r\n    - '[x={ Get-DatumSourceFile -Path $File }=]'\r\n```\r\n\r\n## Dynamic Resolution Precedence\r\n\r\nOne 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:\r\n\r\n```yaml\r\nResolutionPrecedence:\r\n  - AllNodes\\$($Node.Environment)\\$($Node.NodeName)\r\n  - '[x= \"Environment\\$($Node.Environment)\" =]'\r\n  - Locations\\$($Node.Location)\r\n  - '[x={ $Node.Role | ForEach-Object { \"Roles\\$_\" } } =]'\r\n  - Baselines\\Security\r\n  - Baselines\\$($Node.Baseline)\r\n  - Baselines\\DscLcm\r\n```\r\n\r\nThe 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.\r\n\r\n## Nested References\r\n\r\nDatum.InvokeCommand automatically resolves nested embedded references. If the result of an embedded command itself contains another embedded command pattern, it is recursively evaluated:\r\n\r\n```yaml\r\n# If $Datum.Global.Template returns '[x={ Get-Date }=]',\r\n# the inner command is automatically resolved\r\nValue: '[x={ $Datum.Global.Template }=]'\r\n```\r\n\r\n## Configuration\r\n\r\n### Customizing Delimiters\r\n\r\nThe header and footer delimiters are configured in [source/Config/Datum.InvokeCommand.Config.psd1](source/Config/Datum.InvokeCommand.Config.psd1):\r\n\r\n```powershell\r\n@{\r\n    Header = '[x='\r\n    Footer = '=]'\r\n}\r\n```\r\n\r\nChange these values to use different delimiters, for example:\r\n\r\n```powershell\r\n@{\r\n    Header = '[Command='\r\n    Footer = ']'\r\n}\r\n```\r\n\r\n### Error Handling\r\n\r\nThe module respects the `DatumHandlersThrowOnError` property in the Datum definition:\r\n\r\n```yaml\r\n# In Datum.yml\r\nDatumHandlersThrowOnError: true\r\n```\r\n\r\n| Setting | Behavior |\r\n|---------|----------|\r\n| `$false` (default) | Errors emit warnings and return the original input value. |\r\n| `$true` | Errors are terminating and stop processing immediately. |\r\n\r\n## Exported Functions\r\n\r\n### Invoke-InvokeCommandAction\r\n\r\nThe primary action handler. Evaluates embedded commands within Datum configuration data.\r\n\r\n```powershell\r\nInvoke-InvokeCommandAction\r\n    -InputObject \u003cObject\u003e\r\n    [-Datum \u003cHashtable\u003e]\r\n    [-Node \u003cObject\u003e]\r\n    [-ProjectPath \u003cString\u003e]\r\n```\r\n\r\n### Test-InvokeCommandFilter\r\n\r\nThe filter function that determines whether a value contains an embedded command.\r\n\r\n```powershell\r\nTest-InvokeCommandFilter\r\n    [-InputObject \u003cObject\u003e]\r\n    [-ReturnValue]\r\n```\r\n\r\nFor detailed parameter documentation, use `Get-Help`:\r\n\r\n```powershell\r\nGet-Help Invoke-InvokeCommandAction -Full\r\nGet-Help Test-InvokeCommandFilter -Full\r\n```\r\n\r\n## Complete Example\r\n\r\nBelow is a complete example of a DSC configuration data project structure using Datum.InvokeCommand.\r\n\r\n### Datum.yml\r\n\r\n```yaml\r\nResolutionPrecedence:\r\n  - AllNodes\\$($Node.Environment)\\$($Node.NodeName)\r\n  - '[x= \"Environment\\$($Node.Environment)\" =]'\r\n  - Locations\\$($Node.Location)\r\n  - '[x={ $Node.Role | ForEach-Object { \"Roles\\$_\" } } =]'\r\n  - Baselines\\Security\r\n  - Baselines\\$($Node.Baseline)\r\n  - Baselines\\DscLcm\r\n\r\nDatumHandlers:\r\n  Datum.InvokeCommand::InvokeCommand:\r\n    SkipDuringLoad: true\r\n\r\ndefault_lookup_options: MostSpecific\r\n\r\nlookup_options:\r\n  Configurations:\r\n    merge_basetype_array: Unique\r\n```\r\n\r\n### AllNodes/Dev/DSCFile01.yml\r\n\r\n```yaml\r\nNodeName: '[x={ $Node.Name }=]'\r\nEnvironment: '[x={ $File.Directory.Name }=]'\r\nRole: FileServer\r\nDescription: '[x= \"$($Node.Role) in $($Node.Environment)\" =]'\r\nLocation: Frankfurt\r\nBaseline: Server\r\nIpNumber: 100\r\n\r\nNetworkIpConfiguration:\r\n  Interfaces:\r\n    - InterfaceAlias: Ethernet\r\n      IpAddress: '[x= \"$($Datum.Global.Network.IP.Subnet1).$($Node.IpNumber)\" =]'\r\n      Prefix: 24\r\n      Gateway: '[x= \"$($Datum.Global.Network.IP.Subnet1).50\" =]'\r\n      DnsServer:\r\n        - '[x= \"$($Datum.Global.Network.IP.Subnet1).10\" =]'\r\n      DisableNetbios: true\r\n\r\nDscTagging:\r\n  Layers:\r\n    - '[x={ Get-DatumSourceFile -Path $File }=]'\r\n```\r\n\r\n### Multi-Role Node (AllNodes/Dev/DSCFileWeb01.yml)\r\n\r\n```yaml\r\nNodeName: '[x={ $Node.Name }=]'\r\nEnvironment: '[x={ $File.Directory.Name }=]'\r\nRole:\r\n  - FileServer\r\n  - WebServer\r\nDescription: '[x= \"$($Node.Role) in $($Node.Environment)\" =]'\r\nLocation: Frankfurt\r\n```\r\n\r\nWhen 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.\r\n\r\n### Resolving Configuration\r\n\r\n```powershell\r\nImport-Module datum\r\nImport-Module Datum.InvokeCommand\r\n\r\n# Load the Datum structure\r\n$datum = New-DatumStructure -DefinitionFile .\\DscConfigData\\Datum.yml\r\n\r\n# Get all nodes\r\n$allNodes = Get-DatumNodesRecursive -Nodes $datum.AllNodes -Depth 4\r\n\r\n# Compute RSOP for a specific node\r\n$rsop = Get-DatumRsop -Datum $datum -AllNodes $allNodes -Filter { $_.Name -eq 'DSCFile01' }\r\n\r\n# Access resolved values\r\n$rsop.NodeName          # 'DSCFile01'\r\n$rsop.Description       # 'FileServer in Dev'\r\n$rsop.NetworkIpConfiguration.Interfaces[0].IpAddress  # e.g., '192.168.1.100'\r\n```\r\n\r\n## Relationship to Datum\r\n\r\nThis 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.\r\n\r\n**Key Datum concepts relevant to this module:**\r\n\r\n- **[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.\r\n- **ResolutionPrecedence**: The ordered list of paths Datum searches to resolve a value, supporting hierarchical overrides.\r\n- **RSOP (Resultant Set of Policy)**: The fully merged configuration for a node, computed by walking the `ResolutionPrecedence` list and merging values.\r\n- **Merge Strategies**: Datum supports various merge strategies (`MostSpecific`, `deep`, `Unique`, etc.) that control how values from different levels are combined.\r\n\r\nFor the complete Datum documentation, visit [https://github.com/gaelcolas/datum/](https://github.com/gaelcolas/datum/).\r\n\r\n## Related Projects\r\n\r\n- [Datum](https://github.com/gaelcolas/datum/) - The main Datum framework for hierarchical configuration data\r\n- [Datum.ProtectedData](https://github.com/gaelcolas/Datum.ProtectedData) - Datum handler for encrypting/decrypting secrets\r\n- [DscWorkshop](https://github.com/dsccommunity/DscWorkshop) - Full DSC CI/CD pipeline example using Datum\r\n\r\n## Contributing\r\n\r\nPlease check out common DSC Community [contributing guidelines](https://dsccommunity.org/guidelines/contributing).\r\n\r\nFor test information, see the [Testing Guidelines](https://dsccommunity.org/guidelines/testing-guidelines/#running-tests).\r\n\r\n## License\r\n\r\nThis project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.\r\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fraandree%2Fdatum.invokecommand","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fraandree%2Fdatum.invokecommand","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fraandree%2Fdatum.invokecommand/lists"}