From fb52633d4f57b97b72a75a19f02761fb0bef6184 Mon Sep 17 00:00:00 2001 From: "Steve Lee (POWERSHELL HE/HIM) (from Dev Box)" Date: Mon, 6 Apr 2026 20:02:28 -0700 Subject: [PATCH 1/5] Fix duplicates in listing resources and add condition to manifests --- .../PowerShell_adapter.dsc.resource.json | 1 + .../powershell/powershell.dsc.resource.json | 1 + dsc/tests/dsc_resource_list.tests.ps1 | 74 +++++++++++++++---- extensions/appx/appx.dsc.extension.json | 1 + .../powershell/powershell.dsc.extension.json | 1 + lib/dsc-lib/src/discovery/mod.rs | 18 ++++- resources/PSScript/psscript.dsc.resource.json | 1 + .../sshd-subsystem.dsc.resource.json | 1 + .../sshd-subsystemList.dsc.resource.json | 1 + .../sshdconfig/sshd-windows.dsc.resource.json | 1 + .../sshdconfig/sshd_config.dsc.resource.json | 1 + 11 files changed, 84 insertions(+), 17 deletions(-) diff --git a/adapters/powershell/PowerShell_adapter.dsc.resource.json b/adapters/powershell/PowerShell_adapter.dsc.resource.json index 49d233078..954191224 100644 --- a/adapters/powershell/PowerShell_adapter.dsc.resource.json +++ b/adapters/powershell/PowerShell_adapter.dsc.resource.json @@ -7,6 +7,7 @@ "tags": [ "PowerShell" ], + "condition": "[not(equals(tryWhich('pwsh'), null()))]", "adapter": { "list": { "executable": "pwsh", diff --git a/adapters/powershell/powershell.dsc.resource.json b/adapters/powershell/powershell.dsc.resource.json index 397649f4a..23cd6246a 100644 --- a/adapters/powershell/powershell.dsc.resource.json +++ b/adapters/powershell/powershell.dsc.resource.json @@ -8,6 +8,7 @@ "tags": [ "PowerShell" ], + "condition": "[not(equals(tryWhich('pwsh'), null()))]", "adapter": { "list": { "executable": "pwsh", diff --git a/dsc/tests/dsc_resource_list.tests.ps1 b/dsc/tests/dsc_resource_list.tests.ps1 index 88d70ec9f..8d4f4317b 100644 --- a/dsc/tests/dsc_resource_list.tests.ps1 +++ b/dsc/tests/dsc_resource_list.tests.ps1 @@ -38,20 +38,27 @@ Describe 'Tests for listing resources' { ) { param($tags, $description, $expectedCount, $expectedType) - if ($tags -and $description) { - $resources = dsc resource list --tags $tags --description $description | ConvertFrom-Json - } - elseif ($tags) { - $resources = dsc resource list --tags $tags | ConvertFrom-Json - } - else { - $resources = dsc resource list --description $description | ConvertFrom-Json - } + try { + # Need to restrict the search as more resources are being added like from PS7 + $env:DSC_RESOURCE_PATH = Split-Path (Get-Command dsc).Source -Parent - $LASTEXITCODE | Should -Be 0 - $resources.Count | Should -Be $expectedCount - if ($expectedCount -gt 0) { - $resources.type | Should -BeExactly $expectedType + if ($tags -and $description) { + $resources = dsc resource list --tags $tags --description $description | ConvertFrom-Json + } + elseif ($tags) { + $resources = dsc resource list --tags $tags | ConvertFrom-Json + } + else { + $resources = dsc resource list --description $description | ConvertFrom-Json + } + + $LASTEXITCODE | Should -Be 0 + $resources.Count | Should -Be $expectedCount + if ($expectedCount -gt 0) { + $resources.type | Should -BeExactly $expectedType + } + } finally { + $env:DSC_RESOURCE_PATH = $null } } @@ -111,4 +118,45 @@ Describe 'Tests for listing resources' { } $foundWideLine | Should -BeTrue } + + It 'No duplicates based on type name and version are returned' { + $resource_manifest = @' +{ + "$schema": "https://aka.ms/dsc/schemas/v3/bundled/resource/manifest.json", + "type": "Microsoft.DSC.Debug/EchoDupe", + "version": "1.2.3", + "description": "A duplicate resource for testing", + "get": { + "executable": "dscecho", + "args": [ + { + "jsonInputArg": "--input", + "mandatory": true + } + ] + }, + "schema": { + "command": { + "executable": "dscecho" + } + } +} +'@ + $manifestPath = Join-Path $TestDrive "echoDupeManifest.json" + $manifestDupePath = Join-Path $TestDrive "echoDupeManifestDuplicate.json" + Set-Content -Path $manifestPath -Value $resource_manifest + Set-Content -Path $manifestDupePath -Value $resource_manifest + + try { + $env:DSC_RESOURCE_PATH = $TestDrive + [System.IO.Path]::PathSeparator + $env:PATH + $resources = dsc resource list | ConvertFrom-Json + $LASTEXITCODE | Should -Be 0 + $resourceGroups = $resources | Group-Object -Property type, version + foreach ($group in $resourceGroups) { + $group.Count | Should -Be 1 -Because ($resources | ConvertTo-Json -Depth 20) + } + } finally { + $env:DSC_RESOURCE_PATH = $null + } + } } diff --git a/extensions/appx/appx.dsc.extension.json b/extensions/appx/appx.dsc.extension.json index d08294863..f588a1e64 100644 --- a/extensions/appx/appx.dsc.extension.json +++ b/extensions/appx/appx.dsc.extension.json @@ -3,6 +3,7 @@ "type": "Microsoft.Windows.Appx/Discover", "version": "0.1.0", "description": "Discovers DSC resources packaged as Appx packages.", + "condition": "[not(equals(tryWhich('pwsh'), null()))]", "discover": { "executable": "powershell", "args": [ diff --git a/extensions/powershell/powershell.dsc.extension.json b/extensions/powershell/powershell.dsc.extension.json index 9096e4fa1..5323dc614 100644 --- a/extensions/powershell/powershell.dsc.extension.json +++ b/extensions/powershell/powershell.dsc.extension.json @@ -3,6 +3,7 @@ "type": "Microsoft.PowerShell/Discover", "version": "0.1.0", "description": "Discovers DSC resources packaged in PowerShell 7 modules.", + "condition": "[not(equals(tryWhich('pwsh'), null()))]", "discover": { "executable": "pwsh", "args": [ diff --git a/lib/dsc-lib/src/discovery/mod.rs b/lib/dsc-lib/src/discovery/mod.rs index 6446d9996..c3380b206 100644 --- a/lib/dsc-lib/src/discovery/mod.rs +++ b/lib/dsc-lib/src/discovery/mod.rs @@ -68,7 +68,7 @@ impl Discovery { Box::new(command_discovery::CommandDiscovery::new(progress_format)), ]; - let mut resources: Vec = Vec::new(); + let mut resources: BTreeMap = BTreeMap::new(); for mut discovery_type in discovery_types { @@ -81,8 +81,18 @@ impl Discovery { }; for (_resource_name, found_resources) in discovered_resources { - for resource in found_resources { - resources.push(resource.clone()); + for manifest in found_resources { + match manifest { + ImportedManifest::Resource(ref resource) => { + let key = format!("{}@{}", resource.type_name, resource.version); + if resources.contains_key(&key) { + continue; // if we already have this resource, we can skip it + } else { + resources.insert(key, manifest.clone()); + } + }, + _ => {} // we only need to cache resources + } } }; @@ -91,7 +101,7 @@ impl Discovery { } } - resources + resources.into_iter().map(|(_key, value)| value).collect::>() } pub fn get_extensions(&mut self, capability: &Capability) -> Vec { diff --git a/resources/PSScript/psscript.dsc.resource.json b/resources/PSScript/psscript.dsc.resource.json index fda07988d..c189434a1 100644 --- a/resources/PSScript/psscript.dsc.resource.json +++ b/resources/PSScript/psscript.dsc.resource.json @@ -3,6 +3,7 @@ "type": "Microsoft.DSC.Transitional/PowerShellScript", "description": "Enable running PowerShell 7 scripts inline", "version": "0.1.0", + "condition": "[not(equals(tryWhich('pwsh'), null()))]", "get": { "executable": "pwsh", "args": [ diff --git a/resources/sshdconfig/sshd-subsystem.dsc.resource.json b/resources/sshdconfig/sshd-subsystem.dsc.resource.json index 32b9923c5..c11850730 100644 --- a/resources/sshdconfig/sshd-subsystem.dsc.resource.json +++ b/resources/sshdconfig/sshd-subsystem.dsc.resource.json @@ -3,6 +3,7 @@ "type": "Microsoft.OpenSSH.SSHD/Subsystem", "description": "Manage SSH Server Subsystem keyword (single entry)", "version": "0.1.0", + "condition": "[not(equals(tryWhich('sshd'), null()))]", "get": { "executable": "sshdconfig", "args": [ diff --git a/resources/sshdconfig/sshd-subsystemList.dsc.resource.json b/resources/sshdconfig/sshd-subsystemList.dsc.resource.json index 1d64fb9c9..6fe9a6c9a 100644 --- a/resources/sshdconfig/sshd-subsystemList.dsc.resource.json +++ b/resources/sshdconfig/sshd-subsystemList.dsc.resource.json @@ -3,6 +3,7 @@ "type": "Microsoft.OpenSSH.SSHD/SubsystemList", "description": "Manage SSH Server Subsystem keyword (accepts multiple entries)", "version": "0.1.0", + "condition": "[not(equals(tryWhich('sshd'), null()))]", "get": { "executable": "sshdconfig", "args": [ diff --git a/resources/sshdconfig/sshd-windows.dsc.resource.json b/resources/sshdconfig/sshd-windows.dsc.resource.json index 6a92d0a12..814b9d1df 100644 --- a/resources/sshdconfig/sshd-windows.dsc.resource.json +++ b/resources/sshdconfig/sshd-windows.dsc.resource.json @@ -5,6 +5,7 @@ "tags": [ "Windows" ], + "condition": "[not(equals(tryWhich('sshd'), null()))]", "version": "0.1.0", "get": { "executable": "sshdconfig", diff --git a/resources/sshdconfig/sshd_config.dsc.resource.json b/resources/sshdconfig/sshd_config.dsc.resource.json index aafa95d6f..223f4f05c 100644 --- a/resources/sshdconfig/sshd_config.dsc.resource.json +++ b/resources/sshdconfig/sshd_config.dsc.resource.json @@ -2,6 +2,7 @@ "$schema": "https://aka.ms/dsc/schemas/v3/bundled/resource/manifest.json", "type": "Microsoft.OpenSSH.SSHD/sshd_config", "description": "Manage SSH Server Configuration", + "condition": "[not(equals(tryWhich('sshd'), null()))]", "version": "0.1.0", "get": { "executable": "sshdconfig", From fb8cf43dd6168519e04734a482b76a7b7a6c4fdd Mon Sep 17 00:00:00 2001 From: "Steve Lee (POWERSHELL HE/HIM) (from Dev Box)" Date: Tue, 7 Apr 2026 14:10:09 -0700 Subject: [PATCH 2/5] fix mcp list_dsc_resources and listing extensions --- data.build.json | 6 ++++++ dsc/src/mcp/list_dsc_resources.rs | 7 +++---- dsc/tests/dsc_mcp.tests.ps1 | 4 ++-- lib/dsc-lib/src/discovery/mod.rs | 13 ++++++++++--- 4 files changed, 21 insertions(+), 9 deletions(-) diff --git a/data.build.json b/data.build.json index bd4e18e01..bd36e3f5d 100644 --- a/data.build.json +++ b/data.build.json @@ -29,6 +29,8 @@ "runcommandonset", "sshdconfig", "sshd_config.dsc.resource.json", + "sshd-subsystem.dsc.resource.json", + "sshd-subsystemList.dsc.resource.json", "y2j" ], "macOS": [ @@ -56,6 +58,8 @@ "runcommandonset", "sshdconfig", "sshd_config.dsc.resource.json", + "sshd-subsystem.dsc.resource.json", + "sshd-subsystemList.dsc.resource.json", "y2j" ], "Windows": [ @@ -92,6 +96,8 @@ "sshdconfig.exe", "sshd-windows.dsc.resource.json", "sshd_config.dsc.resource.json", + "sshd-subsystem.dsc.resource.json", + "sshd-subsystemList.dsc.resource.json", "windowspowershell.dsc.resource.json", "windowsupdate.dsc.resource.json", "wu_dsc.exe", diff --git a/dsc/src/mcp/list_dsc_resources.rs b/dsc/src/mcp/list_dsc_resources.rs index 7b53d7d05..0047d0e58 100644 --- a/dsc/src/mcp/list_dsc_resources.rs +++ b/dsc/src/mcp/list_dsc_resources.rs @@ -12,7 +12,6 @@ use rmcp::{ErrorData as McpError, Json, tool, tool_router, handler::server::wrap use rust_i18n::t; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use std::collections::BTreeMap; use tokio::task; #[derive(Serialize, JsonSchema)] @@ -63,7 +62,7 @@ impl McpServer { }, None => None, }; - let mut resources = BTreeMap::::new(); + let mut resources = Vec::::new(); for resource in dsc.list_available(&DiscoveryKind::Resource, &TypeNameFilter::default(), adapter_filter, ProgressFormat::None) { if let Resource(resource) = resource { let summary = ResourceSummary { @@ -72,10 +71,10 @@ impl McpServer { description: resource.description.clone(), require_adapter: resource.require_adapter, }; - resources.insert(resource.type_name.clone(), summary); + resources.push(summary); } } - Ok(ResourceListResult { resources: resources.into_values().collect() }) + Ok(ResourceListResult { resources }) }).await.map_err(|e| McpError::internal_error(e.to_string(), None))??; Ok(Json(result)) diff --git a/dsc/tests/dsc_mcp.tests.ps1 b/dsc/tests/dsc_mcp.tests.ps1 index ab51b52d0..aa40c24d0 100644 --- a/dsc/tests/dsc_mcp.tests.ps1 +++ b/dsc/tests/dsc_mcp.tests.ps1 @@ -104,8 +104,8 @@ Describe 'Tests for MCP server' { $response = Send-McpRequest -request $mcpRequest $response.id | Should -BeGreaterOrEqual 3 - $resources = dsc resource list | ConvertFrom-Json -Depth 20 | Select-Object type, kind, description -Unique - $response.result.structuredContent.resources.Count | Should -Be $resources.Count + $resources = dsc resource list | ConvertFrom-Json -Depth 20 | Select-Object type, kind, description + $response.result.structuredContent.resources.Count | Should -Be $resources.Count -Because "MCP:`n$($response.result.structuredContent.resources | Format-Table | Out-String)`nDSC:`n$($resources | Format-Table | Out-String)" for ($i = 0; $i -lt $resources.Count; $i++) { ($response.result.structuredContent.resources[$i].psobject.properties | Measure-Object).Count | Should -BeGreaterOrEqual 3 $response.result.structuredContent.resources[$i].type | Should -BeExactly $resources[$i].type -Because ($response.result.structuredContent | ConvertTo-Json -Depth 20 | Out-String) diff --git a/lib/dsc-lib/src/discovery/mod.rs b/lib/dsc-lib/src/discovery/mod.rs index c3380b206..29e12a14c 100644 --- a/lib/dsc-lib/src/discovery/mod.rs +++ b/lib/dsc-lib/src/discovery/mod.rs @@ -81,17 +81,24 @@ impl Discovery { }; for (_resource_name, found_resources) in discovered_resources { - for manifest in found_resources { + 'manifests: for manifest in found_resources { match manifest { ImportedManifest::Resource(ref resource) => { let key = format!("{}@{}", resource.type_name, resource.version); if resources.contains_key(&key) { - continue; // if we already have this resource, we can skip it + continue 'manifests; // if we already have this resource, we can skip it } else { resources.insert(key, manifest.clone()); } }, - _ => {} // we only need to cache resources + ImportedManifest::Extension(ref extension) => { + let key = format!("{}@{}", extension.type_name, extension.version); + if resources.contains_key(&key) { + continue 'manifests; // if we already have this extension, we can skip it + } else { + resources.insert(key, manifest.clone()); + } + } } } }; From fabc8ba6eefffb6de7a0c1605717a9f416bcc1b7 Mon Sep 17 00:00:00 2001 From: "Steve Lee (POWERSHELL HE/HIM) (from Dev Box)" Date: Tue, 7 Apr 2026 16:32:01 -0700 Subject: [PATCH 3/5] address copilot feedback --- dsc/tests/dsc_resource_list.tests.ps1 | 7 ++++--- extensions/appx/appx.dsc.extension.json | 1 - lib/dsc-lib/src/discovery/mod.rs | 25 ++++++++----------------- 3 files changed, 12 insertions(+), 21 deletions(-) diff --git a/dsc/tests/dsc_resource_list.tests.ps1 b/dsc/tests/dsc_resource_list.tests.ps1 index 8d4f4317b..8f9b82fbb 100644 --- a/dsc/tests/dsc_resource_list.tests.ps1 +++ b/dsc/tests/dsc_resource_list.tests.ps1 @@ -38,6 +38,7 @@ Describe 'Tests for listing resources' { ) { param($tags, $description, $expectedCount, $expectedType) + $oldPath = $env:DSC_RESOURCE_PATH try { # Need to restrict the search as more resources are being added like from PS7 $env:DSC_RESOURCE_PATH = Split-Path (Get-Command dsc).Source -Parent @@ -58,7 +59,7 @@ Describe 'Tests for listing resources' { $resources.type | Should -BeExactly $expectedType } } finally { - $env:DSC_RESOURCE_PATH = $null + $env:DSC_RESOURCE_PATH = $oldPath } } @@ -142,8 +143,8 @@ Describe 'Tests for listing resources' { } } '@ - $manifestPath = Join-Path $TestDrive "echoDupeManifest.json" - $manifestDupePath = Join-Path $TestDrive "echoDupeManifestDuplicate.json" + $manifestPath = Join-Path $TestDrive "echoDupeManifest.dsc.resource.json" + $manifestDupePath = Join-Path $TestDrive "echoDupeManifestDuplicate.dsc.resource.json" Set-Content -Path $manifestPath -Value $resource_manifest Set-Content -Path $manifestDupePath -Value $resource_manifest diff --git a/extensions/appx/appx.dsc.extension.json b/extensions/appx/appx.dsc.extension.json index f588a1e64..d08294863 100644 --- a/extensions/appx/appx.dsc.extension.json +++ b/extensions/appx/appx.dsc.extension.json @@ -3,7 +3,6 @@ "type": "Microsoft.Windows.Appx/Discover", "version": "0.1.0", "description": "Discovers DSC resources packaged as Appx packages.", - "condition": "[not(equals(tryWhich('pwsh'), null()))]", "discover": { "executable": "powershell", "args": [ diff --git a/lib/dsc-lib/src/discovery/mod.rs b/lib/dsc-lib/src/discovery/mod.rs index 29e12a14c..c2409e8d1 100644 --- a/lib/dsc-lib/src/discovery/mod.rs +++ b/lib/dsc-lib/src/discovery/mod.rs @@ -81,25 +81,16 @@ impl Discovery { }; for (_resource_name, found_resources) in discovered_resources { - 'manifests: for manifest in found_resources { - match manifest { - ImportedManifest::Resource(ref resource) => { - let key = format!("{}@{}", resource.type_name, resource.version); - if resources.contains_key(&key) { - continue 'manifests; // if we already have this resource, we can skip it - } else { - resources.insert(key, manifest.clone()); - } + for manifest in found_resources { + let key = match &manifest { + ImportedManifest::Resource(resource) => { + format!("{}@{}", resource.type_name.to_lowercase(), resource.version) }, - ImportedManifest::Extension(ref extension) => { - let key = format!("{}@{}", extension.type_name, extension.version); - if resources.contains_key(&key) { - continue 'manifests; // if we already have this extension, we can skip it - } else { - resources.insert(key, manifest.clone()); - } + ImportedManifest::Extension(extension) => { + format!("{}@{}", extension.type_name.to_lowercase(), extension.version) } - } + }; + resources.insert(key, manifest); } }; From b8c51db4c71338bcb5a3f4ae72dfe6605196972f Mon Sep 17 00:00:00 2001 From: "Steve Lee (POWERSHELL HE/HIM) (from Dev Box)" Date: Tue, 7 Apr 2026 17:37:53 -0700 Subject: [PATCH 4/5] fix clippy --- lib/dsc-lib/src/discovery/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/dsc-lib/src/discovery/mod.rs b/lib/dsc-lib/src/discovery/mod.rs index c2409e8d1..58565ae40 100644 --- a/lib/dsc-lib/src/discovery/mod.rs +++ b/lib/dsc-lib/src/discovery/mod.rs @@ -99,7 +99,7 @@ impl Discovery { } } - resources.into_iter().map(|(_key, value)| value).collect::>() + resources.into_values().collect::>() } pub fn get_extensions(&mut self, capability: &Capability) -> Vec { From 322be7f420573da3f7b3eeee2ab56a0ec3c34ae0 Mon Sep 17 00:00:00 2001 From: "Steve Lee (POWERSHELL HE/HIM) (from Dev Box)" Date: Thu, 9 Apr 2026 13:41:06 -0700 Subject: [PATCH 5/5] update test to preserve DSC_RESOURCE_PATH if set --- dsc/tests/dsc_resource_list.tests.ps1 | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dsc/tests/dsc_resource_list.tests.ps1 b/dsc/tests/dsc_resource_list.tests.ps1 index 8f9b82fbb..267c72ec5 100644 --- a/dsc/tests/dsc_resource_list.tests.ps1 +++ b/dsc/tests/dsc_resource_list.tests.ps1 @@ -148,6 +148,7 @@ Describe 'Tests for listing resources' { Set-Content -Path $manifestPath -Value $resource_manifest Set-Content -Path $manifestDupePath -Value $resource_manifest + $oldPath = $env:DSC_RESOURCE_PATH try { $env:DSC_RESOURCE_PATH = $TestDrive + [System.IO.Path]::PathSeparator + $env:PATH $resources = dsc resource list | ConvertFrom-Json @@ -157,7 +158,7 @@ Describe 'Tests for listing resources' { $group.Count | Should -Be 1 -Because ($resources | ConvertTo-Json -Depth 20) } } finally { - $env:DSC_RESOURCE_PATH = $null + $env:DSC_RESOURCE_PATH = $oldPath } } }