Writing an Extension Vault for PowerShell SecretManagement Preview 4
Tags: powershell
Published: 2021-01-14 19:37:53

> _This article was written by Adam Listek. He is a frequent contributor to the Adam the Automator (ATA) blog. If you'd like to read more from this author, check out his [ATA author page](http://adamtheautomator.com/author/alistek). Be sure to also check out [more how-to posts on cloud computing, system administration, IT, and DevOps on adamtheautomator.com](https://adamtheautomator.com)!_

<!-- wp:paragraph -->
<p>If you've ever hardcoded a password, an API key, or a private certificate in a script, stop! You need to secure that sensitive information! One way to do that is with the PowerShell&nbsp;<a href="https://github.com/PowerShell/SecretManagement">SecretManagement</a>&nbsp;module. Offering a convenient way for a user to store and retrieve secrets using PowerShell, the SecretManagement module also has the ability to interface with different back-end systems such as Azure KeyStore or KeePass too using an <a href="https://github.com/PowerShell/SecretManagement/tree/master/ExtensionModules">extension vault</a>!</p>
<!-- /wp:paragraph -->

<!-- wp:quote -->
<blockquote class="wp-block-quote"><p><em>An extension vault is a term used within the SecretManagement module to refer to a nested PowerShell module used within the SecretManagement module. You'll also hear vaults referred to as extension modules also.</em></p></blockquote>
<!-- /wp:quote -->

<!-- wp:paragraph -->
<p>In this article, we create an extension vault that is backed by the open-source .NET database&nbsp;<a href="https://www.litedb.org/">LiteDB</a>. An extension module can use any back-end, but using LiteDB allows us to use a .NET native simple database that demonstrates the SecretManagement module capabilities and how to interact with different systems.</p>
<!-- /wp:paragraph -->

<!-- wp:quote -->
<blockquote class="wp-block-quote"><p><em>Note: Don't get confused with the "secret" in the name. Even though this module is meant to be used for "secret" management, as you'll see in this tutorial, it really could be used as a frontend or just about any data source, if you wish.</em></p></blockquote>
<!-- /wp:quote -->

<!-- wp:heading -->
<h2 id="h-prerequisites">Prerequisites</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>This article is a tutorial. If you'd like to follow along, please be sure you have the following prerequisites in order.</p>
<!-- /wp:paragraph -->

<!-- wp:list -->
<ul><li><a href="https://adamtheautomator.com/updating-to-powershell-7/">PowerShell 7</a></li><li>The Powershell SecretManagement module - You can install this module by running <code>Install-Module -Name 'Microsoft.PowerShell.SecretManagement' -AllowPrerelease</code>. This tutorial will be using the most current version at the time of writing which is Preview 4.</li><li>The LiteDB database - Though you can create any backing store for your secret database, this article demonstrates creating an extension module to leverage LiteDB in the background. You can install LiteDB by running <code>Install-Package -Name 'LiteDB' -MinimumVersion '5.0.9' -Source 'nuget.org' -Scope 'CurrentUser'</code></li></ul>
<!-- /wp:list -->

<!-- wp:heading -->
<h2 id="h-understanding-the-powershell-secretmanagement-module">Understanding the PowerShell SecretManagement Module</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>The PowerShell SecretManagement module is a <a class="wpil_keyword_link" href="https://adamtheautomator.com/powershell-modules/" title="PowerShell module" data-wpil-keyword-link="linked">PowerShell module</a> that provides a standardized, Microsoft-supported way of interacting with sensitive information. Think of this module as a better way of storing and retrieving sensitive information than using <code>Get-Credential</code> or perhaps storing <a href="https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.security/convertto-securestring?view=powershell-7">secure strings</a> in common text files.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>This module has two main object types; secrets and vaults. A vault is a data store that stores secrets. To store and retrieve secrets, you must first <em>register</em> a vault to then add secrets to that vault. You can then <em>unregister</em> that vault if you no longer need to store secrets in that data source.</p>
<!-- /wp:paragraph -->

