Public/Remove-PatPlaylistItem.ps1

function Remove-PatPlaylistItem {
    <#
    .SYNOPSIS
        Removes an item from a playlist on a Plex server.
 
    .DESCRIPTION
        Removes a single item from a playlist. The item is identified by its
        playlist-specific item ID (playlistItemId), not the media's rating key.
        Use Get-PatPlaylist -IncludeItems to retrieve the PlaylistItemId values.
 
    .PARAMETER PlaylistId
        The unique identifier of the playlist containing the item.
 
    .PARAMETER PlaylistItemId
        The playlist-specific item ID of the item to remove. This is different
        from the media's rating key - it identifies the item's position in this
        specific playlist. Obtain this value from Get-PatPlaylist -IncludeItems.
 
    .PARAMETER ServerName
        The name of a stored server to use. Use Get-PatStoredServer to see available servers.
        This is more convenient than ServerUri as you don't need to remember the URI or token.
 
    .PARAMETER ServerUri
        The base URI of the Plex server (e.g., http://plex.example.com:32400).
        If not specified, uses the default stored server.
 
    .PARAMETER Token
        The Plex authentication token. Required when using -ServerUri to authenticate
        with the server. If not specified with -ServerUri, requests may fail with 401.
 
    .PARAMETER PassThru
        If specified, returns the updated playlist object.
 
    .EXAMPLE
        Remove-PatPlaylistItem -PlaylistId 12345 -PlaylistItemId 67890
 
        Removes the item with playlist item ID 67890 from playlist 12345.
 
    .EXAMPLE
        Remove-PatPlaylistItem -PlaylistId 12345 -PlaylistItemId 67890 -ServerName 'Home'
 
        Removes an item from a playlist on the stored server named 'Home'.
 
    .EXAMPLE
        Get-PatPlaylist -PlaylistName 'My List' -IncludeItems |
            Select-Object -ExpandProperty Items |
            Where-Object { $_.Title -eq 'Unwanted Movie' } |
            Remove-PatPlaylistItem
 
        Removes a specific movie from the playlist by title.
 
    .EXAMPLE
        $playlist = Get-PatPlaylist -PlaylistName 'Watch Later' -IncludeItems
        $playlist.Items | Select-Object -First 1 | Remove-PatPlaylistItem -PassThru
 
        Removes the first item from a playlist and returns the updated playlist.
 
    .EXAMPLE
        Remove-PatPlaylistItem -PlaylistId 12345 -PlaylistItemId 67890 -WhatIf
 
        Shows what would be removed without actually removing it.
 
    .OUTPUTS
        PlexAutomationToolkit.Playlist (when -PassThru is specified)
 
        Returns the updated playlist object showing the new item count.
 
    .NOTES
        The PlaylistItemId is specific to the playlist and represents the item's
        position/association within that playlist. It is not the same as the media's
        rating key (ratingKey). Always use Get-PatPlaylist -IncludeItems to get the
        correct PlaylistItemId values.
    #>

    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'Medium')]
    param (
        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName)]
        [ValidateRange(1, [int]::MaxValue)]
        [int]
        $PlaylistId,

        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName)]
        [ValidateRange(1, [int]::MaxValue)]
        [int]
        $PlaylistItemId,

        [Parameter(Mandatory = $false, ValueFromPipelineByPropertyName)]
        [string]
        $ServerName,

        [Parameter(Mandatory = $false, ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [ValidateScript({ Test-PatServerUri -Uri $_ })]
        [string]
        $ServerUri,

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

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

    begin {
        # Cache for server context (will be set per-item in process block if ServerUri varies)
        $script:cachedServerContext = $null
        $script:cachedServerName = $null
        $script:cachedServerUri = $null
        $script:cachedToken = $null
    }

    process {
        try {
            # Resolve server context (cache if server parameters are the same)
            if ($ServerName -ne $script:cachedServerName -or $ServerUri -ne $script:cachedServerUri -or $Token -ne $script:cachedToken) {
                try {
                    $script:cachedServerContext = Resolve-PatServerContext -ServerName $ServerName -ServerUri $ServerUri -Token $Token
                    $script:cachedServerName = $ServerName
                    $script:cachedServerUri = $ServerUri
                    $script:cachedToken = $Token
                }
                catch {
                    throw "Failed to resolve server: $($_.Exception.Message)"
                }
            }

            $effectiveUri = $script:cachedServerContext.Uri
            $headers = $script:cachedServerContext.Headers

            # Get item info for ShouldProcess message
            $itemTitle = "Item $PlaylistItemId"
            try {
                # Only pass ServerUri if explicitly specified
                $getParameters = @{ PlaylistId = $PlaylistId; IncludeItems = $true; ErrorAction = 'SilentlyContinue' }
                if ($script:cachedServerContext.WasExplicitUri) { $getParameters['ServerUri'] = $effectiveUri }
                $playlist = Get-PatPlaylist @getParameters
                if ($playlist -and $playlist.Items) {
                    $item = $playlist.Items | Where-Object { $_.PlaylistItemId -eq $PlaylistItemId }
                    if ($item) {
                        $itemTitle = "'$($item.Title)'"
                    }
                }
            }
            catch {
                Write-Verbose "Could not retrieve item info"
            }

            $target = "$itemTitle from playlist $PlaylistId"

            if ($PSCmdlet.ShouldProcess($target, 'Remove from playlist')) {
                $endpoint = "/playlists/$PlaylistId/items/$PlaylistItemId"
                $uri = Join-PatUri -BaseUri $effectiveUri -Endpoint $endpoint

                Write-Verbose "Removing item $PlaylistItemId from playlist $PlaylistId"

                $null = Invoke-PatApi -Uri $uri -Method 'DELETE' -Headers $headers -ErrorAction 'Stop'

                if ($PassThru) {
                    # Only pass ServerUri if explicitly specified
                    $getParameters = @{ PlaylistId = $PlaylistId; ErrorAction = 'Stop' }
                    if ($script:cachedServerContext.WasExplicitUri) { $getParameters['ServerUri'] = $effectiveUri }
                    Get-PatPlaylist @getParameters
                }
            }
        }
        catch {
            throw "Failed to remove item from playlist: $($_.Exception.Message)"
        }
    }
}