Public/Update-SecretRotationAccountPassword.ps1

function Update-SecretRotationAccountPassword {
    <#
    .SYNOPSIS
        Generates a new password, applies it to a configured target's identity backend, and
        splits it via Shamir Secret Sharing.
    .DESCRIPTION
        Reads the named target from the Posh-SecretRotation config, generates a new random
        password at that target's configured entropy (Posh-SecretSharing's
        New-SecretSharingPassword), applies it to the target's backend (Active Directory, Entra
        ID, or a config-defined Custom handler script), and - only once that backend update
        actually succeeds - splits the new password into Shamir shares
        (Posh-SecretSharing's Split-SecretSharingSecret) per the target's configured quorum
        scheme.

        A password that was generated but never confirmed applied to the backend is never split
        or returned: Set-SecretRotationBackendPassword throws on any backend failure, and this
        cmdlet does not catch that exception before it reaches the caller - it only adds a log
        entry first. There would be nothing for the returned shares to reconstruct into a live
        credential otherwise.

        Supports -WhatIf/-Confirm (ConfirmImpact High) since this changes a real account's
        password on a real backend - unlike Posh-SecretSharing's pure-computation New-*
        cmdlets, this one has a real, external side effect.
    .PARAMETER Target
        Name of a target block under the config's 'targets' section (e.g. 'corp-ad-svcaccount1').
        Validated against the live config at call time; throws with the list of available target
        names if not found.
    .PARAMETER BackendParameter
        Optional hashtable merged over the target's own 'backendConfig', caller's values winning
        on key collision. Lets a caller override or supply backend properties (e.g. a different
        'identity') without editing the config file.
    .PARAMETER OutputPath
        Optional folder. When given, writes one file per returned share to this folder (see
        Export-SecretRotationShareFile) - never a single combined file, since that would defeat
        the point of splitting the password.
    .OUTPUTS
        PSCustomObject[]. The share objects returned by Split-SecretSharingSecret, unchanged.
    .EXAMPLE
        Update-SecretRotationAccountPassword -Target 'corp-ad-svcaccount1'
        Rotates the AD account's password and returns its Shamir shares to the pipeline.
    .EXAMPLE
        Update-SecretRotationAccountPassword -Target 'corp-ad-svcaccount1' -OutputPath 'C:\rotation-output'
        Same, and also writes one file per share under C:\rotation-output.
    .EXAMPLE
        Update-SecretRotationAccountPassword -Target 'corp-ad-svcaccount1' -BackendParameter @{ identity = 'svc-account2' } -WhatIf
        Shows what would happen for a different identity than the one in the config, without
        changing anything.
    #>

    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')]
    [OutputType([PSCustomObject])]
    param(
        [Parameter(Mandatory)]
        [string] $Target,

        [Parameter()]
        [hashtable] $BackendParameter = @{},

        [Parameter()]
        [string] $OutputPath
    )

    $config = Read-SecretRotationConfigFile
    $targetConfig = Get-SecretRotationTargetConfig -Config $config -TargetName $Target

    Write-SecretRotationLog -Config $config -Level Debug -CmdletName $MyInvocation.MyCommand.Name `
        -TargetName $Target -Message 'Cmdlet invoked' -BoundParameters $PSBoundParameters

    # Merge -BackendParameter over the target's own backendConfig - caller's values win.
    $mergedBackendConfig = @{}
    if ($targetConfig.backendConfig) {
        foreach ($property in $targetConfig.backendConfig.PSObject.Properties) {
            $mergedBackendConfig[$property.Name] = $property.Value
        }
    }
    foreach ($key in $BackendParameter.Keys) {
        $mergedBackendConfig[$key] = $BackendParameter[$key]
    }

    $identifier = $mergedBackendConfig.identity
    if (-not $identifier) {
        throw "Target '$Target' has no 'identity' in its backendConfig (or -BackendParameter) - nothing to rotate."
    }

    if (-not $PSCmdlet.ShouldProcess($identifier, "Rotate password on backend '$($targetConfig.backend)' and split via Shamir Secret Sharing")) {
        return
    }

    $password = New-SecretSharingPassword -Entropy ([int]$targetConfig.entropy)

    $backendPassword = $password
    if ($targetConfig.passwordRepresentation -eq 'String') {
        $ptr = [System.Runtime.InteropServices.Marshal]::SecureStringToGlobalAllocUnicode($password)
        try {
            $backendPassword = [System.Runtime.InteropServices.Marshal]::PtrToStringUni($ptr)
        } finally {
            [System.Runtime.InteropServices.Marshal]::ZeroFreeGlobalAllocUnicode($ptr)
        }
    }

    try {
        Set-SecretRotationBackendPassword -Backend $targetConfig.backend -Identifier $identifier `
            -Password $backendPassword -BackendConfig $mergedBackendConfig `
            -CustomHandler $targetConfig.customHandler -ConfigDirectory (Split-Path -Path (Get-SecretRotationConfigPath) -Parent)
    } catch {
        Write-SecretRotationLog -Config $config -Level Error -CmdletName $MyInvocation.MyCommand.Name `
            -TargetName $Target -Message "Backend password update failed: $_"
        throw
    }

    Write-SecretRotationLog -Config $config -Level Information -CmdletName $MyInvocation.MyCommand.Name `
        -TargetName $Target -Message "Password rotated on backend '$($targetConfig.backend)' for identity '$identifier'"

    $groupSplat = @{}
    if ($targetConfig.shamir.groupThreshold) { $groupSplat.GroupThreshold = [int]$targetConfig.shamir.groupThreshold }

    $groups = @()
    foreach ($groupConfig in $targetConfig.shamir.groups) {
        $groups += @{ Threshold = [int]$groupConfig.threshold; Count = [int]$groupConfig.count }
    }

    $shares = Split-SecretSharingSecret -Secret $password -Group $groups @groupSplat

    Write-SecretRotationLog -Config $config -Level Information -CmdletName $MyInvocation.MyCommand.Name `
        -TargetName $Target -Message "Password split into $(@($shares).Count) share(s)"

    if ($OutputPath) {
        $shares | Export-SecretRotationShareFile -OutputPath $OutputPath | Out-Null
    }

    $shares
}