From b725f2c711eb6b5291f505bb482a0ae28bc5216c Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Fri, 22 Aug 2025 09:30:23 +0200 Subject: [PATCH 1/3] Implement Microsoft.PowerShell.SecretManagement extension --- build.ps1 | 12 ++++-- extensions/powershell/secret/copy_files.txt | 2 + .../microsoft.powershell.dsc.extension.json | 22 ++++++++++ .../secret/microsoft.powershell.secret.ps1 | 25 +++++++++++ .../microsoft.powershell.secret.tests.ps1 | 41 +++++++++++++++++++ 5 files changed, 99 insertions(+), 3 deletions(-) create mode 100644 extensions/powershell/secret/copy_files.txt create mode 100644 extensions/powershell/secret/microsoft.powershell.dsc.extension.json create mode 100644 extensions/powershell/secret/microsoft.powershell.secret.ps1 create mode 100644 extensions/powershell/secret/microsoft.powershell.secret.tests.ps1 diff --git a/build.ps1 b/build.ps1 index 78f71004a..fe49fbf9e 100755 --- a/build.ps1 +++ b/build.ps1 @@ -60,6 +60,8 @@ $filesForWindowsPackage = @( 'assertion.dsc.resource.json', 'group.dsc.resource.json', 'include.dsc.resource.json', + 'microsoft.powershell.dsc.extension.json', + 'microsoft.powershell.secret.ps1', 'NOTICE.txt', 'osinfo.exe', 'osinfo.dsc.resource.json', @@ -98,6 +100,8 @@ $filesForLinuxPackage = @( 'apt.dsc.resource.sh', 'group.dsc.resource.json', 'include.dsc.resource.json', + 'microsoft.powershell.dsc.extension.json', + 'microsoft.powershell.secret.ps1', 'NOTICE.txt', 'osinfo', 'osinfo.dsc.resource.json', @@ -123,6 +127,8 @@ $filesForMacPackage = @( 'brew.dsc.resource.sh', 'group.dsc.resource.json', 'include.dsc.resource.json', + 'microsoft.powershell.dsc.extension.json', + 'microsoft.powershell.secret.ps1', 'NOTICE.txt', 'osinfo', 'osinfo.dsc.resource.json', @@ -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/secret") + $macOS_projects = @("resources/brew", "extensions/powershell/secret") + $linux_projects = @("resources/apt", "extensions/powershell/secret") # projects are in dependency order $projects = @( diff --git a/extensions/powershell/secret/copy_files.txt b/extensions/powershell/secret/copy_files.txt new file mode 100644 index 000000000..b9e7ea40d --- /dev/null +++ b/extensions/powershell/secret/copy_files.txt @@ -0,0 +1,2 @@ +microsoft.powershell.secret.ps1 +microsoft.powershell.dsc.extension.json diff --git a/extensions/powershell/secret/microsoft.powershell.dsc.extension.json b/extensions/powershell/secret/microsoft.powershell.dsc.extension.json new file mode 100644 index 000000000..4c81021da --- /dev/null +++ b/extensions/powershell/secret/microsoft.powershell.dsc.extension.json @@ -0,0 +1,22 @@ +{ + "$schema": "https://aka.ms/dsc/schemas/v3/bundled/extension/manifest.json", + "type": "Microsoft.PowerShell/SecretManagement", + "version": "0.1.0", + "description": "Retrieve secrets using the Microsoft.PowerShell.SecretManagement module", + "secret": { + "executable": "pwsh", + "args": [ + "-NoLogo", + "-NonInteractive", + "-NoProfile", + "-Command", + "./microsoft.powershell.secret.ps1", + { + "nameArg": "-Name" + }, + { + "vaultArg": "-Vault" + } + ] + } +} diff --git a/extensions/powershell/secret/microsoft.powershell.secret.ps1 b/extensions/powershell/secret/microsoft.powershell.secret.ps1 new file mode 100644 index 000000000..928b47b0c --- /dev/null +++ b/extensions/powershell/secret/microsoft.powershell.secret.ps1 @@ -0,0 +1,25 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +[CmdletBinding()] +param( + [Parameter(Mandatory = $true)] + [string]$Name, + [Parameter()] + [string]$Vault +) + +if (Get-Command Get-Secret -ErrorAction Ignore) { + $secretParams = @{ + Name = $Name + AsPlainText = $true + } + + if (-not ([string]::IsNullOrEmpty($Vault))) { + $secretParams['Vault'] = $Vault + } + + $secret = Get-Secret @secretParams -ErrorAction Ignore + + Write-Output $secret +} \ No newline at end of file diff --git a/extensions/powershell/secret/microsoft.powershell.secret.tests.ps1 b/extensions/powershell/secret/microsoft.powershell.secret.tests.ps1 new file mode 100644 index 000000000..285eaf123 --- /dev/null +++ b/extensions/powershell/secret/microsoft.powershell.secret.tests.ps1 @@ -0,0 +1,41 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +BeforeDiscovery { + $runningInCI = $false +} + +BeforeAll { + $FullyQualifiedName = @() + $FullyQualifiedName += @{ModuleName="Microsoft.PowerShell.SecretManagement";ModuleVersion="1.1.2"} + $FullyQualifiedName += @{ModuleName="Microsoft.PowerShell.SecretStore";ModuleVersion="1.0.6"} + foreach ($module in $FullyQualifiedName) { + if (-not (Get-Module -ListAvailable -FullyQualifiedName $module)) { + Save-PSResource -Name $module.ModuleName -Version $module.ModuleVersion -Path $TestDrive -Repository PSGallery -TrustRepository + } + } + + $env:PSModulePath += [System.IO.Path]::PathSeparator + $TestDrive +} + +Describe 'Tests for PowerShell Secret Management' -Skip:($runningInCI) { + It 'Should get secret from default store' { + # Instead of doing it in the BeforeAll block, reset the store here as we know we are running in the CI + Reset-SecretStore -Password (ConvertTo-SecureString -AsPlainText -String 'P@ssw0rd' -Force) -Force + Register-SecretVault -Name 'VaultA' -ModuleName 'Microsoft.PowerShell.SecretStore' -DefaultVault + Set-Secret -Name TestSecret -Secret "Super@SecretPassword" + + $configYaml = @' + $schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json + resources: + - name: Echo + type: Microsoft.DSC.Debug/Echo + properties: + output: "[secret('TestSecret')]" +'@ + $out = dsc -l trace config get -i $configYaml 2> $TestDrive/error.log | ConvertFrom-Json + $LASTEXITCODE | Should -Be 0 -Because (Get-Content -Raw -Path $TestDrive/error.log) + $out.results.Count | Should -Be 1 + $out.results[0].result.actualState.Output | Should -BeExactly 'Super@SecretPassword' + } +} \ No newline at end of file From 29d76fb3d29b7f95e5c5597965bd9fe1840cb42f Mon Sep 17 00:00:00 2001 From: GijsR Date: Mon, 25 Aug 2025 13:29:23 +0200 Subject: [PATCH 2/3] Fix discovery test --- dsc/tests/dsc_extension_discover.tests.ps1 | 46 +++++++++++----------- test.ps1 | 14 +++++++ 2 files changed, 37 insertions(+), 23 deletions(-) create mode 100644 test.ps1 diff --git a/dsc/tests/dsc_extension_discover.tests.ps1 b/dsc/tests/dsc_extension_discover.tests.ps1 index d37455465..6c645c921 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/SecretManagement'; version = '0.1.0'; capabilities = @('secret') } + @{ 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/SecretManagement'; version = '0.1.0'; capabilities = @('secret') } + @{ 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/test.ps1 b/test.ps1 new file mode 100644 index 000000000..5e49f02d3 --- /dev/null +++ b/test.ps1 @@ -0,0 +1,14 @@ +configuration MyConfiguration { + Import-DscResource -ModuleName PSDesiredStateConfiguration + Node localhost + { + Environment CreatePathEnvironmentVariable + { + Name = 'TestPathEnvironmentVariable' + Value = 'TestValue' + Ensure = 'Present' + Path = $true + Target = @('Process') + } + } + } \ No newline at end of file From 85cbaf587a0ad6b375f85f287b085fa10e4ab562 Mon Sep 17 00:00:00 2001 From: GijsR Date: Mon, 25 Aug 2025 14:11:42 +0200 Subject: [PATCH 3/3] Remove unused file --- test.ps1 | 14 -------------- 1 file changed, 14 deletions(-) delete mode 100644 test.ps1 diff --git a/test.ps1 b/test.ps1 deleted file mode 100644 index 5e49f02d3..000000000 --- a/test.ps1 +++ /dev/null @@ -1,14 +0,0 @@ -configuration MyConfiguration { - Import-DscResource -ModuleName PSDesiredStateConfiguration - Node localhost - { - Environment CreatePathEnvironmentVariable - { - Name = 'TestPathEnvironmentVariable' - Value = 'TestValue' - Ensure = 'Present' - Path = $true - Target = @('Process') - } - } - } \ No newline at end of file