<!-- wp:quote -->
<blockquote class="wp-block-quote"><p><em>When you install the module, there are no actual vaults available. If you want to install the previously default extension vault, run <code>Install-Module -Name 'Microsoft.PowerShell.SecretStore' -AllowPrerelease</code>. Otherwise, keep reading as you'll learn how to create a custom one with LiteDb.</em></p></blockquote>
<!-- /wp:quote -->

<!-- wp:paragraph -->
<p>You'll find a few different cmdlets in the module to interact with the various objects.</p>
<!-- /wp:paragraph -->

<!-- wp:list -->
<ul><li><code>Get-Secret</code> - Retrieve a secret from a vault.</li><li><code>Get-SecretInfo</code> - Retrieve information about one or more secrets in a vault.</li><li><code>Get-SecretVault</code> - Retrieve information about a specific vault.</li><li><code>Register-SecretVault</code> - Register a new secret vault.</li><li><code>Remove-Secret</code> - Remove a secret from a vault.</li><li><code>Set-Secret</code> - Create a new secret or update an existing secret in a vault.</li><li><code>Test-SecretVault</code> - Verify that a vault is correctly configured.</li><li><code>Unregister-SecretVault</code> - Unregister a previously registered vault.</li></ul>
<!-- /wp:list -->

<!-- wp:paragraph -->
<p>To actually make any of the above cmdlets functional, you need to back them with a vault. To do that, the SecretManagement module requires you to build your own functions with the same name essentially overwriting the default cmdlets.</p>
<!-- /wp:paragraph -->

<!-- wp:quote -->
<blockquote class="wp-block-quote"><p><em>If your extension does not offer the ability to add or update a secret, you could implement the function but only throw an error message stating that it is not supported.</em></p></blockquote>
<!-- /wp:quote -->

<!-- wp:paragraph -->
<p>Next, we will explore how to structure a PowerShell SecretManagement extension vault and start to implement the commands.</p>
<!-- /wp:paragraph -->

<!-- wp:heading -->
<h2 id="h-creating-a-powershell-secretmanagement-extension-vault">Creating a PowerShell SecretManagement Extension Vault</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>Now that you have seen the basic cmdlet structure and objects you'll be working with, let's now learn how to create an example extension vault!</p>
<!-- /wp:paragraph -->

<!-- wp:quote -->
<blockquote class="wp-block-quote"><p><em>The root directory to create the first parent module directory is in the SecretManagement module's ExtensionModules folder. You can find this directory by running <code>(Get-Module -Name Microsoft.PowerShell.SecretManagement).Path | Split-Path -Parent</code>.</em></p></blockquote>
<!-- /wp:quote -->

<!-- wp:heading {"level":3} -->
<h3 id="h-create-the-main-vault-folder-and-manifest">Create the Main Vault Folder and Manifest</h3>
<!-- /wp:heading -->

<!-- wp:list {"ordered":true} -->
<ol><li>Open up PowerShell and create a variable holding the path to the <em>ExtensionsModule</em> parent directory which all underlying module folders and files will be created. </li></ol>
<!-- /wp:list -->

