From b82c610f896bc2fc7f4ae6c55ce34ee9415ab669 Mon Sep 17 00:00:00 2001 From: Steve Lee Date: Tue, 9 Jun 2026 21:24:51 -0700 Subject: [PATCH 01/12] Enable `export` operation to have own schema --- Cargo.lock | 10 +- dsc/tests/dsc_resource_export.tests.ps1 | 20 ++++ lib/dsc-lib-jsonschema/.versions.json | 3 +- lib/dsc-lib/locales/en-us.toml | 1 + .../src/dscresources/command_resource.rs | 51 ++++++++- .../src/dscresources/resource_manifest.rs | 13 +++ tools/dsctest/Cargo.toml | 1 + tools/dsctest/dsctest.dsc.manifests.json | 104 ++++++++++++++++++ tools/dsctest/src/args.rs | 8 ++ tools/dsctest/src/export_schema.rs | 86 +++++++++++++++ tools/dsctest/src/main.rs | 11 ++ 11 files changed, 295 insertions(+), 13 deletions(-) create mode 100644 dsc/tests/dsc_resource_export.tests.ps1 create mode 100644 tools/dsctest/src/export_schema.rs diff --git a/Cargo.lock b/Cargo.lock index 1f2f37d97..03d5ed53c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -979,6 +979,7 @@ name = "dsctest" version = "0.1.0" dependencies = [ "clap", + "regex", "registry", "schemars", "serde", @@ -4239,15 +4240,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" -[[package]] -name = "windows_feature" -version = "0.1.0" -dependencies = [ - "rust-i18n", - "serde", - "serde_json", -] - [[package]] name = "windows_firewall" version = "0.1.0" diff --git a/dsc/tests/dsc_resource_export.tests.ps1 b/dsc/tests/dsc_resource_export.tests.ps1 new file mode 100644 index 000000000..f780f87f6 --- /dev/null +++ b/dsc/tests/dsc_resource_export.tests.ps1 @@ -0,0 +1,20 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +Describe 'Resource export tests' { + It "Export with accepts input '' and returns filtered results" -TestCases @( + @{ resource = 'Test/ExportSchemaCommand'; json = '{ "name": "Gijs" }'; expected = @('Gijs') }, + @{ resource = 'Test/ExportSchemaCommand'; json = '{ "name": "*e*" }'; expected = @('Steve', 'Tess') }, + @{ resource = 'Test/ExportSchemaEmbedded'; json = '{ "name": "Gijs" }'; expected = @('Gijs') }, + @{ resource = 'Test/ExportSchemaEmbedded'; json = '{ "name": "*e*" }'; expected = @('Steve', 'Tess') }, + @{ resource = 'Test/ExportSchemaNoFiltering'; json = '{ "name": "Gijs" }'; expected = @('Steve', 'Tess', 'Gijs') } + ){ + param($resource, $json, $expected) + + $output = dsc resource export -r $resource -i $json 2>$TESTDRIVE/error.log | ConvertFrom-Json + $errorlog = Get-Content "$TESTDRIVE/error.log" -Raw + $LASTEXITCODE | Should -Be 0 -Because $errorlog + $output.resources.count | Should -Be $expected.Count -Because ($output | ConvertTo-Json -Depth 4) + $output.resources.properties.name | Should -Be $expected -Because ($output | ConvertTo-Json -Depth 4) + } +} diff --git a/lib/dsc-lib-jsonschema/.versions.json b/lib/dsc-lib-jsonschema/.versions.json index ccb9af183..15f6ca427 100644 --- a/lib/dsc-lib-jsonschema/.versions.json +++ b/lib/dsc-lib-jsonschema/.versions.json @@ -1,10 +1,11 @@ { "latestMajor": "V3", "latestMinor": "V3_2", - "latestPatch": "V3_2_1", + "latestPatch": "V3_2_2", "all": [ "V3", "V3_2", + "V3_2_2", "V3_2_1", "V3_2_0", "V3_1", diff --git a/lib/dsc-lib/locales/en-us.toml b/lib/dsc-lib/locales/en-us.toml index f5f89b4e2..b9e30f2e3 100644 --- a/lib/dsc-lib/locales/en-us.toml +++ b/lib/dsc-lib/locales/en-us.toml @@ -195,6 +195,7 @@ whatIfWarning = "Resource '%{resource}' uses deprecated 'whatIf' operation. See securityContextRequired = "Operation '%{operation}' for resource '%{resource}' requires security context '%{context}'" noAdaptedContent = "No adapted content available for resource '%{resource}'" invalidAdaptedContent = "Invalid adapted content for resource '%{resource}': %{error}" +exportNoFilteringWithInput = "Resource '%{resource}' does not support filtering export results and input was provided" [dscresources.dscresource] invokeGet = "Invoking get for '%{resource}'" diff --git a/lib/dsc-lib/src/dscresources/command_resource.rs b/lib/dsc-lib/src/dscresources/command_resource.rs index 59f1e6f43..d29d28e58 100644 --- a/lib/dsc-lib/src/dscresources/command_resource.rs +++ b/lib/dsc-lib/src/dscresources/command_resource.rs @@ -7,8 +7,8 @@ use jsonschema::Validator; use rust_i18n::t; use serde::Deserialize; use serde_json::{Map, Value}; -use std::{collections::HashMap, env, path::Path, process::Stdio}; -use crate::{configure::{config_doc::{ExecutionKind, SecurityContextKind}, config_result::{ResourceGetResult, ResourceTestResult}}, dscresources::{resource_manifest::SchemaArgKind}, types::ExitCodesMap, util::canonicalize_which}; +use std::{collections::HashMap, env, path::{Path, PathBuf}, process::Stdio}; +use crate::{configure::{config_doc::{ExecutionKind, SecurityContextKind}, config_result::{ResourceGetResult, ResourceTestResult}}, dscresources::resource_manifest::{ExportSchemaKind, SchemaArgKind}, types::{ExitCodesMap, FullyQualifiedTypeName}, util::canonicalize_which}; use crate::dscerror::DscError; use super::{ dscresource::{get_diff, redact, DscResource}, @@ -586,6 +586,34 @@ pub fn get_schema(resource: &DscResource, target_resource: Option<&DscResource>) } } +pub fn get_export_schema(resource: &DscResource, target_resource: Option<&DscResource>) -> Result { + let Some(manifest) = &resource.manifest else { + return Err(DscError::MissingManifest(resource.type_name.to_string())); + }; + let Some(export) = manifest.export.as_ref() else { + return Err(DscError::SchemaNotAvailable(resource.type_name.to_string())); + }; + + match export.schema { + Some(ExportSchemaKind::Command(ref command)) => { + let resource_type = match target_resource { + Some(r) => r.type_name.clone(), + None => resource.type_name.clone(), + }; + let args = process_schema_args(command.args.as_ref(), &CommandResourceInfo { type_name: resource_type, path: None }); + let (_exit_code, stdout, _stderr) = invoke_command(&command.executable, args, None, Some(&resource.directory), None, manifest.exit_codes.as_ref())?; + Ok(stdout) + }, + Some(ExportSchemaKind::Embedded(ref schema)) => { + let json = serde_json::to_string(schema)?; + Ok(json) + }, + _ => { + get_schema(resource, target_resource) + } + } +} + /// Invoke the export operation on a resource /// /// # Arguments @@ -636,9 +664,26 @@ pub fn invoke_export(resource: &DscResource, input: Option<&str>, target_resourc None => resource, }; validate_security_context(&export.require_security_context, &command_resource.type_name, "export")?; + if let Some(input) = input { + let input = if export.schema == Some(ExportSchemaKind::NoFiltering) { + "" + } else { + input + }; if !input.is_empty() { - verify_json_from_manifest(resource, input, target_resource)?; + let schema = serde_json::from_str(&get_export_schema(resource, target_resource)?)?; + let compiled_schema = match Validator::new(&schema) { + Ok(schema) => schema, + Err(e) => { + return Err(DscError::Schema(e.to_string())); + }, + }; + let json: Value = serde_json::from_str(input)?; + if let Err(err) = compiled_schema.validate(&json) { + return Err(DscError::Schema(err.to_string())); + } + command_input = get_command_input(export.input.as_ref(), input)?; } diff --git a/lib/dsc-lib/src/dscresources/resource_manifest.rs b/lib/dsc-lib/src/dscresources/resource_manifest.rs index 93d558712..704f50e13 100644 --- a/lib/dsc-lib/src/dscresources/resource_manifest.rs +++ b/lib/dsc-lib/src/dscresources/resource_manifest.rs @@ -206,6 +206,18 @@ pub enum SchemaKind { Embedded(Value), } +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, DscRepoSchema)] +#[dsc_repo_schema(base_name = "manifest.exportSchema", folder_path = "definitions")] +#[serde(rename_all = "camelCase")] +pub enum ExportSchemaKind { + /// The export schema is returned by running a command. + Command(SchemaCommand), + /// The export schema is embedded in the manifest. + Embedded(Value), + /// The export operation does not support filtering. + NoFiltering, +} + #[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)] pub struct SchemaCommand { /// The command to run to get the schema. @@ -321,6 +333,7 @@ pub struct ExportMethod { /// The security context required to run the Export method. Default if not specified is `current`. #[serde(rename = "requireSecurityContext", skip_serializing_if = "Option::is_none")] pub require_security_context: Option, + pub schema: Option, } #[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, DscRepoSchema)] diff --git a/tools/dsctest/Cargo.toml b/tools/dsctest/Cargo.toml index b339babbb..f502c610e 100644 --- a/tools/dsctest/Cargo.toml +++ b/tools/dsctest/Cargo.toml @@ -7,6 +7,7 @@ edition = "2024" [dependencies] clap = { workspace = true } +regex = { workspace = true } schemars = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } diff --git a/tools/dsctest/dsctest.dsc.manifests.json b/tools/dsctest/dsctest.dsc.manifests.json index 681dbbf4d..8c2346003 100644 --- a/tools/dsctest/dsctest.dsc.manifests.json +++ b/tools/dsctest/dsctest.dsc.manifests.json @@ -1405,6 +1405,110 @@ ] } } + }, + { + "$schema": "https://aka.ms/dsc/schemas/v3/bundled/resource/manifest.json", + "type": "Test/ExportSchemaCommand", + "version": "0.1.0", + "description": "Test resource for export specific schema", + "export": { + "executable": "dsctest", + "args": [ + "export-schema", + { + "jsonInputArg": "--input", + "mandatory": true + } + ], + "schema": { + "command":{ + "executable": "dsctest", + "args": [ + "schema", + "-s", + "export-schema" + ] + } + } + }, + "schema": { + "command": { + "executable": "dsctest", + "args": [ + "schema", + "-s", + "export-get-schema" + ] + } + } + }, + { + "$schema": "https://aka.ms/dsc/schemas/v3/bundled/resource/manifest.json", + "type": "Test/ExportSchemaEmbedded", + "version": "0.1.0", + "description": "Test resource for export specific schema", + "export": { + "executable": "dsctest", + "args": [ + "export-schema", + { + "jsonInputArg": "--input", + "mandatory": true + } + ], + "schema": { + "embedded": { + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "http://example.com/export-schema", + "title": "Export Schema", + "type": "object", + "properties": { + "name": { + "type": "string", + "title": "Export Property", + "description": "Can contain wildcards for export filtering." + } + } + } + } + }, + "schema": { + "command": { + "executable": "dsctest", + "args": [ + "schema", + "-s", + "export-get-schema" + ] + } + } + }, + { + "$schema": "https://aka.ms/dsc/schemas/v3/bundled/resource/manifest.json", + "type": "Test/ExportSchemaNoFiltering", + "version": "0.1.0", + "description": "Test resource for export specific schema", + "export": { + "executable": "dsctest", + "args": [ + "export-schema", + { + "jsonInputArg": "--input", + "mandatory": true + } + ], + "schema": "noFiltering" + }, + "schema": { + "command": { + "executable": "dsctest", + "args": [ + "schema", + "-s", + "export-get-schema" + ] + } + } } ] } diff --git a/tools/dsctest/src/args.rs b/tools/dsctest/src/args.rs index 0e681e27e..a0b28ec59 100644 --- a/tools/dsctest/src/args.rs +++ b/tools/dsctest/src/args.rs @@ -11,6 +11,8 @@ pub enum Schemas { Exist, ExitCode, Export, + ExportGetSchema, + ExportSchema, Exporter, Get, InDesiredState, @@ -95,6 +97,12 @@ pub enum SubCommand { input: String, }, + #[clap(name = "export-schema", about = "Test export specific schema")] + ExportSchema { + #[clap(name = "input", short, long, help = "The input to the export schema command as JSON")] + input: String, + }, + #[clap(name = "exporter", about = "Exports different types of resources")] Exporter { #[clap(name = "input", short, long, help = "The input to the exporter command as JSON")] diff --git a/tools/dsctest/src/export_schema.rs b/tools/dsctest/src/export_schema.rs new file mode 100644 index 000000000..3318dbf07 --- /dev/null +++ b/tools/dsctest/src/export_schema.rs @@ -0,0 +1,86 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use std::fmt::Display; + +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)] +pub enum Names { + Gijs, + Steve, + Tess, +} + +impl Display for Names { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Names::Gijs => write!(f, "Gijs"), + Names::Steve => write!(f, "Steve"), + Names::Tess => write!(f, "Tess"), + } + } +} + +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)] +#[serde(deny_unknown_fields)] +pub struct Schema { + pub name: Names, +} + +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)] +#[serde(deny_unknown_fields)] +pub struct ExportSchema { + pub name: String, +} + +pub fn invoke_export_schema(input: &str) -> String { + let instances = vec![ + Schema { + name: Names::Steve, + }, + Schema { + name: Names::Tess, + }, + Schema { + name: Names::Gijs, + }, + ]; + let filter: ExportSchema = if !input.is_empty() { + match serde_json::from_str(input) { + Ok(filter) => filter, + Err(err) => { + eprintln!("Error JSON does not match schema: {err}"); + std::process::exit(1); + } + } + } else { + ExportSchema { + name: "*".to_string(), + } + }; + let filtered_instances: Vec = if filter.name.contains("*") { + // convert the wildcard to a regex + let regex = filter.name.replace("*", ".*"); + let regex = regex::Regex::new(®ex).unwrap(); + instances + .into_iter() + .filter(|instance| regex.is_match(&instance.name.to_string())) + .collect() + } else { + instances + .into_iter() + .filter(|instance| instance.name.to_string() == filter.name) + .collect() + }; + let mut output = String::new(); + let mut count = filtered_instances.len(); + for instance in &filtered_instances { + output.push_str(serde_json::to_string(instance).unwrap().as_str()); + if count > 1 { + output.push('\n'); + } + count -= 1; + } + output +} diff --git a/tools/dsctest/src/main.rs b/tools/dsctest/src/main.rs index 060d07969..39decf60e 100644 --- a/tools/dsctest/src/main.rs +++ b/tools/dsctest/src/main.rs @@ -7,6 +7,7 @@ mod delete; mod exist; mod exit_code; mod export; +mod export_schema; mod exporter; mod get; mod in_desired_state; @@ -31,6 +32,7 @@ use crate::delete::Delete; use crate::exist::{Exist, State}; use crate::exit_code::ExitCode; use crate::export::Export; +use crate::export_schema::{ExportSchema, invoke_export_schema}; use crate::exporter::{Exporter, Resource}; use crate::get::Get; use crate::in_desired_state::InDesiredState; @@ -132,6 +134,9 @@ fn main() { } String::new() }, + SubCommand::ExportSchema { input } => { + invoke_export_schema(&input) + }, SubCommand::Exporter { input } => { let exporter = match serde_json::from_str::(&input) { Ok(exporter) => exporter, @@ -300,6 +305,12 @@ fn main() { Schemas::Export => { schema_for!(Export) }, + Schemas::ExportGetSchema => { + schema_for!(export_schema::Schema) + }, + Schemas::ExportSchema => { + schema_for!(ExportSchema) + }, Schemas::Exporter => { schema_for!(Exporter) }, From 5dcabc9d9df88645aad1caf847cfe8407ba8bf55 Mon Sep 17 00:00:00 2001 From: Steve Lee Date: Tue, 9 Jun 2026 21:29:32 -0700 Subject: [PATCH 02/12] no need for helper to be public --- lib/dsc-lib/src/dscresources/command_resource.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/dsc-lib/src/dscresources/command_resource.rs b/lib/dsc-lib/src/dscresources/command_resource.rs index d29d28e58..add342e33 100644 --- a/lib/dsc-lib/src/dscresources/command_resource.rs +++ b/lib/dsc-lib/src/dscresources/command_resource.rs @@ -586,7 +586,7 @@ pub fn get_schema(resource: &DscResource, target_resource: Option<&DscResource>) } } -pub fn get_export_schema(resource: &DscResource, target_resource: Option<&DscResource>) -> Result { +fn get_export_schema(resource: &DscResource, target_resource: Option<&DscResource>) -> Result { let Some(manifest) = &resource.manifest else { return Err(DscError::MissingManifest(resource.type_name.to_string())); }; From ae1033597dc1f03c026fe9b5d8182a970c4e50ed Mon Sep 17 00:00:00 2001 From: Steve Lee Date: Wed, 10 Jun 2026 10:07:38 -0700 Subject: [PATCH 03/12] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- tools/dsctest/dsctest.dsc.manifests.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tools/dsctest/dsctest.dsc.manifests.json b/tools/dsctest/dsctest.dsc.manifests.json index 8c2346003..446c28655 100644 --- a/tools/dsctest/dsctest.dsc.manifests.json +++ b/tools/dsctest/dsctest.dsc.manifests.json @@ -1462,13 +1462,17 @@ "$id": "http://example.com/export-schema", "title": "Export Schema", "type": "object", + "additionalProperties": false, "properties": { "name": { "type": "string", "title": "Export Property", "description": "Can contain wildcards for export filtering." } - } + }, + "required": [ + "name" + ] } } }, From 8672bb01bf2ae78b1f520a65da8d1f55afadfae2 Mon Sep 17 00:00:00 2001 From: Steve Lee Date: Wed, 10 Jun 2026 14:17:27 -0700 Subject: [PATCH 04/12] fix use of `validate` for export --- .../src/dscresources/command_resource.rs | 51 ++++++++++++------- 1 file changed, 33 insertions(+), 18 deletions(-) diff --git a/lib/dsc-lib/src/dscresources/command_resource.rs b/lib/dsc-lib/src/dscresources/command_resource.rs index add342e33..d6eba5be5 100644 --- a/lib/dsc-lib/src/dscresources/command_resource.rs +++ b/lib/dsc-lib/src/dscresources/command_resource.rs @@ -586,15 +586,30 @@ pub fn get_schema(resource: &DscResource, target_resource: Option<&DscResource>) } } -fn get_export_schema(resource: &DscResource, target_resource: Option<&DscResource>) -> Result { +fn verify_with_export_schema(input: &str, resource: &DscResource, target_resource: Option<&DscResource>) -> Result<(), DscError> { let Some(manifest) = &resource.manifest else { return Err(DscError::MissingManifest(resource.type_name.to_string())); }; + + if manifest.validate.is_some() { + let result = invoke_validate(resource, input, target_resource)?; + if result.valid { + return Ok(()); + } + + let reason = result + .reason + .map(|s| s.trim().to_string()) + .filter(|s| !s.is_empty()) + .unwrap_or_else(|| t!("dscresources.commandResource.resourceInvalidJson").to_string()); + return Err(DscError::Validation(reason)); + } + let Some(export) = manifest.export.as_ref() else { return Err(DscError::SchemaNotAvailable(resource.type_name.to_string())); }; - match export.schema { + let schema = match export.schema { Some(ExportSchemaKind::Command(ref command)) => { let resource_type = match target_resource { Some(r) => r.type_name.clone(), @@ -602,16 +617,27 @@ fn get_export_schema(resource: &DscResource, target_resource: Option<&DscResourc }; let args = process_schema_args(command.args.as_ref(), &CommandResourceInfo { type_name: resource_type, path: None }); let (_exit_code, stdout, _stderr) = invoke_command(&command.executable, args, None, Some(&resource.directory), None, manifest.exit_codes.as_ref())?; - Ok(stdout) + stdout }, Some(ExportSchemaKind::Embedded(ref schema)) => { - let json = serde_json::to_string(schema)?; - Ok(json) + serde_json::to_string(schema)? }, _ => { - get_schema(resource, target_resource) + get_schema(resource, target_resource)? } + }; + let schema = serde_json::from_str(&schema)?; + let compiled_schema = match Validator::new(&schema) { + Ok(schema) => schema, + Err(e) => { + return Err(DscError::Schema(e.to_string())); + }, + }; + let json: Value = serde_json::from_str(input)?; + if let Err(err) = compiled_schema.validate(&json) { + return Err(DscError::Schema(err.to_string())); } + Ok(()) } /// Invoke the export operation on a resource @@ -672,18 +698,7 @@ pub fn invoke_export(resource: &DscResource, input: Option<&str>, target_resourc input }; if !input.is_empty() { - let schema = serde_json::from_str(&get_export_schema(resource, target_resource)?)?; - let compiled_schema = match Validator::new(&schema) { - Ok(schema) => schema, - Err(e) => { - return Err(DscError::Schema(e.to_string())); - }, - }; - let json: Value = serde_json::from_str(input)?; - if let Err(err) = compiled_schema.validate(&json) { - return Err(DscError::Schema(err.to_string())); - } - + verify_with_export_schema(input, resource, target_resource)?; command_input = get_command_input(export.input.as_ref(), input)?; } From 51a25a3faf5088e0b257cb65d2f0cb48a9a9b108 Mon Sep 17 00:00:00 2001 From: Steve Lee Date: Wed, 10 Jun 2026 15:10:53 -0700 Subject: [PATCH 05/12] change to fallback to validate instead of trying it first --- lib/dsc-lib/src/dscresources/command_resource.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/dsc-lib/src/dscresources/command_resource.rs b/lib/dsc-lib/src/dscresources/command_resource.rs index d6eba5be5..80eaf1137 100644 --- a/lib/dsc-lib/src/dscresources/command_resource.rs +++ b/lib/dsc-lib/src/dscresources/command_resource.rs @@ -591,7 +591,11 @@ fn verify_with_export_schema(input: &str, resource: &DscResource, target_resourc return Err(DscError::MissingManifest(resource.type_name.to_string())); }; - if manifest.validate.is_some() { + let Some(export) = manifest.export.as_ref() else { + return Err(DscError::SchemaNotAvailable(resource.type_name.to_string())); + }; + + if export.schema.is_none() && manifest.validate.is_some() { let result = invoke_validate(resource, input, target_resource)?; if result.valid { return Ok(()); @@ -605,10 +609,6 @@ fn verify_with_export_schema(input: &str, resource: &DscResource, target_resourc return Err(DscError::Validation(reason)); } - let Some(export) = manifest.export.as_ref() else { - return Err(DscError::SchemaNotAvailable(resource.type_name.to_string())); - }; - let schema = match export.schema { Some(ExportSchemaKind::Command(ref command)) => { let resource_type = match target_resource { From 6fc0685730da7d2fb227371510a29e374f2acf0e Mon Sep 17 00:00:00 2001 From: Steve Lee Date: Tue, 16 Jun 2026 15:11:44 -0700 Subject: [PATCH 06/12] address feedback --- Cargo.toml | 4 +- .../src/dscresources/command_resource.rs | 46 ++++++++++--------- 2 files changed, 27 insertions(+), 23 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b792f8307..616eb8f65 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -48,6 +48,7 @@ default-members = [ "lib/dsc-lib-registry", "resources/runcommandonset", "lib/dsc-lib-security_context", + "resources/dism_dsc", "resources/sshdconfig", "resources/WindowsUpdate", "resources/windows_service", @@ -57,8 +58,7 @@ default-members = [ "grammars/tree-sitter-dscexpression", "grammars/tree-sitter-ssh-server-config", "y2j", - "xtask", - "resources/dism_dsc" + "xtask" ] [workspace.metadata.groups] diff --git a/lib/dsc-lib/src/dscresources/command_resource.rs b/lib/dsc-lib/src/dscresources/command_resource.rs index 80eaf1137..1622d9156 100644 --- a/lib/dsc-lib/src/dscresources/command_resource.rs +++ b/lib/dsc-lib/src/dscresources/command_resource.rs @@ -7,8 +7,8 @@ use jsonschema::Validator; use rust_i18n::t; use serde::Deserialize; use serde_json::{Map, Value}; -use std::{collections::HashMap, env, path::{Path, PathBuf}, process::Stdio}; -use crate::{configure::{config_doc::{ExecutionKind, SecurityContextKind}, config_result::{ResourceGetResult, ResourceTestResult}}, dscresources::resource_manifest::{ExportSchemaKind, SchemaArgKind}, types::{ExitCodesMap, FullyQualifiedTypeName}, util::canonicalize_which}; +use std::{collections::HashMap, env, path::Path, process::Stdio}; +use crate::{configure::{config_doc::{ExecutionKind, SecurityContextKind}, config_result::{ResourceGetResult, ResourceTestResult}}, dscresources::resource_manifest::{ExportSchemaKind, SchemaArgKind}, types::{ExitCodesMap}, util::canonicalize_which}; use crate::dscerror::DscError; use super::{ dscresource::{get_diff, redact, DscResource}, @@ -595,34 +595,38 @@ fn verify_with_export_schema(input: &str, resource: &DscResource, target_resourc return Err(DscError::SchemaNotAvailable(resource.type_name.to_string())); }; - if export.schema.is_none() && manifest.validate.is_some() { - let result = invoke_validate(resource, input, target_resource)?; - if result.valid { - return Ok(()); - } - - let reason = result - .reason - .map(|s| s.trim().to_string()) - .filter(|s| !s.is_empty()) - .unwrap_or_else(|| t!("dscresources.commandResource.resourceInvalidJson").to_string()); - return Err(DscError::Validation(reason)); - } + let command_resource = match target_resource { + Some(r) => r, + None => resource, + }; let schema = match export.schema { Some(ExportSchemaKind::Command(ref command)) => { - let resource_type = match target_resource { - Some(r) => r.type_name.clone(), - None => resource.type_name.clone(), - }; - let args = process_schema_args(command.args.as_ref(), &CommandResourceInfo { type_name: resource_type, path: None }); + let args = process_schema_args(command.args.as_ref(), command_resource); let (_exit_code, stdout, _stderr) = invoke_command(&command.executable, args, None, Some(&resource.directory), None, manifest.exit_codes.as_ref())?; stdout }, Some(ExportSchemaKind::Embedded(ref schema)) => { serde_json::to_string(schema)? }, - _ => { + Some(ExportSchemaKind::NoFiltering) => { + return Ok(()); + }, + None => { + if manifest.validate.is_some() { + let result = invoke_validate(resource, input, target_resource)?; + if result.valid { + return Ok(()); + } + + let reason = result + .reason + .map(|s| s.trim().to_string()) + .filter(|s| !s.is_empty()) + .unwrap_or_else(|| t!("dscresources.commandResource.resourceInvalidJson").to_string()); + return Err(DscError::Validation(reason)); + } + get_schema(resource, target_resource)? } }; From 7c4b2e5f76d9182712603b22e4308fa900cbdccb Mon Sep 17 00:00:00 2001 From: Steve Lee Date: Tue, 16 Jun 2026 15:28:19 -0700 Subject: [PATCH 07/12] remove unused string --- lib/dsc-lib/locales/en-us.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/dsc-lib/locales/en-us.toml b/lib/dsc-lib/locales/en-us.toml index b9e30f2e3..f5f89b4e2 100644 --- a/lib/dsc-lib/locales/en-us.toml +++ b/lib/dsc-lib/locales/en-us.toml @@ -195,7 +195,6 @@ whatIfWarning = "Resource '%{resource}' uses deprecated 'whatIf' operation. See securityContextRequired = "Operation '%{operation}' for resource '%{resource}' requires security context '%{context}'" noAdaptedContent = "No adapted content available for resource '%{resource}'" invalidAdaptedContent = "Invalid adapted content for resource '%{resource}': %{error}" -exportNoFilteringWithInput = "Resource '%{resource}' does not support filtering export results and input was provided" [dscresources.dscresource] invokeGet = "Invoking get for '%{resource}'" From 02e72d238f6d15a71d4ce4e9ec5c9a722f896f10 Mon Sep 17 00:00:00 2001 From: "Steve Lee (POWERSHELL HE/HIM) (from Dev Box)" Date: Wed, 17 Jun 2026 14:33:07 -0700 Subject: [PATCH 08/12] increase stack size --- Cargo.lock | 8 ++++---- Cargo.toml | 2 +- dsc/src/main.rs | 8 ++++++++ 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 03d5ed53c..46262c465 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2625,9 +2625,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.12.3" +version = "1.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba" dependencies = [ "aho-corasick", "memchr", @@ -2648,9 +2648,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.10" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" [[package]] name = "registry" diff --git a/Cargo.toml b/Cargo.toml index 616eb8f65..9826d8efa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -197,7 +197,7 @@ quote = { version = "1.0" } # used by other crates rand = { version = "0.10.1" } # dsc, dsc-lib -regex = { version = "1.12.3" } +regex = { version = "1.12.4" } # registry, dsc-lib, dsc-lib-registry, dsctest registry = { version = "1.3" } # dsc diff --git a/dsc/src/main.rs b/dsc/src/main.rs index b66b0ca2e..c3bf3b7e5 100644 --- a/dsc/src/main.rs +++ b/dsc/src/main.rs @@ -29,6 +29,14 @@ pub mod util; i18n!("locales", fallback = "en-us"); fn main() { + std::thread::Builder::new() + .stack_size(8 * 1024 * 1024) // Default stack is too small on Windows causing overflow + .spawn(|| { + dsc_main(); + }).unwrap().join().unwrap(); +} + +fn dsc_main() { #[cfg(debug_assertions)] check_debug(); From 3bcd8d53d1f4d6a5bf9f82a3c05b50649e9f997b Mon Sep 17 00:00:00 2001 From: "Steve Lee (POWERSHELL HE/HIM) (from Dev Box)" Date: Wed, 17 Jun 2026 14:43:54 -0700 Subject: [PATCH 09/12] refactor for unix --- dsc/src/main.rs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/dsc/src/main.rs b/dsc/src/main.rs index c3bf3b7e5..952957ac6 100644 --- a/dsc/src/main.rs +++ b/dsc/src/main.rs @@ -29,11 +29,14 @@ pub mod util; i18n!("locales", fallback = "en-us"); fn main() { - std::thread::Builder::new() - .stack_size(8 * 1024 * 1024) // Default stack is too small on Windows causing overflow - .spawn(|| { - dsc_main(); - }).unwrap().join().unwrap(); + let mut builder = std::thread::Builder::new(); + #[cfg(windows)] + { + builder = builder.stack_size(8 * 1024 * 1024); // Default stack is too small on Windows causing overflow + } + builder.spawn(|| { + dsc_main(); + }).unwrap().join().unwrap(); } fn dsc_main() { From 012cc84acf8e47e5165c6b5321e52f21ac997b6a Mon Sep 17 00:00:00 2001 From: "Steve Lee (POWERSHELL HE/HIM) (from Dev Box)" Date: Wed, 17 Jun 2026 14:45:32 -0700 Subject: [PATCH 10/12] shrink stack on Windows to 2MB --- dsc/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dsc/src/main.rs b/dsc/src/main.rs index 952957ac6..2df32a594 100644 --- a/dsc/src/main.rs +++ b/dsc/src/main.rs @@ -32,7 +32,7 @@ fn main() { let mut builder = std::thread::Builder::new(); #[cfg(windows)] { - builder = builder.stack_size(8 * 1024 * 1024); // Default stack is too small on Windows causing overflow + builder = builder.stack_size(2 * 1024 * 1024); // Default stack is too small on Windows causing overflow } builder.spawn(|| { dsc_main(); From 6d15ed766423c04f3823627102643ba43d2e3d19 Mon Sep 17 00:00:00 2001 From: "Steve Lee (POWERSHELL HE/HIM) (from Dev Box)" Date: Wed, 17 Jun 2026 15:01:15 -0700 Subject: [PATCH 11/12] refactor `main()` since mut builder causes build issue on non-Windows --- dsc/src/main.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/dsc/src/main.rs b/dsc/src/main.rs index 2df32a594..b7264a7be 100644 --- a/dsc/src/main.rs +++ b/dsc/src/main.rs @@ -29,14 +29,18 @@ pub mod util; i18n!("locales", fallback = "en-us"); fn main() { - let mut builder = std::thread::Builder::new(); #[cfg(windows)] { + let mut builder = std::thread::Builder::new(); builder = builder.stack_size(2 * 1024 * 1024); // Default stack is too small on Windows causing overflow + builder.spawn(|| { + dsc_main(); + }).unwrap().join().unwrap(); } - builder.spawn(|| { + #[cfg(not(windows))] + { dsc_main(); - }).unwrap().join().unwrap(); + } } fn dsc_main() { From a79cbe41df84cb9ee3875c532eb2e95f61d022e1 Mon Sep 17 00:00:00 2001 From: "Steve Lee (POWERSHELL HE/HIM) (from Dev Box)" Date: Wed, 17 Jun 2026 20:57:49 -0700 Subject: [PATCH 12/12] fix test --- dsc/tests/dsc_extension_discover.tests.ps1 | 1 - 1 file changed, 1 deletion(-) diff --git a/dsc/tests/dsc_extension_discover.tests.ps1 b/dsc/tests/dsc_extension_discover.tests.ps1 index 57ab35f1b..95a22a968 100644 --- a/dsc/tests/dsc_extension_discover.tests.ps1 +++ b/dsc/tests/dsc_extension_discover.tests.ps1 @@ -109,7 +109,6 @@ Describe 'Discover extension tests' { $out.type | Should -Be 'Test/DiscoverRelative' $out = dsc resource list 2> $TestDrive/error.log $LASTEXITCODE | Should -Be 0 - $out | Should -BeNullOrEmpty $errorMessage = Get-Content -Path "$TestDrive/error.log" -Raw $errorMessage | Should -BeLike '*is not an absolute path*' } finally {