My Profile Photo Title

Thoughts about DevOps and automation from a Windows guy


Storing PowerShell Module Default Values in Your User Profile feature image

Storing PowerShell Module Default Values in Your User Profile

As you develop your PowerShell modules, you may run into the issue where many of your module cmdlets require a common parameter in order to function. This may be a FQDN for an API endpoint or a folder path your module uses. You could of course, supply this parameter each time you invoke the cmdlet, but this is tedious when working with the module interactively. Below you will see an option for storing PowerShell module default values in your user profile.

Module Configuration Repository

I like to create a module configuration repository under the user profile directory using a .modulename naming convention. Below is an example of my PasswordState module repository under my user profile. In this folder is an options.json file that holds just a couple of common parameters that my PasswordState cmdlets will need.

Initializing the Repository

The cmdlet below is part of my PasswordState module that accesses the REST API and retrieves/updates passwords from a secure password vault. This cmdlet will create the module repository if it does not exist and create an options.json with the values provided.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
function Initialize-PasswordStateRepository {
    <#
        .SYNOPSIS
            Creates PasswordState configuration repository under $env:USERNAME\.passwordstate
        .DESCRIPTION
            Creates PasswordState configuration repository under $env:USERNAME\.passwordstate
            and options.json file to store default values used by other PasswordState cmdlets.
        .PARAMETER ApiEndpoint
            The Uri of your PasswordState site. (i.e. https://passwordstate.local/api)
        .PARAMETER Repository
            Path to credential repository. Default is $env:USERPROFILE\.passwordstate
        .EXAMPLE
            Initialize-PasswordStateRepository -ApiEndpoint 'https://passwordstate.local/api'
        .EXAMPLE
            Initialize-PasswordStateRepository -ApiEndpoint 'https://passwordstate.local/api' -Repository 'C:\PasswordStateCreds'
    #>
    [cmdletbinding()]
    param(
        [Parameter(Mandatory)]
        [string]$ApiEndpoint,

        [string]$Repository = (Join-Path -path $env:USERPROFILE -ChildPath '.passwordstate' -Verbose:$false)
    )

    # If necessary, create our repository under $env:USERNAME\.passwordstate
    $repoPath = (Join-Path -path $env:USERPROFILE -ChildPath '.passwordstate')
    if (-not (Test-Path -Path $repoPath -Verbose:$false)) {
        Write-Debug -Message "Creating PasswordState configuration repository: $repoPath"
        New-Item -ItemType Directory -Path $repoPath -Verbose:$false | Out-Null
    } else {
        Write-Debug -Message "PasswordState configuration repository appears to already be created at [$repoPath]"
    }

    $options = @{
        api_endpoint = $ApiEndpoint
        credential_repository = $Repository
    }

    $json = $options | ConvertTo-Json
    Write-Debug -Message $json
    $json | Out-File -FilePath (Join-Path -Path $repoPath -ChildPath 'options.json') -Force -Confirm:$false -Verbose:$false    
}

Referencing Repository Values

Below is a cmdlet internal to the PasswordState module (this cmdlet is not exported to the outside world) that will look inside the options.json file for a given option and return the result.

I prefer to name cmdlets / functions that are internal to the module with a _Verb-Noun naming scheme. This makes it clear that the cmdlet / function is not meant to be exported.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function _GetDefault {
    [cmdletbinding()]
    param(
        [Parameter(Mandatory)]
        [string]$Option
    )

    $repo = (Join-Path -path $env:USERPROFILE -ChildPath '.passwordstate')

    if (Test-Path -Path $repo -Verbose:$false) {

        $options = (Join-Path -Path $repo -ChildPath 'options.json')

        if (Test-Path -Path $options ) {
            $obj = Get-Content -Path $options -Raw | ConvertFrom-Json
            return $obj.$Option
        } else {
            Write-Error -Message "Unable to find [$options]"
        }
    } else {
        Write-Error -Message "Undable to find PasswordState configuration repository at [$repo]"
    }
}

Using Default values in cmdlets

Now that we have a place to store our default options and a way to reference those values, other cmdlets in the module can be written to look inside this module repository for default values to parameters. Below is a cmdlet from PasswordState that will list all available API keys that have been securely exported to disk.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
function Get-PasswordStateApiKey {
    <#
        .SYNOPSIS
            List available PasswordState API keys in the repository.
        .DESCRIPTION
            List available PasswordState API keys in the repository.
        .PARAMETER Repository
            Path to repository. Default is $env:USERPROFILE\.passwordstate
        .EXAMPLE
            Get-PasswordStateApiKey
        .EXAMPLE
            Get-PasswordStateApiKey | Format-Table
        .EXAMPLE
            Get-PasswordStateApiKey -Repository c:\users\joe\data\.customrepo
    #>
    [cmdletbinding()]
    param(
        [string]$Repository = (_GetDefault -Option 'credential_repository')
    )

    if (-not (Test-Path -Path $Repository)) {
        Write-Error 'PasswordState key repository does not exist!'
        break
    }

    $items = Get-ChildItem -Path $Repository -Filter '*.cred'
    return $items
}

Notice the single parameter to this function:

1
2
3
4
[cmdletbinding()]
param(
    [string]$Repository = (_GetDefault -Option 'credential_repository')
)

This default value will only be executed if no value is given for $Repository. Below you can see how this is used in practice. Notice that the two ways of invoking the cmdlet produce identical results. One is specifying the credential repository explicitly, and the other is relying on the value returned from which will look inside the options.json in the module repository and return the default value for ‘credential_repository’.

Cheers

Sharing is caring