Public/Set-InfisicalSecret.ps1

# Set-InfisicalSecret.ps1
# Updates an existing secret in Infisical.
# Called by: User directly.
# Dependencies: InfisicalSession class, InfisicalSecret class, Invoke-InfisicalApi, Get-InfisicalSession, ConvertTo-InfisicalBody

function Set-InfisicalSecret {
    <#
    .SYNOPSIS
        Updates an existing secret in Infisical.

    .DESCRIPTION
        Updates the value and/or comment of an existing secret in Infisical.
        Accepts SecureString values by default to prevent plaintext in memory.
        A secondary -PlainTextValue parameter is available for convenience.

    .PARAMETER Name
        The name (key) of the secret to update.

    .PARAMETER Value
        The new secret value as a SecureString.

    .PARAMETER PlainTextValue
        The new secret value as a plain string. Issues a warning because the
        plaintext string remains in managed memory.

    .PARAMETER Environment
        The environment slug. Overrides the session default if specified.

    .PARAMETER SecretPath
        The Infisical folder path. Defaults to "/".

    .PARAMETER ProjectId
        The project/workspace ID. Overrides the session default if specified.

    .PARAMETER Comment
        An optional note or comment to set on the secret.

    .PARAMETER SkipMultilineEncoding
        Skip encoding for multiline secret values.

    .PARAMETER TagIds
        An array of tag IDs to attach to the secret.

    .PARAMETER Metadata
        A hashtable of key-value metadata pairs to set on the secret.

    .PARAMETER ReminderRepeatDays
        Interval for secret rotation reminders, measured in days.

    .PARAMETER ReminderNote
        A note to include in rotation reminder notification emails.

    .PARAMETER NewName
        Rename the secret to this new name.

    .PARAMETER Type
        The secret type: 'shared' (default) or 'personal'.

    .PARAMETER PassThru
        Return the updated InfisicalSecret object.

    .EXAMPLE
        $newValue = Read-Host -AsSecureString -Prompt 'New value'
        Set-InfisicalSecret -Name 'DATABASE_URL' -Value $newValue

        Updates the DATABASE_URL secret with a new SecureString value.

    .EXAMPLE
        Set-InfisicalSecret 'API_KEY' -PlainTextValue 'sk-new-key' -PassThru

        Updates the secret and returns the updated object.

    .OUTPUTS
        [InfisicalSecret] when -PassThru is specified; otherwise, no output.

    .NOTES
        Use SecureString values whenever possible. The -PlainTextValue parameter
        is provided for convenience but the plaintext string will remain in
        managed memory until garbage collected.

    .LINK
        Get-InfisicalSecret
    .LINK
        New-InfisicalSecret
    .LINK
        Remove-InfisicalSecret
    #>

    [CmdletBinding(SupportsShouldProcess, DefaultParameterSetName = 'SecureValue')]
    [OutputType([InfisicalSecret])]
    param(
        [Parameter(Mandatory, Position = 0, ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [string] $Name,

        [Parameter(Mandatory, Position = 1, ParameterSetName = 'SecureValue')]
        [ValidateNotNull()]
        [System.Security.SecureString] $Value,

        [Parameter(Mandatory, ParameterSetName = 'PlainTextValue')]
        [ValidateNotNullOrEmpty()]
        [string] $PlainTextValue,

        [Parameter(ValueFromPipelineByPropertyName)]
        [string] $Environment,

        [Parameter(ValueFromPipelineByPropertyName)]
        [Alias('Path')]
        [string] $SecretPath = '/',

        [Parameter(ValueFromPipelineByPropertyName)]
        [string] $ProjectId,

        [Parameter()]
        [string] $Comment,

        [Parameter()]
        [switch] $SkipMultilineEncoding,

        [Parameter()]
        [string[]] $TagIds,

        [Parameter()]
        [hashtable] $Metadata,

        [Parameter()]
        [ValidateRange(1, 365)]
        [int] $ReminderRepeatDays,

        [Parameter()]
        [ValidateLength(0, 1024)]
        [string] $ReminderNote,

        [Parameter()]
        [string] $NewName,

        [Parameter()]
        [ValidateSet('shared', 'personal')]
        [string] $Type,

        [Parameter()]
        [switch] $PassThru
    )

    process {
        $session = Get-InfisicalSession

        # Handle PlainTextValue — warn and convert to SecureString
        if ($PSCmdlet.ParameterSetName -eq 'PlainTextValue') {
            Write-Warning 'Set-InfisicalSecret: Using -PlainTextValue. The plaintext string remains in managed memory. Prefer -Value with SecureString for better security.'
            $Value = [System.Security.SecureString]::new()
            foreach ($char in $PlainTextValue.ToCharArray()) {
                $Value.AppendChar($char)
            }
            $Value.MakeReadOnly()
        }

        $resolvedEnvironment = if ([string]::IsNullOrEmpty($Environment)) { $session.DefaultEnvironment } else { $Environment }

        if ($PSCmdlet.ShouldProcess("Updating secret '$Name' in path '$SecretPath' (environment: $resolvedEnvironment)")) {
            $bodyParams = @{
                Session              = $session
                Environment          = $Environment
                SecretPath           = $SecretPath
                ProjectId            = $ProjectId
                SecretValue          = $Value
                Comment              = $Comment
                SkipMultilineEncoding = $SkipMultilineEncoding
            }
            if ($null -ne $TagIds -and $TagIds.Count -gt 0)                { $bodyParams['TagIds'] = $TagIds }
            if ($null -ne $Metadata -and $Metadata.Count -gt 0)            { $bodyParams['SecretMetadata'] = $Metadata }
            if ($PSBoundParameters.ContainsKey('ReminderRepeatDays'))       { $bodyParams['ReminderRepeatDays'] = $ReminderRepeatDays }
            if (-not [string]::IsNullOrEmpty($ReminderNote))               { $bodyParams['ReminderNote'] = $ReminderNote }
            if (-not [string]::IsNullOrEmpty($NewName))                    { $bodyParams['NewSecretName'] = $NewName }
            if (-not [string]::IsNullOrEmpty($Type))                       { $bodyParams['Type'] = $Type }
            $body = ConvertTo-InfisicalBody @bodyParams

            $encodedName = [System.Uri]::EscapeDataString($Name)
            $response = Invoke-InfisicalApi -Method PATCH -Endpoint "/api/v4/secrets/$encodedName" -Body $body -Session $session

            if ($PassThru.IsPresent -and $null -ne $response -and $null -ne $response.secret) {
                $resolvedProjectId = if ([string]::IsNullOrEmpty($ProjectId)) { $session.ProjectId } else { $ProjectId }
                return ConvertTo-InfisicalSecret -SecretData $response.secret -Environment $resolvedEnvironment -ProjectId $resolvedProjectId -FallbackPath $SecretPath
            }
        }
    }
}