Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion dsc/examples/hello_world.dsc.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@

targetScope = 'desiredStateConfiguration'

param text string = 'Hello, world!'

// use workaround where Bicep currently requires version in date format
resource echo 'Microsoft.DSC.Debug/Echo@2025-08-27' = {
name: 'exampleEcho'
properties: {
output: 'Hello, world!'
output: text
}
}

Expand Down
3 changes: 3 additions & 0 deletions dsc/examples/hello_world.dsc.bicepparam
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
using 'hello_world.dsc.bicep'

param text = 'This is a parameterized hello world!'
3 changes: 0 additions & 3 deletions dsc/locales/en-us.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,7 @@ mcpAbout = "Use DSC as a MCP server"
[main]
ctrlCReceived = "Ctrl-C received"
failedCtrlCHandler = "Failed to set Ctrl-C handler"
failedReadingParametersFile = "Failed to read parameters file"
readingParametersFromStdin = "Reading parameters from STDIN"
generatingCompleter = "Generating completion script for"
readingParametersFile = "Reading parameters from file"
mergingParameters = "Merging inline parameters with parameters file (inline takes precedence)"
failedMergingParameters = "Failed to merge parameters"
usingDscVersion = "Running DSC version"
Expand Down
41 changes: 7 additions & 34 deletions dsc/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@
use args::{Args, SubCommand};
use clap::{CommandFactory, Parser};
use clap_complete::generate;
use dsc_lib::progress::ProgressFormat;
use mcp::start_mcp_server;
use rust_i18n::{i18n, t};
use std::{io, io::Read, process::exit};
use std::{io, process::exit};
use sysinfo::{Process, RefreshKind, System, get_current_pid, ProcessRefreshKind};
use tracing::{error, info, warn, debug};
use dsc_lib::progress::ProgressFormat;

use crate::util::EXIT_INVALID_INPUT;
use crate::util::{EXIT_INVALID_INPUT, get_input};