<!-- wp:code -->
<pre class="wp-block-code"><code lang="powershell" class="language-powershell">$vaultParentPath = (Get-Module -Name Microsoft.PowerShell.SecretManagement).Path | Split-Path -Parent. </code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>2. Create the parent module directory in the <em>ExtensionModules</em> directory. Name this directory <em>SecretManagement.&lt;Vault source&gt;</em>. In this case, the directory is called <em>SecretManagement.LiteDB.</em></p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code lang="powershell" class="language-powershell">New-Item -Path "$vaultParentPath\SecretManagement.LiteDb" -ItemType Directory</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>3. Create a module manifest for the <em>SecretManagement.LiteDb</em> module outlining some common manifest attributes.</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code lang="powershell" class="language-powershell">New-ModuleManifest -CompatiblePSEditions Core -Author '&lt;you&gt;' -NestedModules './SecretManagement.LiteDB.Extension/SecretManagement.LiteDB.Extension.psd1' -RequiredModules 'Microsoft.Powershell.SecretManagement' -PowerShellVersion 7.0 -Tags 'SecretManagement' -Path "$vaultParentPath\SecretManagement.LiteDb"</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>4. Once New-ModuleManifest creates the template manifest, open it up and ensure it mirrors the below manifest.</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code lang="powershell" class="language-powershell">@{
  ModuleVersion        = '0.0.0.1'
  # Make this only compatible with PowerShell Core/7
  CompatiblePSEditions = @('Core')
  GUID                 = '&lt;guid here&gt;' ## Generate a GUID with New-Guid
  Author               = 'Adam Listek'
  # Pointed to the nested module definition file
  NestedModules   = @(
    './SecretManagement.LiteDB.Extension/SecretManagement.LiteDB.Extension.psd1'
  )
  # Make this module dependant on the SecretManagement module
  RequiredModules = @(
    "Microsoft.Powershell.SecretManagement"
  )
  # Set the minimum <a class="wpil_keyword_link" href="https://adamtheautomator.com/check-powershell-version/" title="PowerShell version" data-wpil-keyword-link="linked">PowerShell version</a> of 7.0 as Core is the only one supported by the primary module and this is the recommended PowerShell version
  PowershellVersion = '7.0'
  FunctionsToExport = @()
  CmdletsToExport   = @()
  VariablesToExport = @()
  AliasesToExport   = @()
  # Not necessary, but recommended to tag your module with SecretManagement to assist PowerShell Gallery users in finding your module
  PrivateData = @{
    PSData = @{
      Tags = @('SecretManagement')
    }
  }
}</code></pre>
<!-- /wp:code -->

<!-- wp:heading {"level":3} -->
<h3 id="h-creating-the-extension-vault-directory-and-manifest">Creating the Extension Vault Directory and Manifest</h3>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>Once you've created the main extension folder and manifest, next create the extension vault module with its manifest. This module is a nested module inside of the main parent extension module directory.</p>
<!-- /wp:paragraph -->

<!-- wp:list {"ordered":true} -->
<ol><li>Create the extension vault's folder. </li></ol>
<!-- /wp:list -->

<!-- wp:code -->
<pre class="wp-block-code"><code lang="powershell" class="language-powershell">New-Item -Path "$vaultParentPath\SecretManagement.LiteDb\SecretManagement.LiteDB.Extension" -ItemType Directory</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>2. Create the module manifest. This manifest only requires two attributes; RootModule and FunctionsToExport.</p>
<!-- /wp:paragraph -->

<!-- wp:quote -->
<blockquote class="wp-block-quote"><p><em>You'll notice the FunctionsToExport parameter shows the same function names as the SecretManagement module itself. An extension vault's commands "overwrite" the default SecretManagement modules.</em></p></blockquote>
<!-- /wp:quote -->

<!-- wp:code -->
<pre class="wp-block-code"><code lang="powershell" class="language-powershell">New-ModuleManifest -RootModule '.\SecretManagement.LiteDB.Extension.psm1' -FunctionsToExport @('Set-Secret','Get-Secret','Remove-Secret','Get-SecretInfo','Test-SecretVault') -Path "$vaultParentPath\SecretManagement.LiteDb\SecretManagement.LiteDB.Extension.psd1"</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>3. Once New-ModuleManifest creates the template manifest, open it up and ensure it mirrors the below manifest.</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code lang="powershell" class="language-powershell">@{
  ModuleVersion     = '0.0.0.1'
  # The command definitions located in the same directory as this module
  RootModule        = '.\SecretManagement.LiteDB.Extension.psm1'
  # Only export the functions that the SecretManagement module expects
  FunctionsToExport = @('Set-Secret','Get-Secret','Remove-Secret','Get-SecretInfo','Test-SecretVault')
}</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>Now that our module is all set up, let's implement the functions themselves! As mentioned before, to allow the PowerShell SecretManagement module to interact with your extension vault, you must define functions with the <em>exact</em> same name as the ones shown below other than the LiteDb-specific function <code>Open-Database</code>.</p>
<!-- /wp:paragraph -->

<!-- wp:heading -->
<h2 id="h-create-the-vault-function-scaffolding">Create the Vault Function "Scaffolding"</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>Since you'll be devouring a lot of code in this article, first build the PSM1 module "scaffolding". This will allow you to insert code into this framework as you go along.</p>
<!-- /wp:paragraph -->

