Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions extensions/powershell/secret/copy_files.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
microsoft.powershell.secret.ps1
microsoft.powershell.dsc.extension.json
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding copy_files.txt suggests these files should be included in build/package outputs, but packaging.ps1 currently only builds/copies extensions/appx under the extensions/ tree. Without adding extensions/powershell/secret to the build/packaging project list (or otherwise wiring this directory into packaging), these files likely won’t ship in produced artifacts.

Suggested change
microsoft.powershell.dsc.extension.json

Copilot uses AI. Check for mistakes.
Original file line number Diff line number Diff line change
@@ -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"
}
]
}
}
25 changes: 25 additions & 0 deletions extensions/powershell/secret/microsoft.powershell.secret.ps1
Original file line number Diff line number Diff line change
@@ -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
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Trailing whitespace after -ErrorAction Ignore can cause noisy diffs/lint failures in some pipelines; please remove the extra space at end of line.

Suggested change
$secret = Get-Secret @secretParams -ErrorAction Ignore
$secret = Get-Secret @secretParams -ErrorAction Ignore

Copilot uses AI. Check for mistakes.

Write-Output $secret
Comment on lines +22 to +24
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If Get-Secret isn’t available (module missing) or Get-Secret fails for reasons other than “not found”, this script silently produces no output due to the Get-Command guard and -ErrorAction Ignore. That can surface as a misleading “Secret '' not found” from DSC. Consider emitting a clear error to stderr and returning a non-zero exit code when prerequisites are missing or when Get-Secret errors unexpectedly, while still returning no output for the “secret not found” case.

Suggested change
$secret = Get-Secret @secretParams -ErrorAction Ignore
Write-Output $secret
try {
$secret = Get-Secret @secretParams -ErrorAction Stop
Write-Output $secret
}
catch {
$errorRecord = $_
$exceptionTypeName = $errorRecord.Exception.GetType().Name
$fullyQualifiedErrorId = $errorRecord.FullyQualifiedErrorId
# Treat "secret not found" as a non-error and return no output to preserve existing behavior.
if ($exceptionTypeName -eq 'SecretNotFoundException' -or
$fullyQualifiedErrorId -like '*SecretNotFound*') {
return
}
Write-Error -Message ("Failed to retrieve secret '{0}': {1}" -f $Name, $errorRecord.Exception.Message)
exit 1
}
}
else {
Write-Error -Message "Required cmdlet 'Get-Secret' was not found. Ensure the Microsoft.PowerShell.SecretManagement module is installed and imported."
exit 1

Copilot uses AI. Check for mistakes.
}
41 changes: 41 additions & 0 deletions extensions/powershell/secret/microsoft.powershell.secret.tests.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.

BeforeDiscovery {
$runningInCI = $false
}
Comment on lines +4 to +6
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$runningInCI is hard-coded to $false, but the Describe block uses -Skip:($runningInCI), so these tests will always run (including on developer machines). Because the test later calls Reset-SecretStore, this can wipe a developer’s existing SecretStore; please detect CI via environment variables (e.g., GITHUB_RUN_ID/TF_BUILD) and gate the suite appropriately (or remove the destructive calls outside CI).

Copilot uses AI. Check for mistakes.

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
Comment on lines +12 to +14
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test downloads modules directly from PSGallery. In this repo’s CI, packaging.ps1 switches to the internal CFS mirror when $env:TF_BUILD is set; using PSGallery here can fail in ADO/offline environments. Consider selecting the repository dynamically (and registering CFS when needed) to match the build/test pipeline.

Suggested change
foreach ($module in $FullyQualifiedName) {
if (-not (Get-Module -ListAvailable -FullyQualifiedName $module)) {
Save-PSResource -Name $module.ModuleName -Version $module.ModuleVersion -Path $TestDrive -Repository PSGallery -TrustRepository
# Select repository dynamically to align with CI configuration
$repositoryName = 'PSGallery'
if ($env:TF_BUILD -and (Get-Command -Name Get-PSResourceRepository -ErrorAction SilentlyContinue)) {
$cfsRepo = Get-PSResourceRepository -Name 'CFS' -ErrorAction SilentlyContinue
if ($null -ne $cfsRepo) {
$repositoryName = 'CFS'
}
}
foreach ($module in $FullyQualifiedName) {
if (-not (Get-Module -ListAvailable -FullyQualifiedName $module)) {
Save-PSResource -Name $module.ModuleName -Version $module.ModuleVersion -Path $TestDrive -Repository $repositoryName -TrustRepository

Copilot uses AI. Check for mistakes.
}
}

$env:PSModulePath += [System.IO.Path]::PathSeparator + $TestDrive
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$env:PSModulePath is modified but never restored. This can leak state into later Pester suites in the same run; please capture the original value in BeforeAll and restore it in AfterAll (similar to how other tests restore $env:PATH).

Copilot uses AI. Check for mistakes.
}

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
Comment on lines +23 to +25
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reset-SecretStore resets the current user’s SecretStore and can delete/overwrite existing secrets. If this suite is intended for CI-only execution, please ensure it is skipped locally; otherwise, consider using an isolated/temporary store configuration so running Invoke-Pester doesn’t destroy a developer’s SecretStore state.

Copilot uses AI. Check for mistakes.
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'
}
}
Loading