#[cfg(debug_assertions)]
use crossterm::event;
Expand Down Expand Up @@ -54,38 +54,11 @@ fn main() {
generate(shell, &mut cmd, "dsc", &mut io::stdout());
},
SubCommand::Config { subcommand, parameters, parameters_file, system_root, as_group, as_assert, as_include } => {
// Read parameters from file if provided
let file_params = if let Some(file_name) = &parameters_file {
if file_name == "-" {
info!("{}", t!("main.readingParametersFromStdin"));
let mut stdin = Vec::<u8>::new();
match io::stdin().read_to_end(&mut stdin) {
Ok(_) => {
match String::from_utf8(stdin) {
Ok(input) => Some(input),
Err(err) => {
error!("{}: {err}", t!("util.invalidUtf8"));
exit(EXIT_INVALID_INPUT);
}
}
},
Err(err) => {
error!("{}: {err}", t!("util.failedToReadStdin"));
exit(EXIT_INVALID_INPUT);
}
}
} else {
info!("{}: {file_name}", t!("main.readingParametersFile"));
match std::fs::read_to_string(file_name) {
Ok(content) => Some(content),
Err(err) => {
error!("{} '{file_name}': {err}", t!("main.failedReadingParametersFile"));
exit(util::EXIT_INVALID_INPUT);
}
}
}
} else {
let params = get_input(None, parameters_file.as_ref());
let file_params = if params.is_empty() {
None
} else {
Some(params)
};

let merged_parameters = match (file_params, parameters) {
Expand Down
1 change: 1 addition & 0 deletions dsc/src/subcommand.rs
Original file line number Diff line number Diff line change
Expand Up @@ -607,6 +607,7 @@ fn list_extensions(dsc: &mut DscManager, extension_name: Option<&String>, format
let capability_types = [
(ExtensionCapability::Discover, "d"),
(ExtensionCapability::Secret, "s"),
(ExtensionCapability::Import, "i"),
];
let mut capabilities = "-".repeat(capability_types.len());

Expand Down
2 changes: 1 addition & 1 deletion dsc/tests/dsc_expressions.tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ Describe 'Expressions tests' {
"@
$out = dsc config get -i $yaml 2>&1
$LASTEXITCODE | Should -Be 2
$out | Should -BeLike "*ERROR*"
,$out | Should -BeLike "*ERROR*" -Because ($out | Out-String)
}

It 'Multi-line string literals work' {
Expand Down
26 changes: 17 additions & 9 deletions dsc/tests/dsc_extension_discover.tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -24,29 +24,37 @@ Describe 'Discover extension tests' {
$out = dsc extension list | ConvertFrom-Json
$LASTEXITCODE | Should -Be 0
if ($IsWindows) {
$out.Count | Should -Be 3 -Because ($out | Out-String)
$out.Count | Should -Be 4 -Because ($out | Out-String)
$out[0].type | Should -Be 'Microsoft.DSC.Extension/Bicep'
$out[0].version | Should -Be '0.1.0'
$out[0].capabilities | Should -BeExactly @('import')
$out[0].manifest | Should -Not -BeNullOrEmpty
$out[1].type | Should -Be 'Microsoft.Windows.Appx/Discover'
$out[1].version | Should -Be '0.1.0'
$out[1].capabilities | Should -BeExactly @('discover')
$out[1].type | Should -BeExactly 'Microsoft.DSC.Extension/BicepParameters'
$out[1].version | Should -BeExactly '0.1.0'
$out[1].capabilities | Should -BeExactly @('import')
$out[1].manifest | Should -Not -BeNullOrEmpty
$out[2].type | Should -BeExactly 'Test/Discover'
$out[2].version | Should -BeExactly '0.1.0'
$out[2].type | Should -Be 'Microsoft.Windows.Appx/Discover'
$out[2].version | Should -Be '0.1.0'
$out[2].capabilities | Should -BeExactly @('discover')
$out[2].manifest | Should -Not -BeNullOrEmpty
$out[3].type | Should -BeExactly 'Test/Discover'
$out[3].version | Should -BeExactly '0.1.0'
$out[3].capabilities | Should -BeExactly @('discover')
$out[3].manifest | Should -Not -BeNullOrEmpty
} else {
$out.Count | Should -Be 2 -Because ($out | Out-String)
$out.Count | Should -Be 3 -Because ($out | Out-String)
$out[0].type | Should -Be 'Microsoft.DSC.Extension/Bicep'
$out[0].version | Should -Be '0.1.0'
$out[0].capabilities | Should -BeExactly @('import')
$out[0].manifest | Should -Not -BeNullOrEmpty
$out[1].type | Should -BeExactly 'Test/Discover'
$out[1].type | Should -BeExactly 'Microsoft.DSC.Extension/BicepParameters'
$out[1].version | Should -BeExactly '0.1.0'
$out[1].capabilities | Should -BeExactly @('discover')
$out[1].capabilities | Should -BeExactly @('import')
$out[1].manifest | Should -Not -BeNullOrEmpty
$out[2].type | Should -BeExactly 'Test/Discover'
$out[2].version | Should -BeExactly '0.1.0'
$out[2].capabilities | Should -BeExactly @('discover')
$out[2].manifest | Should -Not -BeNullOrEmpty
}
}

Expand Down
63 changes: 61 additions & 2 deletions dsc/tests/dsc_parameters.tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,65 @@ Describe 'Parameters tests' {
$errorMessage | Should -BeLike "*ERROR*Empty input provided*"
}

It 'Parameters in ARM syntax are supported' {
$config_yaml = @"
`$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json
parameters:
myString:
type: string
myObject:
type: object
myArray:
type: array
myInt:
type: int
myBool:
type: bool
resources:
- name: echo
type: Microsoft.DSC.Debug/Echo
properties:
output: >-
[concat(
parameters('myString'),
'-',
parameters('myObject').prop1,
'-',
parameters('myArray')[0],
parameters('myArray')[1],
'-',
string(parameters('myInt')),
'-',
string(parameters('myBool')
)]
"@
$params = @{
parameters = @{
myString = @{
value = 'Hello'
}
myObject = @{
value = @{
prop1 = 'World'
}
}
myArray = @{
value = @('Item1', 'Item2')
}
myInt = @{
value = 123
}
myBool = @{
value = $true
}
}
} | ConvertTo-Json -Compress -Depth 5

$out = $config_yaml | dsc -l trace config -p $params get -f - 2> $TestDrive/error.log | ConvertFrom-Json
$LASTEXITCODE | Should -Be 0 -Because (Get-Content -Path $TestDrive/error.log -Raw | Out-String)
$out.results[0].result.actualState.output | Should -BeExactly 'Hello-World-Item1Item2-123-true'
}

It 'Invalid parameters read from STDIN result in error' {
$params = @{
osFamily = 'Windows'
Expand All @@ -403,7 +462,7 @@ Describe 'Parameters tests' {
$LASTEXITCODE | Should -Be 4
$out | Should -BeNullOrEmpty
$errorMessage = Get-Content -Path $TestDrive/error.log -Raw
$errorMessage | Should -BeLike "*ERROR*Parameter input failure: JSON: missing field ````parameters````*"
$errorMessage | Should -BeLike "*ERROR*Invalid parameters format: missing field ````parameters````*"
}

It 'Parameters can reference other parameters in defaultValue: simple nested' {
Expand Down Expand Up @@ -935,7 +994,7 @@ parameters:
}
else {
$expectedOutput = "{0}-{1}" -f $fileValue, $inlineValue
}
}
$out.results[0].result.actualState.output | Should -BeExactly $expectedOutput
}

Expand Down
3 changes: 2 additions & 1 deletion extensions/bicep/.project.data.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
"Kind": "Extension",
"CopyFiles": {
"All": [
"bicep.dsc.extension.json"
"bicep.dsc.extension.json",
"bicepparams.dsc.extension.json"
]
}
}
8 changes: 8 additions & 0 deletions extensions/bicep/bicep.tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -94,4 +94,12 @@ resource invalid 'Microsoft.DSC.Extension/Bicep:1.0' = {
$content | Should -Match "Importing file '$bicepFile' with extension 'Microsoft.DSC.Extension/Bicep'"
$content | Should -Match "BCP033"
}

It 'Example bicep parameters file should work' {
$bicepFile = Resolve-Path -Path "$PSScriptRoot\..\..\dsc\examples\hello_world.dsc.bicep"
$bicepParamFile = Resolve-Path -Path "$PSScriptRoot\..\..\dsc\examples\hello_world.dsc.bicepparam"
$out = dsc -l trace config --parameters-file $bicepParamFile get --file $bicepFile 2>$TestDrive/error.log | ConvertFrom-Json
$LASTEXITCODE | Should -Be 0 -Because (Get-Content -Path $TestDrive/error.log -Raw | Out-String)
$out.results[0].result.actualState.output | Should -BeExactly 'This is a parameterized hello world!'
}
}
19 changes: 19 additions & 0 deletions extensions/bicep/bicepparams.dsc.extension.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"$schema": "https://aka.ms/dsc/schemas/v3/bundled/extension/manifest.json",
"type": "Microsoft.DSC.Extension/BicepParameters",
"version": "0.1.0",
"description": "Enable passing Bicep parameters file directly to DSC, but requires bicep executable to be available.",
"condition": "[not(equals(tryWhich('bicep'), null()))]",
"import": {
"fileExtensions": ["bicepparam"],
"executable": "bicep",
"args": [
"build-params",
{
"fileArg": ""
},
"--stdout"
],
"output": "[json(json(stdout()).parametersJson)]"
}
}
14 changes: 14 additions & 0 deletions lib/dsc-lib/locales/en-us.toml
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,11 @@ secureOutputSkipped = "Secure output '%{name}' is skipped"
outputTypeNotMatch = "Output '%{name}' type does not match expected type '%{expected_type}'"
copyNotSupported = "Copy for output '%{name}' is currently not supported"

[configure.parameters]
importingParametersFromComplexInput = "Importing parameters from complex input"
importingParametersFromInput = "Importing parameters from simple input"
invalidParamsFormat = "Invalid parameters format: %{error}"

[discovery.commandDiscovery]
couldNotReadSetting = "Could not read 'resourcePath' setting"
appendingEnvPath = "Appending PATH to resourcePath"
Expand Down Expand Up @@ -224,6 +229,7 @@ importingFile = "Importing file '%{file}' with extension '%{extension}'"
importNotSupported = "Import is not supported by extension '%{extension}' for file '%{file}'"
importNoResults = "Extension '%{extension}' returned no results for import"
secretMultipleLinesReturned = "Extension '%{extension}' returned multiple lines which is not supported for secrets"
importProcessingOutput = "Processing output from extension '%{extension}'"

[extensions.extension_manifest]
extensionManifestSchemaTitle = "Extension manifest schema URI"
Expand Down Expand Up @@ -538,6 +544,10 @@ invoked = "skip function"
invalidNumberToSkip = "Second argument must be an integer"
invalidOriginalValue = "First argument must be an array or string"

[functions.stdout]
description = "Returns the standard output from the last executed resource. Can only be used in output definitions."
noStdoutAvailable = "No standard output is available from the last executed resource"

[functions.string]
description = "Converts a value to a string"

Expand Down Expand Up @@ -657,6 +667,10 @@ indexOutOfBounds = "Index is out of bounds"
indexOnNonArray = "Index access on non-array value"
invalidIndexType = "Invalid index type"
propertyNameNotString = "Property name is not a string"
accessorResult = "Accessor result: %{result}"
evaluatingMemberAccessor = "Evaluating member accessor: %{name}"
evaluatingIndexAccessor = "Evaluating index accessor: %{index}"
evaluatingIndexExpression = "Evaluating index expression: %{expression}"

[parser.functions]
foundErrorNode = "Found error node parsing function"
Expand Down
2 changes: 2 additions & 0 deletions lib/dsc-lib/src/configure/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ pub struct Context {
pub restart_required: Option<Vec<RestartRequired>>,
pub security_context: SecurityContextKind,
pub start_datetime: DateTime<Local>,
pub stdout: Option<String>,
pub system_root: PathBuf,
pub user_functions: HashMap<String, UserFunctionDefinition>,
pub variables: Map<String, Value>,
Expand All @@ -60,6 +61,7 @@ impl Context {
SecurityContext::User => SecurityContextKind::Restricted,
},
start_datetime: chrono::Local::now(),
stdout: None,
system_root: get_default_os_system_root(),
user_functions: HashMap::new(),
variables: Map::new(),
Expand Down
5 changes: 3 additions & 2 deletions lib/dsc-lib/src/configure/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
// Licensed under the MIT License.

use crate::configure::context::{Context, ProcessMode};
use crate::configure::{config_doc::{ExecutionKind, IntOrExpression, Metadata, Parameter, Resource, RestartRequired, ValueOrCopy}, parameters::Input};
use crate::configure::parameters::import_parameters;
use crate::configure::{config_doc::{ExecutionKind, IntOrExpression, Metadata, Parameter, Resource, RestartRequired, ValueOrCopy}};
use crate::discovery::discovery_trait::DiscoveryFilter;
use crate::dscerror::DscError;
use crate::dscresources::{
Expand Down Expand Up @@ -836,7 +837,7 @@ impl Configurator {
// process input parameters first
if let Some(parameters_input) = parameters_input {
trace!("parameters_input: {parameters_input}");
let input_parameters: HashMap<String, Value> = serde_json::from_value::<Input>(parameters_input.clone())?.parameters;
let input_parameters: HashMap<String, Value> = import_parameters(parameters_input)?;

for (name, value) in input_parameters {
if let Some(constraint) = parameters.get(&name) {
Expand Down
Loading