<!-- wp:list {"ordered":true} -->
<ol><li>Create the extension vault PSM1 module by first creating a text file. </li></ol>
<!-- /wp:list -->

<!-- wp:code -->
<pre class="wp-block-code"><code lang="powershell" class="language-powershell">New-Item -Path "$vaultParentPath\SecretManagement.LiteDb\SecretManagement.LiteDB.Extension\SecretManagement.LiteDB.Extension.psm1" -ItemType File</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>2. Copy and paste the following "scaffolding" code into the PSM1 file. Code-specific comments are inline.</p>
<!-- /wp:paragraph -->

<!-- wp:quote -->
<blockquote class="wp-block-quote"><p><em>You must use the standard function names defined above in the Understanding section to interact with the SecretManagement module. You can also add your own functions to the module as "helper" functions if you'd like as shown in the below PSM1 file (Open-Database).</em></p></blockquote>
<!-- /wp:quote -->

<!-- wp:code -->
<pre class="wp-block-code"><code lang="powershell" class="language-powershell"># Necessary to correctly override and implement the existing SecretManagement functions
Using Namespace Microsoft.PowerShell.SecretManagement

# An internal function, specific to this LiteDB vault, that will be used to open the database for interaction
Function Open-Database {}

# The following five functions are the standard SecretManagement functions that we will implement
Function Get-Secret {}

Function Set-Secret {}

Function Remove-Secret {} 

Function Get-SecretInfo {}

Function Test-SecretVault {}</code></pre>
<!-- /wp:code -->

<!-- wp:heading -->
<h2 id="h-build-the-functions">Build the Functions</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>The next step is to add functionality to the extension vault. This functionality means adding functions to the extension vault (module).</p>
<!-- /wp:paragraph -->

<!-- wp:quote -->
<blockquote class="wp-block-quote"><p><em>Though we are creating an interface to store secrets, in this tutorial, we are not implementing the database's encryption, and in a production database that would be recommended. Once the database has been encrypted, decryption is done by passing a password upon opening the database for reading or writing.</em></p></blockquote>
<!-- /wp:quote -->

<!-- wp:heading {"level":3} -->
<h3 id="h-creating-the-helper-open-database-function">Creating the "Helper" <code>Open-Database</code> Function</h3>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>Typically, you'll only be building the standard set of functions but for LiteDB, the tutorial has a "helper" function called <code>Open-Database</code>. This function is strictly an internal function utilized by the extension module to simplify opening the database and making it available to the exposed functions. Code comments in-line.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>Copy and paste this code into your <em>$vaultParentPath\SecretManagement.LiteDb\SecretManagement.LiteDB.Extension\SecretManagement.LiteDB.Extension.psm1</em> module.</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code lang="powershell" class="language-powershell">Function Open-Database {
  [CmdletBinding()]
  
  Param (
    # The path to the actual database file on disk
    [String]$Path
  )

  Begin {
    # Make the vault parameters from the Register-SecretVault function available
    $VaultParameters = (Get-SecretVault -Name $VaultName).VaultParameters
  }

  Process {
		# Look for if the type has already been added. If it has not, attempt to find the DLL path and perform an Add-Type to make the LiteDB class available
    If ( -Not ([System.Management.Automation.PSTypeName]'LiteDB.LiteDatabase').Type ) {
      $standardAssemblyFullPath = (<a class="wpil_keyword_link" href="https://adamtheautomator.com/powershell-get-childitem/" title="Get-ChildItem" data-wpil-keyword-link="linked">Get-ChildItem</a> -Filter '*.dll' -Recurse (Split-Path (Get-Package -Name 'LiteDB').Source)).FullName | Where-Object {$_ -Like "*standard*"} | Select-Object -Last 1

      Add-Type -Path $standardAssemblyFullPath -ErrorAction 'SilentlyContinue'
    }

    Write-Verbose "Opening Database: $Path" -Verbose:$VaultParameters.Verbose
		
		# Despite the New method, this will open an existing database or create a new one if that database does not exist on disk
    $Database = [LiteDB.LiteDatabase]::New($Path)

    If ($Database) {
      Write-Verbose "Database Opened: $($Database | Out-String)" -Verbose:$VaultParameters.Verbose

			# Output the database variable for use by consuming functions
      $Database
    } Else {
      Throw "Failed to open Database"
    }
  }
}
</code></pre>
<!-- /wp:code -->

