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:allowLoginTime=Get-DateIsTwoFactorAuth=$trueTwoFactorMethods=@('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 automaticallySet-Context-ID 'ModuleSettings'-Vault 'MyModule'-Context @{
DefaultApiEndpoint='https://api.example.com'TimeoutSeconds=30
}
# Store user credentialsSet-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 modulefunctionSet-MyModuleContext {
param(
[Parameter(Mandatory)]
[string] $ID,
[Parameter(Mandatory)]
[object] $Context
)
Set-Context-ID $ID-Vault 'MyModule'-Context $Context
}
functionGet-MyModuleContext {
param(
[string] $ID='*'
)
Get-Context-ID $ID-Vault 'MyModule'
}Store module-wide settings that persist across sessions:
# Initialize module settings on first loadif (-not (Get-Context-ID 'Settings'-Vault 'MyModule'-ErrorAction SilentlyContinue)) {
Set-Context-ID 'Settings'-Vault 'MyModule'-Context @{
DefaultUser=$nullApiEndpoint='https://api.example.com'EnableLogging=$false
}
}Handle multiple user accounts within your module:
# Store user-specific datafunctionConnect-MyService {
param(
[Parameter(Mandatory)]
[string] $Username,
[Parameter(Mandatory)]
[SecureString] $ApiKey
)
# Store user contextSet-Context-ID "User.$Username"-Vault 'MyModule'-Context @{
Username=$UsernameApiKey=$ApiKeyConnectedAt=Get-Date
}
# Update module settings to remember the current user$moduleSettings=Get-Context-ID 'Settings'-Vault 'MyModule'$moduleSettings.DefaultUser=$UsernameSet-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 existif (-not (Get-Context-ID 'ModuleSettings'-Vault $VaultName-ErrorAction SilentlyContinue)) {
Set-Context-ID 'ModuleSettings'-Vault $VaultName-Context @{
DefaultOrganization=$nullApiEndpoint='https://api.github.com'DefaultUser=$null
}
}
# Public function to connect userfunctionConnect-GitHub {
param(
[Parameter(Mandatory)]
[string] $Username,
[Parameter(Mandatory)]
[string] $Token
)
# Store user context with secure tokenSet-Context-ID "User.$Username"-Vault $VaultName-Context @{
Username=$UsernameToken=$Token|ConvertTo-SecureString-AsPlainText -Force
Organizations=@()
LastConnected=Get-Date
}
# Set as default user$settings=Get-Context-ID 'ModuleSettings'-Vault $VaultName$settings.DefaultUser=$UsernameSet-Context-ID 'ModuleSettings'-Vault $VaultName-Context $settingsWrite-Host"Connected to GitHub as $Username"
}
# Public function to get current user contextfunctionGet-GitHubUser {
$settings=Get-Context-ID 'ModuleSettings'-Vault $VaultNameif ($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 vaultsGet-ContextVault# Get specific vault informationGet-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 contextGet-Context-ID 'UserSettings'-Vault 'MyModule'# Get all contexts in a vaultGet-Context-Vault 'MyModule'# Remove a contextRemove-Context-ID 'UserSettings'-Vault 'MyModule'# Rename a contextRename-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