Modules
Modules copied to clipboard
[SecretManagement] ArgumentCompleter / cached secret info
#Implement an argument completer for the SecretManagement module.
Currently if you want to use the cmdlet, you need to know your secrets / vault name.
The Get-SecretInfo
cmdlet is super useful for that.
That being said, it could be implemented directly into the module using an argument completer for the vaults and secrets name
Proposed technical implementation details (optional)
Here's a simple example that work with cmdlets for the Vault parameters and secrets parameters.
Register-ArgumentCompleter -CommandName Get-Secret, Set-Secret, Get-SecretInfo -ParameterName Vault -ScriptBlock {
Get-SecretVault | Select -ExpandProperty Name | foreach-object {
[System.Management.Automation.CompletionResult]::new($_)
}
}
Register-ArgumentCompleter -CommandName Get-Secret, Remove-Secret -ParameterName Name -ScriptBlock {
param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameter)
$VaultNameFilter = $fakeBoundParameter.Vault
(Get-SecretInfo -Vault $VaultNameFilter ) | Select -ExpandProperty Name | foreach-object {
[System.Management.Automation.CompletionResult]::new($_)
}
}
The only drawback is that relying on Get-SecretInfo as it is means that you have to wait on remote vault querying, which can add a couple of seconds. (But since the value completion is not triggered automatically, that shouldn't be a problem)
Still, for the Vault parameter, it is perfect.
For the Name, I think it make sense anyway (value completion must be manuall triggered so it's a choice of whether you enter your name manually (if you know it) or trigger the value completion, which is the equivalent of running a Get-SecretInfo
in the console then copy/paste the name.
I think it could be nice to have a the possibility to cache results from Get-SecretInfo. That would benefit the argument completion.
Unless having secret name saved somewhere is a security concern for some ? Anyway, expanding on that idea, I made a second version that implement a new Get-SecretInfo.
That new Get-SecretInfo (exposed through an alias to override the original) expose a new [Switch] Cache
parameter which will use a cached version of the credentials, rendering the argument completion for secret names instant.
Calling Get-SecretInfo
normally will refresh the cached informations.
Get-SecretInfo -Cache
will use cached informations and / or generate them if they are not present.
function Get-SecretInfo {
[CmdletBinding()]
param (
[String]$Vault,
[String]$Name,
[Switch]$Cache
)
$CacheParamstr = 'Cache'
$VaultPath = "$env:USERPROFILE\Powershell\SecretsManagement\Vaults"
$UseCache = $False
if ($PSBoundParameters.ContainsKey($CacheParamstr)) {
$UseCache = $PSBoundParameters.Item($CacheParamstr)
[void]$PSBoundParameters.Remove($CacheParamstr)
}
if (! (Test-Path $VaultPath)) { New-Item $VaultPath -ItemType Directory }
$VaultNames = [System.Collections.Generic.List[String]]::new()
$CachedOutput = [System.Collections.Generic.List[PSObject]]::new()
if ($UseCache) {
if ($PSBoundParameters.ContainsKey('Vault')) {
$VaultNames.Add($PSBoundParameters.Item('Vault'))
}
else {
$VaultNames = @(Get-SecretVault | Select -ExpandProperty Name)
}
foreach ($Vault in $VaultNames) {
if ($Vault -eq 'BuiltInLocalVault') {
$CachedOutput.Add((Get-SecretInfo -Vault 'BuiltInLocalVault'))
continue
}
$CurrVaultPath = "$VaultPath\$Vault.json"
if (Test-Path -Path $CurrVaultPath) {
$value = (Get-Content -Path $CurrVaultPath -Raw | ConvertFrom-Json) | Select -Property Name, @{n = 'Type'; e = { [Microsoft.PowerShell.SecretManagement.SecretType]$_.Type } }, VaultName
$CachedOutput.Add($value)
}
else {
try {
$CurrentVault = @(Microsoft.PowerShell.SecretManagement\Get-SecretInfo -Vault $Vault)
$CurrentVault | Select | ConvertTo-Json | Out-File $CurrVaultPath
$CachedOutput.Add($CurrentVault)
}
catch {
}
}
}
return $CachedOutput
}
if (! $PSBoundParameters.ContainsKey('Vault')) { Get-ChildItem -Path $VaultPath | Remove-Item }
$Output = Microsoft.PowerShell.SecretManagement\Get-SecretInfo @PSBoundParameters
$GrpVault = $Output | Group-Object -Property VaultName
foreach ($grp in $GrpVault) {
if ($grp.Name -eq 'BuiltInLocalVault') { Continue }
$CurrVaultPath = "$VaultPath\$($grp.Name).json"
$grp.Group | ConvertTo-Json | Out-File $CurrVaultPath
}
return $Output
}
Register-ArgumentCompleter -CommandName Get-Secret, Set-Secret, Get-SecretInfo, Test-SecretVault -ParameterName Vault -ScriptBlock {
Get-SecretVault | Select -ExpandProperty Name | foreach-object {
[System.Management.Automation.CompletionResult]::new($_)
}
}
Register-ArgumentCompleter -CommandName Unregister-SecretVault -ParameterName Name -ScriptBlock {
Get-SecretVault | Select -ExpandProperty Name | foreach-object {
[System.Management.Automation.CompletionResult]::new($_)
}
}
Register-ArgumentCompleter -CommandName Get-Secret, Remove-Secret -ParameterName Name -ScriptBlock {
param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameter)
$VaultNameFilter = $fakeBoundParameter.Vault
(Get-SecretInfo -Vault $VaultNameFilter -cache) | Select -ExpandProperty Name | foreach-object {
[System.Management.Automation.CompletionResult]::new($_)
}
}
Both solution (original without caching and my favorite, with caching), if implemented, would provide a greater code writing experience.
I added missing cmdlet and published a version on the Powershell Gallery. https://www.powershellgallery.com/packages/SecretManagementArgumentCompleter/1.0.0
I am leaving this feature request open as I still think it would be great if supported natively.