<!-- wp:quote -->
<blockquote class="wp-block-quote"><p><em>You may notice that the above snippet is doing something odd, <code>Write-Verbose "Verbose Message" -Verbose:$VaultParameters.Verbose</code>. If you attempt to display a verbose message by passing in the <code>-Verbose</code> parameter to the function, these internal messages will not display. This makes troubleshooting difficult. To overcome that, pass an additional boolean parameter to the <code>-Verbose</code> parameter to force the verbose messaging to show.</em></p></blockquote>
<!-- /wp:quote -->

<!-- wp:heading {"level":3} -->
<h3 id="h-creating-the-get-secret-function">Creating the Get-Secret Function</h3>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>Now that you have defined the <code>Open-Database</code> utility function, create the <code>Get-Secret</code> function. This function will most likely be one of the most used functions. This function queries the data source (LiteDb in this case) and returns the "secret". Any specific code comments are in line.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>Copy and paste this code into your <em>$vaultParentPath\SecretManagement.LiteDb\SecretManagement.LiteDB.Extension\SecretManagement.LiteDB.Extension.psm1</em> module.</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code lang="powershell" class="language-powershell">Function Get-Secret {
  [CmdletBinding()]

  Param (
    [String]    $Name,
    [String]    $VaultName,
    [Hashtable] $AdditionalParameters
  )

  Begin {
    $VaultParameters = (Get-SecretVault -Name $VaultName).VaultParameters

    $Database = Open-Database -Path $VaultParameters.Path
  }

  Process {
    Try {
      # Retrieve the collection (i.e. table) to store the secrets in
      $Collection = $Database.GetCollection($VaultParameters.Collection)
      # Make sure that an index exists on the Name field so that queries work properly
      $Collection.EnsureIndex('Name') | Out-Null

      Write-Verbose "Collection Opened: $($Collection | Out-String)" -Verbose:$VaultParameters.Verbose
    } Catch {
      Throw "Unable to open collection."
    }

    # Locate a single secret as defined by the name passed in
    $Secret = $Collection.FindOne("`$.Name = '$Name'")
    Write-Verbose "Result: $($Secret | Out-String)" -Verbose:$VaultParameters.Verbose

    If ($Secret) {
      # If a secret exists, return the raw value
      $Secret['Value'].RawValue
    } Else {
			# If no secret is found, return $null which is required by the SecretManagement module
      Return $Null
    }
  }

  End {
    Write-Verbose "Closing Database" -Verbose:$VaultParameters.Verbose
    $Database.Dispose()
  }
}
</code></pre>
<!-- /wp:code -->

