From 5fa43483ee7183302fe187b1cb7ad1fd57627a31 Mon Sep 17 00:00:00 2001 From: "Steve Lee (POWERSHELL HE/HIM) (from Dev Box)" Date: Mon, 23 Mar 2026 14:46:47 -0700 Subject: [PATCH 1/4] Fix not failing discovery when finding unsupported manifests --- dsc/tests/dsc_discovery.tests.ps1 | 27 +++++++++ dsc/tests/dsc_extension_discover.tests.ps1 | 57 +++++++++++++++++++ lib/dsc-lib/locales/en-us.toml | 2 + .../src/discovery/command_discovery.rs | 4 +- lib/dsc-lib/src/discovery/mod.rs | 4 +- lib/dsc-lib/src/extensions/discover.rs | 9 ++- 6 files changed, 98 insertions(+), 5 deletions(-) diff --git a/dsc/tests/dsc_discovery.tests.ps1 b/dsc/tests/dsc_discovery.tests.ps1 index a6809ec60..41fad2980 100644 --- a/dsc/tests/dsc_discovery.tests.ps1 +++ b/dsc/tests/dsc_discovery.tests.ps1 @@ -342,4 +342,31 @@ Describe 'tests for resource discovery' { $traceLog | Should -Match "Skipping resource discovery due to 'resourceDiscovery' mode set to 'DuringDeployment'" } } + + It 'Invalid resource manifest will generate info message' { + $invalidManifest = @' + { + "$schema": "https://aka.ms/dsc/schemas/v3/bundled/resource/manifest.json", + "type": "Test/InvalidManifest", + "version": "0.1.0", + "get": { + "executable": "dsctest", + "unexpectedField": "This field is not expected in the get section and should be ignored by the discovery process" + }, + "newProperty": "This property is not expected in the manifest and should be ignored by the discovery process" + } +'@ + Set-Content -Path "$testdrive/test.dsc.resource.json" -Value $invalidManifest + try { + $env:DSC_RESOURCE_PATH = $testdrive + [System.IO.Path]::PathSeparator + $env:PATH + + $out = dsc -l info resource list 'Test/InvalidManifest' 2> "$testdrive/error.txt" | ConvertFrom-Json + $LASTEXITCODE | Should -Be 0 + $out | Should -BeNullOrEmpty -Because (Get-Content -Raw -Path "$testdrive/error.txt") + $errorLog = Get-Content -Raw -Path "$testdrive/error.txt" + $errorLog | Should -Match "INFO Failed to load manifest '.*test.dsc.resource.json':" -Because $errorLog + } finally { + $env:DSC_RESOURCE_PATH = $null + } + } } diff --git a/dsc/tests/dsc_extension_discover.tests.ps1 b/dsc/tests/dsc_extension_discover.tests.ps1 index 2be1256db..581c6df55 100644 --- a/dsc/tests/dsc_extension_discover.tests.ps1 +++ b/dsc/tests/dsc_extension_discover.tests.ps1 @@ -148,4 +148,61 @@ Describe 'Discover extension tests' { $env:DSC_RESOURCE_PATH = $null } } + + It 'Invalid manifest from extension discovery should not fail overall discovery' { + $invalidManifest = @' + { + "$schema": "https://aka.ms/dsc/schemas/v3/bundled/resource/manifest.json", + "type": "Test/InvalidManifest", + "version": "0.1.0", + "get": { + "executable": "dsctest", + "unexpectedField": "This field is not expected in the get section and should be ignored by the discovery process" + }, + "newProperty": "This property is not expected in the manifest and should be ignored by the discovery process" + } +'@ + $resourceScript = @' + $resource = @{ + manifestPath = "$env:TestDrive" + [System.IO.Path]::DirectorySeparatorChar + 'invalidManifest.dsc.json' + } + $resource | ConvertTo-Json -Compress +'@ + $extendionManifest = @' +{ + "$schema": "https://aka.ms/dsc/schemas/v3/bundled/resource/manifest.json", + "type": "Test/DiscoverInvalid", + "version": "0.1.0", + "description": "Test discover resource, this is a really long description to test that the table can be rendered without truncating the description text from this extension.", + "discover": { + "executable": "pwsh", + "args": [ + "-NoLogo", + "-NonInteractive", + "-NoProfile", + "-Command", + "./discover.ps1" + ] + } +} +'@ + + Set-Content -Path "$TestDrive/invalidManifest.dsc.json" -Value $invalidManifest + Set-Content -Path "$TestDrive/discover.ps1" -Value $resourceScript + Set-Content -Path "$TestDrive/extension.dsc.extension.json" -Value $extendionManifest + try { + $env:DSC_RESOURCE_PATH = $TestDrive + [System.IO.Path]::PathSeparator + $env:PATH + $env:TestDrive = $TestDrive + $out = dsc -l info resource list 2> $TestDrive/error.log | ConvertFrom-Json + $LASTEXITCODE | Should -Be 0 + # The invalid manifest should be skipped and not included in the discovered resources + foreach ($resource in $out) { + $resource.type | Should -Not -Be 'Test/InvalidManifest' + } + (Get-Content -Path "$TestDrive/error.log" -Raw) | Should -BeLike "*Failed to load manifest '*invalidManifest.dsc.json':*" -Because (Get-Content -Path "$TestDrive/error.log" -Raw | Out-String) + } finally { + $env:DSC_RESOURCE_PATH = $null + $env:TestDrive = $null + } + } } diff --git a/lib/dsc-lib/locales/en-us.toml b/lib/dsc-lib/locales/en-us.toml index 617aac17c..1cbd860ec 100644 --- a/lib/dsc-lib/locales/en-us.toml +++ b/lib/dsc-lib/locales/en-us.toml @@ -140,6 +140,7 @@ conditionNotBoolean = "Condition '%{condition}' did not evaluate to a boolean" conditionNotMet = "Condition '%{condition}' not met, skipping manifest at '%{path}' for resource '%{resource}'" adaptedResourcePathNotFound = "Adapted resource '%{resource}' path not found: %{path}" invalidManifestFileName = "Invalid manifest file name '%{path}'" +failedLoadManifest = "Failed to load manifest '%{path}': %{err}" [dscresources.commandResource] invokeGet = "Invoking get for '%{resource}'" @@ -252,6 +253,7 @@ 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}'" deprecationMessage = "Extension '%{extension}' is deprecated: %{message}" +failedLoadManifest = "Failed to load manifest '%{path}': %{err}" [extensions.extension_manifest] extensionManifestSchemaTitle = "Extension manifest schema URI" diff --git a/lib/dsc-lib/src/discovery/command_discovery.rs b/lib/dsc-lib/src/discovery/command_discovery.rs index cdb899744..a5803ae7f 100644 --- a/lib/dsc-lib/src/discovery/command_discovery.rs +++ b/lib/dsc-lib/src/discovery/command_discovery.rs @@ -277,8 +277,8 @@ impl ResourceDiscovery for CommandDiscovery { // At this point we can't determine whether or not the bad manifest contains // resource that is requested by resource/config operation // if it is, then "ResouceNotFound" error will be issued later - // and here we just write as warning - warn!("{e}"); + // and here we just write as info + info!("{}", t!("discovery.commandDiscovery.failedLoadManifest", path = path.to_string_lossy(), err = e).to_string()); continue; }, }; diff --git a/lib/dsc-lib/src/discovery/mod.rs b/lib/dsc-lib/src/discovery/mod.rs index 38c76113f..2b62c0b12 100644 --- a/lib/dsc-lib/src/discovery/mod.rs +++ b/lib/dsc-lib/src/discovery/mod.rs @@ -13,7 +13,7 @@ use core::result::Result::Ok; use semver::{Version, VersionReq}; use std::collections::BTreeMap; use command_discovery::{CommandDiscovery, ImportedManifest}; -use tracing::error; +use tracing::info; #[derive(Clone)] pub struct Discovery { @@ -61,7 +61,7 @@ impl Discovery { let discovered_resources = match discovery_type.list_available(kind, type_name_filter, adapter_name_filter) { Ok(value) => value, Err(err) => { - error!("{err}"); + info!("{err}"); continue; } }; diff --git a/lib/dsc-lib/src/extensions/discover.rs b/lib/dsc-lib/src/extensions/discover.rs index 42549328b..075571810 100644 --- a/lib/dsc-lib/src/extensions/discover.rs +++ b/lib/dsc-lib/src/extensions/discover.rs @@ -99,7 +99,14 @@ impl DscExtension { return Err(DscError::Extension(t!("extensions.dscextension.discoverNotAbsolutePath", extension = self.type_name.clone(), path = discover_result.manifest_path.display()).to_string())); } // Currently we don't support extensions discovering other extensions - for imported_manifest in load_manifest(&discover_result.manifest_path)? { + let manifests = match load_manifest(&discover_result.manifest_path) { + Ok(manifests) => manifests, + Err(err) => { + info!("{}", t!("extensions.dscextension.failedLoadManifest", path = discover_result.manifest_path.to_string_lossy(), err = err).to_string()); + continue; + } + }; + for imported_manifest in manifests { if let ImportedManifest::Resource(resource) = imported_manifest { resources.push(resource); } From 47f9457ff915691aff237ee1d351e32ed289ff98 Mon Sep 17 00:00:00 2001 From: "Steve Lee (POWERSHELL HE/HIM) (from Dev Box)" Date: Mon, 23 Mar 2026 15:00:09 -0700 Subject: [PATCH 2/4] address copilot feedback --- dsc/tests/dsc_extension_discover.tests.ps1 | 10 +++++----- lib/dsc-lib/locales/en-us.toml | 2 +- lib/dsc-lib/src/discovery/command_discovery.rs | 2 +- lib/dsc-lib/src/extensions/discover.rs | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/dsc/tests/dsc_extension_discover.tests.ps1 b/dsc/tests/dsc_extension_discover.tests.ps1 index 581c6df55..dbdc075b6 100644 --- a/dsc/tests/dsc_extension_discover.tests.ps1 +++ b/dsc/tests/dsc_extension_discover.tests.ps1 @@ -164,11 +164,11 @@ Describe 'Discover extension tests' { '@ $resourceScript = @' $resource = @{ - manifestPath = "$env:TestDrive" + [System.IO.Path]::DirectorySeparatorChar + 'invalidManifest.dsc.json' + manifestPath = "$env:TestDrive" + [System.IO.Path]::DirectorySeparatorChar + 'invalidManifest.dsc.resource.json' } $resource | ConvertTo-Json -Compress '@ - $extendionManifest = @' + $extensionManifest = @' { "$schema": "https://aka.ms/dsc/schemas/v3/bundled/resource/manifest.json", "type": "Test/DiscoverInvalid", @@ -187,9 +187,9 @@ Describe 'Discover extension tests' { } '@ - Set-Content -Path "$TestDrive/invalidManifest.dsc.json" -Value $invalidManifest + Set-Content -Path "$TestDrive/invalidManifest.dsc.resource.json" -Value $invalidManifest Set-Content -Path "$TestDrive/discover.ps1" -Value $resourceScript - Set-Content -Path "$TestDrive/extension.dsc.extension.json" -Value $extendionManifest + Set-Content -Path "$TestDrive/extension.dsc.extension.json" -Value $extensionManifest try { $env:DSC_RESOURCE_PATH = $TestDrive + [System.IO.Path]::PathSeparator + $env:PATH $env:TestDrive = $TestDrive @@ -199,7 +199,7 @@ Describe 'Discover extension tests' { foreach ($resource in $out) { $resource.type | Should -Not -Be 'Test/InvalidManifest' } - (Get-Content -Path "$TestDrive/error.log" -Raw) | Should -BeLike "*Failed to load manifest '*invalidManifest.dsc.json':*" -Because (Get-Content -Path "$TestDrive/error.log" -Raw | Out-String) + (Get-Content -Path "$TestDrive/error.log" -Raw) | Should -BeLike "*Extension 'Test/DiscoverInvalid' failed to load manifest '*invalidManifest.dsc.resource.json':*" -Because (Get-Content -Path "$TestDrive/error.log" -Raw | Out-String) } finally { $env:DSC_RESOURCE_PATH = $null $env:TestDrive = $null diff --git a/lib/dsc-lib/locales/en-us.toml b/lib/dsc-lib/locales/en-us.toml index 1cbd860ec..1cea065eb 100644 --- a/lib/dsc-lib/locales/en-us.toml +++ b/lib/dsc-lib/locales/en-us.toml @@ -253,7 +253,7 @@ 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}'" deprecationMessage = "Extension '%{extension}' is deprecated: %{message}" -failedLoadManifest = "Failed to load manifest '%{path}': %{err}" +failedLoadManifest = "Extension '%{extension}' failed to load manifest '%{path}': %{err}" [extensions.extension_manifest] extensionManifestSchemaTitle = "Extension manifest schema URI" diff --git a/lib/dsc-lib/src/discovery/command_discovery.rs b/lib/dsc-lib/src/discovery/command_discovery.rs index a5803ae7f..4989bf387 100644 --- a/lib/dsc-lib/src/discovery/command_discovery.rs +++ b/lib/dsc-lib/src/discovery/command_discovery.rs @@ -278,7 +278,7 @@ impl ResourceDiscovery for CommandDiscovery { // resource that is requested by resource/config operation // if it is, then "ResouceNotFound" error will be issued later // and here we just write as info - info!("{}", t!("discovery.commandDiscovery.failedLoadManifest", path = path.to_string_lossy(), err = e).to_string()); + info!("{}", t!("discovery.commandDiscovery.failedLoadManifest", path = path.to_string_lossy(), err = e)); continue; }, }; diff --git a/lib/dsc-lib/src/extensions/discover.rs b/lib/dsc-lib/src/extensions/discover.rs index 075571810..1ca76cc51 100644 --- a/lib/dsc-lib/src/extensions/discover.rs +++ b/lib/dsc-lib/src/extensions/discover.rs @@ -102,7 +102,7 @@ impl DscExtension { let manifests = match load_manifest(&discover_result.manifest_path) { Ok(manifests) => manifests, Err(err) => { - info!("{}", t!("extensions.dscextension.failedLoadManifest", path = discover_result.manifest_path.to_string_lossy(), err = err).to_string()); + info!("{}", t!("extensions.dscextension.failedLoadManifest", extension = self.type_name, path = discover_result.manifest_path.to_string_lossy(), err = err)); continue; } }; From 7af23115ecec0a9c25b8538d3f0c0c243fb4abda Mon Sep 17 00:00:00 2001 From: "Steve Lee (POWERSHELL HE/HIM) (from Dev Box)" Date: Mon, 23 Mar 2026 15:18:27 -0700 Subject: [PATCH 3/4] fix message to be error --- dsc/tests/dsc_extension_discover.tests.ps1 | 2 +- lib/dsc-lib/src/discovery/mod.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/dsc/tests/dsc_extension_discover.tests.ps1 b/dsc/tests/dsc_extension_discover.tests.ps1 index dbdc075b6..30c9c92d1 100644 --- a/dsc/tests/dsc_extension_discover.tests.ps1 +++ b/dsc/tests/dsc_extension_discover.tests.ps1 @@ -194,7 +194,7 @@ Describe 'Discover extension tests' { $env:DSC_RESOURCE_PATH = $TestDrive + [System.IO.Path]::PathSeparator + $env:PATH $env:TestDrive = $TestDrive $out = dsc -l info resource list 2> $TestDrive/error.log | ConvertFrom-Json - $LASTEXITCODE | Should -Be 0 + $LASTEXITCODE | Should -Be 0 -Because (Get-Content -Path "$TestDrive/error.log" -Raw | Out-String) # The invalid manifest should be skipped and not included in the discovered resources foreach ($resource in $out) { $resource.type | Should -Not -Be 'Test/InvalidManifest' diff --git a/lib/dsc-lib/src/discovery/mod.rs b/lib/dsc-lib/src/discovery/mod.rs index 2b62c0b12..38c76113f 100644 --- a/lib/dsc-lib/src/discovery/mod.rs +++ b/lib/dsc-lib/src/discovery/mod.rs @@ -13,7 +13,7 @@ use core::result::Result::Ok; use semver::{Version, VersionReq}; use std::collections::BTreeMap; use command_discovery::{CommandDiscovery, ImportedManifest}; -use tracing::info; +use tracing::error; #[derive(Clone)] pub struct Discovery { @@ -61,7 +61,7 @@ impl Discovery { let discovered_resources = match discovery_type.list_available(kind, type_name_filter, adapter_name_filter) { Ok(value) => value, Err(err) => { - info!("{err}"); + error!("{err}"); continue; } }; From cace46025818bd465d348a8be947c5e5d6127584 Mon Sep 17 00:00:00 2001 From: "Steve Lee (POWERSHELL HE/HIM) (from Dev Box)" Date: Tue, 24 Mar 2026 14:00:26 -0700 Subject: [PATCH 4/4] Fix trace message to not have the path twice since it's in the nested error --- dsc/tests/dsc_discovery.tests.ps1 | 2 +- dsc/tests/dsc_extension_discover.tests.ps1 | 2 +- lib/dsc-lib/locales/en-us.toml | 4 ++-- lib/dsc-lib/src/discovery/command_discovery.rs | 2 +- lib/dsc-lib/src/extensions/discover.rs | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/dsc/tests/dsc_discovery.tests.ps1 b/dsc/tests/dsc_discovery.tests.ps1 index 41fad2980..8e7c42b64 100644 --- a/dsc/tests/dsc_discovery.tests.ps1 +++ b/dsc/tests/dsc_discovery.tests.ps1 @@ -364,7 +364,7 @@ Describe 'tests for resource discovery' { $LASTEXITCODE | Should -Be 0 $out | Should -BeNullOrEmpty -Because (Get-Content -Raw -Path "$testdrive/error.txt") $errorLog = Get-Content -Raw -Path "$testdrive/error.txt" - $errorLog | Should -Match "INFO Failed to load manifest '.*test.dsc.resource.json':" -Because $errorLog + $errorLog | Should -BeLike "*INFO Failed to load manifest: Invalid manifest for resource '*test.dsc.resource.json'*" -Because $errorLog } finally { $env:DSC_RESOURCE_PATH = $null } diff --git a/dsc/tests/dsc_extension_discover.tests.ps1 b/dsc/tests/dsc_extension_discover.tests.ps1 index 30c9c92d1..6e3cd372c 100644 --- a/dsc/tests/dsc_extension_discover.tests.ps1 +++ b/dsc/tests/dsc_extension_discover.tests.ps1 @@ -199,7 +199,7 @@ Describe 'Discover extension tests' { foreach ($resource in $out) { $resource.type | Should -Not -Be 'Test/InvalidManifest' } - (Get-Content -Path "$TestDrive/error.log" -Raw) | Should -BeLike "*Extension 'Test/DiscoverInvalid' failed to load manifest '*invalidManifest.dsc.resource.json':*" -Because (Get-Content -Path "$TestDrive/error.log" -Raw | Out-String) + (Get-Content -Path "$TestDrive/error.log" -Raw) | Should -BeLike "*INFO Extension 'Test/DiscoverInvalid' failed to load manifest: Invalid manifest for resource '*invalidManifest.dsc.resource.json'*" -Because (Get-Content -Path "$TestDrive/error.log" -Raw | Out-String) } finally { $env:DSC_RESOURCE_PATH = $null $env:TestDrive = $null diff --git a/lib/dsc-lib/locales/en-us.toml b/lib/dsc-lib/locales/en-us.toml index 1cea065eb..2fe07e7f8 100644 --- a/lib/dsc-lib/locales/en-us.toml +++ b/lib/dsc-lib/locales/en-us.toml @@ -140,7 +140,7 @@ conditionNotBoolean = "Condition '%{condition}' did not evaluate to a boolean" conditionNotMet = "Condition '%{condition}' not met, skipping manifest at '%{path}' for resource '%{resource}'" adaptedResourcePathNotFound = "Adapted resource '%{resource}' path not found: %{path}" invalidManifestFileName = "Invalid manifest file name '%{path}'" -failedLoadManifest = "Failed to load manifest '%{path}': %{err}" +failedLoadManifest = "Failed to load manifest: %{err}" [dscresources.commandResource] invokeGet = "Invoking get for '%{resource}'" @@ -253,7 +253,7 @@ 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}'" deprecationMessage = "Extension '%{extension}' is deprecated: %{message}" -failedLoadManifest = "Extension '%{extension}' failed to load manifest '%{path}': %{err}" +failedLoadManifest = "Extension '%{extension}' failed to load manifest: %{err}" [extensions.extension_manifest] extensionManifestSchemaTitle = "Extension manifest schema URI" diff --git a/lib/dsc-lib/src/discovery/command_discovery.rs b/lib/dsc-lib/src/discovery/command_discovery.rs index 4989bf387..2baba3ac7 100644 --- a/lib/dsc-lib/src/discovery/command_discovery.rs +++ b/lib/dsc-lib/src/discovery/command_discovery.rs @@ -278,7 +278,7 @@ impl ResourceDiscovery for CommandDiscovery { // resource that is requested by resource/config operation // if it is, then "ResouceNotFound" error will be issued later // and here we just write as info - info!("{}", t!("discovery.commandDiscovery.failedLoadManifest", path = path.to_string_lossy(), err = e)); + info!("{}", t!("discovery.commandDiscovery.failedLoadManifest", err = e)); continue; }, }; diff --git a/lib/dsc-lib/src/extensions/discover.rs b/lib/dsc-lib/src/extensions/discover.rs index 1ca76cc51..4b017ae3f 100644 --- a/lib/dsc-lib/src/extensions/discover.rs +++ b/lib/dsc-lib/src/extensions/discover.rs @@ -102,7 +102,7 @@ impl DscExtension { let manifests = match load_manifest(&discover_result.manifest_path) { Ok(manifests) => manifests, Err(err) => { - info!("{}", t!("extensions.dscextension.failedLoadManifest", extension = self.type_name, path = discover_result.manifest_path.to_string_lossy(), err = err)); + info!("{}", t!("extensions.dscextension.failedLoadManifest", extension = self.type_name, err = err)); continue; } };