Modules typically handle two types of data that benefit from persistent secure storage and management: module settings and user settings and secrets.
This module introduces the concept of Contexts, which enable persistent and secure data storage for PowerShell modules. It allows module developers
to separate user and module data from the module code, enabling users to resume their work without needing to reconfigure the module or log in again,
provided the service supports session refresh mechanisms (e.g., refresh tokens).
The module uses NaCl-based encryption, provided by the libsodium library (delivered via the Sodium module),
to encrypt and decrypt Context data. The Sodium module is automatically installed when you install this
module.
The concept of Context is widely used to represent a collection of data that is relevant to a specific use-case. In this module,
a Context is a way to securely persist user and module data and offers a set of functions to manage this across modules that implement it.
Data that is stored in a Context can include user-specific settings, secrets, and module configuration data.
A Context is identified by a unique ID in the module that implements it.
Any data that can be represented in JSON format can be stored in a Context.
Contexts can be grouped into ContextVaults, which are logical containers for related contexts. This allows for organization and management of
contexts, especially when dealing with multiple users or modules. Vaults are automatically created when you store a context, and they can be managed
using the provided functions in this module.
$HOME/.contextvaults/
├── GitHub/
│ ├── 64a5bbaf-96b8-4090-a77d-75e02ab6c4e0.json
│ ├── f201dc50-c163-4a7a-8d69-aea7f696737d.json
│ └── shard
├── AzureDevOps/
│ ├── cf49fceb-38d1-47da-a0ae-219ac40e4d8c.json
│ ├── b521a424-dd1c-445b-a0d6-c26a29d93654.json
│ └── shard
In this example there are two named vaults (GitHub and AzureDevOps). Each vault contains its own shard file (for encryption) and two context
files (with unique GUID filenames) that store encrypted context data. Contexts in different vaults are completely isolated from each other.
To store data to disk, use the Set-Context function. The function needs an ID and the data object.
The object can be anything that can be converted and represented in JSON format.
Set-Context -ID 'john_doe' -Vault 'GitHub' -Context ([PSCustomObject]@{
Username = 'john_doe'
AuthToken = 'ghp_12345ABCDE67890FGHIJ' | ConvertTo-SecureString -AsPlainText -Force # gitleaks:allow
LoginTime = Get-Date
IsTwoFactorAuth = $true
TwoFactorMethods = @('TOTP', 'SMS')
})The object that is passed into Set-Context is first analyzed. If the object contains any SecureString values, they are converted to plain-text and
prefixed with [SECURESTRING]. This indicates that these values should be restored back to SecureString. The whole object is then converted to a
JSON string.
{
"ID": "john_doe",
"Username": "john_doe",
"AuthToken": "[SECURESTRING]ghp_12345ABCDE67890FGHIJ",
"LoginTime": "2024-11-21T21:16:56.2518249+01:00",
"IsTwoFactorAuth": true,
"TwoFactorMethods": ["TOTP", "SMS"]
}Finally the data is encrypted using the Sodium module and saved to disk. The file is stored in the user's home
directory $HOME/.contextvaults/<VaultName>/<context_id>.json, where <context_id> is a generated GUID, providing a unique name for the file.
The encrypted JSON representation of the data is added to metadata object that holds other info such as the ID of the Context and the path to the
file where it is stored.
{
"ID": "github.com/john_doe",
"Vault": "GitHub",
"Path": "C:\\Users\\JohnDoe\\.contextvaults\\GitHub\\d2edaa6e-95a1-41a0-b6ef-0ecc5d116030.json",
"Context": "0kGmtbQiEtih7 --< encrypted context data >-- ceqbMiBilUvEzO1Lk"
}The metadata can be accessed using the Get-ContextInfo function.
You can install the module from the PowerShell Gallery using the following command:
Install-PSResource -Name Context -TrustRepository -Repository PSGallery
Import-Module -Name ContextThis section shows how to integrate the Context module into your PowerShell module to provide persistent, secure storage for module settings and user data.
The simplest way to implement Contexts is using Set-Context, which automatically creates the vault if it doesn't exist:
# Store module configuration - vault is created automatically
Set-Context -ID 'ModuleSettings' -Vault 'MyModule' -Context @{
DefaultApiEndpoint = 'https://api.example.com'
TimeoutSeconds = 30
}
# Store user credentials
Set-Context -ID 'User.JohnDoe' -Vault 'MyModule' -Context @{
Username = 'johndoe'
ApiKey = 'secret-key' | ConvertTo-SecureString -AsPlainText -Force
LastLogin = Get-Date
}Create module-specific wrapper functions to provide a familiar interface for your users:
# In your module
function Set-MyModuleContext {
param(
[Parameter(Mandatory)]
[string] $ID,
[Parameter(Mandatory)]
[object] $Context
)
Set-Context -ID $ID -Vault 'MyModule' -Context $Context
}
function Get-MyModuleContext {
param(
[string] $ID = '*'
)
Get-Context -ID $ID -Vault 'MyModule'
}Store module-wide settings that persist across sessions:
# Initialize module settings on first load
if (-not (Get-Context -ID 'Settings' -Vault 'MyModule' -ErrorAction SilentlyContinue)) {
Set-Context -ID 'Settings' -Vault 'MyModule' -Context @{
DefaultUser = $null
ApiEndpoint = 'https://api.example.com'
EnableLogging = $false
}
}Handle multiple user accounts within your module:
# Store user-specific data
function Connect-MyService {
param(
[Parameter(Mandatory)]
[string] $Username,
[Parameter(Mandatory)]
[SecureString] $ApiKey
)
# Store user context
Set-Context -ID "User.$Username" -Vault 'MyModule' -Context @{
Username = $Username
ApiKey = $ApiKey
ConnectedAt = Get-Date
}
# Update module settings to remember the current user
$moduleSettings = Get-Context -ID 'Settings' -Vault 'MyModule'
$moduleSettings.DefaultUser = $Username
Set-Context -ID 'Settings' -Vault 'MyModule' -Context $moduleSettings
}Here's a complete example of how to implement Contexts in a hypothetical GitHub module:
# Module initialization (in .psm1 file)
$VaultName = 'GitHub'
# Initialize module settings if they don't exist
if (-not (Get-Context -ID 'ModuleSettings' -Vault $VaultName -ErrorAction SilentlyContinue)) {
Set-Context -ID 'ModuleSettings' -Vault $VaultName -Context @{
DefaultOrganization = $null
ApiEndpoint = 'https://api.github.com'
DefaultUser = $null
}
}
# Public function to connect user
function Connect-GitHub {
param(
[Parameter(Mandatory)]
[string] $Username,
[Parameter(Mandatory)]
[string] $Token
)
# Store user context with secure token
Set-Context -ID "User.$Username" -Vault $VaultName -Context @{
Username = $Username
Token = $Token | ConvertTo-SecureString -AsPlainText -Force
Organizations = @()
LastConnected = Get-Date
}
# Set as default user
$settings = Get-Context -ID 'ModuleSettings' -Vault $VaultName
$settings.DefaultUser = $Username
Set-Context -ID 'ModuleSettings' -Vault $VaultName -Context $settings
Write-Host "Connected to GitHub as $Username"
}
# Public function to get current user context
function Get-GitHubUser {
$settings = Get-Context -ID 'ModuleSettings' -Vault $VaultName
if ($settings.DefaultUser) {
Get-Context -ID "User.$($settings.DefaultUser)" -Vault $VaultName
}
}- Automatic Vault Creation:
Set-Contextcreates the vault automatically if it doesn't exist, and preserves existing encryption keys - Consistent Vault Naming: Use your module name as the vault name for organization
- Wrapper Functions: Provide module-specific functions that hide the vault parameter from users
- SecureString Support: The Context module automatically handles
SecureStringencryption and decryption - Module Settings: Store module-wide configuration separate from user-specific data
For most use cases, you don't need to manage vaults directly since Set-Context creates them automatically. However, you can manage vaults explicitly when needed:
# List all vaults
Get-ContextVault
# Get specific vault information
Get-ContextVault -Name "MyModule"
# Remove a vault and all its contexts (use with caution)
Remove-ContextVault -Name "OldModule"# Store a context (creates vault automatically)
Set-Context -ID 'UserSettings' -Vault 'MyModule' -Context @{
Theme = 'Dark'
Language = 'en-US'
}
# Retrieve a context
Get-Context -ID 'UserSettings' -Vault 'MyModule'
# Get all contexts in a vault
Get-Context -Vault 'MyModule'
# Remove a context
Remove-Context -ID 'UserSettings' -Vault 'MyModule'
# Rename a context
Rename-Context -ID 'OldName' -NewID 'NewName' -Vault 'MyModule'
# Get context metadata (without decrypting)
Get-ContextInfo -Vault 'MyModule'- Vault Requirement: Every context must be stored in a named vault - there is no default vault
- Automatic Vault Creation:
Set-Contextautomatically creates vaults if they don't exist - Encryption Key Preservation: Existing vault encryption keys are preserved when using
Set-Context - Vault Isolation: Each vault is isolated with its own encryption keys and storage directory
- Storage Location: Vaults are stored under
$HOME/.contextvaults/<VaultName>/ - SecureString Support: The module automatically handles encryption/decryption of
SecureStringvalues