<!-- wp:heading {"level":3} -->
<h3 id="h-creating-the-set-secret-function">Creating the Set-Secret Function</h3>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>Next, you'll need a function to store data in the extension vault. To do that, you'll need the <code>Set-Secret</code> function This function will either add or update an existing secret depending on what already exists in the database. Any specific code comments are in line.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>Copy and paste this code into your <em>$vaultParentPath\SecretManagement.LiteDb\SecretManagement.LiteDB.Extension\SecretManagement.LiteDB.Extension.psm1</em> module.</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code lang="powershell" class="language-powershell">Function Set-Secret {
  [CmdletBinding()]

  Param (
    [String]    $Name,
    [Object]    $Secret,
    [String]    $VaultName,
    [Hashtable] $AdditionalParameters
  )

  Begin {
    $VaultParameters = (Get-SecretVault -Name $VaultName).VaultParameters

    $Database = Open-Database -Path $VaultParameters.Path
  }

  Process {
    Try {
      # Retrieve the collection (i.e. table) to store the secrets in
      $Collection = $Database.GetCollection($VaultParameters.Collection)
      # Make sure that an index exists on the Name field so that queries work properly
      $Collection.EnsureIndex('Name') | Out-Null

      Write-Verbose "Collection Opened: $($Collection | Out-String)" -Verbose:$VaultParameters.Verbose
    } Catch {
      Throw "Unable to open collection."
    }

    # Attempt to locate an existing secret in the database
    $Item = $Collection.FindOne("`$.Name = '$Name'")

    If ($Item) {
      # If a secret exists, set the value of that secret to the new secret value passed in
      $Item['value'] = $Secret

      Write-Verbose "Retrieved Existing Item to Update: $($Item | Out-String)" -Verbose:$VaultParameters.Verbose

      Try {
        # Attempt to update the existing secret with the new secret value
        $Collection.Update($Item)
      } Catch {
        Throw "Unable to update item."
      }

			# SecretManagement requires a boolean value to be returned
      Return $True
    } Else {
      # Since the data stored in the database is of the type BSON (Binary Structured Object Notation), we need a utility mapper that converts the PowerShell objects into BSON
      $BSONMapper = [LiteDB.BSONMapper]::New()

			# Construct the password object
      $PasswordObject = @{
        "Name"  = $Name
        "Value" = $Secret
      }

      Write-Verbose "Creating New Entry: $($PasswordObject | Out-String)" -Verbose:$VaultParameters.Verbose

      Try {
        # Attempt to insert the password object as a new entry
        $Collection.Insert($BSONMapper.ToDocument($PasswordObject))
      } Catch {
        Throw "Unable to insert item."
      }

      Return $True
    }
  }

  End {
    Write-Verbose "Closing Database" -Verbose:$VaultParameters.Verbose
    $Database.Dispose()
  }
}
</code></pre>
<!-- /wp:code -->

<!-- wp:heading {"level":3} -->
<h3 id="h-creating-the-remove-secret-function">Creating the Remove-Secret Function</h3>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>You'll need a way to remove secrets from the extension vault. For that, you'll need the <code>Remove-Secret</code> function. This function identifies an existing entry and removes it via the <code>_id</code> attribute. Any specific code comments are in line.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>Copy and paste this code into your <em>$vaultParentPath\SecretManagement.LiteDb\SecretManagement.LiteDB.Extension\SecretManagement.LiteDB.Extension.psm1</em> module.</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code lang="powershell" class="language-powershell">Function Remove-Secret {
  [CmdletBinding()]

  Param (
    [String]    $Name,
    [String]    $VaultName,
    [Hashtable] $AdditionalParameters
  )

  Begin {
    $VaultParameters = (Get-SecretVault -Name $VaultName).VaultParameters

    $Database = Open-Database -Path $VaultParameters.Path
  }

  Process {
    Try {
      # Retrieve the collection (i.e. table) to store the secrets in
      $Collection = $Database.GetCollection($VaultParameters.Collection)
      # Make sure that an index exists on the Name field so that queries work properly
      $Collection.EnsureIndex('Name') | Out-Null

      Write-Verbose "Collection Opened: $($Collection | Out-String)" -Verbose:$VaultParameters.Verbose
    } Catch {
      Throw "Unable to open collection."
    }

		# Attempt to locate an entry to remove
    $Item = $Collection.FindOne("`$.Name = '$Name'")

    If ($Item) {
      Write-Verbose "Removing Item: $($Item | Out-String)" -Verbose:$VaultParameters.Verbose

      Try {
				# Remove the entry using the _id attribute, internal to the LiteDB database
        $Collection.Delete($Item['_id'].RawValue)
      } Catch {
        Throw "Unable to delete item."
      }

			# SecretManagement requires a boolean value to be returned
      Return $True
    } Else {
      Return $False
    }
  }

  End {
    Write-Verbose "Closing Database" -Verbose:$VaultParameters.Verbose
    $Database.Dispose()
  }
}
</code></pre>
<!-- /wp:code -->

<!-- wp:heading {"level":3} -->
<h3 id="h-creating-the-get-secretinfo-function">Creating the Get-SecretInfo Function</h3>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>Next up, create the <code>Get-SecretInfo</code> function. This function is similar to <code>Get-Secret</code> because it queries the data source but it doesn't return the vault. This function returns various information about the secret such as any metadata associated with the secret. Any specific code comments are in line.</p>
<!-- /wp:paragraph -->

