Store.psm1

[CmdletBinding()]
param()

$scriptName = 'Store'
Write-Verbose "[$scriptName] - Importing module"

#region - From [functions] - [private]
Write-Verbose "[$scriptName] - [functions] - [private] - Processing folder"

#region - From [functions] - [private] - [Get-StoreVariable]
Write-Verbose "[$scriptName] - [functions] - [private] - [Get-StoreVariable] - Importing"

function Get-StoreVariable {
    <#
        .SYNOPSIS
        Get a variable from the store.

        .EXAMPLE
        Get-StoreVariable

        Gets all the variables in the store.

        .EXAMPLE
        Get-StoreVariable -Name 'Name'

        Gets the value of the variable with the name 'Name'.
    #>

    [CmdletBinding()]
    [OutputType([object])]
    param(
        [Parameter()]
        [string] $Name
    )

    if (-not $Name) {
        $script:Store
    } else {
        $script:Store.$Name
    }
}

Write-Verbose "[$scriptName] - [functions] - [private] - [Get-StoreVariable] - Done"
#endregion - From [functions] - [private] - [Get-StoreVariable]
#region - From [functions] - [private] - [Initialize-SecretStore]
Write-Verbose "[$scriptName] - [functions] - [private] - [Initialize-SecretStore] - Importing"

#Requires -Modules Microsoft.PowerShell.SecretManagement
#Requires -Modules Microsoft.PowerShell.SecretStore

function Initialize-SecretStore {
    <#
        .SYNOPSIS
        Initialize a secret vault.

        .DESCRIPTION
        Initialize a secret vault. If the vault does not exist, it will be created.

        .EXAMPLE
        Initialize-SecretStore -Name 'SecretStore' -Type 'Microsoft.PowerShell.SecretStore'

        Initializes a secret vault named 'SecretStore' using the 'Microsoft.PowerShell.SecretStore' module.

        .NOTES
        For more information about secret vaults, see
        https://learn.microsoft.com/en-us/powershell/utility-modules/secretmanagement/overview?view=ps-modules
    #>

    [OutputType([void])]
    [CmdletBinding()]
    param (
        # The name of the secret vault.
        [Parameter()]
        [string] $Name = 'SecretStore',

        # The type of the secret vault.
        [Parameter()]
        [Alias('ModuleName')]
        [string] $Type = 'Microsoft.PowerShell.SecretStore'
    )

    $vault = Get-SecretVault | Where-Object { $_.ModuleName -eq $Type }
    if (-not $vault) {
        Write-Verbose "[$Type] - Registering"

        switch ($Type) {
            'Microsoft.PowerShell.SecretStore' {
                $vaultParameters = @{
                    Authentication  = 'None'
                    PasswordTimeout = -1
                    Interaction     = 'None'
                    Scope           = 'CurrentUser'
                    WarningAction   = 'SilentlyContinue'
                    Confirm         = $false
                    Force           = $true
                }
                Reset-SecretStore @vaultParameters
            }
        }
        Write-Verbose "[$Type] - Done"
    } else {
        Write-Verbose "[$Type] - already registered"
    }

    $secretStore = Get-SecretVault | Where-Object { $_.Name -eq $Name }
    if (-not $secretStore) {
        Write-Verbose "[$Name] - Registering"
        $secretVault = @{
            Name         = $Name
            ModuleName   = $Type
            DefaultVault = $true
            Description  = 'SecretStore'
        }
        Register-SecretVault @secretVault
        Write-Verbose "[$Name] - Done"
    } else {
        Write-Verbose "[$Name] - already registered"
    }

    Set-StoreVariable -Name 'SecretVaultName' -Value $Name
    Set-StoreVariable -Name 'SecretVaultType' -Value $Type

}

Write-Verbose "[$scriptName] - [functions] - [private] - [Initialize-SecretStore] - Done"
#endregion - From [functions] - [private] - [Initialize-SecretStore]
#region - From [functions] - [private] - [Initialize-VariableStore]
Write-Verbose "[$scriptName] - [functions] - [private] - [Initialize-VariableStore] - Importing"

