From dd4163420d64b8811328a3b2da64da3155ef12cc Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Thu, 21 Aug 2025 14:38:26 +0200 Subject: [PATCH 01/31] Init --- .vscode/launch.json | 78 +++++++++---------- extensions/powershell/powershell-discover.ps1 | 13 ++++ 2 files changed, 52 insertions(+), 39 deletions(-) create mode 100644 extensions/powershell/powershell-discover.ps1 diff --git a/.vscode/launch.json b/.vscode/launch.json index c0fe66904..00879d03f 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,39 +1,39 @@ -{ - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - { - "type": "lldb", - "request": "launch", - "name": "Debug config", - "program": "${workspaceFolder}/config/target/debug/config", - "args": [ - "list", - "r*" - ], - "cwd": "${workspaceFolder}" - }, - { - "name": "(macOS) Attach", - "type": "lldb", - "request": "attach", - "pid": "${command:pickMyProcess}", - }, - { - "name": "(Windows) Attach", - "type": "cppvsdbg", - "request": "attach", - "processId": "${command:pickProcess}", - }, - { - "name": "Debug sshdconfig", - "type": "cppvsdbg", - "request": "launch", - "program": "${workspaceFolder}/sshdconfig/target/debug/sshdconfig.exe", - "args": ["get"], - "cwd": "${workspaceFolder}" - } - ] -} +// { +// // Use IntelliSense to learn about possible attributes. +// // Hover to view descriptions of existing attributes. +// // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 +// "version": "0.2.0", +// "configurations": [ +// { +// "type": "lldb", +// "request": "launch", +// "name": "Debug config", +// "program": "${workspaceFolder}/config/target/debug/config", +// "args": [ +// "list", +// "r*" +// ], +// "cwd": "${workspaceFolder}" +// }, +// { +// "name": "(macOS) Attach", +// "type": "lldb", +// "request": "attach", +// "pid": "${command:pickMyProcess}", +// }, +// { +// "name": "(Windows) Attach", +// "type": "cppvsdbg", +// "request": "attach", +// "processId": "${command:pickProcess}", +// }, +// { +// "name": "Debug sshdconfig", +// "type": "cppvsdbg", +// "request": "launch", +// "program": "${workspaceFolder}/sshdconfig/target/debug/sshdconfig.exe", +// "args": ["get"], +// "cwd": "${workspaceFolder}" +// } +// ] +// } diff --git a/extensions/powershell/powershell-discover.ps1 b/extensions/powershell/powershell-discover.ps1 new file mode 100644 index 000000000..bfa56b08e --- /dev/null +++ b/extensions/powershell/powershell-discover.ps1 @@ -0,0 +1,13 @@ +$psPaths = $env:PSModulePath -split [System.IO.Path]::PathSeparator | Where-Object { $_ -notmatch 'WindowsPowerShell' } +$manifests = [System.Collections.Generic.List[hashtable]]::new() + +$psPaths | ForEach-Object -Parallel { + $queue = $using:manifests + $files = Get-ChildItem -Path $_ -Recurse -File -Include '*.dsc.resource.json', '*.dsc.resource.yaml', '*.dsc.resource.yml' -ErrorAction Ignore + foreach ($file in $files) { + $m = @{ manifestPath = $file.FullName } + $queue.Add($m) + } +} -ThrottleLimit 10 + +$manifests | ConvertTo-Json -Compress \ No newline at end of file From 9e04ed5e6e960a7ac46c2c0fa1cfc688d489a99a Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Thu, 21 Aug 2025 14:40:57 +0200 Subject: [PATCH 02/31] Use concurrentbag --- extensions/powershell/powershell-discover.ps1 | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/extensions/powershell/powershell-discover.ps1 b/extensions/powershell/powershell-discover.ps1 index bfa56b08e..578cfd6b1 100644 --- a/extensions/powershell/powershell-discover.ps1 +++ b/extensions/powershell/powershell-discover.ps1 @@ -1,13 +1,15 @@ $psPaths = $env:PSModulePath -split [System.IO.Path]::PathSeparator | Where-Object { $_ -notmatch 'WindowsPowerShell' } -$manifests = [System.Collections.Generic.List[hashtable]]::new() + +$m = [System.Collections.Concurrent.ConcurrentBag[hashtable]]::new() $psPaths | ForEach-Object -Parallel { - $queue = $using:manifests - $files = Get-ChildItem -Path $_ -Recurse -File -Include '*.dsc.resource.json', '*.dsc.resource.yaml', '*.dsc.resource.yml' -ErrorAction Ignore + $queue = $using:m + $files = Get-ChildItem -Path $_ -Recurse -File -Filter '*.dsc.resource.*' -ErrorAction Ignore | + Where-Object -Property Extension -In @('.json', '.yaml', '.yml') + foreach ($file in $files) { - $m = @{ manifestPath = $file.FullName } - $queue.Add($m) + $queue.Add(@{ manifestPath = $file.FullName }) } } -ThrottleLimit 10 -$manifests | ConvertTo-Json -Compress \ No newline at end of file +@($m) | ConvertTo-Json -Compress \ No newline at end of file From dae94e474d5ff56dc5e23826f10c275af945cba5 Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Thu, 21 Aug 2025 14:49:30 +0200 Subject: [PATCH 03/31] Use .NET --- extensions/powershell/powershell-discover.ps1 | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/extensions/powershell/powershell-discover.ps1 b/extensions/powershell/powershell-discover.ps1 index 578cfd6b1..b44f3cb5d 100644 --- a/extensions/powershell/powershell-discover.ps1 +++ b/extensions/powershell/powershell-discover.ps1 @@ -4,12 +4,14 @@ $m = [System.Collections.Concurrent.ConcurrentBag[hashtable]]::new() $psPaths | ForEach-Object -Parallel { $queue = $using:m - $files = Get-ChildItem -Path $_ -Recurse -File -Filter '*.dsc.resource.*' -ErrorAction Ignore | - Where-Object -Property Extension -In @('.json', '.yaml', '.yml') - - foreach ($file in $files) { - $queue.Add(@{ manifestPath = $file.FullName }) + $searchPatterns = @('*.dsc.resource.json', '*.dsc.resource.yaml', '*.dsc.resource.yml') + foreach ($pattern in $searchPatterns) { + try { + [System.IO.Directory]::EnumerateFiles($_, $pattern, 'AllDirectories') | ForEach-Object { + $queue.Add(@{ manifestPath = $_ }) + } + } catch { } } -} -ThrottleLimit 10 +} -ThrottleLimit 30 -@($m) | ConvertTo-Json -Compress \ No newline at end of file +[array]$m | ConvertTo-Json -Compress \ No newline at end of file From 6ec70d70e768cb05381ab29f0992392d8c7c3934 Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Thu, 21 Aug 2025 15:26:38 +0200 Subject: [PATCH 04/31] Use .NET with enumeration option --- .vscode/launch.json | 78 +++++++++---------- build.ps1 | 12 ++- dsc/tests/dsc_extension_discover.tests.ps1 | 46 +++++------ extensions/powershell/copy_files.txt | 2 + extensions/powershell/powershell-discover.ps1 | 17 ---- extensions/powershell/powershell.discover.ps1 | 18 +++++ .../powershell/powershell.discover.tests.ps1 | 32 ++++++++ .../powershell/powershell.dsc.extension.json | 18 +++++ 8 files changed, 141 insertions(+), 82 deletions(-) create mode 100644 extensions/powershell/copy_files.txt delete mode 100644 extensions/powershell/powershell-discover.ps1 create mode 100644 extensions/powershell/powershell.discover.ps1 create mode 100644 extensions/powershell/powershell.discover.tests.ps1 create mode 100644 extensions/powershell/powershell.dsc.extension.json diff --git a/.vscode/launch.json b/.vscode/launch.json index 00879d03f..c0fe66904 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,39 +1,39 @@ -// { -// // Use IntelliSense to learn about possible attributes. -// // Hover to view descriptions of existing attributes. -// // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 -// "version": "0.2.0", -// "configurations": [ -// { -// "type": "lldb", -// "request": "launch", -// "name": "Debug config", -// "program": "${workspaceFolder}/config/target/debug/config", -// "args": [ -// "list", -// "r*" -// ], -// "cwd": "${workspaceFolder}" -// }, -// { -// "name": "(macOS) Attach", -// "type": "lldb", -// "request": "attach", -// "pid": "${command:pickMyProcess}", -// }, -// { -// "name": "(Windows) Attach", -// "type": "cppvsdbg", -// "request": "attach", -// "processId": "${command:pickProcess}", -// }, -// { -// "name": "Debug sshdconfig", -// "type": "cppvsdbg", -// "request": "launch", -// "program": "${workspaceFolder}/sshdconfig/target/debug/sshdconfig.exe", -// "args": ["get"], -// "cwd": "${workspaceFolder}" -// } -// ] -// } +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "lldb", + "request": "launch", + "name": "Debug config", + "program": "${workspaceFolder}/config/target/debug/config", + "args": [ + "list", + "r*" + ], + "cwd": "${workspaceFolder}" + }, + { + "name": "(macOS) Attach", + "type": "lldb", + "request": "attach", + "pid": "${command:pickMyProcess}", + }, + { + "name": "(Windows) Attach", + "type": "cppvsdbg", + "request": "attach", + "processId": "${command:pickProcess}", + }, + { + "name": "Debug sshdconfig", + "type": "cppvsdbg", + "request": "launch", + "program": "${workspaceFolder}/sshdconfig/target/debug/sshdconfig.exe", + "args": ["get"], + "cwd": "${workspaceFolder}" + } + ] +} diff --git a/build.ps1 b/build.ps1 index c551877d5..223e83f73 100755 --- a/build.ps1 +++ b/build.ps1 @@ -60,6 +60,8 @@ $filesForWindowsPackage = @( 'NOTICE.txt', 'osinfo.exe', 'osinfo.dsc.resource.json', + 'powershell.discover.ps1', + 'powershell.dsc.extension.json', 'powershell.dsc.resource.json', 'psDscAdapter/', 'psscript.ps1', @@ -98,6 +100,8 @@ $filesForLinuxPackage = @( 'NOTICE.txt', 'osinfo', 'osinfo.dsc.resource.json', + 'powershell.discover.ps1', + 'powershell.dsc.extension.json', 'powershell.dsc.resource.json', 'psDscAdapter/', 'psscript.ps1', @@ -123,6 +127,8 @@ $filesForMacPackage = @( 'NOTICE.txt', 'osinfo', 'osinfo.dsc.resource.json', + 'powershell.discover.ps1', + 'powershell.dsc.extension.json', 'powershell.dsc.resource.json', 'psDscAdapter/', 'psscript.ps1', @@ -324,9 +330,9 @@ if (!$SkipBuild) { } # make sure dependencies are built first so clippy runs correctly - $windows_projects = @("pal", "registry_lib", "registry", "reboot_pending", "wmi-adapter", "configurations/windows", 'extensions/appx') - $macOS_projects = @("resources/brew") - $linux_projects = @("resources/apt") + $windows_projects = @("pal", "registry_lib", "registry", "reboot_pending", "wmi-adapter", "configurations/windows", "extensions/appx", "extensions/powershell") + $macOS_projects = @("resources/brew", "extensions/powershell") + $linux_projects = @("resources/apt", "extensions/powershell") # projects are in dependency order $projects = @( diff --git a/dsc/tests/dsc_extension_discover.tests.ps1 b/dsc/tests/dsc_extension_discover.tests.ps1 index d37455465..f09cca1ad 100644 --- a/dsc/tests/dsc_extension_discover.tests.ps1 +++ b/dsc/tests/dsc_extension_discover.tests.ps1 @@ -23,30 +23,30 @@ Describe 'Discover extension tests' { It 'Discover extensions' { $out = dsc extension list | ConvertFrom-Json $LASTEXITCODE | Should -Be 0 - if ($IsWindows) { - $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 -Be 'Microsoft.Windows.Appx/Discover' - $out[1].version | Should -Be '0.1.0' - $out[1].capabilities | Should -BeExactly @('discover') - $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 + $expectedExtensions = if ($IsWindows) { + @( + @{ type = 'Microsoft.DSC.Extension/Bicep'; version = '0.1.0'; capabilities = @('import') } + @{ type = 'Microsoft.DSC.Transitional/PSDesiredStateConfiguration'; version = '0.1.0'; capabilities = @('import') } + @{ type = 'Microsoft.Windows.Appx/Discover'; version = '0.1.0'; capabilities = @('discover') } + @{ type = 'Microsoft.PowerShell/Discover'; version = '0.1.0'; capabilities = @('discover') } + @{ type = 'Test/Discover'; version = '0.1.0'; capabilities = @('discover') } + ) } else { - $out.Count | Should -Be 2 -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].version | Should -BeExactly '0.1.0' - $out[1].capabilities | Should -BeExactly @('discover') - $out[1].manifest | Should -Not -BeNullOrEmpty + @( + @{ type = 'Microsoft.DSC.Extension/Bicep'; version = '0.1.0'; capabilities = @('import') } + @{ type = 'Microsoft.PowerShell/Discover'; version = '0.1.0'; capabilities = @('discover') } + @{ type = 'Test/Discover'; version = '0.1.0'; capabilities = @('discover') } + ) + } + + $out.Count | Should -Be $expectedExtensions.Count -Because ($out | Out-String) + + foreach ($expected in $expectedExtensions) { + $extension = $out | Where-Object { $_.type -eq $expected.type } + $extension | Should -Not -BeNullOrEmpty -Because "Extension $($expected.type) should exist" + $extension.version | Should -BeExactly $expected.version + $extension.capabilities | Should -BeExactly $expected.capabilities + $extension.manifest | Should -Not -BeNullOrEmpty } } diff --git a/extensions/powershell/copy_files.txt b/extensions/powershell/copy_files.txt new file mode 100644 index 000000000..e3b12dc13 --- /dev/null +++ b/extensions/powershell/copy_files.txt @@ -0,0 +1,2 @@ +powershell.discover.ps1 +powershell.dsc.extension.json diff --git a/extensions/powershell/powershell-discover.ps1 b/extensions/powershell/powershell-discover.ps1 deleted file mode 100644 index b44f3cb5d..000000000 --- a/extensions/powershell/powershell-discover.ps1 +++ /dev/null @@ -1,17 +0,0 @@ -$psPaths = $env:PSModulePath -split [System.IO.Path]::PathSeparator | Where-Object { $_ -notmatch 'WindowsPowerShell' } - -$m = [System.Collections.Concurrent.ConcurrentBag[hashtable]]::new() - -$psPaths | ForEach-Object -Parallel { - $queue = $using:m - $searchPatterns = @('*.dsc.resource.json', '*.dsc.resource.yaml', '*.dsc.resource.yml') - foreach ($pattern in $searchPatterns) { - try { - [System.IO.Directory]::EnumerateFiles($_, $pattern, 'AllDirectories') | ForEach-Object { - $queue.Add(@{ manifestPath = $_ }) - } - } catch { } - } -} -ThrottleLimit 30 - -[array]$m | ConvertTo-Json -Compress \ No newline at end of file diff --git a/extensions/powershell/powershell.discover.ps1 b/extensions/powershell/powershell.discover.ps1 new file mode 100644 index 000000000..43c4130d3 --- /dev/null +++ b/extensions/powershell/powershell.discover.ps1 @@ -0,0 +1,18 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +$psPaths = $env:PSModulePath -split [System.IO.Path]::PathSeparator | Where-Object { $_ -notmatch 'WindowsPowerShell' } + +$manifests = $psPaths | ForEach-Object -Parallel { + $searchPatterns = @('*.dsc.resource.json', '*.dsc.resource.yaml', '*.dsc.resource.yml') + $enumOptions = [System.IO.EnumerationOptions]@{ IgnoreInaccessible = $false; RecurseSubdirectories = $true } + foreach ($pattern in $searchPatterns) { + try { + [System.IO.Directory]::EnumerateFiles($_, $pattern, $enumOptions) | ForEach-Object { + @{ manifestPath = $_ } + } + } catch { } + } +} -ThrottleLimit 10 + +$manifests | ForEach-Object { $_ | ConvertTo-Json -Compress } \ No newline at end of file diff --git a/extensions/powershell/powershell.discover.tests.ps1 b/extensions/powershell/powershell.discover.tests.ps1 new file mode 100644 index 000000000..6b321d471 --- /dev/null +++ b/extensions/powershell/powershell.discover.tests.ps1 @@ -0,0 +1,32 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +BeforeAll { + $fakeManifest = @{ + '$schema' = "https://aka.ms/dsc/schemas/v3/bundled/resource/manifest.json" + type = "Test/FakeResource" + version = "0.1.0" + get = @{ + executable = "fakeResource" + args = @( + "get", + @{ + jsonInputArg = "--input" + mandatory = $true + } + ) + } + } + + $manifestPath = Join-Path $TestDrive "fake.dsc.resource.json" + $fakeManifest | ConvertTo-Json -Depth 10 | Set-Content -Path $manifestPath + $env:PSModulePath += [System.IO.Path]::PathSeparator + $TestDrive +} + +Describe 'Tests for PowerShell resource discovery' { + It 'Should find DSC PowerShell resources' { + $out = dsc resource list | ConvertFrom-Json + $LASTEXITCODE | Should -Be 0 + $out.directory | Should -Contain $TestDrive + } +} diff --git a/extensions/powershell/powershell.dsc.extension.json b/extensions/powershell/powershell.dsc.extension.json new file mode 100644 index 000000000..aa10cffa3 --- /dev/null +++ b/extensions/powershell/powershell.dsc.extension.json @@ -0,0 +1,18 @@ +{ + "$schema": "https://aka.ms/dsc/schemas/v3/bundled/resource/manifest.json", + "type": "Microsoft.PowerShell/Discover", + "version": "0.1.0", + "description": "Discovers DSC resources packaged in PowerShell 7 modules.", + "discover": { + "executable": "pwsh", + "args": [ + "-NoLogo", + "-NonInteractive", + "-ExecutionPolicy", + "Bypass", + "-NoProfile", + "-Command", + "./powershell.discover.ps1" + ] + } +} From d13045dbb5c27521e58a8ccfc758dba0c02308ac Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Thu, 21 Aug 2025 14:38:26 +0200 Subject: [PATCH 05/31] Init --- .vscode/launch.json | 78 +++++++++---------- extensions/powershell/powershell-discover.ps1 | 13 ++++ 2 files changed, 52 insertions(+), 39 deletions(-) create mode 100644 extensions/powershell/powershell-discover.ps1 diff --git a/.vscode/launch.json b/.vscode/launch.json index c0fe66904..00879d03f 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,39 +1,39 @@ -{ - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - { - "type": "lldb", - "request": "launch", - "name": "Debug config", - "program": "${workspaceFolder}/config/target/debug/config", - "args": [ - "list", - "r*" - ], - "cwd": "${workspaceFolder}" - }, - { - "name": "(macOS) Attach", - "type": "lldb", - "request": "attach", - "pid": "${command:pickMyProcess}", - }, - { - "name": "(Windows) Attach", - "type": "cppvsdbg", - "request": "attach", - "processId": "${command:pickProcess}", - }, - { - "name": "Debug sshdconfig", - "type": "cppvsdbg", - "request": "launch", - "program": "${workspaceFolder}/sshdconfig/target/debug/sshdconfig.exe", - "args": ["get"], - "cwd": "${workspaceFolder}" - } - ] -} +// { +// // Use IntelliSense to learn about possible attributes. +// // Hover to view descriptions of existing attributes. +// // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 +// "version": "0.2.0", +// "configurations": [ +// { +// "type": "lldb", +// "request": "launch", +// "name": "Debug config", +// "program": "${workspaceFolder}/config/target/debug/config", +// "args": [ +// "list", +// "r*" +// ], +// "cwd": "${workspaceFolder}" +// }, +// { +// "name": "(macOS) Attach", +// "type": "lldb", +// "request": "attach", +// "pid": "${command:pickMyProcess}", +// }, +// { +// "name": "(Windows) Attach", +// "type": "cppvsdbg", +// "request": "attach", +// "processId": "${command:pickProcess}", +// }, +// { +// "name": "Debug sshdconfig", +// "type": "cppvsdbg", +// "request": "launch", +// "program": "${workspaceFolder}/sshdconfig/target/debug/sshdconfig.exe", +// "args": ["get"], +// "cwd": "${workspaceFolder}" +// } +// ] +// } diff --git a/extensions/powershell/powershell-discover.ps1 b/extensions/powershell/powershell-discover.ps1 new file mode 100644 index 000000000..bfa56b08e --- /dev/null +++ b/extensions/powershell/powershell-discover.ps1 @@ -0,0 +1,13 @@ +$psPaths = $env:PSModulePath -split [System.IO.Path]::PathSeparator | Where-Object { $_ -notmatch 'WindowsPowerShell' } +$manifests = [System.Collections.Generic.List[hashtable]]::new() + +$psPaths | ForEach-Object -Parallel { + $queue = $using:manifests + $files = Get-ChildItem -Path $_ -Recurse -File -Include '*.dsc.resource.json', '*.dsc.resource.yaml', '*.dsc.resource.yml' -ErrorAction Ignore + foreach ($file in $files) { + $m = @{ manifestPath = $file.FullName } + $queue.Add($m) + } +} -ThrottleLimit 10 + +$manifests | ConvertTo-Json -Compress \ No newline at end of file From 33b7ab2dfbb773c2d84aa92e14e2eb44b14ba12b Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Thu, 21 Aug 2025 14:40:57 +0200 Subject: [PATCH 06/31] Use concurrentbag --- extensions/powershell/powershell-discover.ps1 | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/extensions/powershell/powershell-discover.ps1 b/extensions/powershell/powershell-discover.ps1 index bfa56b08e..578cfd6b1 100644 --- a/extensions/powershell/powershell-discover.ps1 +++ b/extensions/powershell/powershell-discover.ps1 @@ -1,13 +1,15 @@ $psPaths = $env:PSModulePath -split [System.IO.Path]::PathSeparator | Where-Object { $_ -notmatch 'WindowsPowerShell' } -$manifests = [System.Collections.Generic.List[hashtable]]::new() + +$m = [System.Collections.Concurrent.ConcurrentBag[hashtable]]::new() $psPaths | ForEach-Object -Parallel { - $queue = $using:manifests - $files = Get-ChildItem -Path $_ -Recurse -File -Include '*.dsc.resource.json', '*.dsc.resource.yaml', '*.dsc.resource.yml' -ErrorAction Ignore + $queue = $using:m + $files = Get-ChildItem -Path $_ -Recurse -File -Filter '*.dsc.resource.*' -ErrorAction Ignore | + Where-Object -Property Extension -In @('.json', '.yaml', '.yml') + foreach ($file in $files) { - $m = @{ manifestPath = $file.FullName } - $queue.Add($m) + $queue.Add(@{ manifestPath = $file.FullName }) } } -ThrottleLimit 10 -$manifests | ConvertTo-Json -Compress \ No newline at end of file +@($m) | ConvertTo-Json -Compress \ No newline at end of file From f12038ed1e8fafff87b74bddd744dbb2d27634e6 Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Thu, 21 Aug 2025 14:49:30 +0200 Subject: [PATCH 07/31] Use .NET --- extensions/powershell/powershell-discover.ps1 | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/extensions/powershell/powershell-discover.ps1 b/extensions/powershell/powershell-discover.ps1 index 578cfd6b1..b44f3cb5d 100644 --- a/extensions/powershell/powershell-discover.ps1 +++ b/extensions/powershell/powershell-discover.ps1 @@ -4,12 +4,14 @@ $m = [System.Collections.Concurrent.ConcurrentBag[hashtable]]::new() $psPaths | ForEach-Object -Parallel { $queue = $using:m - $files = Get-ChildItem -Path $_ -Recurse -File -Filter '*.dsc.resource.*' -ErrorAction Ignore | - Where-Object -Property Extension -In @('.json', '.yaml', '.yml') - - foreach ($file in $files) { - $queue.Add(@{ manifestPath = $file.FullName }) + $searchPatterns = @('*.dsc.resource.json', '*.dsc.resource.yaml', '*.dsc.resource.yml') + foreach ($pattern in $searchPatterns) { + try { + [System.IO.Directory]::EnumerateFiles($_, $pattern, 'AllDirectories') | ForEach-Object { + $queue.Add(@{ manifestPath = $_ }) + } + } catch { } } -} -ThrottleLimit 10 +} -ThrottleLimit 30 -@($m) | ConvertTo-Json -Compress \ No newline at end of file +[array]$m | ConvertTo-Json -Compress \ No newline at end of file From 991f7ce027fd663bc71d9044c186198e4f11e69d Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Thu, 21 Aug 2025 15:26:38 +0200 Subject: [PATCH 08/31] Use .NET with enumeration option --- .vscode/launch.json | 78 +++++++++---------- build.ps1 | 12 ++- dsc/tests/dsc_extension_discover.tests.ps1 | 46 +++++------ extensions/powershell/copy_files.txt | 2 + extensions/powershell/powershell-discover.ps1 | 17 ---- extensions/powershell/powershell.discover.ps1 | 18 +++++ .../powershell/powershell.discover.tests.ps1 | 32 ++++++++ .../powershell/powershell.dsc.extension.json | 18 +++++ 8 files changed, 141 insertions(+), 82 deletions(-) create mode 100644 extensions/powershell/copy_files.txt delete mode 100644 extensions/powershell/powershell-discover.ps1 create mode 100644 extensions/powershell/powershell.discover.ps1 create mode 100644 extensions/powershell/powershell.discover.tests.ps1 create mode 100644 extensions/powershell/powershell.dsc.extension.json diff --git a/.vscode/launch.json b/.vscode/launch.json index 00879d03f..c0fe66904 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,39 +1,39 @@ -// { -// // Use IntelliSense to learn about possible attributes. -// // Hover to view descriptions of existing attributes. -// // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 -// "version": "0.2.0", -// "configurations": [ -// { -// "type": "lldb", -// "request": "launch", -// "name": "Debug config", -// "program": "${workspaceFolder}/config/target/debug/config", -// "args": [ -// "list", -// "r*" -// ], -// "cwd": "${workspaceFolder}" -// }, -// { -// "name": "(macOS) Attach", -// "type": "lldb", -// "request": "attach", -// "pid": "${command:pickMyProcess}", -// }, -// { -// "name": "(Windows) Attach", -// "type": "cppvsdbg", -// "request": "attach", -// "processId": "${command:pickProcess}", -// }, -// { -// "name": "Debug sshdconfig", -// "type": "cppvsdbg", -// "request": "launch", -// "program": "${workspaceFolder}/sshdconfig/target/debug/sshdconfig.exe", -// "args": ["get"], -// "cwd": "${workspaceFolder}" -// } -// ] -// } +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "lldb", + "request": "launch", + "name": "Debug config", + "program": "${workspaceFolder}/config/target/debug/config", + "args": [ + "list", + "r*" + ], + "cwd": "${workspaceFolder}" + }, + { + "name": "(macOS) Attach", + "type": "lldb", + "request": "attach", + "pid": "${command:pickMyProcess}", + }, + { + "name": "(Windows) Attach", + "type": "cppvsdbg", + "request": "attach", + "processId": "${command:pickProcess}", + }, + { + "name": "Debug sshdconfig", + "type": "cppvsdbg", + "request": "launch", + "program": "${workspaceFolder}/sshdconfig/target/debug/sshdconfig.exe", + "args": ["get"], + "cwd": "${workspaceFolder}" + } + ] +} diff --git a/build.ps1 b/build.ps1 index 78f71004a..375bbb78a 100755 --- a/build.ps1 +++ b/build.ps1 @@ -63,6 +63,8 @@ $filesForWindowsPackage = @( 'NOTICE.txt', 'osinfo.exe', 'osinfo.dsc.resource.json', + 'powershell.discover.ps1', + 'powershell.dsc.extension.json', 'powershell.dsc.resource.json', 'psDscAdapter/', 'psscript.ps1', @@ -101,6 +103,8 @@ $filesForLinuxPackage = @( 'NOTICE.txt', 'osinfo', 'osinfo.dsc.resource.json', + 'powershell.discover.ps1', + 'powershell.dsc.extension.json', 'powershell.dsc.resource.json', 'psDscAdapter/', 'psscript.ps1', @@ -126,6 +130,8 @@ $filesForMacPackage = @( 'NOTICE.txt', 'osinfo', 'osinfo.dsc.resource.json', + 'powershell.discover.ps1', + 'powershell.dsc.extension.json', 'powershell.dsc.resource.json', 'psDscAdapter/', 'psscript.ps1', @@ -331,9 +337,9 @@ if (!$SkipBuild) { New-Item -ItemType Directory $target -ErrorAction Ignore > $null # make sure dependencies are built first so clippy runs correctly - $windows_projects = @("pal", "registry_lib", "registry", "reboot_pending", "wmi-adapter", "configurations/windows", 'extensions/appx') - $macOS_projects = @("resources/brew") - $linux_projects = @("resources/apt") + $windows_projects = @("pal", "registry_lib", "registry", "reboot_pending", "wmi-adapter", "configurations/windows", "extensions/appx", "extensions/powershell") + $macOS_projects = @("resources/brew", "extensions/powershell") + $linux_projects = @("resources/apt", "extensions/powershell") # projects are in dependency order $projects = @( diff --git a/dsc/tests/dsc_extension_discover.tests.ps1 b/dsc/tests/dsc_extension_discover.tests.ps1 index d37455465..f09cca1ad 100644 --- a/dsc/tests/dsc_extension_discover.tests.ps1 +++ b/dsc/tests/dsc_extension_discover.tests.ps1 @@ -23,30 +23,30 @@ Describe 'Discover extension tests' { It 'Discover extensions' { $out = dsc extension list | ConvertFrom-Json $LASTEXITCODE | Should -Be 0 - if ($IsWindows) { - $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 -Be 'Microsoft.Windows.Appx/Discover' - $out[1].version | Should -Be '0.1.0' - $out[1].capabilities | Should -BeExactly @('discover') - $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 + $expectedExtensions = if ($IsWindows) { + @( + @{ type = 'Microsoft.DSC.Extension/Bicep'; version = '0.1.0'; capabilities = @('import') } + @{ type = 'Microsoft.DSC.Transitional/PSDesiredStateConfiguration'; version = '0.1.0'; capabilities = @('import') } + @{ type = 'Microsoft.Windows.Appx/Discover'; version = '0.1.0'; capabilities = @('discover') } + @{ type = 'Microsoft.PowerShell/Discover'; version = '0.1.0'; capabilities = @('discover') } + @{ type = 'Test/Discover'; version = '0.1.0'; capabilities = @('discover') } + ) } else { - $out.Count | Should -Be 2 -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].version | Should -BeExactly '0.1.0' - $out[1].capabilities | Should -BeExactly @('discover') - $out[1].manifest | Should -Not -BeNullOrEmpty + @( + @{ type = 'Microsoft.DSC.Extension/Bicep'; version = '0.1.0'; capabilities = @('import') } + @{ type = 'Microsoft.PowerShell/Discover'; version = '0.1.0'; capabilities = @('discover') } + @{ type = 'Test/Discover'; version = '0.1.0'; capabilities = @('discover') } + ) + } + + $out.Count | Should -Be $expectedExtensions.Count -Because ($out | Out-String) + + foreach ($expected in $expectedExtensions) { + $extension = $out | Where-Object { $_.type -eq $expected.type } + $extension | Should -Not -BeNullOrEmpty -Because "Extension $($expected.type) should exist" + $extension.version | Should -BeExactly $expected.version + $extension.capabilities | Should -BeExactly $expected.capabilities + $extension.manifest | Should -Not -BeNullOrEmpty } } diff --git a/extensions/powershell/copy_files.txt b/extensions/powershell/copy_files.txt new file mode 100644 index 000000000..e3b12dc13 --- /dev/null +++ b/extensions/powershell/copy_files.txt @@ -0,0 +1,2 @@ +powershell.discover.ps1 +powershell.dsc.extension.json diff --git a/extensions/powershell/powershell-discover.ps1 b/extensions/powershell/powershell-discover.ps1 deleted file mode 100644 index b44f3cb5d..000000000 --- a/extensions/powershell/powershell-discover.ps1 +++ /dev/null @@ -1,17 +0,0 @@ -$psPaths = $env:PSModulePath -split [System.IO.Path]::PathSeparator | Where-Object { $_ -notmatch 'WindowsPowerShell' } - -$m = [System.Collections.Concurrent.ConcurrentBag[hashtable]]::new() - -$psPaths | ForEach-Object -Parallel { - $queue = $using:m - $searchPatterns = @('*.dsc.resource.json', '*.dsc.resource.yaml', '*.dsc.resource.yml') - foreach ($pattern in $searchPatterns) { - try { - [System.IO.Directory]::EnumerateFiles($_, $pattern, 'AllDirectories') | ForEach-Object { - $queue.Add(@{ manifestPath = $_ }) - } - } catch { } - } -} -ThrottleLimit 30 - -[array]$m | ConvertTo-Json -Compress \ No newline at end of file diff --git a/extensions/powershell/powershell.discover.ps1 b/extensions/powershell/powershell.discover.ps1 new file mode 100644 index 000000000..43c4130d3 --- /dev/null +++ b/extensions/powershell/powershell.discover.ps1 @@ -0,0 +1,18 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +$psPaths = $env:PSModulePath -split [System.IO.Path]::PathSeparator | Where-Object { $_ -notmatch 'WindowsPowerShell' } + +$manifests = $psPaths | ForEach-Object -Parallel { + $searchPatterns = @('*.dsc.resource.json', '*.dsc.resource.yaml', '*.dsc.resource.yml') + $enumOptions = [System.IO.EnumerationOptions]@{ IgnoreInaccessible = $false; RecurseSubdirectories = $true } + foreach ($pattern in $searchPatterns) { + try { + [System.IO.Directory]::EnumerateFiles($_, $pattern, $enumOptions) | ForEach-Object { + @{ manifestPath = $_ } + } + } catch { } + } +} -ThrottleLimit 10 + +$manifests | ForEach-Object { $_ | ConvertTo-Json -Compress } \ No newline at end of file diff --git a/extensions/powershell/powershell.discover.tests.ps1 b/extensions/powershell/powershell.discover.tests.ps1 new file mode 100644 index 000000000..6b321d471 --- /dev/null +++ b/extensions/powershell/powershell.discover.tests.ps1 @@ -0,0 +1,32 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +BeforeAll { + $fakeManifest = @{ + '$schema' = "https://aka.ms/dsc/schemas/v3/bundled/resource/manifest.json" + type = "Test/FakeResource" + version = "0.1.0" + get = @{ + executable = "fakeResource" + args = @( + "get", + @{ + jsonInputArg = "--input" + mandatory = $true + } + ) + } + } + + $manifestPath = Join-Path $TestDrive "fake.dsc.resource.json" + $fakeManifest | ConvertTo-Json -Depth 10 | Set-Content -Path $manifestPath + $env:PSModulePath += [System.IO.Path]::PathSeparator + $TestDrive +} + +Describe 'Tests for PowerShell resource discovery' { + It 'Should find DSC PowerShell resources' { + $out = dsc resource list | ConvertFrom-Json + $LASTEXITCODE | Should -Be 0 + $out.directory | Should -Contain $TestDrive + } +} diff --git a/extensions/powershell/powershell.dsc.extension.json b/extensions/powershell/powershell.dsc.extension.json new file mode 100644 index 000000000..aa10cffa3 --- /dev/null +++ b/extensions/powershell/powershell.dsc.extension.json @@ -0,0 +1,18 @@ +{ + "$schema": "https://aka.ms/dsc/schemas/v3/bundled/resource/manifest.json", + "type": "Microsoft.PowerShell/Discover", + "version": "0.1.0", + "description": "Discovers DSC resources packaged in PowerShell 7 modules.", + "discover": { + "executable": "pwsh", + "args": [ + "-NoLogo", + "-NonInteractive", + "-ExecutionPolicy", + "Bypass", + "-NoProfile", + "-Command", + "./powershell.discover.ps1" + ] + } +} From 1e80819244d8af75e931aea586022fc9ce9b2540 Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Wed, 27 Aug 2025 03:10:22 +0200 Subject: [PATCH 09/31] Remove undiscovered resource --- 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 f09cca1ad..3c7c44a54 100644 --- a/dsc/tests/dsc_extension_discover.tests.ps1 +++ b/dsc/tests/dsc_extension_discover.tests.ps1 @@ -26,7 +26,6 @@ Describe 'Discover extension tests' { $expectedExtensions = if ($IsWindows) { @( @{ type = 'Microsoft.DSC.Extension/Bicep'; version = '0.1.0'; capabilities = @('import') } - @{ type = 'Microsoft.DSC.Transitional/PSDesiredStateConfiguration'; version = '0.1.0'; capabilities = @('import') } @{ type = 'Microsoft.Windows.Appx/Discover'; version = '0.1.0'; capabilities = @('discover') } @{ type = 'Microsoft.PowerShell/Discover'; version = '0.1.0'; capabilities = @('discover') } @{ type = 'Test/Discover'; version = '0.1.0'; capabilities = @('discover') } From 69ef04258f058c05dfb64c8200e9cd6a9d461805 Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Thu, 21 Aug 2025 14:38:26 +0200 Subject: [PATCH 10/31] Init --- .vscode/launch.json | 78 +++++++++---------- extensions/powershell/powershell-discover.ps1 | 13 ++++ 2 files changed, 52 insertions(+), 39 deletions(-) create mode 100644 extensions/powershell/powershell-discover.ps1 diff --git a/.vscode/launch.json b/.vscode/launch.json index c0fe66904..00879d03f 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,39 +1,39 @@ -{ - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - { - "type": "lldb", - "request": "launch", - "name": "Debug config", - "program": "${workspaceFolder}/config/target/debug/config", - "args": [ - "list", - "r*" - ], - "cwd": "${workspaceFolder}" - }, - { - "name": "(macOS) Attach", - "type": "lldb", - "request": "attach", - "pid": "${command:pickMyProcess}", - }, - { - "name": "(Windows) Attach", - "type": "cppvsdbg", - "request": "attach", - "processId": "${command:pickProcess}", - }, - { - "name": "Debug sshdconfig", - "type": "cppvsdbg", - "request": "launch", - "program": "${workspaceFolder}/sshdconfig/target/debug/sshdconfig.exe", - "args": ["get"], - "cwd": "${workspaceFolder}" - } - ] -} +// { +// // Use IntelliSense to learn about possible attributes. +// // Hover to view descriptions of existing attributes. +// // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 +// "version": "0.2.0", +// "configurations": [ +// { +// "type": "lldb", +// "request": "launch", +// "name": "Debug config", +// "program": "${workspaceFolder}/config/target/debug/config", +// "args": [ +// "list", +// "r*" +// ], +// "cwd": "${workspaceFolder}" +// }, +// { +// "name": "(macOS) Attach", +// "type": "lldb", +// "request": "attach", +// "pid": "${command:pickMyProcess}", +// }, +// { +// "name": "(Windows) Attach", +// "type": "cppvsdbg", +// "request": "attach", +// "processId": "${command:pickProcess}", +// }, +// { +// "name": "Debug sshdconfig", +// "type": "cppvsdbg", +// "request": "launch", +// "program": "${workspaceFolder}/sshdconfig/target/debug/sshdconfig.exe", +// "args": ["get"], +// "cwd": "${workspaceFolder}" +// } +// ] +// } diff --git a/extensions/powershell/powershell-discover.ps1 b/extensions/powershell/powershell-discover.ps1 new file mode 100644 index 000000000..bfa56b08e --- /dev/null +++ b/extensions/powershell/powershell-discover.ps1 @@ -0,0 +1,13 @@ +$psPaths = $env:PSModulePath -split [System.IO.Path]::PathSeparator | Where-Object { $_ -notmatch 'WindowsPowerShell' } +$manifests = [System.Collections.Generic.List[hashtable]]::new() + +$psPaths | ForEach-Object -Parallel { + $queue = $using:manifests + $files = Get-ChildItem -Path $_ -Recurse -File -Include '*.dsc.resource.json', '*.dsc.resource.yaml', '*.dsc.resource.yml' -ErrorAction Ignore + foreach ($file in $files) { + $m = @{ manifestPath = $file.FullName } + $queue.Add($m) + } +} -ThrottleLimit 10 + +$manifests | ConvertTo-Json -Compress \ No newline at end of file From f7c606afba2331af20c47b66ebad37e681c92d0c Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Thu, 21 Aug 2025 14:40:57 +0200 Subject: [PATCH 11/31] Use concurrentbag --- extensions/powershell/powershell-discover.ps1 | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/extensions/powershell/powershell-discover.ps1 b/extensions/powershell/powershell-discover.ps1 index bfa56b08e..578cfd6b1 100644 --- a/extensions/powershell/powershell-discover.ps1 +++ b/extensions/powershell/powershell-discover.ps1 @@ -1,13 +1,15 @@ $psPaths = $env:PSModulePath -split [System.IO.Path]::PathSeparator | Where-Object { $_ -notmatch 'WindowsPowerShell' } -$manifests = [System.Collections.Generic.List[hashtable]]::new() + +$m = [System.Collections.Concurrent.ConcurrentBag[hashtable]]::new() $psPaths | ForEach-Object -Parallel { - $queue = $using:manifests - $files = Get-ChildItem -Path $_ -Recurse -File -Include '*.dsc.resource.json', '*.dsc.resource.yaml', '*.dsc.resource.yml' -ErrorAction Ignore + $queue = $using:m + $files = Get-ChildItem -Path $_ -Recurse -File -Filter '*.dsc.resource.*' -ErrorAction Ignore | + Where-Object -Property Extension -In @('.json', '.yaml', '.yml') + foreach ($file in $files) { - $m = @{ manifestPath = $file.FullName } - $queue.Add($m) + $queue.Add(@{ manifestPath = $file.FullName }) } } -ThrottleLimit 10 -$manifests | ConvertTo-Json -Compress \ No newline at end of file +@($m) | ConvertTo-Json -Compress \ No newline at end of file From f93e69120e5f3647eaae0b30baf94b1c1a32e8d6 Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Thu, 21 Aug 2025 14:49:30 +0200 Subject: [PATCH 12/31] Use .NET --- extensions/powershell/powershell-discover.ps1 | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/extensions/powershell/powershell-discover.ps1 b/extensions/powershell/powershell-discover.ps1 index 578cfd6b1..b44f3cb5d 100644 --- a/extensions/powershell/powershell-discover.ps1 +++ b/extensions/powershell/powershell-discover.ps1 @@ -4,12 +4,14 @@ $m = [System.Collections.Concurrent.ConcurrentBag[hashtable]]::new() $psPaths | ForEach-Object -Parallel { $queue = $using:m - $files = Get-ChildItem -Path $_ -Recurse -File -Filter '*.dsc.resource.*' -ErrorAction Ignore | - Where-Object -Property Extension -In @('.json', '.yaml', '.yml') - - foreach ($file in $files) { - $queue.Add(@{ manifestPath = $file.FullName }) + $searchPatterns = @('*.dsc.resource.json', '*.dsc.resource.yaml', '*.dsc.resource.yml') + foreach ($pattern in $searchPatterns) { + try { + [System.IO.Directory]::EnumerateFiles($_, $pattern, 'AllDirectories') | ForEach-Object { + $queue.Add(@{ manifestPath = $_ }) + } + } catch { } } -} -ThrottleLimit 10 +} -ThrottleLimit 30 -@($m) | ConvertTo-Json -Compress \ No newline at end of file +[array]$m | ConvertTo-Json -Compress \ No newline at end of file From 16bf2f55f2ca750eb27c4dd078e783add69f19a7 Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Thu, 21 Aug 2025 15:26:38 +0200 Subject: [PATCH 13/31] Use .NET with enumeration option --- .vscode/launch.json | 78 +++++++++---------- build.ps1 | 12 ++- dsc/tests/dsc_extension_discover.tests.ps1 | 46 +++++------ extensions/powershell/copy_files.txt | 2 + extensions/powershell/powershell-discover.ps1 | 17 ---- extensions/powershell/powershell.discover.ps1 | 18 +++++ .../powershell/powershell.discover.tests.ps1 | 32 ++++++++ .../powershell/powershell.dsc.extension.json | 18 +++++ 8 files changed, 141 insertions(+), 82 deletions(-) create mode 100644 extensions/powershell/copy_files.txt delete mode 100644 extensions/powershell/powershell-discover.ps1 create mode 100644 extensions/powershell/powershell.discover.ps1 create mode 100644 extensions/powershell/powershell.discover.tests.ps1 create mode 100644 extensions/powershell/powershell.dsc.extension.json diff --git a/.vscode/launch.json b/.vscode/launch.json index 00879d03f..c0fe66904 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,39 +1,39 @@ -// { -// // Use IntelliSense to learn about possible attributes. -// // Hover to view descriptions of existing attributes. -// // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 -// "version": "0.2.0", -// "configurations": [ -// { -// "type": "lldb", -// "request": "launch", -// "name": "Debug config", -// "program": "${workspaceFolder}/config/target/debug/config", -// "args": [ -// "list", -// "r*" -// ], -// "cwd": "${workspaceFolder}" -// }, -// { -// "name": "(macOS) Attach", -// "type": "lldb", -// "request": "attach", -// "pid": "${command:pickMyProcess}", -// }, -// { -// "name": "(Windows) Attach", -// "type": "cppvsdbg", -// "request": "attach", -// "processId": "${command:pickProcess}", -// }, -// { -// "name": "Debug sshdconfig", -// "type": "cppvsdbg", -// "request": "launch", -// "program": "${workspaceFolder}/sshdconfig/target/debug/sshdconfig.exe", -// "args": ["get"], -// "cwd": "${workspaceFolder}" -// } -// ] -// } +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "lldb", + "request": "launch", + "name": "Debug config", + "program": "${workspaceFolder}/config/target/debug/config", + "args": [ + "list", + "r*" + ], + "cwd": "${workspaceFolder}" + }, + { + "name": "(macOS) Attach", + "type": "lldb", + "request": "attach", + "pid": "${command:pickMyProcess}", + }, + { + "name": "(Windows) Attach", + "type": "cppvsdbg", + "request": "attach", + "processId": "${command:pickProcess}", + }, + { + "name": "Debug sshdconfig", + "type": "cppvsdbg", + "request": "launch", + "program": "${workspaceFolder}/sshdconfig/target/debug/sshdconfig.exe", + "args": ["get"], + "cwd": "${workspaceFolder}" + } + ] +} diff --git a/build.ps1 b/build.ps1 index 78f71004a..375bbb78a 100755 --- a/build.ps1 +++ b/build.ps1 @@ -63,6 +63,8 @@ $filesForWindowsPackage = @( 'NOTICE.txt', 'osinfo.exe', 'osinfo.dsc.resource.json', + 'powershell.discover.ps1', + 'powershell.dsc.extension.json', 'powershell.dsc.resource.json', 'psDscAdapter/', 'psscript.ps1', @@ -101,6 +103,8 @@ $filesForLinuxPackage = @( 'NOTICE.txt', 'osinfo', 'osinfo.dsc.resource.json', + 'powershell.discover.ps1', + 'powershell.dsc.extension.json', 'powershell.dsc.resource.json', 'psDscAdapter/', 'psscript.ps1', @@ -126,6 +130,8 @@ $filesForMacPackage = @( 'NOTICE.txt', 'osinfo', 'osinfo.dsc.resource.json', + 'powershell.discover.ps1', + 'powershell.dsc.extension.json', 'powershell.dsc.resource.json', 'psDscAdapter/', 'psscript.ps1', @@ -331,9 +337,9 @@ if (!$SkipBuild) { New-Item -ItemType Directory $target -ErrorAction Ignore > $null # make sure dependencies are built first so clippy runs correctly - $windows_projects = @("pal", "registry_lib", "registry", "reboot_pending", "wmi-adapter", "configurations/windows", 'extensions/appx') - $macOS_projects = @("resources/brew") - $linux_projects = @("resources/apt") + $windows_projects = @("pal", "registry_lib", "registry", "reboot_pending", "wmi-adapter", "configurations/windows", "extensions/appx", "extensions/powershell") + $macOS_projects = @("resources/brew", "extensions/powershell") + $linux_projects = @("resources/apt", "extensions/powershell") # projects are in dependency order $projects = @( diff --git a/dsc/tests/dsc_extension_discover.tests.ps1 b/dsc/tests/dsc_extension_discover.tests.ps1 index 03da8c429..1ad519047 100644 --- a/dsc/tests/dsc_extension_discover.tests.ps1 +++ b/dsc/tests/dsc_extension_discover.tests.ps1 @@ -23,30 +23,30 @@ Describe 'Discover extension tests' { It 'Discover extensions' { $out = dsc extension list | ConvertFrom-Json $LASTEXITCODE | Should -Be 0 - if ($IsWindows) { - $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 -Be 'Microsoft.Windows.Appx/Discover' - $out[1].version | Should -Be '0.1.0' - $out[1].capabilities | Should -BeExactly @('discover') - $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 + $expectedExtensions = if ($IsWindows) { + @( + @{ type = 'Microsoft.DSC.Extension/Bicep'; version = '0.1.0'; capabilities = @('import') } + @{ type = 'Microsoft.DSC.Transitional/PSDesiredStateConfiguration'; version = '0.1.0'; capabilities = @('import') } + @{ type = 'Microsoft.Windows.Appx/Discover'; version = '0.1.0'; capabilities = @('discover') } + @{ type = 'Microsoft.PowerShell/Discover'; version = '0.1.0'; capabilities = @('discover') } + @{ type = 'Test/Discover'; version = '0.1.0'; capabilities = @('discover') } + ) } else { - $out.Count | Should -Be 2 -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].version | Should -BeExactly '0.1.0' - $out[1].capabilities | Should -BeExactly @('discover') - $out[1].manifest | Should -Not -BeNullOrEmpty + @( + @{ type = 'Microsoft.DSC.Extension/Bicep'; version = '0.1.0'; capabilities = @('import') } + @{ type = 'Microsoft.PowerShell/Discover'; version = '0.1.0'; capabilities = @('discover') } + @{ type = 'Test/Discover'; version = '0.1.0'; capabilities = @('discover') } + ) + } + + $out.Count | Should -Be $expectedExtensions.Count -Because ($out | Out-String) + + foreach ($expected in $expectedExtensions) { + $extension = $out | Where-Object { $_.type -eq $expected.type } + $extension | Should -Not -BeNullOrEmpty -Because "Extension $($expected.type) should exist" + $extension.version | Should -BeExactly $expected.version + $extension.capabilities | Should -BeExactly $expected.capabilities + $extension.manifest | Should -Not -BeNullOrEmpty } } diff --git a/extensions/powershell/copy_files.txt b/extensions/powershell/copy_files.txt new file mode 100644 index 000000000..e3b12dc13 --- /dev/null +++ b/extensions/powershell/copy_files.txt @@ -0,0 +1,2 @@ +powershell.discover.ps1 +powershell.dsc.extension.json diff --git a/extensions/powershell/powershell-discover.ps1 b/extensions/powershell/powershell-discover.ps1 deleted file mode 100644 index b44f3cb5d..000000000 --- a/extensions/powershell/powershell-discover.ps1 +++ /dev/null @@ -1,17 +0,0 @@ -$psPaths = $env:PSModulePath -split [System.IO.Path]::PathSeparator | Where-Object { $_ -notmatch 'WindowsPowerShell' } - -$m = [System.Collections.Concurrent.ConcurrentBag[hashtable]]::new() - -$psPaths | ForEach-Object -Parallel { - $queue = $using:m - $searchPatterns = @('*.dsc.resource.json', '*.dsc.resource.yaml', '*.dsc.resource.yml') - foreach ($pattern in $searchPatterns) { - try { - [System.IO.Directory]::EnumerateFiles($_, $pattern, 'AllDirectories') | ForEach-Object { - $queue.Add(@{ manifestPath = $_ }) - } - } catch { } - } -} -ThrottleLimit 30 - -[array]$m | ConvertTo-Json -Compress \ No newline at end of file diff --git a/extensions/powershell/powershell.discover.ps1 b/extensions/powershell/powershell.discover.ps1 new file mode 100644 index 000000000..43c4130d3 --- /dev/null +++ b/extensions/powershell/powershell.discover.ps1 @@ -0,0 +1,18 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +$psPaths = $env:PSModulePath -split [System.IO.Path]::PathSeparator | Where-Object { $_ -notmatch 'WindowsPowerShell' } + +$manifests = $psPaths | ForEach-Object -Parallel { + $searchPatterns = @('*.dsc.resource.json', '*.dsc.resource.yaml', '*.dsc.resource.yml') + $enumOptions = [System.IO.EnumerationOptions]@{ IgnoreInaccessible = $false; RecurseSubdirectories = $true } + foreach ($pattern in $searchPatterns) { + try { + [System.IO.Directory]::EnumerateFiles($_, $pattern, $enumOptions) | ForEach-Object { + @{ manifestPath = $_ } + } + } catch { } + } +} -ThrottleLimit 10 + +$manifests | ForEach-Object { $_ | ConvertTo-Json -Compress } \ No newline at end of file diff --git a/extensions/powershell/powershell.discover.tests.ps1 b/extensions/powershell/powershell.discover.tests.ps1 new file mode 100644 index 000000000..6b321d471 --- /dev/null +++ b/extensions/powershell/powershell.discover.tests.ps1 @@ -0,0 +1,32 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +BeforeAll { + $fakeManifest = @{ + '$schema' = "https://aka.ms/dsc/schemas/v3/bundled/resource/manifest.json" + type = "Test/FakeResource" + version = "0.1.0" + get = @{ + executable = "fakeResource" + args = @( + "get", + @{ + jsonInputArg = "--input" + mandatory = $true + } + ) + } + } + + $manifestPath = Join-Path $TestDrive "fake.dsc.resource.json" + $fakeManifest | ConvertTo-Json -Depth 10 | Set-Content -Path $manifestPath + $env:PSModulePath += [System.IO.Path]::PathSeparator + $TestDrive +} + +Describe 'Tests for PowerShell resource discovery' { + It 'Should find DSC PowerShell resources' { + $out = dsc resource list | ConvertFrom-Json + $LASTEXITCODE | Should -Be 0 + $out.directory | Should -Contain $TestDrive + } +} diff --git a/extensions/powershell/powershell.dsc.extension.json b/extensions/powershell/powershell.dsc.extension.json new file mode 100644 index 000000000..aa10cffa3 --- /dev/null +++ b/extensions/powershell/powershell.dsc.extension.json @@ -0,0 +1,18 @@ +{ + "$schema": "https://aka.ms/dsc/schemas/v3/bundled/resource/manifest.json", + "type": "Microsoft.PowerShell/Discover", + "version": "0.1.0", + "description": "Discovers DSC resources packaged in PowerShell 7 modules.", + "discover": { + "executable": "pwsh", + "args": [ + "-NoLogo", + "-NonInteractive", + "-ExecutionPolicy", + "Bypass", + "-NoProfile", + "-Command", + "./powershell.discover.ps1" + ] + } +} From 34375b6c7bc41d7398782bf029929836969c5b8e Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Thu, 21 Aug 2025 14:38:26 +0200 Subject: [PATCH 14/31] Init --- .vscode/launch.json | 78 +++++++++---------- extensions/powershell/powershell-discover.ps1 | 13 ++++ 2 files changed, 52 insertions(+), 39 deletions(-) create mode 100644 extensions/powershell/powershell-discover.ps1 diff --git a/.vscode/launch.json b/.vscode/launch.json index c0fe66904..00879d03f 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,39 +1,39 @@ -{ - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - { - "type": "lldb", - "request": "launch", - "name": "Debug config", - "program": "${workspaceFolder}/config/target/debug/config", - "args": [ - "list", - "r*" - ], - "cwd": "${workspaceFolder}" - }, - { - "name": "(macOS) Attach", - "type": "lldb", - "request": "attach", - "pid": "${command:pickMyProcess}", - }, - { - "name": "(Windows) Attach", - "type": "cppvsdbg", - "request": "attach", - "processId": "${command:pickProcess}", - }, - { - "name": "Debug sshdconfig", - "type": "cppvsdbg", - "request": "launch", - "program": "${workspaceFolder}/sshdconfig/target/debug/sshdconfig.exe", - "args": ["get"], - "cwd": "${workspaceFolder}" - } - ] -} +// { +// // Use IntelliSense to learn about possible attributes. +// // Hover to view descriptions of existing attributes. +// // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 +// "version": "0.2.0", +// "configurations": [ +// { +// "type": "lldb", +// "request": "launch", +// "name": "Debug config", +// "program": "${workspaceFolder}/config/target/debug/config", +// "args": [ +// "list", +// "r*" +// ], +// "cwd": "${workspaceFolder}" +// }, +// { +// "name": "(macOS) Attach", +// "type": "lldb", +// "request": "attach", +// "pid": "${command:pickMyProcess}", +// }, +// { +// "name": "(Windows) Attach", +// "type": "cppvsdbg", +// "request": "attach", +// "processId": "${command:pickProcess}", +// }, +// { +// "name": "Debug sshdconfig", +// "type": "cppvsdbg", +// "request": "launch", +// "program": "${workspaceFolder}/sshdconfig/target/debug/sshdconfig.exe", +// "args": ["get"], +// "cwd": "${workspaceFolder}" +// } +// ] +// } diff --git a/extensions/powershell/powershell-discover.ps1 b/extensions/powershell/powershell-discover.ps1 new file mode 100644 index 000000000..bfa56b08e --- /dev/null +++ b/extensions/powershell/powershell-discover.ps1 @@ -0,0 +1,13 @@ +$psPaths = $env:PSModulePath -split [System.IO.Path]::PathSeparator | Where-Object { $_ -notmatch 'WindowsPowerShell' } +$manifests = [System.Collections.Generic.List[hashtable]]::new() + +$psPaths | ForEach-Object -Parallel { + $queue = $using:manifests + $files = Get-ChildItem -Path $_ -Recurse -File -Include '*.dsc.resource.json', '*.dsc.resource.yaml', '*.dsc.resource.yml' -ErrorAction Ignore + foreach ($file in $files) { + $m = @{ manifestPath = $file.FullName } + $queue.Add($m) + } +} -ThrottleLimit 10 + +$manifests | ConvertTo-Json -Compress \ No newline at end of file From 16b41c4a6d8ac949692b36ef52e4f7c74746c1c5 Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Thu, 21 Aug 2025 14:40:57 +0200 Subject: [PATCH 15/31] Use concurrentbag --- extensions/powershell/powershell-discover.ps1 | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/extensions/powershell/powershell-discover.ps1 b/extensions/powershell/powershell-discover.ps1 index bfa56b08e..578cfd6b1 100644 --- a/extensions/powershell/powershell-discover.ps1 +++ b/extensions/powershell/powershell-discover.ps1 @@ -1,13 +1,15 @@ $psPaths = $env:PSModulePath -split [System.IO.Path]::PathSeparator | Where-Object { $_ -notmatch 'WindowsPowerShell' } -$manifests = [System.Collections.Generic.List[hashtable]]::new() + +$m = [System.Collections.Concurrent.ConcurrentBag[hashtable]]::new() $psPaths | ForEach-Object -Parallel { - $queue = $using:manifests - $files = Get-ChildItem -Path $_ -Recurse -File -Include '*.dsc.resource.json', '*.dsc.resource.yaml', '*.dsc.resource.yml' -ErrorAction Ignore + $queue = $using:m + $files = Get-ChildItem -Path $_ -Recurse -File -Filter '*.dsc.resource.*' -ErrorAction Ignore | + Where-Object -Property Extension -In @('.json', '.yaml', '.yml') + foreach ($file in $files) { - $m = @{ manifestPath = $file.FullName } - $queue.Add($m) + $queue.Add(@{ manifestPath = $file.FullName }) } } -ThrottleLimit 10 -$manifests | ConvertTo-Json -Compress \ No newline at end of file +@($m) | ConvertTo-Json -Compress \ No newline at end of file From 3037ffe9a54689e2139dfe32a186733570aa73c8 Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Thu, 21 Aug 2025 14:49:30 +0200 Subject: [PATCH 16/31] Use .NET --- extensions/powershell/powershell-discover.ps1 | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/extensions/powershell/powershell-discover.ps1 b/extensions/powershell/powershell-discover.ps1 index 578cfd6b1..b44f3cb5d 100644 --- a/extensions/powershell/powershell-discover.ps1 +++ b/extensions/powershell/powershell-discover.ps1 @@ -4,12 +4,14 @@ $m = [System.Collections.Concurrent.ConcurrentBag[hashtable]]::new() $psPaths | ForEach-Object -Parallel { $queue = $using:m - $files = Get-ChildItem -Path $_ -Recurse -File -Filter '*.dsc.resource.*' -ErrorAction Ignore | - Where-Object -Property Extension -In @('.json', '.yaml', '.yml') - - foreach ($file in $files) { - $queue.Add(@{ manifestPath = $file.FullName }) + $searchPatterns = @('*.dsc.resource.json', '*.dsc.resource.yaml', '*.dsc.resource.yml') + foreach ($pattern in $searchPatterns) { + try { + [System.IO.Directory]::EnumerateFiles($_, $pattern, 'AllDirectories') | ForEach-Object { + $queue.Add(@{ manifestPath = $_ }) + } + } catch { } } -} -ThrottleLimit 10 +} -ThrottleLimit 30 -@($m) | ConvertTo-Json -Compress \ No newline at end of file +[array]$m | ConvertTo-Json -Compress \ No newline at end of file From 1ef648234ca99d60d3b14a5a66d736ccb8ede617 Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Thu, 21 Aug 2025 15:26:38 +0200 Subject: [PATCH 17/31] Use .NET with enumeration option --- .vscode/launch.json | 78 +++++++++---------- extensions/powershell/powershell-discover.ps1 | 17 ---- 2 files changed, 39 insertions(+), 56 deletions(-) delete mode 100644 extensions/powershell/powershell-discover.ps1 diff --git a/.vscode/launch.json b/.vscode/launch.json index 00879d03f..c0fe66904 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,39 +1,39 @@ -// { -// // Use IntelliSense to learn about possible attributes. -// // Hover to view descriptions of existing attributes. -// // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 -// "version": "0.2.0", -// "configurations": [ -// { -// "type": "lldb", -// "request": "launch", -// "name": "Debug config", -// "program": "${workspaceFolder}/config/target/debug/config", -// "args": [ -// "list", -// "r*" -// ], -// "cwd": "${workspaceFolder}" -// }, -// { -// "name": "(macOS) Attach", -// "type": "lldb", -// "request": "attach", -// "pid": "${command:pickMyProcess}", -// }, -// { -// "name": "(Windows) Attach", -// "type": "cppvsdbg", -// "request": "attach", -// "processId": "${command:pickProcess}", -// }, -// { -// "name": "Debug sshdconfig", -// "type": "cppvsdbg", -// "request": "launch", -// "program": "${workspaceFolder}/sshdconfig/target/debug/sshdconfig.exe", -// "args": ["get"], -// "cwd": "${workspaceFolder}" -// } -// ] -// } +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "lldb", + "request": "launch", + "name": "Debug config", + "program": "${workspaceFolder}/config/target/debug/config", + "args": [ + "list", + "r*" + ], + "cwd": "${workspaceFolder}" + }, + { + "name": "(macOS) Attach", + "type": "lldb", + "request": "attach", + "pid": "${command:pickMyProcess}", + }, + { + "name": "(Windows) Attach", + "type": "cppvsdbg", + "request": "attach", + "processId": "${command:pickProcess}", + }, + { + "name": "Debug sshdconfig", + "type": "cppvsdbg", + "request": "launch", + "program": "${workspaceFolder}/sshdconfig/target/debug/sshdconfig.exe", + "args": ["get"], + "cwd": "${workspaceFolder}" + } + ] +} diff --git a/extensions/powershell/powershell-discover.ps1 b/extensions/powershell/powershell-discover.ps1 deleted file mode 100644 index b44f3cb5d..000000000 --- a/extensions/powershell/powershell-discover.ps1 +++ /dev/null @@ -1,17 +0,0 @@ -$psPaths = $env:PSModulePath -split [System.IO.Path]::PathSeparator | Where-Object { $_ -notmatch 'WindowsPowerShell' } - -$m = [System.Collections.Concurrent.ConcurrentBag[hashtable]]::new() - -$psPaths | ForEach-Object -Parallel { - $queue = $using:m - $searchPatterns = @('*.dsc.resource.json', '*.dsc.resource.yaml', '*.dsc.resource.yml') - foreach ($pattern in $searchPatterns) { - try { - [System.IO.Directory]::EnumerateFiles($_, $pattern, 'AllDirectories') | ForEach-Object { - $queue.Add(@{ manifestPath = $_ }) - } - } catch { } - } -} -ThrottleLimit 30 - -[array]$m | ConvertTo-Json -Compress \ No newline at end of file From f15195a8f75b771d6f9522b832b296c086a5d8eb Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Wed, 27 Aug 2025 03:10:22 +0200 Subject: [PATCH 18/31] Remove undiscovered resource --- 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 1ad519047..b854fc3a4 100644 --- a/dsc/tests/dsc_extension_discover.tests.ps1 +++ b/dsc/tests/dsc_extension_discover.tests.ps1 @@ -26,7 +26,6 @@ Describe 'Discover extension tests' { $expectedExtensions = if ($IsWindows) { @( @{ type = 'Microsoft.DSC.Extension/Bicep'; version = '0.1.0'; capabilities = @('import') } - @{ type = 'Microsoft.DSC.Transitional/PSDesiredStateConfiguration'; version = '0.1.0'; capabilities = @('import') } @{ type = 'Microsoft.Windows.Appx/Discover'; version = '0.1.0'; capabilities = @('discover') } @{ type = 'Microsoft.PowerShell/Discover'; version = '0.1.0'; capabilities = @('discover') } @{ type = 'Test/Discover'; version = '0.1.0'; capabilities = @('discover') } From a7263ba067eaf319ffc5533d3808cade4b2e8287 Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Wed, 3 Sep 2025 19:20:02 +0200 Subject: [PATCH 19/31] Fix point 2 --- extensions/powershell/powershell.discover.ps1 | 35 +++++++++++-------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/extensions/powershell/powershell.discover.ps1 b/extensions/powershell/powershell.discover.ps1 index 43c4130d3..6f05b0b2f 100644 --- a/extensions/powershell/powershell.discover.ps1 +++ b/extensions/powershell/powershell.discover.ps1 @@ -1,18 +1,25 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. -$psPaths = $env:PSModulePath -split [System.IO.Path]::PathSeparator | Where-Object { $_ -notmatch 'WindowsPowerShell' } +[CmdletBinding()] +param () -$manifests = $psPaths | ForEach-Object -Parallel { - $searchPatterns = @('*.dsc.resource.json', '*.dsc.resource.yaml', '*.dsc.resource.yml') - $enumOptions = [System.IO.EnumerationOptions]@{ IgnoreInaccessible = $false; RecurseSubdirectories = $true } - foreach ($pattern in $searchPatterns) { - try { - [System.IO.Directory]::EnumerateFiles($_, $pattern, $enumOptions) | ForEach-Object { - @{ manifestPath = $_ } - } - } catch { } - } -} -ThrottleLimit 10 - -$manifests | ForEach-Object { $_ | ConvertTo-Json -Compress } \ No newline at end of file +begin { + $psPaths = $env:PSModulePath -split [System.IO.Path]::PathSeparator | Where-Object { $_ -notmatch 'WindowsPowerShell' } +} +process { + $manifests = $psPaths | ForEach-Object -Parallel { + $searchPatterns = @('*.dsc.resource.json', '*.dsc.resource.yaml', '*.dsc.resource.yml') + $enumOptions = [System.IO.EnumerationOptions]@{ IgnoreInaccessible = $false; RecurseSubdirectories = $true } + foreach ($pattern in $searchPatterns) { + try { + [System.IO.Directory]::EnumerateFiles($_, $pattern, $enumOptions) | ForEach-Object { + @{ manifestPath = $_ } + } + } catch { } + } + } -ThrottleLimit 10 +} +end { + $manifests | ForEach-Object { $_ | ConvertTo-Json -Compress } +} \ No newline at end of file From 640c664af1947ae25a50de1e5dd4dc3ae04efb2b Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Thu, 4 Sep 2025 04:17:49 +0200 Subject: [PATCH 20/31] Add newline --- extensions/powershell/powershell.discover.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/powershell/powershell.discover.ps1 b/extensions/powershell/powershell.discover.ps1 index 6f05b0b2f..5ae2e32c5 100644 --- a/extensions/powershell/powershell.discover.ps1 +++ b/extensions/powershell/powershell.discover.ps1 @@ -22,4 +22,4 @@ process { } end { $manifests | ForEach-Object { $_ | ConvertTo-Json -Compress } -} \ No newline at end of file +} From 861a017cd1930d08729279fe4a3747cb1c9d4be6 Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Wed, 8 Oct 2025 02:27:13 +0200 Subject: [PATCH 21/31] Add caching --- extensions/powershell/powershell.discover.ps1 | 146 ++++++++++++++++-- .../powershell/powershell.discover.tests.ps1 | 98 ++++++++++++ 2 files changed, 229 insertions(+), 15 deletions(-) diff --git a/extensions/powershell/powershell.discover.ps1 b/extensions/powershell/powershell.discover.ps1 index 5ae2e32c5..e1f163ff0 100644 --- a/extensions/powershell/powershell.discover.ps1 +++ b/extensions/powershell/powershell.discover.ps1 @@ -4,22 +4,138 @@ [CmdletBinding()] param () -begin { - $psPaths = $env:PSModulePath -split [System.IO.Path]::PathSeparator | Where-Object { $_ -notmatch 'WindowsPowerShell' } +function Write-DscTrace { + param( + [Parameter(Mandatory = $false)] + [ValidateSet('Error', 'Warn', 'Info', 'Debug', 'Trace')] + [string]$Operation = 'Debug', + + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] + [string]$Message + ) + + $trace = @{$Operation.ToLower() = $Message } | ConvertTo-Json -Compress + $host.ui.WriteErrorLine($trace) } -process { - $manifests = $psPaths | ForEach-Object -Parallel { - $searchPatterns = @('*.dsc.resource.json', '*.dsc.resource.yaml', '*.dsc.resource.yml') - $enumOptions = [System.IO.EnumerationOptions]@{ IgnoreInaccessible = $false; RecurseSubdirectories = $true } - foreach ($pattern in $searchPatterns) { - try { - [System.IO.Directory]::EnumerateFiles($_, $pattern, $enumOptions) | ForEach-Object { - @{ manifestPath = $_ } - } - } catch { } + +function Get-CacheFilePath { + if ($IsWindows) { + Join-Path $env:LocalAppData "dsc\PowerShellDiscoverCache.json" + } else { + Join-Path $env:HOME ".dsc" "PowerShellDiscoverCache.json" + } +} + +function Test-CacheValid { + param([string]$CacheFilePath, [string[]]$PSPaths) + + if (-not (Test-Path $CacheFilePath)) { + "Cache file not found '$CacheFilePath'" | Write-DscTrace + return $false + } + + try { + "Reading cache file '$CacheFilePath'" | Write-DscTrace + $cache = Get-Content -Raw $CacheFilePath | ConvertFrom-Json + + "Checking cache for stale entries" | Write-DscTrace + foreach ($entry in $cache.PathInfo.PSObject.Properties) { + $path = $entry.Name + if (-not (Test-Path $path)) { + "Detected non-existent cache entry '$path'" | Write-DscTrace + return $false + } + + $currentLastWrite = (Get-Item $path).LastWriteTimeUtc + $cachedLastWrite = [DateTime]$entry.Value + + if ($currentLastWrite -ne $cachedLastWrite) { + "Detected stale cache entry '$path' (cached: $cachedLastWrite, current: $currentLastWrite)" | Write-DscTrace + return $false + } + } + + "Checking cache for stale PSModulePath" | Write-DscTrace + $cachedPaths = [string[]]$cache.PSModulePaths + if ($cachedPaths.Count -ne $PSPaths.Count) { + "PSModulePath count changed (cached: $($cachedPaths.Count), current: $($PSPaths.Count))" | Write-DscTrace + return $false } - } -ThrottleLimit 10 + + $diff = Compare-Object $cachedPaths $PSPaths + if ($null -ne $diff) { + "PSModulePath contents changed" | Write-DscTrace + return $false + } + + "Cache is valid" | Write-DscTrace + return $true + } catch { + "Stale cached entries detected: $_" | Write-DscTrace + return $false + } } -end { - $manifests | ForEach-Object { $_ | ConvertTo-Json -Compress } + +function Invoke-DscResourceDiscovery { + [CmdletBinding()] + param() + + begin { + $psPaths = $env:PSModulePath -split [System.IO.Path]::PathSeparator | Where-Object { $_ -notmatch 'WindowsPowerShell' } + "Discovered $($psPaths.Count) PSModulePath segments (excluding WindowsPowerShell)" | Write-DscTrace + + $cacheFilePath = Get-CacheFilePath + $useCache = Test-CacheValid -CacheFilePath $cacheFilePath -PSPaths $psPaths + } + process { + if ($useCache) { + "Using cached manifests" | Write-DscTrace + $cache = Get-Content -Raw $cacheFilePath | ConvertFrom-Json + $manifests = $cache.Manifests + "Retrieved $($manifests.Count) manifests from cache" | Write-DscTrace + } else { + "Performing full discovery" | Write-DscTrace + $manifests = $psPaths | ForEach-Object -Parallel { + $searchPatterns = @('*.dsc.resource.json', '*.dsc.resource.yaml', '*.dsc.resource.yml') + $enumOptions = [System.IO.EnumerationOptions]@{ IgnoreInaccessible = $false; RecurseSubdirectories = $true } + foreach ($pattern in $searchPatterns) { + try { + [System.IO.Directory]::EnumerateFiles($_, $pattern, $enumOptions) | ForEach-Object { + @{ manifestPath = $_ } + } + } catch { } + } + } -ThrottleLimit 10 + + "Discovered $($manifests.Count) manifests" | Write-DscTrace + + "Building cache" | Write-DscTrace + $pathInfo = @{} + foreach ($path in $psPaths) { + if (Test-Path $path) { + $pathInfo[$path] = (Get-Item $path).LastWriteTimeUtc + } + } + + $cacheObject = @{ + PSModulePaths = $psPaths + PathInfo = $pathInfo + Manifests = $manifests + } + + $cacheDir = Split-Path $cacheFilePath -Parent + if (-not (Test-Path $cacheDir)) { + "Creating cache directory '$cacheDir'" | Write-DscTrace + New-Item -ItemType Directory -Path $cacheDir -Force | Out-Null + } + "Saving cache to '$cacheFilePath'" | Write-DscTrace + $cacheObject | ConvertTo-Json -Depth 10 | Set-Content -Path $cacheFilePath -Force + } + } + end { + $manifests | ForEach-Object { $_ | ConvertTo-Json -Compress } + } } + +Invoke-DscResourceDiscovery + diff --git a/extensions/powershell/powershell.discover.tests.ps1 b/extensions/powershell/powershell.discover.tests.ps1 index 6b321d471..2b9fb209c 100644 --- a/extensions/powershell/powershell.discover.tests.ps1 +++ b/extensions/powershell/powershell.discover.tests.ps1 @@ -21,12 +21,110 @@ BeforeAll { $manifestPath = Join-Path $TestDrive "fake.dsc.resource.json" $fakeManifest | ConvertTo-Json -Depth 10 | Set-Content -Path $manifestPath $env:PSModulePath += [System.IO.Path]::PathSeparator + $TestDrive + + $script:discoverScript = Join-Path $PSScriptRoot "powershell.discover.ps1" + + $cacheFilePath = if ($IsWindows) { + Join-Path $env:LocalAppData "dsc\PowerShellDiscoverCache.json" + } else { + Join-Path $env:HOME ".dsc" "PowerShellDiscoverCache.json" + } + $script:cacheFilePath = $cacheFilePath } Describe 'Tests for PowerShell resource discovery' { + BeforeEach { + # Clean cache before each test + Remove-Item -Force -ErrorAction SilentlyContinue -Path $script:cacheFilePath + } + It 'Should find DSC PowerShell resources' { $out = dsc resource list | ConvertFrom-Json $LASTEXITCODE | Should -Be 0 $out.directory | Should -Contain $TestDrive } + + It 'Should create cache file on first run' { + $script:cacheFilePath | Should -Not -Exist + + $out = & $script:discoverScript 2>&1 + + $script:cacheFilePath | Should -Exist + + $cache = Get-Content -Raw $script:cacheFilePath | ConvertFrom-Json + $cache.PSModulePaths | Should -Not -BeNullOrEmpty + $cache.PathInfo | Should -Not -BeNullOrEmpty + $cache.Manifests | Should -Not -BeNullOrEmpty + } + + It 'Should use cache on subsequent runs' { + $null = & $script:discoverScript 2>&1 + $script:cacheFilePath | Should -Exist + + $cacheLastWriteTime = (Get-Item $script:cacheFilePath).LastWriteTimeUtc + + Start-Sleep -Milliseconds 100 + + $null = & $script:discoverScript 2>&1 + + $newLastWriteTime = (Get-Item $script:cacheFilePath).LastWriteTimeUtc + $newLastWriteTime | Should -Be $cacheLastWriteTime + } + + It 'Should invalidate cache when PSModulePath changes' { + $null = & $script:discoverScript 2>&1 + $script:cacheFilePath | Should -Exist + + $cache = Get-Content -Raw $script:cacheFilePath | ConvertFrom-Json + $originalPaths = $cache.PSModulePaths + $cache.PSModulePaths = @($originalPaths[0]) # Remove some paths + $cache | ConvertTo-Json -Depth 10 | Set-Content -Path $script:cacheFilePath -Force + + $cacheLastWriteTime = (Get-Item $script:cacheFilePath).LastWriteTimeUtc + Start-Sleep -Milliseconds 100 + + $null = & $script:discoverScript 2>&1 + + $newLastWriteTime = (Get-Item $script:cacheFilePath).LastWriteTimeUtc + $newLastWriteTime | Should -Not -Be $cacheLastWriteTime + } + + It 'Should invalidate cache when module directory is modified' { + $null = & $script:discoverScript 2>&1 + $script:cacheFilePath | Should -Exist + + $cache = Get-Content -Raw $script:cacheFilePath | ConvertFrom-Json + + $firstPath = $cache.PathInfo.PSObject.Properties | Select-Object -First 1 + if ($firstPath) { + $oldTimestamp = [DateTime]$firstPath.Value + $newTimestamp = $oldTimestamp.AddDays(-1) + $cache.PathInfo.($firstPath.Name) = $newTimestamp + $cache | ConvertTo-Json -Depth 10 | Set-Content -Path $script:cacheFilePath -Force + + $cacheLastWriteTime = (Get-Item $script:cacheFilePath).LastWriteTimeUtc + Start-Sleep -Milliseconds 100 + + $null = & $script:discoverScript 2>&1 + + $newLastWriteTime = (Get-Item $script:cacheFilePath).LastWriteTimeUtc + $newLastWriteTime | Should -Not -Be $cacheLastWriteTime + } + } + + It 'Should rebuild cache if cache file is corrupted' { + "{ invalid json }" | Set-Content -Path $script:cacheFilePath -Force + $script:cacheFilePath | Should -Exist + + $null = & $script:discoverScript 2>&1 + + $cache = Get-Content -Raw $script:cacheFilePath | ConvertFrom-Json + $cache.PSModulePaths | Should -Not -BeNullOrEmpty + $cache.PathInfo | Should -Not -BeNullOrEmpty + } + + It 'Should include test manifest in discovery results' { + $out = & $script:discoverScript | ConvertFrom-Json + $out.manifestPath | Should -Contain $manifestPath + } } From 48202c74dcb4cabb6d843293ee64ce27b82416a0 Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Wed, 8 Oct 2025 04:00:46 +0200 Subject: [PATCH 22/31] Debug tests --- .../powershell/powershell.discover.tests.ps1 | 139 +++++++++--------- 1 file changed, 69 insertions(+), 70 deletions(-) diff --git a/extensions/powershell/powershell.discover.tests.ps1 b/extensions/powershell/powershell.discover.tests.ps1 index 2b9fb209c..938984dd5 100644 --- a/extensions/powershell/powershell.discover.tests.ps1 +++ b/extensions/powershell/powershell.discover.tests.ps1 @@ -24,19 +24,18 @@ BeforeAll { $script:discoverScript = Join-Path $PSScriptRoot "powershell.discover.ps1" - $cacheFilePath = if ($IsWindows) { - Join-Path $env:LocalAppData "dsc\PowerShellDiscoverCache.json" - } else { - Join-Path $env:HOME ".dsc" "PowerShellDiscoverCache.json" - } - $script:cacheFilePath = $cacheFilePath + # $cacheFilePath = if ($IsWindows) { + # Join-Path $env:LocalAppData "dsc\PowerShellDiscoverCache.json" + # } else { + # Join-Path $env:HOME ".dsc" "PowerShellDiscoverCache.json" + # } + # $script:cacheFilePath = $cacheFilePath } Describe 'Tests for PowerShell resource discovery' { - BeforeEach { - # Clean cache before each test - Remove-Item -Force -ErrorAction SilentlyContinue -Path $script:cacheFilePath - } + # BeforeAll { + # Remove-Item -Force -ErrorAction SilentlyContinue -Path $script:cacheFilePath + # } It 'Should find DSC PowerShell resources' { $out = dsc resource list | ConvertFrom-Json @@ -44,87 +43,87 @@ Describe 'Tests for PowerShell resource discovery' { $out.directory | Should -Contain $TestDrive } - It 'Should create cache file on first run' { - $script:cacheFilePath | Should -Not -Exist + # It 'Should create cache file on first run' { + # $script:cacheFilePath | Should -Not -Exist - $out = & $script:discoverScript 2>&1 + # $out = & $script:discoverScript 2>&1 - $script:cacheFilePath | Should -Exist + # $script:cacheFilePath | Should -Exist - $cache = Get-Content -Raw $script:cacheFilePath | ConvertFrom-Json - $cache.PSModulePaths | Should -Not -BeNullOrEmpty - $cache.PathInfo | Should -Not -BeNullOrEmpty - $cache.Manifests | Should -Not -BeNullOrEmpty - } + # $cache = Get-Content -Raw $script:cacheFilePath | ConvertFrom-Json + # $cache.PSModulePaths | Should -Not -BeNullOrEmpty + # $cache.PathInfo | Should -Not -BeNullOrEmpty + # $cache.Manifests | Should -Not -BeNullOrEmpty + # } - It 'Should use cache on subsequent runs' { - $null = & $script:discoverScript 2>&1 - $script:cacheFilePath | Should -Exist + # It 'Should use cache on subsequent runs' { + # $null = & $script:discoverScript 2>&1 + # $script:cacheFilePath | Should -Exist - $cacheLastWriteTime = (Get-Item $script:cacheFilePath).LastWriteTimeUtc + # $cacheLastWriteTime = (Get-Item $script:cacheFilePath).LastWriteTimeUtc - Start-Sleep -Milliseconds 100 + # Start-Sleep -Milliseconds 100 - $null = & $script:discoverScript 2>&1 + # $null = & $script:discoverScript 2>&1 - $newLastWriteTime = (Get-Item $script:cacheFilePath).LastWriteTimeUtc - $newLastWriteTime | Should -Be $cacheLastWriteTime - } + # $newLastWriteTime = (Get-Item $script:cacheFilePath).LastWriteTimeUtc + # $newLastWriteTime | Should -Be $cacheLastWriteTime + # } - It 'Should invalidate cache when PSModulePath changes' { - $null = & $script:discoverScript 2>&1 - $script:cacheFilePath | Should -Exist + # It 'Should invalidate cache when PSModulePath changes' { + # $null = & $script:discoverScript 2>&1 + # $script:cacheFilePath | Should -Exist - $cache = Get-Content -Raw $script:cacheFilePath | ConvertFrom-Json - $originalPaths = $cache.PSModulePaths - $cache.PSModulePaths = @($originalPaths[0]) # Remove some paths - $cache | ConvertTo-Json -Depth 10 | Set-Content -Path $script:cacheFilePath -Force + # $cache = Get-Content -Raw $script:cacheFilePath | ConvertFrom-Json + # $originalPaths = $cache.PSModulePaths + # $cache.PSModulePaths = @($originalPaths[0]) # Remove some paths + # $cache | ConvertTo-Json -Depth 10 | Set-Content -Path $script:cacheFilePath -Force - $cacheLastWriteTime = (Get-Item $script:cacheFilePath).LastWriteTimeUtc - Start-Sleep -Milliseconds 100 + # $cacheLastWriteTime = (Get-Item $script:cacheFilePath).LastWriteTimeUtc + # Start-Sleep -Milliseconds 100 - $null = & $script:discoverScript 2>&1 + # $null = & $script:discoverScript 2>&1 - $newLastWriteTime = (Get-Item $script:cacheFilePath).LastWriteTimeUtc - $newLastWriteTime | Should -Not -Be $cacheLastWriteTime - } + # $newLastWriteTime = (Get-Item $script:cacheFilePath).LastWriteTimeUtc + # $newLastWriteTime | Should -Not -Be $cacheLastWriteTime + # } - It 'Should invalidate cache when module directory is modified' { - $null = & $script:discoverScript 2>&1 - $script:cacheFilePath | Should -Exist + # It 'Should invalidate cache when module directory is modified' { + # $null = & $script:discoverScript 2>&1 + # $script:cacheFilePath | Should -Exist - $cache = Get-Content -Raw $script:cacheFilePath | ConvertFrom-Json + # $cache = Get-Content -Raw $script:cacheFilePath | ConvertFrom-Json - $firstPath = $cache.PathInfo.PSObject.Properties | Select-Object -First 1 - if ($firstPath) { - $oldTimestamp = [DateTime]$firstPath.Value - $newTimestamp = $oldTimestamp.AddDays(-1) - $cache.PathInfo.($firstPath.Name) = $newTimestamp - $cache | ConvertTo-Json -Depth 10 | Set-Content -Path $script:cacheFilePath -Force + # $firstPath = $cache.PathInfo.PSObject.Properties | Select-Object -First 1 + # if ($firstPath) { + # $oldTimestamp = [DateTime]$firstPath.Value + # $newTimestamp = $oldTimestamp.AddDays(-1) + # $cache.PathInfo.($firstPath.Name) = $newTimestamp + # $cache | ConvertTo-Json -Depth 10 | Set-Content -Path $script:cacheFilePath -Force - $cacheLastWriteTime = (Get-Item $script:cacheFilePath).LastWriteTimeUtc - Start-Sleep -Milliseconds 100 + # $cacheLastWriteTime = (Get-Item $script:cacheFilePath).LastWriteTimeUtc + # Start-Sleep -Milliseconds 100 - $null = & $script:discoverScript 2>&1 + # $null = & $script:discoverScript 2>&1 - $newLastWriteTime = (Get-Item $script:cacheFilePath).LastWriteTimeUtc - $newLastWriteTime | Should -Not -Be $cacheLastWriteTime - } - } + # $newLastWriteTime = (Get-Item $script:cacheFilePath).LastWriteTimeUtc + # $newLastWriteTime | Should -Not -Be $cacheLastWriteTime + # } + # } - It 'Should rebuild cache if cache file is corrupted' { - "{ invalid json }" | Set-Content -Path $script:cacheFilePath -Force - $script:cacheFilePath | Should -Exist + # It 'Should rebuild cache if cache file is corrupted' { + # "{ invalid json }" | Set-Content -Path $script:cacheFilePath -Force + # $script:cacheFilePath | Should -Exist - $null = & $script:discoverScript 2>&1 + # $null = & $script:discoverScript 2>&1 - $cache = Get-Content -Raw $script:cacheFilePath | ConvertFrom-Json - $cache.PSModulePaths | Should -Not -BeNullOrEmpty - $cache.PathInfo | Should -Not -BeNullOrEmpty - } + # $cache = Get-Content -Raw $script:cacheFilePath | ConvertFrom-Json + # $cache.PSModulePaths | Should -Not -BeNullOrEmpty + # $cache.PathInfo | Should -Not -BeNullOrEmpty + # } - It 'Should include test manifest in discovery results' { - $out = & $script:discoverScript | ConvertFrom-Json - $out.manifestPath | Should -Contain $manifestPath - } + # It 'Should include test manifest in discovery results' { + # $out = & $script:discoverScript | ConvertFrom-Json + # $out.manifestPath | Should -Contain $manifestPath + # } } From e99c6625836b3851b9687c6a6cd227c6e6de11ed Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Wed, 8 Oct 2025 04:31:01 +0200 Subject: [PATCH 23/31] Format file --- .../powershell/powershell.discover.tests.ps1 | 198 +++++++++--------- .../powershell/powershell.dsc.extension.json | 32 +-- 2 files changed, 115 insertions(+), 115 deletions(-) diff --git a/extensions/powershell/powershell.discover.tests.ps1 b/extensions/powershell/powershell.discover.tests.ps1 index 938984dd5..5ea7c3e82 100644 --- a/extensions/powershell/powershell.discover.tests.ps1 +++ b/extensions/powershell/powershell.discover.tests.ps1 @@ -1,129 +1,129 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. +# # Copyright (c) Microsoft Corporation. +# # Licensed under the MIT License. -BeforeAll { - $fakeManifest = @{ - '$schema' = "https://aka.ms/dsc/schemas/v3/bundled/resource/manifest.json" - type = "Test/FakeResource" - version = "0.1.0" - get = @{ - executable = "fakeResource" - args = @( - "get", - @{ - jsonInputArg = "--input" - mandatory = $true - } - ) - } - } +# BeforeAll { +# $fakeManifest = @{ +# '$schema' = "https://aka.ms/dsc/schemas/v3/bundled/resource/manifest.json" +# type = "Test/FakeResource" +# version = "0.1.0" +# get = @{ +# executable = "fakeResource" +# args = @( +# "get", +# @{ +# jsonInputArg = "--input" +# mandatory = $true +# } +# ) +# } +# } - $manifestPath = Join-Path $TestDrive "fake.dsc.resource.json" - $fakeManifest | ConvertTo-Json -Depth 10 | Set-Content -Path $manifestPath - $env:PSModulePath += [System.IO.Path]::PathSeparator + $TestDrive +# $manifestPath = Join-Path $TestDrive "fake.dsc.resource.json" +# $fakeManifest | ConvertTo-Json -Depth 10 | Set-Content -Path $manifestPath +# $env:PSModulePath += [System.IO.Path]::PathSeparator + $TestDrive - $script:discoverScript = Join-Path $PSScriptRoot "powershell.discover.ps1" +# $script:discoverScript = Join-Path $PSScriptRoot "powershell.discover.ps1" - # $cacheFilePath = if ($IsWindows) { - # Join-Path $env:LocalAppData "dsc\PowerShellDiscoverCache.json" - # } else { - # Join-Path $env:HOME ".dsc" "PowerShellDiscoverCache.json" - # } - # $script:cacheFilePath = $cacheFilePath -} +# # $cacheFilePath = if ($IsWindows) { +# # Join-Path $env:LocalAppData "dsc\PowerShellDiscoverCache.json" +# # } else { +# # Join-Path $env:HOME ".dsc" "PowerShellDiscoverCache.json" +# # } +# # $script:cacheFilePath = $cacheFilePath +# } -Describe 'Tests for PowerShell resource discovery' { - # BeforeAll { - # Remove-Item -Force -ErrorAction SilentlyContinue -Path $script:cacheFilePath - # } +# Describe 'Tests for PowerShell resource discovery' { +# # BeforeAll { +# # Remove-Item -Force -ErrorAction SilentlyContinue -Path $script:cacheFilePath +# # } - It 'Should find DSC PowerShell resources' { - $out = dsc resource list | ConvertFrom-Json - $LASTEXITCODE | Should -Be 0 - $out.directory | Should -Contain $TestDrive - } +# It 'Should find DSC PowerShell resources' { +# $out = dsc resource list | ConvertFrom-Json +# $LASTEXITCODE | Should -Be 0 +# $out.directory | Should -Contain $TestDrive +# } - # It 'Should create cache file on first run' { - # $script:cacheFilePath | Should -Not -Exist +# # It 'Should create cache file on first run' { +# # $script:cacheFilePath | Should -Not -Exist - # $out = & $script:discoverScript 2>&1 +# # $out = & $script:discoverScript 2>&1 - # $script:cacheFilePath | Should -Exist +# # $script:cacheFilePath | Should -Exist - # $cache = Get-Content -Raw $script:cacheFilePath | ConvertFrom-Json - # $cache.PSModulePaths | Should -Not -BeNullOrEmpty - # $cache.PathInfo | Should -Not -BeNullOrEmpty - # $cache.Manifests | Should -Not -BeNullOrEmpty - # } +# # $cache = Get-Content -Raw $script:cacheFilePath | ConvertFrom-Json +# # $cache.PSModulePaths | Should -Not -BeNullOrEmpty +# # $cache.PathInfo | Should -Not -BeNullOrEmpty +# # $cache.Manifests | Should -Not -BeNullOrEmpty +# # } - # It 'Should use cache on subsequent runs' { - # $null = & $script:discoverScript 2>&1 - # $script:cacheFilePath | Should -Exist +# # It 'Should use cache on subsequent runs' { +# # $null = & $script:discoverScript 2>&1 +# # $script:cacheFilePath | Should -Exist - # $cacheLastWriteTime = (Get-Item $script:cacheFilePath).LastWriteTimeUtc +# # $cacheLastWriteTime = (Get-Item $script:cacheFilePath).LastWriteTimeUtc - # Start-Sleep -Milliseconds 100 +# # Start-Sleep -Milliseconds 100 - # $null = & $script:discoverScript 2>&1 +# # $null = & $script:discoverScript 2>&1 - # $newLastWriteTime = (Get-Item $script:cacheFilePath).LastWriteTimeUtc - # $newLastWriteTime | Should -Be $cacheLastWriteTime - # } +# # $newLastWriteTime = (Get-Item $script:cacheFilePath).LastWriteTimeUtc +# # $newLastWriteTime | Should -Be $cacheLastWriteTime +# # } - # It 'Should invalidate cache when PSModulePath changes' { - # $null = & $script:discoverScript 2>&1 - # $script:cacheFilePath | Should -Exist +# # It 'Should invalidate cache when PSModulePath changes' { +# # $null = & $script:discoverScript 2>&1 +# # $script:cacheFilePath | Should -Exist - # $cache = Get-Content -Raw $script:cacheFilePath | ConvertFrom-Json - # $originalPaths = $cache.PSModulePaths - # $cache.PSModulePaths = @($originalPaths[0]) # Remove some paths - # $cache | ConvertTo-Json -Depth 10 | Set-Content -Path $script:cacheFilePath -Force +# # $cache = Get-Content -Raw $script:cacheFilePath | ConvertFrom-Json +# # $originalPaths = $cache.PSModulePaths +# # $cache.PSModulePaths = @($originalPaths[0]) # Remove some paths +# # $cache | ConvertTo-Json -Depth 10 | Set-Content -Path $script:cacheFilePath -Force - # $cacheLastWriteTime = (Get-Item $script:cacheFilePath).LastWriteTimeUtc - # Start-Sleep -Milliseconds 100 +# # $cacheLastWriteTime = (Get-Item $script:cacheFilePath).LastWriteTimeUtc +# # Start-Sleep -Milliseconds 100 - # $null = & $script:discoverScript 2>&1 +# # $null = & $script:discoverScript 2>&1 - # $newLastWriteTime = (Get-Item $script:cacheFilePath).LastWriteTimeUtc - # $newLastWriteTime | Should -Not -Be $cacheLastWriteTime - # } +# # $newLastWriteTime = (Get-Item $script:cacheFilePath).LastWriteTimeUtc +# # $newLastWriteTime | Should -Not -Be $cacheLastWriteTime +# # } - # It 'Should invalidate cache when module directory is modified' { - # $null = & $script:discoverScript 2>&1 - # $script:cacheFilePath | Should -Exist +# # It 'Should invalidate cache when module directory is modified' { +# # $null = & $script:discoverScript 2>&1 +# # $script:cacheFilePath | Should -Exist - # $cache = Get-Content -Raw $script:cacheFilePath | ConvertFrom-Json +# # $cache = Get-Content -Raw $script:cacheFilePath | ConvertFrom-Json - # $firstPath = $cache.PathInfo.PSObject.Properties | Select-Object -First 1 - # if ($firstPath) { - # $oldTimestamp = [DateTime]$firstPath.Value - # $newTimestamp = $oldTimestamp.AddDays(-1) - # $cache.PathInfo.($firstPath.Name) = $newTimestamp - # $cache | ConvertTo-Json -Depth 10 | Set-Content -Path $script:cacheFilePath -Force +# # $firstPath = $cache.PathInfo.PSObject.Properties | Select-Object -First 1 +# # if ($firstPath) { +# # $oldTimestamp = [DateTime]$firstPath.Value +# # $newTimestamp = $oldTimestamp.AddDays(-1) +# # $cache.PathInfo.($firstPath.Name) = $newTimestamp +# # $cache | ConvertTo-Json -Depth 10 | Set-Content -Path $script:cacheFilePath -Force - # $cacheLastWriteTime = (Get-Item $script:cacheFilePath).LastWriteTimeUtc - # Start-Sleep -Milliseconds 100 +# # $cacheLastWriteTime = (Get-Item $script:cacheFilePath).LastWriteTimeUtc +# # Start-Sleep -Milliseconds 100 - # $null = & $script:discoverScript 2>&1 +# # $null = & $script:discoverScript 2>&1 - # $newLastWriteTime = (Get-Item $script:cacheFilePath).LastWriteTimeUtc - # $newLastWriteTime | Should -Not -Be $cacheLastWriteTime - # } - # } +# # $newLastWriteTime = (Get-Item $script:cacheFilePath).LastWriteTimeUtc +# # $newLastWriteTime | Should -Not -Be $cacheLastWriteTime +# # } +# # } - # It 'Should rebuild cache if cache file is corrupted' { - # "{ invalid json }" | Set-Content -Path $script:cacheFilePath -Force - # $script:cacheFilePath | Should -Exist +# # It 'Should rebuild cache if cache file is corrupted' { +# # "{ invalid json }" | Set-Content -Path $script:cacheFilePath -Force +# # $script:cacheFilePath | Should -Exist - # $null = & $script:discoverScript 2>&1 +# # $null = & $script:discoverScript 2>&1 - # $cache = Get-Content -Raw $script:cacheFilePath | ConvertFrom-Json - # $cache.PSModulePaths | Should -Not -BeNullOrEmpty - # $cache.PathInfo | Should -Not -BeNullOrEmpty - # } +# # $cache = Get-Content -Raw $script:cacheFilePath | ConvertFrom-Json +# # $cache.PSModulePaths | Should -Not -BeNullOrEmpty +# # $cache.PathInfo | Should -Not -BeNullOrEmpty +# # } - # It 'Should include test manifest in discovery results' { - # $out = & $script:discoverScript | ConvertFrom-Json - # $out.manifestPath | Should -Contain $manifestPath - # } -} +# # It 'Should include test manifest in discovery results' { +# # $out = & $script:discoverScript | ConvertFrom-Json +# # $out.manifestPath | Should -Contain $manifestPath +# # } +# } diff --git a/extensions/powershell/powershell.dsc.extension.json b/extensions/powershell/powershell.dsc.extension.json index aa10cffa3..9096e4fa1 100644 --- a/extensions/powershell/powershell.dsc.extension.json +++ b/extensions/powershell/powershell.dsc.extension.json @@ -1,18 +1,18 @@ { - "$schema": "https://aka.ms/dsc/schemas/v3/bundled/resource/manifest.json", - "type": "Microsoft.PowerShell/Discover", - "version": "0.1.0", - "description": "Discovers DSC resources packaged in PowerShell 7 modules.", - "discover": { - "executable": "pwsh", - "args": [ - "-NoLogo", - "-NonInteractive", - "-ExecutionPolicy", - "Bypass", - "-NoProfile", - "-Command", - "./powershell.discover.ps1" - ] - } + "$schema": "https://aka.ms/dsc/schemas/v3/bundled/resource/manifest.json", + "type": "Microsoft.PowerShell/Discover", + "version": "0.1.0", + "description": "Discovers DSC resources packaged in PowerShell 7 modules.", + "discover": { + "executable": "pwsh", + "args": [ + "-NoLogo", + "-NonInteractive", + "-ExecutionPolicy", + "Bypass", + "-NoProfile", + "-Command", + "./powershell.discover.ps1" + ] + } } From 0dfe4cd97501ea9f5a0b632e2ccfb119f98bff50 Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Wed, 8 Oct 2025 05:03:51 +0200 Subject: [PATCH 24/31] Revert to working situation --- extensions/powershell/powershell.discover.ps1 | 252 ++++++++++-------- .../powershell/powershell.discover.tests.ps1 | 33 +++ 2 files changed, 171 insertions(+), 114 deletions(-) diff --git a/extensions/powershell/powershell.discover.ps1 b/extensions/powershell/powershell.discover.ps1 index e1f163ff0..5aec24cec 100644 --- a/extensions/powershell/powershell.discover.ps1 +++ b/extensions/powershell/powershell.discover.ps1 @@ -4,138 +4,162 @@ [CmdletBinding()] param () -function Write-DscTrace { - param( - [Parameter(Mandatory = $false)] - [ValidateSet('Error', 'Warn', 'Info', 'Debug', 'Trace')] - [string]$Operation = 'Debug', +begin { + $psPaths = $env:PSModulePath -split [System.IO.Path]::PathSeparator | Where-Object { $_ -notmatch 'WindowsPowerShell' } +} +process { + $manifests = $psPaths | ForEach-Object -Parallel { + $searchPatterns = @('*.dsc.resource.json', '*.dsc.resource.yaml', '*.dsc.resource.yml') + $enumOptions = [System.IO.EnumerationOptions]@{ IgnoreInaccessible = $false; RecurseSubdirectories = $true } + foreach ($pattern in $searchPatterns) { + try { + [System.IO.Directory]::EnumerateFiles($_, $pattern, $enumOptions) | ForEach-Object { + @{ manifestPath = $_ } + } + } catch { } + } + } -ThrottleLimit 10 +} +end { + $manifests | ForEach-Object { $_ | ConvertTo-Json -Compress } +} - [Parameter(Mandatory = $true, ValueFromPipeline = $true)] - [string]$Message - ) - $trace = @{$Operation.ToLower() = $Message } | ConvertTo-Json -Compress - $host.ui.WriteErrorLine($trace) -} +# [CmdletBinding()] +# param () -function Get-CacheFilePath { - if ($IsWindows) { - Join-Path $env:LocalAppData "dsc\PowerShellDiscoverCache.json" - } else { - Join-Path $env:HOME ".dsc" "PowerShellDiscoverCache.json" - } -} +# function Write-DscTrace { +# param( +# [Parameter(Mandatory = $false)] +# [ValidateSet('Error', 'Warn', 'Info', 'Debug', 'Trace')] +# [string]$Operation = 'Debug', + +# [Parameter(Mandatory = $true, ValueFromPipeline = $true)] +# [string]$Message +# ) + +# $trace = @{$Operation.ToLower() = $Message } | ConvertTo-Json -Compress +# $host.ui.WriteErrorLine($trace) +# } -function Test-CacheValid { - param([string]$CacheFilePath, [string[]]$PSPaths) +# function Get-CacheFilePath { +# if ($IsWindows) { +# Join-Path $env:LocalAppData "dsc\PowerShellDiscoverCache.json" +# } else { +# Join-Path $env:HOME ".dsc" "PowerShellDiscoverCache.json" +# } +# } + +# function Test-CacheValid { +# param([string]$CacheFilePath, [string[]]$PSPaths) - if (-not (Test-Path $CacheFilePath)) { - "Cache file not found '$CacheFilePath'" | Write-DscTrace - return $false - } +# if (-not (Test-Path $CacheFilePath)) { +# "Cache file not found '$CacheFilePath'" | Write-DscTrace +# return $false +# } - try { - "Reading cache file '$CacheFilePath'" | Write-DscTrace - $cache = Get-Content -Raw $CacheFilePath | ConvertFrom-Json +# try { +# "Reading cache file '$CacheFilePath'" | Write-DscTrace +# $cache = Get-Content -Raw $CacheFilePath | ConvertFrom-Json - "Checking cache for stale entries" | Write-DscTrace - foreach ($entry in $cache.PathInfo.PSObject.Properties) { - $path = $entry.Name - if (-not (Test-Path $path)) { - "Detected non-existent cache entry '$path'" | Write-DscTrace - return $false - } +# "Checking cache for stale entries" | Write-DscTrace +# foreach ($entry in $cache.PathInfo.PSObject.Properties) { +# $path = $entry.Name +# if (-not (Test-Path $path)) { +# "Detected non-existent cache entry '$path'" | Write-DscTrace +# return $false +# } - $currentLastWrite = (Get-Item $path).LastWriteTimeUtc - $cachedLastWrite = [DateTime]$entry.Value +# $currentLastWrite = (Get-Item $path).LastWriteTimeUtc +# $cachedLastWrite = [DateTime]$entry.Value - if ($currentLastWrite -ne $cachedLastWrite) { - "Detected stale cache entry '$path' (cached: $cachedLastWrite, current: $currentLastWrite)" | Write-DscTrace - return $false - } - } +# if ($currentLastWrite -ne $cachedLastWrite) { +# "Detected stale cache entry '$path' (cached: $cachedLastWrite, current: $currentLastWrite)" | Write-DscTrace +# return $false +# } +# } - "Checking cache for stale PSModulePath" | Write-DscTrace - $cachedPaths = [string[]]$cache.PSModulePaths - if ($cachedPaths.Count -ne $PSPaths.Count) { - "PSModulePath count changed (cached: $($cachedPaths.Count), current: $($PSPaths.Count))" | Write-DscTrace - return $false - } +# "Checking cache for stale PSModulePath" | Write-DscTrace +# $cachedPaths = [string[]]$cache.PSModulePaths +# if ($cachedPaths.Count -ne $PSPaths.Count) { +# "PSModulePath count changed (cached: $($cachedPaths.Count), current: $($PSPaths.Count))" | Write-DscTrace +# return $false +# } - $diff = Compare-Object $cachedPaths $PSPaths - if ($null -ne $diff) { - "PSModulePath contents changed" | Write-DscTrace - return $false - } +# $diff = Compare-Object $cachedPaths $PSPaths +# if ($null -ne $diff) { +# "PSModulePath contents changed" | Write-DscTrace +# return $false +# } - "Cache is valid" | Write-DscTrace - return $true - } catch { - "Stale cached entries detected: $_" | Write-DscTrace - return $false - } -} +# "Cache is valid" | Write-DscTrace +# return $true +# } catch { +# "Stale cached entries detected: $_" | Write-DscTrace +# return $false +# } +# } -function Invoke-DscResourceDiscovery { - [CmdletBinding()] - param() +# function Invoke-DscResourceDiscovery { +# [CmdletBinding()] +# param() - begin { - $psPaths = $env:PSModulePath -split [System.IO.Path]::PathSeparator | Where-Object { $_ -notmatch 'WindowsPowerShell' } - "Discovered $($psPaths.Count) PSModulePath segments (excluding WindowsPowerShell)" | Write-DscTrace +# begin { +# $psPaths = $env:PSModulePath -split [System.IO.Path]::PathSeparator | Where-Object { $_ -notmatch 'WindowsPowerShell' } +# "Discovered $($psPaths.Count) PSModulePath segments (excluding WindowsPowerShell)" | Write-DscTrace - $cacheFilePath = Get-CacheFilePath - $useCache = Test-CacheValid -CacheFilePath $cacheFilePath -PSPaths $psPaths - } - process { - if ($useCache) { - "Using cached manifests" | Write-DscTrace - $cache = Get-Content -Raw $cacheFilePath | ConvertFrom-Json - $manifests = $cache.Manifests - "Retrieved $($manifests.Count) manifests from cache" | Write-DscTrace - } else { - "Performing full discovery" | Write-DscTrace - $manifests = $psPaths | ForEach-Object -Parallel { - $searchPatterns = @('*.dsc.resource.json', '*.dsc.resource.yaml', '*.dsc.resource.yml') - $enumOptions = [System.IO.EnumerationOptions]@{ IgnoreInaccessible = $false; RecurseSubdirectories = $true } - foreach ($pattern in $searchPatterns) { - try { - [System.IO.Directory]::EnumerateFiles($_, $pattern, $enumOptions) | ForEach-Object { - @{ manifestPath = $_ } - } - } catch { } - } - } -ThrottleLimit 10 +# $cacheFilePath = Get-CacheFilePath +# $useCache = Test-CacheValid -CacheFilePath $cacheFilePath -PSPaths $psPaths +# } +# process { +# if ($useCache) { +# "Using cached manifests" | Write-DscTrace +# $cache = Get-Content -Raw $cacheFilePath | ConvertFrom-Json +# $manifests = $cache.Manifests +# "Retrieved $($manifests.Count) manifests from cache" | Write-DscTrace +# } else { +# "Performing full discovery" | Write-DscTrace +# $manifests = $psPaths | ForEach-Object -Parallel { +# $searchPatterns = @('*.dsc.resource.json', '*.dsc.resource.yaml', '*.dsc.resource.yml') +# $enumOptions = [System.IO.EnumerationOptions]@{ IgnoreInaccessible = $false; RecurseSubdirectories = $true } +# foreach ($pattern in $searchPatterns) { +# try { +# [System.IO.Directory]::EnumerateFiles($_, $pattern, $enumOptions) | ForEach-Object { +# @{ manifestPath = $_ } +# } +# } catch { } +# } +# } -ThrottleLimit 10 - "Discovered $($manifests.Count) manifests" | Write-DscTrace +# "Discovered $($manifests.Count) manifests" | Write-DscTrace - "Building cache" | Write-DscTrace - $pathInfo = @{} - foreach ($path in $psPaths) { - if (Test-Path $path) { - $pathInfo[$path] = (Get-Item $path).LastWriteTimeUtc - } - } +# "Building cache" | Write-DscTrace +# $pathInfo = @{} +# foreach ($path in $psPaths) { +# if (Test-Path $path) { +# $pathInfo[$path] = (Get-Item $path).LastWriteTimeUtc +# } +# } - $cacheObject = @{ - PSModulePaths = $psPaths - PathInfo = $pathInfo - Manifests = $manifests - } +# $cacheObject = @{ +# PSModulePaths = $psPaths +# PathInfo = $pathInfo +# Manifests = $manifests +# } - $cacheDir = Split-Path $cacheFilePath -Parent - if (-not (Test-Path $cacheDir)) { - "Creating cache directory '$cacheDir'" | Write-DscTrace - New-Item -ItemType Directory -Path $cacheDir -Force | Out-Null - } - "Saving cache to '$cacheFilePath'" | Write-DscTrace - $cacheObject | ConvertTo-Json -Depth 10 | Set-Content -Path $cacheFilePath -Force - } - } - end { - $manifests | ForEach-Object { $_ | ConvertTo-Json -Compress } - } -} +# $cacheDir = Split-Path $cacheFilePath -Parent +# if (-not (Test-Path $cacheDir)) { +# "Creating cache directory '$cacheDir'" | Write-DscTrace +# New-Item -ItemType Directory -Path $cacheDir -Force | Out-Null +# } +# "Saving cache to '$cacheFilePath'" | Write-DscTrace +# $cacheObject | ConvertTo-Json -Depth 10 | Set-Content -Path $cacheFilePath -Force +# } +# } +# end { +# $manifests | ForEach-Object { $_ | ConvertTo-Json -Compress } +# } +# } -Invoke-DscResourceDiscovery +# Invoke-DscResourceDiscovery diff --git a/extensions/powershell/powershell.discover.tests.ps1 b/extensions/powershell/powershell.discover.tests.ps1 index 5ea7c3e82..4fc720a57 100644 --- a/extensions/powershell/powershell.discover.tests.ps1 +++ b/extensions/powershell/powershell.discover.tests.ps1 @@ -1,3 +1,36 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +BeforeAll { + $fakeManifest = @{ + '$schema' = "https://aka.ms/dsc/schemas/v3/bundled/resource/manifest.json" + type = "Test/FakeResource" + version = "0.1.0" + get = @{ + executable = "fakeResource" + args = @( + "get", + @{ + jsonInputArg = "--input" + mandatory = $true + } + ) + } + } + + $manifestPath = Join-Path $TestDrive "fake.dsc.resource.json" + $fakeManifest | ConvertTo-Json -Depth 10 | Set-Content -Path $manifestPath + $env:PSModulePath += [System.IO.Path]::PathSeparator + $TestDrive +} + +Describe 'Tests for PowerShell resource discovery' { + It 'Should find DSC PowerShell resources' { + $out = dsc resource list | ConvertFrom-Json + $LASTEXITCODE | Should -Be 0 + $out.directory | Should -Contain $TestDrive + } +} + # # Copyright (c) Microsoft Corporation. # # Licensed under the MIT License. From ff104c20b2d47bb8e318f67834fea4de9896cbc1 Mon Sep 17 00:00:00 2001 From: GijsR Date: Wed, 8 Oct 2025 08:36:40 +0200 Subject: [PATCH 25/31] Create function --- extensions/powershell/powershell.discover.ps1 | 122 ++++++------------ 1 file changed, 43 insertions(+), 79 deletions(-) diff --git a/extensions/powershell/powershell.discover.ps1 b/extensions/powershell/powershell.discover.ps1 index 5aec24cec..9b614bdda 100644 --- a/extensions/powershell/powershell.discover.ps1 +++ b/extensions/powershell/powershell.discover.ps1 @@ -4,25 +4,25 @@ [CmdletBinding()] param () -begin { - $psPaths = $env:PSModulePath -split [System.IO.Path]::PathSeparator | Where-Object { $_ -notmatch 'WindowsPowerShell' } -} -process { - $manifests = $psPaths | ForEach-Object -Parallel { - $searchPatterns = @('*.dsc.resource.json', '*.dsc.resource.yaml', '*.dsc.resource.yml') - $enumOptions = [System.IO.EnumerationOptions]@{ IgnoreInaccessible = $false; RecurseSubdirectories = $true } - foreach ($pattern in $searchPatterns) { - try { - [System.IO.Directory]::EnumerateFiles($_, $pattern, $enumOptions) | ForEach-Object { - @{ manifestPath = $_ } - } - } catch { } - } - } -ThrottleLimit 10 -} -end { - $manifests | ForEach-Object { $_ | ConvertTo-Json -Compress } -} +# begin { +# $psPaths = $env:PSModulePath -split [System.IO.Path]::PathSeparator | Where-Object { $_ -notmatch 'WindowsPowerShell' } +# } +# process { +# $manifests = $psPaths | ForEach-Object -Parallel { +# $searchPatterns = @('*.dsc.resource.json', '*.dsc.resource.yaml', '*.dsc.resource.yml') +# $enumOptions = [System.IO.EnumerationOptions]@{ IgnoreInaccessible = $false; RecurseSubdirectories = $true } +# foreach ($pattern in $searchPatterns) { +# try { +# [System.IO.Directory]::EnumerateFiles($_, $pattern, $enumOptions) | ForEach-Object { +# @{ manifestPath = $_ } +# } +# } catch { } +# } +# } -ThrottleLimit 10 +# } +# end { +# $manifests | ForEach-Object { $_ | ConvertTo-Json -Compress } +# } # [CmdletBinding()] @@ -100,66 +100,30 @@ end { # } # } -# function Invoke-DscResourceDiscovery { -# [CmdletBinding()] -# param() +function Invoke-DscResourceDiscovery { + [CmdletBinding()] + param() -# begin { -# $psPaths = $env:PSModulePath -split [System.IO.Path]::PathSeparator | Where-Object { $_ -notmatch 'WindowsPowerShell' } -# "Discovered $($psPaths.Count) PSModulePath segments (excluding WindowsPowerShell)" | Write-DscTrace - -# $cacheFilePath = Get-CacheFilePath -# $useCache = Test-CacheValid -CacheFilePath $cacheFilePath -PSPaths $psPaths -# } -# process { -# if ($useCache) { -# "Using cached manifests" | Write-DscTrace -# $cache = Get-Content -Raw $cacheFilePath | ConvertFrom-Json -# $manifests = $cache.Manifests -# "Retrieved $($manifests.Count) manifests from cache" | Write-DscTrace -# } else { -# "Performing full discovery" | Write-DscTrace -# $manifests = $psPaths | ForEach-Object -Parallel { -# $searchPatterns = @('*.dsc.resource.json', '*.dsc.resource.yaml', '*.dsc.resource.yml') -# $enumOptions = [System.IO.EnumerationOptions]@{ IgnoreInaccessible = $false; RecurseSubdirectories = $true } -# foreach ($pattern in $searchPatterns) { -# try { -# [System.IO.Directory]::EnumerateFiles($_, $pattern, $enumOptions) | ForEach-Object { -# @{ manifestPath = $_ } -# } -# } catch { } -# } -# } -ThrottleLimit 10 - -# "Discovered $($manifests.Count) manifests" | Write-DscTrace - -# "Building cache" | Write-DscTrace -# $pathInfo = @{} -# foreach ($path in $psPaths) { -# if (Test-Path $path) { -# $pathInfo[$path] = (Get-Item $path).LastWriteTimeUtc -# } -# } - -# $cacheObject = @{ -# PSModulePaths = $psPaths -# PathInfo = $pathInfo -# Manifests = $manifests -# } - -# $cacheDir = Split-Path $cacheFilePath -Parent -# if (-not (Test-Path $cacheDir)) { -# "Creating cache directory '$cacheDir'" | Write-DscTrace -# New-Item -ItemType Directory -Path $cacheDir -Force | Out-Null -# } -# "Saving cache to '$cacheFilePath'" | Write-DscTrace -# $cacheObject | ConvertTo-Json -Depth 10 | Set-Content -Path $cacheFilePath -Force -# } -# } -# end { -# $manifests | ForEach-Object { $_ | ConvertTo-Json -Compress } -# } -# } + begin { + $psPaths = $env:PSModulePath -split [System.IO.Path]::PathSeparator | Where-Object { $_ -notmatch 'WindowsPowerShell' } + } + process { + $manifests = $psPaths | ForEach-Object -Parallel { + $searchPatterns = @('*.dsc.resource.json', '*.dsc.resource.yaml', '*.dsc.resource.yml') + $enumOptions = [System.IO.EnumerationOptions]@{ IgnoreInaccessible = $false; RecurseSubdirectories = $true } + foreach ($pattern in $searchPatterns) { + try { + [System.IO.Directory]::EnumerateFiles($_, $pattern, $enumOptions) | ForEach-Object { + @{ manifestPath = $_ } + } + } catch { } + } + } -ThrottleLimit 10 + } + end { + $manifests | ForEach-Object { $_ | ConvertTo-Json -Compress } + } +} -# Invoke-DscResourceDiscovery +Invoke-DscResourceDiscovery From 091e01759125d9ff212144b442d9abbbfb44241a Mon Sep 17 00:00:00 2001 From: GijsR Date: Wed, 8 Oct 2025 09:14:04 +0200 Subject: [PATCH 26/31] Catch empty value --- extensions/powershell/powershell.discover.ps1 | 204 ++++++++++-------- 1 file changed, 111 insertions(+), 93 deletions(-) diff --git a/extensions/powershell/powershell.discover.ps1 b/extensions/powershell/powershell.discover.ps1 index 9b614bdda..0622fdb68 100644 --- a/extensions/powershell/powershell.discover.ps1 +++ b/extensions/powershell/powershell.discover.ps1 @@ -4,101 +4,77 @@ [CmdletBinding()] param () -# begin { -# $psPaths = $env:PSModulePath -split [System.IO.Path]::PathSeparator | Where-Object { $_ -notmatch 'WindowsPowerShell' } -# } -# process { -# $manifests = $psPaths | ForEach-Object -Parallel { -# $searchPatterns = @('*.dsc.resource.json', '*.dsc.resource.yaml', '*.dsc.resource.yml') -# $enumOptions = [System.IO.EnumerationOptions]@{ IgnoreInaccessible = $false; RecurseSubdirectories = $true } -# foreach ($pattern in $searchPatterns) { -# try { -# [System.IO.Directory]::EnumerateFiles($_, $pattern, $enumOptions) | ForEach-Object { -# @{ manifestPath = $_ } -# } -# } catch { } -# } -# } -ThrottleLimit 10 -# } -# end { -# $manifests | ForEach-Object { $_ | ConvertTo-Json -Compress } -# } +function Write-DscTrace { + param( + [Parameter(Mandatory = $false)] + [ValidateSet('Error', 'Warn', 'Info', 'Debug', 'Trace')] + [string]$Operation = 'Debug', + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] + [string]$Message + ) -# [CmdletBinding()] -# param () - -# function Write-DscTrace { -# param( -# [Parameter(Mandatory = $false)] -# [ValidateSet('Error', 'Warn', 'Info', 'Debug', 'Trace')] -# [string]$Operation = 'Debug', - -# [Parameter(Mandatory = $true, ValueFromPipeline = $true)] -# [string]$Message -# ) - -# $trace = @{$Operation.ToLower() = $Message } | ConvertTo-Json -Compress -# $host.ui.WriteErrorLine($trace) -# } + $trace = @{$Operation.ToLower() = $Message } | ConvertTo-Json -Compress + $host.ui.WriteErrorLine($trace) +} -# function Get-CacheFilePath { -# if ($IsWindows) { -# Join-Path $env:LocalAppData "dsc\PowerShellDiscoverCache.json" -# } else { -# Join-Path $env:HOME ".dsc" "PowerShellDiscoverCache.json" -# } -# } +function Get-CacheFilePath { + if ($IsWindows) { + Join-Path $env:LocalAppData "dsc\PowerShellDiscoverCache.json" + } else { + Join-Path $env:HOME ".dsc" "PowerShellDiscoverCache.json" + } +} -# function Test-CacheValid { -# param([string]$CacheFilePath, [string[]]$PSPaths) +function Test-CacheValid { + param([string]$CacheFilePath, [string[]]$PSPaths) -# if (-not (Test-Path $CacheFilePath)) { -# "Cache file not found '$CacheFilePath'" | Write-DscTrace -# return $false -# } + if (-not (Test-Path $CacheFilePath)) { + "Cache file not found '$CacheFilePath'" | Write-DscTrace + return $false + } -# try { -# "Reading cache file '$CacheFilePath'" | Write-DscTrace -# $cache = Get-Content -Raw $CacheFilePath | ConvertFrom-Json + try { + "Reading cache file '$CacheFilePath'" | Write-DscTrace + $cache = Get-Content -Raw $CacheFilePath | ConvertFrom-Json -# "Checking cache for stale entries" | Write-DscTrace -# foreach ($entry in $cache.PathInfo.PSObject.Properties) { -# $path = $entry.Name -# if (-not (Test-Path $path)) { -# "Detected non-existent cache entry '$path'" | Write-DscTrace -# return $false -# } + "Checking cache for stale entries" | Write-DscTrace + foreach ($entry in $cache.PathInfo.PSObject.Properties) { + $path = $entry.Name + if (-not (Test-Path $path)) { + "Detected non-existent cache entry '$path'" | Write-DscTrace + return $false + } -# $currentLastWrite = (Get-Item $path).LastWriteTimeUtc -# $cachedLastWrite = [DateTime]$entry.Value + $currentLastWrite = (Get-Item $path).LastWriteTimeUtc + $cachedLastWrite = [DateTime]$entry.Value -# if ($currentLastWrite -ne $cachedLastWrite) { -# "Detected stale cache entry '$path' (cached: $cachedLastWrite, current: $currentLastWrite)" | Write-DscTrace -# return $false -# } -# } + if ($currentLastWrite -ne $cachedLastWrite) { + "Detected stale cache entry '$path' (cached: $cachedLastWrite, current: $currentLastWrite)" | Write-DscTrace + return $false + } + } -# "Checking cache for stale PSModulePath" | Write-DscTrace -# $cachedPaths = [string[]]$cache.PSModulePaths -# if ($cachedPaths.Count -ne $PSPaths.Count) { -# "PSModulePath count changed (cached: $($cachedPaths.Count), current: $($PSPaths.Count))" | Write-DscTrace -# return $false -# } + "Checking cache for stale PSModulePath" | Write-DscTrace + $cachedPaths = [string[]]$cache.PSModulePaths + if ($cachedPaths.Count -ne $PSPaths.Count) { + "PSModulePath count changed (cached: $($cachedPaths.Count), current: $($PSPaths.Count))" | Write-DscTrace + return $false + } -# $diff = Compare-Object $cachedPaths $PSPaths -# if ($null -ne $diff) { -# "PSModulePath contents changed" | Write-DscTrace -# return $false -# } + $diff = Compare-Object $cachedPaths $PSPaths + if ($null -ne $diff) { + "PSModulePath contents changed" | Write-DscTrace + return $false + } -# "Cache is valid" | Write-DscTrace -# return $true -# } catch { -# "Stale cached entries detected: $_" | Write-DscTrace -# return $false -# } -# } + "Cache is valid" | Write-DscTrace + return $true + } catch { + "Stale cached entries detected: $_" | Write-DscTrace + return $false + } +} function Invoke-DscResourceDiscovery { [CmdletBinding()] @@ -106,21 +82,63 @@ function Invoke-DscResourceDiscovery { begin { $psPaths = $env:PSModulePath -split [System.IO.Path]::PathSeparator | Where-Object { $_ -notmatch 'WindowsPowerShell' } + "Discovered $($psPaths.Count) PSModulePath segments (excluding WindowsPowerShell)" | Write-DscTrace + + $cacheFilePath = Get-CacheFilePath + $useCache = Test-CacheValid -CacheFilePath $cacheFilePath -PSPaths $psPaths } process { - $manifests = $psPaths | ForEach-Object -Parallel { - $searchPatterns = @('*.dsc.resource.json', '*.dsc.resource.yaml', '*.dsc.resource.yml') - $enumOptions = [System.IO.EnumerationOptions]@{ IgnoreInaccessible = $false; RecurseSubdirectories = $true } - foreach ($pattern in $searchPatterns) { - try { - [System.IO.Directory]::EnumerateFiles($_, $pattern, $enumOptions) | ForEach-Object { - @{ manifestPath = $_ } - } - } catch { } + if ($useCache) { + "Using cached manifests" | Write-DscTrace + $cache = Get-Content -Raw $cacheFilePath | ConvertFrom-Json + $manifests = $cache.Manifests + "Retrieved $($manifests.Count) manifests from cache" | Write-DscTrace + } else { + "Performing full discovery" | Write-DscTrace + $manifests = $psPaths | ForEach-Object -Parallel { + $searchPatterns = @('*.dsc.resource.json', '*.dsc.resource.yaml', '*.dsc.resource.yml') + $enumOptions = [System.IO.EnumerationOptions]@{ IgnoreInaccessible = $false; RecurseSubdirectories = $true } + foreach ($pattern in $searchPatterns) { + try { + [System.IO.Directory]::EnumerateFiles($_, $pattern, $enumOptions) | ForEach-Object { + @{ manifestPath = $_ } + } + } catch { } + } + } -ThrottleLimit 10 + + "Discovered $($manifests.Count) manifests" | Write-DscTrace + + "Building cache" | Write-DscTrace + $pathInfo = @{} + foreach ($path in $psPaths) { + if (Test-Path $path) { + $pathInfo[$path] = (Get-Item $path).LastWriteTimeUtc + } } - } -ThrottleLimit 10 + + $cacheObject = @{ + PSModulePaths = $psPaths + PathInfo = $pathInfo + Manifests = $manifests + } + + $cacheDir = Split-Path $cacheFilePath -Parent + if (-not (Test-Path $cacheDir)) { + "Creating cache directory '$cacheDir'" | Write-DscTrace + New-Item -ItemType Directory -Path $cacheDir -Force | Out-Null + } + "Saving cache to '$cacheFilePath'" | Write-DscTrace + $cacheObject | ConvertTo-Json -Depth 10 | Set-Content -Path $cacheFilePath -Force + } } end { + $manifests + if ($null -eq $manifests) { + "No manifests found" | Write-DscTrace + return + } + $manifests | ForEach-Object { $_ | ConvertTo-Json -Compress } } } From 9b6c9931c4b3002250f9012af196ff8ad1cb4994 Mon Sep 17 00:00:00 2001 From: GijsR Date: Wed, 8 Oct 2025 10:38:57 +0200 Subject: [PATCH 27/31] Re-add tests --- .../powershell/powershell.discover.tests.ps1 | 181 +++++++----------- 1 file changed, 74 insertions(+), 107 deletions(-) diff --git a/extensions/powershell/powershell.discover.tests.ps1 b/extensions/powershell/powershell.discover.tests.ps1 index 4fc720a57..22d71e4fc 100644 --- a/extensions/powershell/powershell.discover.tests.ps1 +++ b/extensions/powershell/powershell.discover.tests.ps1 @@ -21,142 +21,109 @@ BeforeAll { $manifestPath = Join-Path $TestDrive "fake.dsc.resource.json" $fakeManifest | ConvertTo-Json -Depth 10 | Set-Content -Path $manifestPath $env:PSModulePath += [System.IO.Path]::PathSeparator + $TestDrive + + $script:discoverScript = Join-Path $PSScriptRoot "powershell.discover.ps1" + + # $cacheFilePath = if ($IsWindows) { + # Join-Path $env:LocalAppData "dsc\PowerShellDiscoverCache.json" + # } else { + # Join-Path $env:HOME ".dsc" "PowerShellDiscoverCache.json" + # } + # $script:cacheFilePath = $cacheFilePath } Describe 'Tests for PowerShell resource discovery' { + BeforeAll { + Remove-Item -Force -ErrorAction SilentlyContinue -Path $script:cacheFilePath + } + It 'Should find DSC PowerShell resources' { $out = dsc resource list | ConvertFrom-Json $LASTEXITCODE | Should -Be 0 $out.directory | Should -Contain $TestDrive } -} - -# # Copyright (c) Microsoft Corporation. -# # Licensed under the MIT License. - -# BeforeAll { -# $fakeManifest = @{ -# '$schema' = "https://aka.ms/dsc/schemas/v3/bundled/resource/manifest.json" -# type = "Test/FakeResource" -# version = "0.1.0" -# get = @{ -# executable = "fakeResource" -# args = @( -# "get", -# @{ -# jsonInputArg = "--input" -# mandatory = $true -# } -# ) -# } -# } - -# $manifestPath = Join-Path $TestDrive "fake.dsc.resource.json" -# $fakeManifest | ConvertTo-Json -Depth 10 | Set-Content -Path $manifestPath -# $env:PSModulePath += [System.IO.Path]::PathSeparator + $TestDrive - -# $script:discoverScript = Join-Path $PSScriptRoot "powershell.discover.ps1" - -# # $cacheFilePath = if ($IsWindows) { -# # Join-Path $env:LocalAppData "dsc\PowerShellDiscoverCache.json" -# # } else { -# # Join-Path $env:HOME ".dsc" "PowerShellDiscoverCache.json" -# # } -# # $script:cacheFilePath = $cacheFilePath -# } - -# Describe 'Tests for PowerShell resource discovery' { -# # BeforeAll { -# # Remove-Item -Force -ErrorAction SilentlyContinue -Path $script:cacheFilePath -# # } - -# It 'Should find DSC PowerShell resources' { -# $out = dsc resource list | ConvertFrom-Json -# $LASTEXITCODE | Should -Be 0 -# $out.directory | Should -Contain $TestDrive -# } -# # It 'Should create cache file on first run' { -# # $script:cacheFilePath | Should -Not -Exist + It 'Should create cache file on first run' { + $script:cacheFilePath | Should -Not -Exist -# # $out = & $script:discoverScript 2>&1 + $null = & $script:discoverScript 2>&1 -# # $script:cacheFilePath | Should -Exist + $script:cacheFilePath | Should -Exist -# # $cache = Get-Content -Raw $script:cacheFilePath | ConvertFrom-Json -# # $cache.PSModulePaths | Should -Not -BeNullOrEmpty -# # $cache.PathInfo | Should -Not -BeNullOrEmpty -# # $cache.Manifests | Should -Not -BeNullOrEmpty -# # } + $cache = Get-Content -Raw $script:cacheFilePath | ConvertFrom-Json + $cache.PSModulePaths | Should -Not -BeNullOrEmpty + $cache.PathInfo | Should -Not -BeNullOrEmpty + $cache.Manifests | Should -Not -BeNullOrEmpty + } -# # It 'Should use cache on subsequent runs' { -# # $null = & $script:discoverScript 2>&1 -# # $script:cacheFilePath | Should -Exist + It 'Should use cache on subsequent runs' { + $null = & $script:discoverScript 2>&1 + $script:cacheFilePath | Should -Exist -# # $cacheLastWriteTime = (Get-Item $script:cacheFilePath).LastWriteTimeUtc + $cacheLastWriteTime = (Get-Item $script:cacheFilePath).LastWriteTimeUtc -# # Start-Sleep -Milliseconds 100 + Start-Sleep -Milliseconds 100 -# # $null = & $script:discoverScript 2>&1 + $null = & $script:discoverScript 2>&1 -# # $newLastWriteTime = (Get-Item $script:cacheFilePath).LastWriteTimeUtc -# # $newLastWriteTime | Should -Be $cacheLastWriteTime -# # } + $newLastWriteTime = (Get-Item $script:cacheFilePath).LastWriteTimeUtc + $newLastWriteTime | Should -Be $cacheLastWriteTime + } -# # It 'Should invalidate cache when PSModulePath changes' { -# # $null = & $script:discoverScript 2>&1 -# # $script:cacheFilePath | Should -Exist + It 'Should invalidate cache when PSModulePath changes' { + $null = & $script:discoverScript 2>&1 + $script:cacheFilePath | Should -Exist -# # $cache = Get-Content -Raw $script:cacheFilePath | ConvertFrom-Json -# # $originalPaths = $cache.PSModulePaths -# # $cache.PSModulePaths = @($originalPaths[0]) # Remove some paths -# # $cache | ConvertTo-Json -Depth 10 | Set-Content -Path $script:cacheFilePath -Force + $cache = Get-Content -Raw $script:cacheFilePath | ConvertFrom-Json + $originalPaths = $cache.PSModulePaths + $cache.PSModulePaths = @($originalPaths[0]) # Remove some paths + $cache | ConvertTo-Json -Depth 10 | Set-Content -Path $script:cacheFilePath -Force -# # $cacheLastWriteTime = (Get-Item $script:cacheFilePath).LastWriteTimeUtc -# # Start-Sleep -Milliseconds 100 + $cacheLastWriteTime = (Get-Item $script:cacheFilePath).LastWriteTimeUtc + Start-Sleep -Milliseconds 100 -# # $null = & $script:discoverScript 2>&1 + $null = & $script:discoverScript 2>&1 -# # $newLastWriteTime = (Get-Item $script:cacheFilePath).LastWriteTimeUtc -# # $newLastWriteTime | Should -Not -Be $cacheLastWriteTime -# # } + $newLastWriteTime = (Get-Item $script:cacheFilePath).LastWriteTimeUtc + $newLastWriteTime | Should -Not -Be $cacheLastWriteTime + } -# # It 'Should invalidate cache when module directory is modified' { -# # $null = & $script:discoverScript 2>&1 -# # $script:cacheFilePath | Should -Exist + It 'Should invalidate cache when module directory is modified' { + $null = & $script:discoverScript 2>&1 + $script:cacheFilePath | Should -Exist -# # $cache = Get-Content -Raw $script:cacheFilePath | ConvertFrom-Json + $cache = Get-Content -Raw $script:cacheFilePath | ConvertFrom-Json -# # $firstPath = $cache.PathInfo.PSObject.Properties | Select-Object -First 1 -# # if ($firstPath) { -# # $oldTimestamp = [DateTime]$firstPath.Value -# # $newTimestamp = $oldTimestamp.AddDays(-1) -# # $cache.PathInfo.($firstPath.Name) = $newTimestamp -# # $cache | ConvertTo-Json -Depth 10 | Set-Content -Path $script:cacheFilePath -Force + $firstPath = $cache.PathInfo.PSObject.Properties | Select-Object -First 1 + if ($firstPath) { + $oldTimestamp = [DateTime]$firstPath.Value + $newTimestamp = $oldTimestamp.AddDays(-1) + $cache.PathInfo.($firstPath.Name) = $newTimestamp + $cache | ConvertTo-Json -Depth 10 | Set-Content -Path $script:cacheFilePath -Force -# # $cacheLastWriteTime = (Get-Item $script:cacheFilePath).LastWriteTimeUtc -# # Start-Sleep -Milliseconds 100 + $cacheLastWriteTime = (Get-Item $script:cacheFilePath).LastWriteTimeUtc + Start-Sleep -Milliseconds 100 -# # $null = & $script:discoverScript 2>&1 + $null = & $script:discoverScript 2>&1 -# # $newLastWriteTime = (Get-Item $script:cacheFilePath).LastWriteTimeUtc -# # $newLastWriteTime | Should -Not -Be $cacheLastWriteTime -# # } -# # } + $newLastWriteTime = (Get-Item $script:cacheFilePath).LastWriteTimeUtc + $newLastWriteTime | Should -Not -Be $cacheLastWriteTime + } + } -# # It 'Should rebuild cache if cache file is corrupted' { -# # "{ invalid json }" | Set-Content -Path $script:cacheFilePath -Force -# # $script:cacheFilePath | Should -Exist + It 'Should rebuild cache if cache file is corrupted' { + "{ invalid json }" | Set-Content -Path $script:cacheFilePath -Force + $script:cacheFilePath | Should -Exist -# # $null = & $script:discoverScript 2>&1 + $null = & $script:discoverScript 2>&1 -# # $cache = Get-Content -Raw $script:cacheFilePath | ConvertFrom-Json -# # $cache.PSModulePaths | Should -Not -BeNullOrEmpty -# # $cache.PathInfo | Should -Not -BeNullOrEmpty -# # } + $cache = Get-Content -Raw $script:cacheFilePath | ConvertFrom-Json + $cache.PSModulePaths | Should -Not -BeNullOrEmpty + $cache.PathInfo | Should -Not -BeNullOrEmpty + } -# # It 'Should include test manifest in discovery results' { -# # $out = & $script:discoverScript | ConvertFrom-Json -# # $out.manifestPath | Should -Contain $manifestPath -# # } -# } + It 'Should include test manifest in discovery results' { + $out = & $script:discoverScript | ConvertFrom-Json + $out.manifestPath | Should -Contain $manifestPath + } +} From 5c736a7ca0939c01fb0349d4a36f8fd758172b51 Mon Sep 17 00:00:00 2001 From: GijsR Date: Wed, 8 Oct 2025 11:25:54 +0200 Subject: [PATCH 28/31] Update tests --- extensions/powershell/powershell.discover.ps1 | 4 ++-- .../powershell/powershell.discover.tests.ps1 | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/extensions/powershell/powershell.discover.ps1 b/extensions/powershell/powershell.discover.ps1 index 0622fdb68..01274cb2f 100644 --- a/extensions/powershell/powershell.discover.ps1 +++ b/extensions/powershell/powershell.discover.ps1 @@ -135,8 +135,8 @@ function Invoke-DscResourceDiscovery { end { $manifests if ($null -eq $manifests) { - "No manifests found" | Write-DscTrace - return + # explicitly return empty hash to avoid null output + $manifests = @{} } $manifests | ForEach-Object { $_ | ConvertTo-Json -Compress } diff --git a/extensions/powershell/powershell.discover.tests.ps1 b/extensions/powershell/powershell.discover.tests.ps1 index 22d71e4fc..56c0702bc 100644 --- a/extensions/powershell/powershell.discover.tests.ps1 +++ b/extensions/powershell/powershell.discover.tests.ps1 @@ -24,16 +24,16 @@ BeforeAll { $script:discoverScript = Join-Path $PSScriptRoot "powershell.discover.ps1" - # $cacheFilePath = if ($IsWindows) { - # Join-Path $env:LocalAppData "dsc\PowerShellDiscoverCache.json" - # } else { - # Join-Path $env:HOME ".dsc" "PowerShellDiscoverCache.json" - # } - # $script:cacheFilePath = $cacheFilePath + $cacheFilePath = if ($IsWindows) { + Join-Path $env:LocalAppData "dsc\PowerShellDiscoverCache.json" + } else { + Join-Path $env:HOME ".dsc" "PowerShellDiscoverCache.json" + } + $script:cacheFilePath = $cacheFilePath } Describe 'Tests for PowerShell resource discovery' { - BeforeAll { + BeforeEach { Remove-Item -Force -ErrorAction SilentlyContinue -Path $script:cacheFilePath } From 12009a9601b52e1b25b6175207e633b79dddbc53 Mon Sep 17 00:00:00 2001 From: GijsR Date: Wed, 8 Oct 2025 12:56:26 +0200 Subject: [PATCH 29/31] Remove Write-DscTrace --- extensions/powershell/powershell.discover.ps1 | 41 ++----------------- .../powershell/powershell.discover.tests.ps1 | 4 +- 2 files changed, 6 insertions(+), 39 deletions(-) diff --git a/extensions/powershell/powershell.discover.ps1 b/extensions/powershell/powershell.discover.ps1 index 01274cb2f..307bf6003 100644 --- a/extensions/powershell/powershell.discover.ps1 +++ b/extensions/powershell/powershell.discover.ps1 @@ -4,20 +4,6 @@ [CmdletBinding()] param () -function Write-DscTrace { - param( - [Parameter(Mandatory = $false)] - [ValidateSet('Error', 'Warn', 'Info', 'Debug', 'Trace')] - [string]$Operation = 'Debug', - - [Parameter(Mandatory = $true, ValueFromPipeline = $true)] - [string]$Message - ) - - $trace = @{$Operation.ToLower() = $Message } | ConvertTo-Json -Compress - $host.ui.WriteErrorLine($trace) -} - function Get-CacheFilePath { if ($IsWindows) { Join-Path $env:LocalAppData "dsc\PowerShellDiscoverCache.json" @@ -30,19 +16,15 @@ function Test-CacheValid { param([string]$CacheFilePath, [string[]]$PSPaths) if (-not (Test-Path $CacheFilePath)) { - "Cache file not found '$CacheFilePath'" | Write-DscTrace return $false } try { - "Reading cache file '$CacheFilePath'" | Write-DscTrace $cache = Get-Content -Raw $CacheFilePath | ConvertFrom-Json - "Checking cache for stale entries" | Write-DscTrace foreach ($entry in $cache.PathInfo.PSObject.Properties) { $path = $entry.Name if (-not (Test-Path $path)) { - "Detected non-existent cache entry '$path'" | Write-DscTrace return $false } @@ -50,28 +32,22 @@ function Test-CacheValid { $cachedLastWrite = [DateTime]$entry.Value if ($currentLastWrite -ne $cachedLastWrite) { - "Detected stale cache entry '$path' (cached: $cachedLastWrite, current: $currentLastWrite)" | Write-DscTrace return $false } } - "Checking cache for stale PSModulePath" | Write-DscTrace $cachedPaths = [string[]]$cache.PSModulePaths if ($cachedPaths.Count -ne $PSPaths.Count) { - "PSModulePath count changed (cached: $($cachedPaths.Count), current: $($PSPaths.Count))" | Write-DscTrace return $false } $diff = Compare-Object $cachedPaths $PSPaths if ($null -ne $diff) { - "PSModulePath contents changed" | Write-DscTrace return $false } - "Cache is valid" | Write-DscTrace return $true } catch { - "Stale cached entries detected: $_" | Write-DscTrace return $false } } @@ -82,19 +58,15 @@ function Invoke-DscResourceDiscovery { begin { $psPaths = $env:PSModulePath -split [System.IO.Path]::PathSeparator | Where-Object { $_ -notmatch 'WindowsPowerShell' } - "Discovered $($psPaths.Count) PSModulePath segments (excluding WindowsPowerShell)" | Write-DscTrace $cacheFilePath = Get-CacheFilePath $useCache = Test-CacheValid -CacheFilePath $cacheFilePath -PSPaths $psPaths } process { if ($useCache) { - "Using cached manifests" | Write-DscTrace $cache = Get-Content -Raw $cacheFilePath | ConvertFrom-Json $manifests = $cache.Manifests - "Retrieved $($manifests.Count) manifests from cache" | Write-DscTrace } else { - "Performing full discovery" | Write-DscTrace $manifests = $psPaths | ForEach-Object -Parallel { $searchPatterns = @('*.dsc.resource.json', '*.dsc.resource.yaml', '*.dsc.resource.yml') $enumOptions = [System.IO.EnumerationOptions]@{ IgnoreInaccessible = $false; RecurseSubdirectories = $true } @@ -107,9 +79,6 @@ function Invoke-DscResourceDiscovery { } } -ThrottleLimit 10 - "Discovered $($manifests.Count) manifests" | Write-DscTrace - - "Building cache" | Write-DscTrace $pathInfo = @{} foreach ($path in $psPaths) { if (Test-Path $path) { @@ -119,22 +88,20 @@ function Invoke-DscResourceDiscovery { $cacheObject = @{ PSModulePaths = $psPaths - PathInfo = $pathInfo - Manifests = $manifests + PathInfo = $pathInfo + Manifests = $manifests } $cacheDir = Split-Path $cacheFilePath -Parent if (-not (Test-Path $cacheDir)) { - "Creating cache directory '$cacheDir'" | Write-DscTrace New-Item -ItemType Directory -Path $cacheDir -Force | Out-Null } - "Saving cache to '$cacheFilePath'" | Write-DscTrace + $cacheObject | ConvertTo-Json -Depth 10 | Set-Content -Path $cacheFilePath -Force } } end { - $manifests - if ($null -eq $manifests) { + if ($null -eq $manifests -or [string]::IsNullOrEmpty($manifests)) { # explicitly return empty hash to avoid null output $manifests = @{} } diff --git a/extensions/powershell/powershell.discover.tests.ps1 b/extensions/powershell/powershell.discover.tests.ps1 index 56c0702bc..a045a34bb 100644 --- a/extensions/powershell/powershell.discover.tests.ps1 +++ b/extensions/powershell/powershell.discover.tests.ps1 @@ -33,14 +33,14 @@ BeforeAll { } Describe 'Tests for PowerShell resource discovery' { - BeforeEach { + BeforeAll { Remove-Item -Force -ErrorAction SilentlyContinue -Path $script:cacheFilePath } It 'Should find DSC PowerShell resources' { $out = dsc resource list | ConvertFrom-Json $LASTEXITCODE | Should -Be 0 - $out.directory | Should -Contain $TestDrive + $out.manifest.type | Should -Contain 'Test/FakeResource' } It 'Should create cache file on first run' { From 4438bf97188da09d4e7d359b08f9ffe178923c0e Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Wed, 8 Oct 2025 19:23:00 +0200 Subject: [PATCH 30/31] Move test plus docs fix --- .../schemas/extension/stdout/discover.md | 8 ++++- extensions/powershell/powershell.discover.ps1 | 7 ++-- .../powershell/powershell.discover.tests.ps1 | 32 ++++++++----------- 3 files changed, 24 insertions(+), 23 deletions(-) diff --git a/docs/reference/schemas/extension/stdout/discover.md b/docs/reference/schemas/extension/stdout/discover.md index fc33b89d6..07744f919 100644 --- a/docs/reference/schemas/extension/stdout/discover.md +++ b/docs/reference/schemas/extension/stdout/discover.md @@ -22,13 +22,16 @@ Type: object ## Description -Represents the actual state of a resource instance in DSCpath to a discovered DSC resource or +Represents the actual state of a resource instance in DSC path to a discovered DSC resource or extension manifest on the system. DSC expects every JSON Line emitted to stdout for the **Discover** operation to adhere to this schema. The output must be a JSON object. The object must define the full path to the discovered manifest. If an extension returns JSON that is invalid against this schema, DSC raises an error. +If the extension doesn't discover any manifests, it must return nothing to stdout. An empty output +indicates no resources were found. + ## Required Properties The output for the `discover` operation must include these properties: @@ -43,6 +46,9 @@ The value for this property must be the absolute path to a manifest file on the manifest can be for a DSC resource or extension. If the returned path doesn't exist, DSC raises an error. +Each discovered manifest must be emitted as a separate JSON Line to stdout. If no manifests are +discovered, the extension must not emit any output to stdout. + ```yaml Type: string Required: true diff --git a/extensions/powershell/powershell.discover.ps1 b/extensions/powershell/powershell.discover.ps1 index 307bf6003..e94678c8e 100644 --- a/extensions/powershell/powershell.discover.ps1 +++ b/extensions/powershell/powershell.discover.ps1 @@ -102,11 +102,10 @@ function Invoke-DscResourceDiscovery { } end { if ($null -eq $manifests -or [string]::IsNullOrEmpty($manifests)) { - # explicitly return empty hash to avoid null output - $manifests = @{} + # Return nothing + } else { + $manifests | ForEach-Object { $_ | ConvertTo-Json -Compress } } - - $manifests | ForEach-Object { $_ | ConvertTo-Json -Compress } } } diff --git a/extensions/powershell/powershell.discover.tests.ps1 b/extensions/powershell/powershell.discover.tests.ps1 index a045a34bb..66203e8fb 100644 --- a/extensions/powershell/powershell.discover.tests.ps1 +++ b/extensions/powershell/powershell.discover.tests.ps1 @@ -4,15 +4,15 @@ BeforeAll { $fakeManifest = @{ '$schema' = "https://aka.ms/dsc/schemas/v3/bundled/resource/manifest.json" - type = "Test/FakeResource" - version = "0.1.0" - get = @{ + type = "Test/FakeResource" + version = "0.1.0" + get = @{ executable = "fakeResource" - args = @( + args = @( "get", @{ jsonInputArg = "--input" - mandatory = $true + mandatory = $true } ) } @@ -30,24 +30,14 @@ BeforeAll { Join-Path $env:HOME ".dsc" "PowerShellDiscoverCache.json" } $script:cacheFilePath = $cacheFilePath + + Remove-Item -Force -ErrorAction SilentlyContinue -Path $script:cacheFilePath } -Describe 'Tests for PowerShell resource discovery' { - BeforeAll { - Remove-Item -Force -ErrorAction SilentlyContinue -Path $script:cacheFilePath - } - - It 'Should find DSC PowerShell resources' { - $out = dsc resource list | ConvertFrom-Json - $LASTEXITCODE | Should -Be 0 - $out.manifest.type | Should -Contain 'Test/FakeResource' - } - +Describe 'Tests for PowerShell resource discovery' { It 'Should create cache file on first run' { $script:cacheFilePath | Should -Not -Exist - $null = & $script:discoverScript 2>&1 - $script:cacheFilePath | Should -Exist $cache = Get-Content -Raw $script:cacheFilePath | ConvertFrom-Json @@ -55,6 +45,12 @@ Describe 'Tests for PowerShell resource discovery' { $cache.PathInfo | Should -Not -BeNullOrEmpty $cache.Manifests | Should -Not -BeNullOrEmpty } + + It 'Should find DSC PowerShell resources' { + $out = dsc resource list | ConvertFrom-Json + $LASTEXITCODE | Should -Be 0 + $out.manifest.type | Should -Contain 'Test/FakeResource' + } It 'Should use cache on subsequent runs' { $null = & $script:discoverScript 2>&1 From d9082b86e08f2442f90bffcd67171d2ce7b8d071 Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Wed, 8 Oct 2025 20:06:42 +0200 Subject: [PATCH 31/31] Merge conflict --- build.ps1 | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/build.ps1 b/build.ps1 index e312538e1..a83db83c4 100755 --- a/build.ps1 +++ b/build.ps1 @@ -353,12 +353,9 @@ if (!$SkipBuild) { New-Item -ItemType Directory $target -ErrorAction Ignore > $null # make sure dependencies are built first so clippy runs correctly - $windows_projects = @("pal", "registry_lib", "registry", "reboot_pending", "wmi-adapter", "configurations/windows", "extensions/appx", "extensions/powershell") + $windows_projects = @("lib/dsc-lib-pal", "lib/dsc-lib-registry", "resources/registry", "resources/reboot_pending", "adapters/wmi", "configurations/windows", "extensions/appx", "extensions/powershell") $macOS_projects = @("resources/brew", "extensions/powershell") $linux_projects = @("resources/apt", "extensions/powershell") - $windows_projects = @("lib/dsc-lib-pal", "lib/dsc-lib-registry", "resources/registry", "resources/reboot_pending", "adapters/wmi", "configurations/windows", 'extensions/appx') - $macOS_projects = @("resources/brew") - $linux_projects = @("resources/apt") # projects are in dependency order $projects = @(