<!-- wp:quote -->
<blockquote class="wp-block-quote"><p><em>There appears to be a bug in Preview 4 of the PowerShell SecretManagement module. Although the definition calls for just the <code>Filter</code> parameter, you cannot make this work without both the <code>Name</code> and <code>Filter</code> parameter added. The <code>Name</code> parameter is then all that's used. This will most likely change in the future.</em></p></blockquote>
<!-- /wp:quote -->

<!-- wp:paragraph -->
<p>Copy and paste this code into your <em>$vaultParentPath\SecretManagement.LiteDb\SecretManagement.LiteDB.Extension\SecretManagement.LiteDB.Extension.psm1</em> module.</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code lang="powershell" class="language-powershell">Function Get-SecretInfo {
  [CmdletBinding()]

  Param(
    [String]    $Name,
    [String]    $Filter,
    [String]    $VaultName,
    [Hashtable] $AdditionalParameters
  )

  Begin {
    $VaultParameters = (Get-SecretVault -Name $VaultName).VaultParameters

    $Database = Open-Database -Path $VaultParameters.Path
  }

  Process {
    Try {
      # Retrieve the collection (i.e. table) to store the secrets in
      $Collection = $Database.GetCollection($VaultParameters.Collection)
      # Make sure that an index exists on the Name field so that queries work properly
      $Collection.EnsureIndex('Name') | Out-Null

      Write-Verbose "Collection Opened: $($Collection | Out-String)" -Verbose:$VaultParameters.Verbose
    } Catch {
      Throw "Unable to open collection."
    }

    Write-Verbose "Testing Name: $Name" -Verbose:$VaultParameters.Verbose
    If ([String]::IsNullOrEmpty($Name)) {
			# If the Name parameter is empty, return all results from the database
      $Results = $Collection.FindAll()
    } Else {
			# If a name is provided return just a single result
      $Results = $Collection.FindOne("`$.Name = '$Name'")
    }

		# SecretManagement requires an array of specific types to be returned, Microsoft.PowerShell.SecretManagement.SecretInformation
		# The first entry is the name of the secret, not the value
		# The second entry is the type, in this case we are only storing strings
		# The third entry is the name of the vault itself
    $ResultsArray = $Results | ForEach-Object {
      [Microsoft.PowerShell.SecretManagement.SecretInformation]::New(
        $PSItem['Name'].RawValue,
        "String",
        $VaultName
      )
    }
	
		# Return an array, and if no results, return an empty array
    If ($ResultsArray) {
      Return $ResultsArray
    } Else {
      Return @()
    }
  }

  End {
    Write-Verbose "Closing Database" -Verbose:$VaultParameters.Verbose
    $Database.Dispose()
  }
}
</code></pre>
<!-- /wp:code -->

<!-- wp:heading {"level":3} -->
<h3 id="h-test-secretvault">Test-SecretVault</h3>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>The final function is the <code>Test-SecretVault</code> which verifies that everything is set up correctly with your vault.</p>
<!-- /wp:paragraph -->

<!-- wp:quote -->
<blockquote class="wp-block-quote"><p><em>The <code>Test-SecretVault</code> function can be built to validate whatever you'd like. The Azure Keystore extension vault, for example, verifies your Azure subscription status.</em></p></blockquote>
<!-- /wp:quote -->

<!-- wp:paragraph -->
<p>For the LiteDb example you're building, the <code>Test-SecretVault</code> function below attempts to see if a database and collection already exist. If so, it returns <code>$true</code>; if not, it returns <code>$false</code>.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>Copy and paste this code into your <em>$vaultParentPath\SecretManagement.LiteDb\SecretManagement.LiteDB.Extension\SecretManagement.LiteDB.Extension.psm1</em> module.</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code lang="powershell" class="language-powershell">Function Test-SecretVault {
  [CmdletBinding()]

  Param (
    [String]    $VaultName,
    [Hashtable] $AdditionalParameters
  )

  Begin {
    $VaultParameters = (Get-SecretVault -Name $VaultName).VaultParameters
  }

  Process {
		# Verify that there is an existing database and that it can be opened with the requested collection available
    If (Test-Path $VaultParameters.Path) {
      $Database   = Open-Database -Path $VaultParameters.Path
      $Collection = $Database.GetCollection($VaultParameters.Collection)

      If ($Database -And $Collection) {
        Return $True
      } Else {
        Return $False
      }
    }

    Return $False
  }

  End {
    $Database.Dispose()
  }
}
</code></pre>
<!-- /wp:code -->