function Initialize-VariableStore {
    <#
        .SYNOPSIS
        Initialize the variable store.
    #>

    [CmdletBinding()]
    [OutputType([void])]
    param (
        [Parameter(Mandatory)]
        [string] $Name
    )

    $folderName = ".$($Name -replace '^\.')".ToLower()
    Write-Verbose "Variable store folder: [$folderName]"
    $configFilePath = Join-Path -Path $HOME -ChildPath "$folderName/config.json"
    Write-Verbose "Variable store file: [$configFilePath]"
    $configFileExists = Test-Path -Path $configFilePath
    Write-Verbose "Variable store file exists: [$configFileExists]"
    if (-not $configFileExists) {
        $null = New-Item -Path $configFilePath -ItemType File -Force
        Set-StoreVariable -Name 'ConfigFilePath' -Value $configFilePath
        Set-StoreVariable -Name 'Name' -Value $Name
    }

    $script:Store = Get-Content -Path $configFilePath | ConvertFrom-Json

}

Write-Verbose "[$scriptName] - [functions] - [private] - [Initialize-VariableStore] - Done"
#endregion - From [functions] - [private] - [Initialize-VariableStore]
#region - From [functions] - [private] - [Set-StoreVariable]
Write-Verbose "[$scriptName] - [functions] - [private] - [Set-StoreVariable] - Importing"

function Set-StoreVariable {
    <#
        .SYNOPSIS
        Set a variable in the store.

        .EXAMPLE
        Set-StoreVariable -Name 'Name' -Value 'MyName'
    #>

    [CmdletBinding(SupportsShouldProcess)]
    param(
        # The name of the variable to set.
        [Parameter(Mandatory)]
        [string] $Name,

        # The value to set.
        [Parameter(Mandatory)]
        [AllowNull()]
        [object] $Value
    )

    if ($PSCmdlet.ShouldProcess("Set variable '$Name' to '$Value'")) {
        if ($null -eq $Value) {
            $script:Store.PSObject.Properties.Remove($Name)
        } else {
            $script:Store | Add-Member -MemberType NoteProperty -Name $Name -Value $Value -Force
        }
        $script:Store | ConvertTo-Json -Depth 100 | Set-Content -Path $script:Store.ConfigFilePath -Force
    }
}

Write-Verbose "[$scriptName] - [functions] - [private] - [Set-StoreVariable] - Done"
#endregion - From [functions] - [private] - [Set-StoreVariable]

Write-Verbose "[$scriptName] - [functions] - [private] - Done"
#endregion - From [functions] - [private]

#region - From [functions] - [public]
Write-Verbose "[$scriptName] - [functions] - [public] - Processing folder"

#region - From [functions] - [public] - [Get-StoreConfig]
Write-Verbose "[$scriptName] - [functions] - [public] - [Get-StoreConfig] - Importing"

#Requires -Modules Microsoft.PowerShell.SecretManagement

function Get-StoreConfig {
    <#
        .SYNOPSIS
        Get configuration value.

        .DESCRIPTION
        Get a named configuration value from the store configuration.

        .EXAMPLE
        Get-StoreConfig -Name ApiBaseUri

        Get the value of ApiBaseUri config.
    #>

    [OutputType([object])]
    [CmdletBinding()]
    param (
        # Choose a configuration name to get.
        [Parameter()]
        [string] $Name,

        # Return the value as plain text if it is a secret.
        [Parameter()]
        [switch] $AsPlainText
    )

    if (-not $Name) {
        return [pscustomobject]@{
            Secrets   = Get-SecretInfo | ForEach-Object {
                [pscustomobject]@{
                    Name  = $_.Name
                    Value = Get-Secret -Name $_.Name -AsPlainText:$AsPlainText -Vault $script:Store.SecretVaultName
                }
            }
            Variables = $script:Store
        }
    }

    $value = Get-StoreVariable -Name $Name

    if (($null -eq $value) -and ((Get-SecretInfo -Vault $script:Store.SecretVaultName).Name -contains $Name)) {
        $value = Get-Secret -Name $Name -AsPlainText:$AsPlainText -Vault $script:Store.SecretVaultName
    }

    $value
}

