Public/Update-PatServerToken.ps1

function Update-PatServerToken {
    <#
    .SYNOPSIS
        Refreshes the authentication token for a stored Plex server.
 
    .DESCRIPTION
        Updates the Plex authentication token for a stored server configuration.
        This is the recommended way to fix expired or invalid tokens without
        removing and re-adding the server.
 
        When called without -Token, performs interactive PIN authentication via
        Connect-PatAccount. When -Token is provided, uses the supplied token
        directly (useful for automation or CI scenarios).
 
        After storing the new token, verifies it by calling the Plex API root
        endpoint and reports the result.
 
    .PARAMETER Name
        The name of the stored server to update. If not specified, uses the
        default server configured via Add-PatServer -Default.
 
    .PARAMETER Token
        A Plex authentication token to use directly. When provided, skips the
        interactive PIN authentication flow. Obtain a token via Connect-PatAccount
        or from Plex account settings.
 
    .PARAMETER TimeoutSeconds
        Maximum time to wait for interactive PIN authorization in seconds
        (default: 300 / 5 minutes). Only applies when -Token is not provided.
 
    .PARAMETER Force
        Suppresses interactive prompts during PIN authentication. When specified,
        automatically opens the browser to the Plex authentication page.
 
    .EXAMPLE
        Update-PatServerToken
 
        Refreshes the token for the default server using interactive PIN
        authentication. Opens a browser to plex.tv/link for authorization.
 
    .EXAMPLE
        Update-PatServerToken -Name 'MyServer'
 
        Refreshes the token for the server named 'MyServer' using interactive
        PIN authentication.
 
    .EXAMPLE
        Update-PatServerToken -Name 'MyServer' -Token $newToken
 
        Updates the token for 'MyServer' using a pre-obtained token, skipping
        the interactive authentication flow.
 
    .EXAMPLE
        Update-PatServerToken -Force
 
        Refreshes the default server token non-interactively, automatically
        opening the browser for PIN authorization.
 
    .OUTPUTS
        PSCustomObject
        Returns an object with the following properties:
        - ServerName: The name of the updated server
        - TokenUpdated: Whether the token was successfully stored
        - Verified: Whether the new token was verified against the Plex API
        - StorageType: Where the token is stored ('Vault' or 'Inline')
 
    .NOTES
        If Microsoft.PowerShell.SecretManagement is installed with a registered
        vault, the new token is stored securely in the vault. Otherwise, the
        token is stored in plaintext in servers.json.
 
    .LINK
        Connect-PatAccount
 
    .LINK
        Test-PatServer
 
    .LINK
        Add-PatServer
    #>

    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'Medium')]
    [OutputType([PSCustomObject])]
    param (
        [Parameter(Mandatory = $false, Position = 0)]
        [ValidateNotNullOrEmpty()]
        [string]
        $Name,

        [Parameter(Mandatory = $false)]
        [ValidateNotNullOrEmpty()]
        [string]
        $Token,

        [Parameter(Mandatory = $false)]
        [ValidateRange(1, 1800)]
        [int]
        $TimeoutSeconds = 300,

        [Parameter(Mandatory = $false)]
        [switch]
        $Force
    )

    try {
        # Resolve target server
        if ($Name) {
            $server = Get-PatStoredServer -Name $Name -ErrorAction 'Stop'
        }
        else {
            $server = Get-PatStoredServer -Default -ErrorAction 'Stop'
        }

        $serverName = $server.name
        Write-Verbose "Updating token for server '$serverName'"

        if ($PSCmdlet.ShouldProcess($serverName, 'Update authentication token')) {
            # Obtain token (inside ShouldProcess to avoid interactive auth during -WhatIf)
            $newToken = $Token
            if (-not $newToken) {
                Write-Verbose "No token provided, starting interactive PIN authentication"
                $newToken = Connect-PatAccount -TimeoutSeconds $TimeoutSeconds -Force:$Force
            }

            # Store the new token
            $storageResult = Set-PatServerToken -ServerName $serverName -Token $newToken

            # Update the server configuration entry
            $configuration = Get-PatServerConfiguration -ErrorAction 'Stop'
            $serverEntry = $configuration.servers | Where-Object { $_.name -eq $serverName }

            if (-not $serverEntry) {
                throw "Server entry '$serverName' was not found in configuration."
            }

            if ($storageResult.StorageType -eq 'Vault') {
                # Remove inline token if present, set vault flag
                if ($serverEntry.PSObject.Properties['token']) {
                    $serverEntry.PSObject.Properties.Remove('token')
                }
                if ($serverEntry.PSObject.Properties['tokenInVault']) {
                    $serverEntry.tokenInVault = $true
                }
                else {
                    $serverEntry | Add-Member -NotePropertyName 'tokenInVault' -NotePropertyValue $true
                }
            }
            else {
                # Store inline token, remove vault flag if present
                if ($serverEntry.PSObject.Properties['token']) {
                    $serverEntry.token = $storageResult.Token
                }
                else {
                    $serverEntry | Add-Member -NotePropertyName 'token' -NotePropertyValue $storageResult.Token
                }
                if ($serverEntry.PSObject.Properties['tokenInVault']) {
                    $serverEntry.PSObject.Properties.Remove('tokenInVault')
                }
            }

            Set-PatServerConfiguration -Configuration $configuration -ErrorAction 'Stop'
            Write-Verbose "Token stored successfully (StorageType: $($storageResult.StorageType))"

            # Verify the new token works (honor localUri/preferLocal if configured)
            $verified = $false
            try {
                $verificationBaseUri = $server.uri
                if ($server.PSObject.Properties['preferLocal'] -and $server.preferLocal -and
                    $server.PSObject.Properties['localUri'] -and $server.localUri) {
                    $verificationBaseUri = $server.localUri
                }
                $verificationUri = Join-PatUri -BaseUri $verificationBaseUri -Endpoint '/'
                $verificationHeaders = @{ Accept = 'application/json' }
                $verificationHeaders['X-Plex-Token'] = $newToken
                $null = Invoke-PatApi -Uri $verificationUri -Headers $verificationHeaders -ErrorAction 'Stop'
                $verified = $true
                Write-Verbose "Token verification successful for server '$serverName'"
            }
            catch {
                Write-Warning "Token was stored but verification failed: $($_.Exception.Message)"
            }

            [PSCustomObject]@{
                ServerName   = $serverName
                TokenUpdated = $true
                Verified     = $verified
                StorageType  = $storageResult.StorageType
            }
        }
    }
    catch {
        throw "Failed to update server token: $($_.Exception.Message)"
    }
}