<!-- wp:heading -->
<h2 id="h-using-the-powershell-secretmanagement-extension-module">Using the PowerShell SecretManagement Extension Module</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>Now that you've built the extension vault, start using it! How?</p>
<!-- /wp:paragraph -->

<!-- wp:list {"ordered":true} -->
<ol><li>First, ensure you have the SecretManagement module loaded. </li></ol>
<!-- /wp:list -->

<!-- wp:code -->
<pre class="wp-block-code"><code lang="powershell" class="language-powershell"># Import the SecretManagement module to make the commands available
Import-Module -Name 'Microsoft.PowerShell.SecretManagement'</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>2. Register LiteDb as an extension vault with the Register-SecretVault cmdlet. To do so, you'll see all of the common parameters you'll need to pass to the cmdlet.</p>
<!-- /wp:paragraph -->

<!-- wp:quote -->
<blockquote class="wp-block-quote"><p><em>The below example is using PowerShell splatting but you can also pass the parameters on the same line if you wish.</em></p></blockquote>
<!-- /wp:quote -->

<!-- wp:code -->
<pre class="wp-block-code"><code lang="powershell" class="language-powershell">$Params = @{
  # Arbitrary name of the vault which could be anything
  "Name"            = 'LiteDBStore'
  # Load the extension module from this directory, in this example, the current one we are in
  "ModuleName"      = './SecretManagement.LiteDB'
  # Define additional parameters to be available to our extension module
  "VaultParameters" = @{
    # Path to the LiteDB database
    'Path'       = "D:\secrets.db"
    # The name of the collection to store the secrets in, think of this as a table
    'Collection' = 'Secrets'
    # Whether to display the Write-Verbose messages in the code
    'Verbose'    = $False
  }
  # Should this vault be treated as the default vault
  "DefaultVault"    = $True
}

Register-SecretVault @Params</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>3. Run the Get-SecretVault command to ensure the SecretManagement module finds the new vault successfully.</p>
<!-- /wp:paragraph -->

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/lrqt7ts6rpl8jnk0zpi7.png)

<!-- wp:paragraph -->
<p>4. Now that the vault has been registered, run a quick test to make sure all the functions work as expected. If all goes well, you should receive no errors with only nice, beautiful output.</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code lang="powershell" class="language-powershell"># Verify that the vault is correctly configured
Test-SecretVault -Name 'LiteDBStore'
# Create a new secret entry
Set-Secret -Name 'FirstSecret' -Secret 'FirstSecretValue'
# Display all secret entries
Get-SecretInfo
# Get the password of the secret as plaintext, default is a securestring
Get-Secret -Name 'FirstSecret' -AsPlainText
# Remove the secret and verify that it is no longer available
Remove-Secret -Name 'FirstSecret' -Vault 'LiteDBStore'
Get-SecretInfo</code></pre>
<!-- /wp:code -->

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/g5c5qek99ps8pd9va9fb.png)

<!-- wp:quote -->
<blockquote class="wp-block-quote"><p><em>To remove and reload a vault, as you make changes, unregister the vault by running <code>Unregister-SecretVault -Name 'LiteDBStore'</code>. If you're developing a new extension vault, also be sure to remove the module as you make changes with <code>Remove-Module -Name 'SecretManagement.LiteDB'</code>. Running <code>Register-SecretVault</code>again automatically imports your module.</em></p></blockquote>
<!-- /wp:quote -->

<!-- wp:heading -->
<h2 id="h-next-steps">Next Steps</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>Writing an extension vault for PowerShell SecretManagement preview 4 is not difficult once you grasped the structure and the intricacies of the SecretManagement module. This common framework will open up a world of possibilities for scripts to not worry about the backing structure of their credentials.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>An extension module could point to a cloud-hosted keystore, or store secrets via an API in almost any location. The functions make abstracting this backend quick and easy!</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>Now find your favorite place to store secrets and see if you can build an extension vault for it using the SecretManagement module!</p>
<!-- /wp:paragraph -->