Write-Verbose "[$scriptName] - [functions] - [public] - [Get-StoreConfig] - Done"
#endregion - From [functions] - [public] - [Get-StoreConfig]
#region - From [functions] - [public] - [Initialize-Store]
Write-Verbose "[$scriptName] - [functions] - [public] - [Initialize-Store] - Importing"

function Initialize-Store {
    <#
        .SYNOPSIS
        Initialize the store for a module.

        .EXAMPLE
        Initialize-Store -Name 'MyStore'
    #>

    [CmdletBinding()]
    param (
        # Name of the store.
        [Parameter(Mandatory)]
        [string] $Name,

        # The name of the secret vault.
        [Parameter()]
        [string] $SecretVaultName = 'SecretStore',

        # The type of the secret vault.
        [Parameter()]
        [string] $SecretVaultType = 'Microsoft.PowerShell.SecretStore'
    )

    Initialize-VariableStore -Name $Name
    Initialize-SecretStore -Name $SecretVaultName -Type $SecretVaultType
}

Write-Verbose "[$scriptName] - [functions] - [public] - [Initialize-Store] - Done"
#endregion - From [functions] - [public] - [Initialize-Store]
#region - From [functions] - [public] - [Set-StoreConfig]
Write-Verbose "[$scriptName] - [functions] - [public] - [Set-StoreConfig] - Importing"

#Requires -Modules Microsoft.PowerShell.SecretManagement

function Set-StoreConfig {
    <#
        .SYNOPSIS
        Set a configuration variables or secret.

        .DESCRIPTION
        Set a configuration variable or secret in the configuration store.

        .EXAMPLE
        Set-StoreConfig -VariableName "ApiBaseUri" -Value 'https://api.github.com'

        Sets a variable called 'ApiBaseUri' in the configuration store (json file).

        .EXAMPLE
        Set-StoreConfig -SecretName "AccessToken" -Value 'myAccessToken'

        Sets a secret called 'AccessToken' in the configuration store (secret vault).
    #>

    [CmdletBinding(SupportsShouldProcess)]
    param (
        # The name of a variable to set.
        [Parameter(Mandatory)]
        [string] $Name,

        # The value to set.
        [Parameter(Mandatory)]
        [AllowNull()]
        [object] $Value
    )

    if ($PSCmdlet.ShouldProcess("Set variable '$Name' to '$Value'")) {
        if ($Value -is [SecureString]) {
            Set-Secret -Name $Name -SecureStringSecret $Value
        } else {
            Set-StoreVariable -Name $Name -Value $Value
        }
    }
}

Write-Verbose "[$scriptName] - [functions] - [public] - [Set-StoreConfig] - Done"
#endregion - From [functions] - [public] - [Set-StoreConfig]

Write-Verbose "[$scriptName] - [functions] - [public] - Done"
#endregion - From [functions] - [public]

#region - From [variables] - [private]
Write-Verbose "[$scriptName] - [variables] - [private] - Processing folder"

#region - From [variables] - [private] - [Store]
Write-Verbose "[$scriptName] - [variables] - [private] - [Store] - Importing"

$script:Store = [PSCustomObject]@{}

Write-Verbose "[$scriptName] - [variables] - [private] - [Store] - Done"
#endregion - From [variables] - [private] - [Store]

Write-Verbose "[$scriptName] - [variables] - [private] - Done"
#endregion - From [variables] - [private]


$exports = @{
    Alias    = '*'
    Cmdlet   = ''
    Function = @(
        'Get-StoreConfig'
        'Initialize-Store'
        'Set-StoreConfig'
    )
}
Export-ModuleMember @exports