Context.psm1
[CmdletBinding()] param() $baseName = [System.IO.Path]::GetFileNameWithoutExtension($PSCommandPath) $script:PSModuleInfo = Test-ModuleManifest -Path "$PSScriptRoot\$baseName.psd1" $script:PSModuleInfo | Format-List | Out-String -Stream | ForEach-Object { Write-Debug $_ } $scriptName = $script:PSModuleInfo.Name Write-Debug "[$scriptName] - Importing module" #region [functions] - [private] Write-Debug "[$scriptName] - [functions] - [private] - Processing folder" #region [functions] - [private] - [Import-Context] Write-Debug "[$scriptName] - [functions] - [private] - [Import-Context] - Importing" #Requires -Modules @{ ModuleName = 'Sodium'; RequiredVersion = '2.1.1' } filter Import-Context { <# .SYNOPSIS Imports the context vault into memory. .DESCRIPTION Imports all context files from the context vault directory into memory. Each context is decrypted using the configured private key and stored in the script-wide context collection for further use. .EXAMPLE Import-Context Output: ```powershell VERBOSE: Importing contexts from vault: [C:\Vault] VERBOSE: Found [3] contexts VERBOSE: Importing context: [123456] ``` Imports all contexts from the context vault into memory. .OUTPUTS [pscustomobject]. .NOTES Represents the imported context object containing ID, Path, and Context properties. .LINK https://psmodule.io/Sodium/Functions/Import-Context/ #> [OutputType([object])] [CmdletBinding()] param() begin { $stackPath = Get-PSCallStackPath Write-Debug "[$stackPath] - Start" if (-not $script:Config.Initialized) { Set-ContextVault } } process { try { Write-Verbose "Importing contexts from vault: [$($script:Config.VaultPath)]" $contextFiles = Get-ChildItem -Path $script:Config.VaultPath -Filter *.json -File -Recurse Write-Verbose "Found [$($contextFiles.Count)] contexts" $contextFiles | ForEach-Object { $contextInfo = Get-Content -Path $_.FullName | ConvertFrom-Json Write-Verbose "Importing context: [$($contextInfo.ID)]" Write-Verbose ($contextInfo | Format-List | Out-String) $params = @{ SealedBox = $contextInfo.Context PublicKey = $script:Config.PublicKey PrivateKey = $script:Config.PrivateKey } $context = ConvertFrom-SodiumSealedBox @params $script:Contexts[$contextInfo.ID] = [pscustomobject]@{ ID = $contextInfo.ID Path = $contextInfo.Path Context = ConvertFrom-ContextJson -JsonString $context } } } catch { Write-Error $_ throw 'Failed to get context' } } end { Write-Debug "[$stackPath] - End" } } Write-Debug "[$scriptName] - [functions] - [private] - [Import-Context] - Done" #endregion [functions] - [private] - [Import-Context] #region [functions] - [private] - [Set-ContextVault] Write-Debug "[$scriptName] - [functions] - [private] - [Set-ContextVault] - Importing" #Requires -Modules @{ ModuleName = 'Sodium'; RequiredVersion = '2.1.1' } function Set-ContextVault { <# .SYNOPSIS Sets the context vault. .DESCRIPTION Sets the context vault. If the vault does not exist, it will be initialized. Once the context vault is set, it will be imported into memory. The vault consists of multiple security shards, including a machine-specific shard, a user-specific shard, and a seed shard stored within the vault directory. .EXAMPLE Set-ContextVault Initializes or loads the context vault, setting up necessary key pairs. .OUTPUTS None. .NOTES This function modifies the script-scoped configuration and imports the vault. .LINK https://psmodule.io/Context/Functions/Set-ContextVault #> [CmdletBinding(SupportsShouldProcess)] param() begin { $stackPath = Get-PSCallStackPath Write-Debug "[$stackPath] - Start" } process { try { Write-Verbose "Loading context vault from [$($script:Config.VaultPath)]" $vaultExists = Test-Path $script:Config.VaultPath Write-Verbose "Vault exists: $vaultExists" if (-not $vaultExists) { Write-Verbose 'Initializing new vault' $null = New-Item -Path $script:Config.VaultPath -ItemType Directory } Write-Verbose 'Checking for existing seed shard' $seedShardPath = Join-Path -Path $script:Config.VaultPath -ChildPath $script:Config.SeedShardPath $seedShardExists = Test-Path $seedShardPath Write-Verbose "Seed shard exists: $seedShardExists" if (-not $seedShardExists) { Write-Verbose 'Creating new seed shard' $keys = New-SodiumKeyPair Set-Content -Path $seedShardPath -Value "$($keys.PrivateKey)$($keys.PublicKey)" } $seedShard = Get-Content -Path $seedShardPath $machineShard = [System.Environment]::MachineName $userShard = [System.Environment]::UserName #$userInputShard = Read-Host -Prompt 'Enter a seed shard' # Eventually 4 shards. +1 for user input. $seed = $machineShard + $userShard + $seedShard + $userInputShard $keys = New-SodiumKeyPair -Seed $seed $script:Config.PrivateKey = $keys.PrivateKey $script:Config.PublicKey = $keys.PublicKey Write-Verbose 'Vault initialized' $script:Config.Initialized = $true } catch { Write-Error $_ throw 'Failed to initialize context vault' } } end { Write-Debug "[$stackPath] - End" Import-Context } } Write-Debug "[$scriptName] - [functions] - [private] - [Set-ContextVault] - Done" #endregion [functions] - [private] - [Set-ContextVault] #region [functions] - [private] - [JsonToObject] Write-Debug "[$scriptName] - [functions] - [private] - [JsonToObject] - Processing folder" #region [functions] - [private] - [JsonToObject] - [Convert-ContextHashtableToObjectRecursive] Write-Debug "[$scriptName] - [functions] - [private] - [JsonToObject] - [Convert-ContextHashtableToObjectRecursive] - Importing" function Convert-ContextHashtableToObjectRecursive { <# .SYNOPSIS Converts a hashtable into a structured context object. .DESCRIPTION This function recursively converts a hashtable into a structured PowerShell object. String values prefixed with '[SECURESTRING]' are converted back to SecureString objects. Other values retain their original data types, including integers, booleans, strings, arrays, and nested objects. .EXAMPLE Convert-ContextHashtableToObjectRecursive -Hashtable @{ Name = 'Test' Token = '[SECURESTRING]TestToken' Nested = @{ Name = 'Nested' Token = '[SECURESTRING]NestedToken' } } Output: ```powershell Name : Test Token : System.Security.SecureString Nested : @{ Name = Nested; Token = System.Security.SecureString } ``` This example converts a hashtable into a structured object, where 'Token' and 'Nested.Token' values are SecureString objects. .OUTPUTS PSCustomObject. .NOTES Returns an object where values are converted to their respective types, including SecureString for sensitive values, arrays for list structures, and nested objects for hashtables. .LINK https://psmodule.io/Context/Functions/Convert-ContextHashtableToObjectRecursive #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute( 'PSAvoidUsingConvertToSecureStringWithPlainText', '', Justification = 'The SecureString is extracted from the object being processed by this function.' )] [OutputType([pscustomobject])] [CmdletBinding()] param ( # Hashtable to convert into a structured context object [Parameter(Mandatory)] [hashtable] $Hashtable ) begin { $stackPath = Get-PSCallStackPath Write-Debug "[$stackPath] - Start" } process { try { $result = [pscustomobject]@{} foreach ($key in $Hashtable.Keys) { $value = $Hashtable[$key] Write-Debug "Processing [$key]" Write-Debug "Value: $value" Write-Debug "Type: $($value.GetType().Name)" if ($value -is [string] -and $value -like '`[SECURESTRING`]*') { Write-Debug "Converting [$key] as [SecureString]" $secureValue = $value -replace '^\[SECURESTRING\]', '' $result | Add-Member -NotePropertyName $key -NotePropertyValue ($secureValue | ConvertTo-SecureString -AsPlainText -Force) } elseif ($value -is [hashtable]) { Write-Debug "Converting [$key] as [hashtable]" $result | Add-Member -NotePropertyName $key -NotePropertyValue (Convert-ContextHashtableToObjectRecursive $value) } elseif ($value -is [array]) { Write-Debug "Converting [$key] as [array], processing elements individually" $result | Add-Member -NotePropertyName $key -NotePropertyValue @( $value | ForEach-Object { if ($_ -is [hashtable]) { Convert-ContextHashtableToObjectRecursive $_ } else { $_ } } ) } else { Write-Debug "Adding [$key] as a standard value" $result | Add-Member -NotePropertyName $key -NotePropertyValue $value } } return $result } catch { Write-Error $_ throw 'Failed to convert hashtable to object' } } end { Write-Debug "[$stackPath] - End" } } Write-Debug "[$scriptName] - [functions] - [private] - [JsonToObject] - [Convert-ContextHashtableToObjectRecursive] - Done" #endregion [functions] - [private] - [JsonToObject] - [Convert-ContextHashtableToObjectRecursive] #region [functions] - [private] - [JsonToObject] - [ConvertFrom-ContextJson] Write-Debug "[$scriptName] - [functions] - [private] - [JsonToObject] - [ConvertFrom-ContextJson] - Importing" function ConvertFrom-ContextJson { <# .SYNOPSIS Converts a JSON string to a context object. .DESCRIPTION Converts a JSON string to a context object. Text prefixed with `[SECURESTRING]` is converted to SecureString objects. Other values are converted to their original types, such as integers, booleans, strings, arrays, and nested objects. .EXAMPLE $content = @' { "Name": "Test", "Token": "[SECURESTRING]TestToken", "Nested": { "Name": "Nested", "Token": "[SECURESTRING]NestedToken" } } '@ ConvertFrom-ContextJson -JsonString $content Output: ```powershell Name : Test Token : System.Security.SecureString Nested : @{Name=Nested; Token=System.Security.SecureString} ``` Converts a JSON string to a context object, ensuring 'Token' and 'Nested.Token' values are SecureString objects. .OUTPUTS [pscustomobject]. .NOTES Returns a PowerShell custom object with SecureString conversion applied where necessary. .LINK https://psmodule.io/Context/Functions/ConvertFrom-ContextJson/ #> [OutputType([pscustomobject])] [CmdletBinding()] param ( # JSON string to convert to context object [Parameter()] [string] $JsonString = '{}' ) begin { $stackPath = Get-PSCallStackPath Write-Debug "[$stackPath] - Start" } process { try { $hashtableObject = $JsonString | ConvertFrom-Json -Depth 100 -AsHashtable return Convert-ContextHashtableToObjectRecursive $hashtableObject } catch { Write-Error $_ throw 'Failed to convert JSON to object' } } end { Write-Debug "[$stackPath] - End" } } Write-Debug "[$scriptName] - [functions] - [private] - [JsonToObject] - [ConvertFrom-ContextJson] - Done" #endregion [functions] - [private] - [JsonToObject] - [ConvertFrom-ContextJson] Write-Debug "[$scriptName] - [functions] - [private] - [JsonToObject] - Done" #endregion [functions] - [private] - [JsonToObject] #region [functions] - [private] - [ObjectToJson] Write-Debug "[$scriptName] - [functions] - [private] - [ObjectToJson] - Processing folder" #region [functions] - [private] - [ObjectToJson] - [Convert-ContextObjectToHashtableRecursive] Write-Debug "[$scriptName] - [functions] - [private] - [ObjectToJson] - [Convert-ContextObjectToHashtableRecursive] - Importing" function Convert-ContextObjectToHashtableRecursive { <# .SYNOPSIS Converts a context object to a hashtable. .DESCRIPTION This function converts a context object to a hashtable. Secure strings are converted to a string representation, prefixed with '[SECURESTRING]'. Datetime objects are converted to a string representation using the 'o' format specifier. Nested context objects are recursively converted to hashtables. .EXAMPLE Convert-ContextObjectToHashtableRecursive -Object ([PSCustomObject]@{ Name = 'MySecret' AccessToken = '123123123' | ConvertTo-SecureString -AsPlainText -Force Nested = @{ Name = 'MyNestedSecret' NestedAccessToken = '123123123' | ConvertTo-SecureString -AsPlainText -Force } }) Output: ```powershell Name : MySecret AccessToken : [SECURESTRING]123123123 Nested : @{Name=MyNestedSecret; NestedAccessToken=[SECURESTRING]123123123} ``` Converts the context object to a hashtable. Secure strings are converted to a string representation. .OUTPUTS hashtable. .NOTES Returns a hashtable representation of the input object. Secure strings are converted to prefixed string values. .LINK https://psmodule.io/Context/Functions/Convert-ContextObjectToHashtableRecursive #> [OutputType([hashtable])] [CmdletBinding()] param ( # The object to convert. [Parameter()] [object] $Object = @{} ) begin { $stackPath = Get-PSCallStackPath Write-Debug "[$stackPath] - Start" } process { try { $result = @{} if ($Object -is [hashtable]) { Write-Debug 'Converting [hashtable] to [PSCustomObject]' $Object = [PSCustomObject]$Object } elseif ($Object -is [string] -or $Object -is [int] -or $Object -is [bool]) { Write-Debug 'returning as string' return $Object } foreach ($property in $Object.PSObject.Properties) { $name = $property.Name $value = $property.Value Write-Debug "Processing [$name]" Write-Debug "Value: $value" Write-Debug "Type: $($value.GetType().Name)" if ($value -is [datetime]) { Write-Debug '- as DateTime' $result[$property.Name] = $value.ToString('o') } elseif ($value -is [string] -or $Object -is [int] -or $Object -is [bool]) { Write-Debug '- as string, int, bool' $result[$property.Name] = $value } elseif ($value -is [System.Security.SecureString]) { Write-Debug '- as SecureString' $value = $value | ConvertFrom-SecureString -AsPlainText $result[$property.Name] = "[SECURESTRING]$value" } elseif ($value -is [psobject] -or $value -is [PSCustomObject] -or $value -is [hashtable]) { Write-Debug '- as PSObject, PSCustomObject or hashtable' $result[$property.Name] = Convert-ContextObjectToHashtableRecursive $value } elseif ($value -is [System.Collections.IEnumerable]) { Write-Debug '- as IEnumerable, including arrays and hashtables' $result[$property.Name] = @( $value | ForEach-Object { Convert-ContextObjectToHashtableRecursive $_ } ) } else { Write-Debug '- as regular value' $result[$property.Name] = $value } } return $result } catch { Write-Error $_ throw 'Failed to convert context object to hashtable' } } end { Write-Debug "[$stackPath] - End" } } Write-Debug "[$scriptName] - [functions] - [private] - [ObjectToJson] - [Convert-ContextObjectToHashtableRecursive] - Done" #endregion [functions] - [private] - [ObjectToJson] - [Convert-ContextObjectToHashtableRecursive] #region [functions] - [private] - [ObjectToJson] - [ConvertTo-ContextJson] Write-Debug "[$scriptName] - [functions] - [private] - [ObjectToJson] - [ConvertTo-ContextJson] - Importing" function ConvertTo-ContextJson { <# .SYNOPSIS Converts an object into a JSON string. .DESCRIPTION Converts objects or hashtables into a JSON string. SecureStrings are converted to plain text strings and prefixed with `[SECURESTRING]`. The conversion is recursive for any nested objects. The function allows converting back using `ConvertFrom-ContextJson`. .EXAMPLE ConvertTo-ContextJson -Context ([pscustomobject]@{ Name = 'MySecret' AccessToken = '123123123' | ConvertTo-SecureString -AsPlainText -Force }) -ID 'CTX-001' Output: ```json { "Name": "MySecret", "AccessToken": "[SECURESTRING]123123123", "ID": "CTX-001" } ``` Converts the given object into a JSON string, ensuring SecureStrings are handled properly. .OUTPUTS System.String. .NOTES A JSON string representation of the provided object, including secure string transformations. .LINK https://psmodule.io/Context/Functions/ConvertTo-ContextJson #> [OutputType([string])] [CmdletBinding()] param ( # The object to convert to a Context JSON string. [Parameter()] [object] $Context = @{}, # The ID of the context. [Parameter(Mandatory)] [string] $ID ) begin { $stackPath = Get-PSCallStackPath Write-Debug "[$stackPath] - Start" } process { try { $processedObject = Convert-ContextObjectToHashtableRecursive $Context $processedObject['ID'] = $ID return ($processedObject | ConvertTo-Json -Depth 100 -Compress) } catch { Write-Error $_ throw 'Failed to convert object to JSON' } } end { Write-Debug "[$stackPath] - End" } } Write-Debug "[$scriptName] - [functions] - [private] - [ObjectToJson] - [ConvertTo-ContextJson] - Done" #endregion [functions] - [private] - [ObjectToJson] - [ConvertTo-ContextJson] Write-Debug "[$scriptName] - [functions] - [private] - [ObjectToJson] - Done" #endregion [functions] - [private] - [ObjectToJson] #region [functions] - [private] - [Utilities] Write-Debug "[$scriptName] - [functions] - [private] - [Utilities] - Processing folder" #region [functions] - [private] - [Utilities] - [PowerShell] Write-Debug "[$scriptName] - [functions] - [private] - [Utilities] - [PowerShell] - Processing folder" #region [functions] - [private] - [Utilities] - [PowerShell] - [Get-PSCallStackPath] Write-Debug "[$scriptName] - [functions] - [private] - [Utilities] - [PowerShell] - [Get-PSCallStackPath] - Importing" function Get-PSCallStackPath { <# .SYNOPSIS Creates a string representation of the current call stack. .DESCRIPTION This function generates a string representation of the current call stack. It allows skipping the first and last elements of the call stack using the `SkipFirst` and `SkipLatest` parameters. By default, it skips the first function (typically `<ScriptBlock>`) and the last function (`Get-PSCallStackPath`) to present a cleaner view of the actual call stack. .EXAMPLE Get-PSCallStackPath Output: ```powershell First-Function\Second-Function\Third-Function ``` Returns the call stack with the first (`<ScriptBlock>`) and last (`Get-PSCallStackPath`) functions removed. .EXAMPLE Get-PSCallStackPath -SkipFirst 0 Output: ```powershell <ScriptBlock>\First-Function\Second-Function\Third-Function ``` Includes the first function (typically `<ScriptBlock>`) in the call stack output. .EXAMPLE Get-PSCallStackPath -SkipLatest 0 Output: ```powershell First-Function\Second-Function\Third-Function\Get-PSCallStackPath ``` Includes the last function (`Get-PSCallStackPath`) in the call stack output. .OUTPUTS System.String. .NOTES A string representing the call stack path, with function names separated by backslashes. .LINK https://psmodule.io/PSCallStack/Functions/Get-PSCallStackPath/ #> [CmdletBinding()] param( # The number of functions to skip from the last function called. # The last function in the stack is this function (`Get-PSCallStackPath`). [Parameter()] [int] $SkipLatest = 1, # The number of functions to skip from the first function called. # The first function is typically `<ScriptBlock>`. [Parameter()] [int] $SkipFirst = 1 ) $skipFirst++ $cmds = (Get-PSCallStack).Command $functionPath = $cmds[($cmds.Count - $skipFirst)..$SkipLatest] -join '\' $functionPath = $functionPath -replace '^.*<ScriptBlock>\\' $functionPath = $functionPath -replace '^.*.ps1\\' return $functionPath } Write-Debug "[$scriptName] - [functions] - [private] - [Utilities] - [PowerShell] - [Get-PSCallStackPath] - Done" #endregion [functions] - [private] - [Utilities] - [PowerShell] - [Get-PSCallStackPath] Write-Debug "[$scriptName] - [functions] - [private] - [Utilities] - [PowerShell] - Done" #endregion [functions] - [private] - [Utilities] - [PowerShell] Write-Debug "[$scriptName] - [functions] - [private] - [Utilities] - Done" #endregion [functions] - [private] - [Utilities] Write-Debug "[$scriptName] - [functions] - [private] - Done" #endregion [functions] - [private] #region [functions] - [public] Write-Debug "[$scriptName] - [functions] - [public] - Processing folder" #region [functions] - [public] - [Get-Context] Write-Debug "[$scriptName] - [functions] - [public] - [Get-Context] - Importing" function Get-Context { <# .SYNOPSIS Retrieves a context from the in-memory context vault. .DESCRIPTION Retrieves a context from the loaded contexts stored in memory. If no ID is specified, all available contexts will be returned. Wildcards are supported to match multiple contexts. .EXAMPLE Get-Context Output: ```powershell ID : Default Context : {Property1=Value1, Property2=Value2} ``` Retrieves all contexts from the context vault (in memory). .EXAMPLE Get-Context -ID 'MySecret' Output: ```powershell ID : MySecret Context : {Key=EncryptedValue} ``` Retrieves the context called 'MySecret' from the context vault (in memory). .EXAMPLE Get-Context -ID 'My*' Output: ```powershell ID : MyConfig Context : {ConfigKey=ConfigValue} ``` Retrieves all contexts that start with 'My' from the context vault (in memory). .OUTPUTS [System.Object] .NOTES Returns a list of contexts matching the specified ID or all contexts if no ID is specified. Each context object contains its ID and corresponding stored properties. .LINK https://psmodule.io/Context/Functions/Get-Context/ #> [OutputType([object])] [CmdletBinding()] param( # The name of the context to retrieve from the vault. Supports wildcards. [Parameter()] [AllowEmptyString()] [SupportsWildcards()] [string] $ID = '*' ) begin { $stackPath = Get-PSCallStackPath Write-Debug "[$stackPath] - Start" if (-not $script:Config.Initialized) { Set-ContextVault } } process { try { Write-Debug "Retrieving contexts - ID: [$ID]" $script:Contexts.Values | Where-Object { $_.ID -like $ID } | Select-Object -ExpandProperty Context } catch { Write-Error $_ throw 'Failed to get context' } } end { Write-Debug "[$stackPath] - End" } } Write-Debug "[$scriptName] - [functions] - [public] - [Get-Context] - Done" #endregion [functions] - [public] - [Get-Context] #region [functions] - [public] - [Remove-Context] Write-Debug "[$scriptName] - [functions] - [public] - [Remove-Context] - Importing" function Remove-Context { <# .SYNOPSIS Removes a context from the context vault. .DESCRIPTION This function removes a context (or multiple contexts) from the vault. It supports: - Supply one or more IDs as strings (e.g. -ID 'Ctx1','Ctx2') - Supply objects that contain an ID property The function accepts pipeline input for easier batch removal. .EXAMPLE Remove-Context -ID 'MySecret' Output: ```powershell Removing context [MySecret] Removed item: MySecret ``` Removes a context called 'MySecret' by specifying its ID. .EXAMPLE Remove-Context -ID 'Ctx1','Ctx2' Output: ```powershell Removing context [Ctx1] Removed item: Ctx1 Removing context [Ctx2] Removed item: Ctx2 ``` Removes two contexts, 'Ctx1' and 'Ctx2'. .EXAMPLE 'Ctx1','Ctx2' | Remove-Context Output: ```powershell Removing context [Ctx1] Removed item: Ctx1 Removing context [Ctx2] Removed item: Ctx2 ``` Removes two contexts, 'Ctx1' and 'Ctx2' via pipeline input. .EXAMPLE $ctxList = @( [PSCustomObject]@{ ID = 'Ctx1' }, [PSCustomObject]@{ ID = 'Ctx2' } ) $ctxList | Remove-Context Output: ```powershell Removing context [Ctx1] Removed item: Ctx1 Removing context [Ctx2] Removed item: Ctx2 ``` Accepts pipeline input: multiple objects each having an ID property. .OUTPUTS [System.String] .NOTES Returns the name of each removed context if successful. .LINK https://psmodule.io/Context/Functions/Remove-Context/ #> [CmdletBinding(SupportsShouldProcess)] param( # One or more IDs as strings of the contexts to remove. [Parameter( Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName )] [string[]] $ID ) begin { $stackPath = Get-PSCallStackPath Write-Debug "[$stackPath] - Begin" if (-not $script:Config.Initialized) { Set-ContextVault } } process { try { foreach ($item in $ID) { Write-Debug "Processing ID [$item]" $script:Contexts.Keys | Where-Object { $_ -like $item } | ForEach-Object { Write-Debug "Removing context [$_]" if ($PSCmdlet.ShouldProcess($_, 'Remove secret')) { $script:Contexts[$_].Path | Remove-Item -Force Write-Verbose "Attempting to remove context: $_" [PSCustomObject]$removedItem = $null if ($script:Contexts.TryRemove($_, [ref]$removedItem)) { Write-Verbose "Removed item: $removedItem" } else { Write-Verbose 'Key not found' } } } } } catch { Write-Error $_ throw 'Failed to remove context' } } end { Write-Debug "[$stackPath] - End" } } Write-Debug "[$scriptName] - [functions] - [public] - [Remove-Context] - Done" #endregion [functions] - [public] - [Remove-Context] #region [functions] - [public] - [Rename-Context] Write-Debug "[$scriptName] - [functions] - [public] - [Rename-Context] - Importing" function Rename-Context { <# .SYNOPSIS Renames a context. .DESCRIPTION This function renames a context by retrieving the existing context with the old ID, setting the new context with the provided new ID, and removing the old context. If a context with the new ID already exists, the operation will fail unless the `-Force` switch is specified. .EXAMPLE Rename-Context -ID 'PSModule.GitHub' -NewID 'PSModule.GitHub2' Output: ```powershell Context 'PSModule.GitHub' renamed to 'PSModule.GitHub2' ``` Renames the context 'PSModule.GitHub' to 'PSModule.GitHub2'. .EXAMPLE 'PSModule.GitHub' | Rename-Context -NewID 'PSModule.GitHub2' Output: ```powershell Context 'PSModule.GitHub' renamed to 'PSModule.GitHub2' ``` Renames the context 'PSModule.GitHub' to 'PSModule.GitHub2' using pipeline input. .OUTPUTS [System.String] .NOTES The confirmation message indicating the successful renaming of the context. .LINK https://psmodule.io/Context/Functions/Rename-Context/ #> [CmdletBinding(SupportsShouldProcess)] param ( # The ID of the context to rename. [Parameter( Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName )] [string] $ID, # The new ID of the context. [Parameter(Mandatory)] [string] $NewID, # Force the rename even if the new ID already exists. [Parameter()] [switch] $Force ) begin { $stackPath = Get-PSCallStackPath Write-Debug "[$stackPath] - Start" if (-not $script:Config.Initialized) { Set-ContextVault } } process { $context = Get-Context -ID $ID if (-not $context) { throw "Context with ID '$ID' not found." } $existingContext = Get-Context -ID $NewID if ($existingContext -and -not $Force) { throw "Context with ID '$NewID' already exists." } if ($PSCmdlet.ShouldProcess("Renaming context '$ID' to '$NewID'")) { try { $context | Set-Context -ID $NewID } catch { Write-Error $_ throw 'Failed to set new context' } try { Remove-Context -ID $ID } catch { Write-Error $_ throw 'Failed to remove old context' } } } end { Write-Debug "[$stackPath] - End" } } Write-Debug "[$scriptName] - [functions] - [public] - [Rename-Context] - Done" #endregion [functions] - [public] - [Rename-Context] #region [functions] - [public] - [Set-Context] Write-Debug "[$scriptName] - [functions] - [public] - [Set-Context] - Importing" #Requires -Modules @{ ModuleName = 'Sodium'; RequiredVersion = '2.1.1' } function Set-Context { <# .SYNOPSIS Set a context and store it in the context vault. .DESCRIPTION If the context does not exist, it will be created. If it already exists, it will be updated. The context is cached in memory for faster access. This function ensures that the context is securely stored using encryption mechanisms. .EXAMPLE Set-Context -ID 'PSModule.GitHub' -Context @{ Name = 'MySecret' } Output: ```powershell ID : PSModule.GitHub Path : C:\Vault\Guid.json Context : @{ Name = 'MySecret' } ``` Creates a context called 'MySecret' in the vault. .EXAMPLE Set-Context -ID 'PSModule.GitHub' -Context @{ Name = 'MySecret'; AccessToken = '123123123' } Output: ```powershell ID : PSModule.GitHub Path : C:\Vault\Guid.json Context : @{ Name = 'MySecret'; AccessToken = '123123123' } ``` Creates a context called 'MySecret' in the vault with additional settings. .OUTPUTS [PSCustomObject] .NOTES Returns an object representing the stored or updated context. The object includes the ID, path, and securely stored context information. .LINK https://psmodule.io/Context/Functions/Set-Context/ #> [OutputType([object])] [CmdletBinding(SupportsShouldProcess)] param( # The ID of the context. [Parameter(ValueFromPipelineByPropertyName)] [string] $ID, # The data of the context. [Parameter(ValueFromPipeline)] [object] $Context = @{}, # Pass the context through the pipeline. [Parameter()] [switch] $PassThru ) begin { $stackPath = Get-PSCallStackPath Write-Debug "[$stackPath] - Start" if (-not $script:Config.Initialized) { Set-ContextVault } } process { try { $existingContextInfo = $script:Contexts[$ID] if (-not $existingContextInfo) { Write-Verbose "Context [$ID] not found in vault" $Guid = [Guid]::NewGuid().ToString() $Path = Join-Path -Path $script:Config.VaultPath -ChildPath "$Guid.json" } else { Write-Verbose "Context [$ID] found in vault" $Path = $existingContextInfo.Path } try { $contextJson = ConvertTo-ContextJson -Context $Context -ID $ID } catch { Write-Error $_ throw 'Failed to convert context to JSON' } $param = [pscustomobject]@{ ID = $ID Path = $Path Context = ConvertTo-SodiumSealedBox -Message $contextJson -PublicKey $script:Config.PublicKey } | ConvertTo-Json -Depth 5 Write-Debug ($param | ConvertTo-Json -Depth 5) if ($PSCmdlet.ShouldProcess($ID, 'Set context')) { Write-Verbose "Setting context [$ID] in vault" Set-Content -Path $Path -Value $param $content = Get-Content -Path $Path $contextInfoObj = $content | ConvertFrom-Json $params = @{ SealedBox = $contextInfoObj.Context PublicKey = $script:Config.PublicKey PrivateKey = $script:Config.PrivateKey } $contextObj = ConvertFrom-SodiumSealedBox @params Write-Verbose ($contextObj | Format-List | Out-String) $script:Contexts[$ID] = [PSCustomObject]@{ ID = $ID Path = $Path Context = ConvertFrom-ContextJson -JsonString $contextObj } } } catch { Write-Error $_ throw 'Failed to set secret' } if ($PassThru) { Get-Context -ID $ID } } end { Write-Debug "[$stackPath] - End" } } Write-Debug "[$scriptName] - [functions] - [public] - [Set-Context] - Done" #endregion [functions] - [public] - [Set-Context] Write-Debug "[$scriptName] - [functions] - [public] - Done" #endregion [functions] - [public] #region [variables] - [private] Write-Debug "[$scriptName] - [variables] - [private] - Processing folder" #region [variables] - [private] - [Config] Write-Debug "[$scriptName] - [variables] - [private] - [Config] - Importing" $script:Config = [pscustomobject]@{ Initialized = $false # Has the vault been initialized? VaultPath = Join-Path -Path $HOME -ChildPath '.contextvault' # Vault directory path SeedShardPath = 'Context.shard' # Seed shard path (relative to VaultPath) PrivateKey = $null # Private key (populated on init) PublicKey = $null # Public key (populated on init) } Write-Debug "[$scriptName] - [variables] - [private] - [Config] - Done" #endregion [variables] - [private] - [Config] #region [variables] - [private] - [Contexts] Write-Debug "[$scriptName] - [variables] - [private] - [Contexts] - Importing" # Using a dictionary to get the benefit of reference type storage in PowerShell while multiple functions are running in parallel. # Using concurrent dictionary to be able to safely access the dictionary from multiple threads /ForEach -Parallel. $script:Contexts = [System.Collections.Concurrent.ConcurrentDictionary[string, [PSCustomObject]]]::new() Write-Debug "[$scriptName] - [variables] - [private] - [Contexts] - Done" #endregion [variables] - [private] - [Contexts] Write-Debug "[$scriptName] - [variables] - [private] - Done" #endregion [variables] - [private] #region [completers] Write-Debug "[$scriptName] - [completers] - Importing" Register-ArgumentCompleter -CommandName 'Get-Context', 'Set-Context', 'Remove-Context', 'Rename-Context' -ParameterName 'ID' -ScriptBlock { param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameter) $null = $commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameter $script:Contexts.Values.ID | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object { [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_) } } Write-Debug "[$scriptName] - [completers] - Done" #endregion [completers] #region [main] Write-Debug "[$scriptName] - [main] - Importing" Set-ContextVault Write-Debug "[$scriptName] - [main] - Done" #endregion [main] #region Member exporter $exports = @{ Alias = '*' Cmdlet = '' Function = @( 'Get-Context' 'Remove-Context' 'Rename-Context' 'Set-Context' ) } Export-ModuleMember @exports #endregion Member exporter |