Public/Remove-DuplicateConfluencePage.ps1

function Remove-DuplicateConfluencePage {
    <#
    .SYNOPSIS
        Removes duplicate Confluence pages that share a title and parent, keeping one.

    .DESCRIPTION
        Remove-DuplicateConfluencePage lists the pages in a space, finds the pages with the given title
        under the given parent and, when there is more than one, deletes all but one. By default the
        newest page (highest version number, then latest modification) is kept; use
        -KeepNewest:$false to keep the oldest.

        Every deletion asks for confirmation unless you pass -Confirm:$false; -WhatIf shows what would
        be deleted. The kept page is returned. Only the pages returned by Get-ConfluencePage for the
        space (up to three API result pages) are considered.

        Called as Remove-DuplicateConfluencePages (the name used before 0.1.0) it still works through
        an alias.

    .PARAMETER SpaceKey
        The key or numeric ID of the space to search.

    .PARAMETER ParentId
        The ID of the parent page of the duplicates.

    .PARAMETER PageTitle
        The exact title of the duplicated page.

    .PARAMETER KeepNewest
        Keep the newest page (default). Use -KeepNewest:$false to keep the oldest page instead.

    .EXAMPLE
        Remove-DuplicateConfluencePage -SpaceKey DOCS -ParentId 1000 -PageTitle 'Weekly report' -WhatIf

        Shows which duplicate "Weekly report" pages under page 1000 would be deleted.

    .EXAMPLE
        Remove-DuplicateConfluencePage -SpaceKey DOCS -ParentId 1000 -PageTitle 'Weekly report' -KeepNewest:$false -Confirm:$false

        Deletes all but the oldest duplicate without prompting.

    .OUTPUTS
        System.Management.Automation.PSCustomObject. The page that was kept.
    #>

    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High')]
    [Alias('Remove-DuplicateConfluencePages')]
    [OutputType([pscustomobject])]
    param (
        [Parameter(Mandatory, HelpMessage = 'The space key or ID.')]
        [string]$SpaceKey,

        [Parameter(Mandatory, HelpMessage = 'The ID of the parent page.')]
        [string]$ParentId,

        [Parameter(Mandatory, HelpMessage = 'The exact page title.')]
        [string]$PageTitle,

        [Parameter(HelpMessage = 'Keep the newest page (default) or, with -KeepNewest:$false, the oldest.')]
        [switch]$KeepNewest
    )

    if (-not $PSBoundParameters.ContainsKey('KeepNewest')) { $KeepNewest = $true }
    Write-Verbose "Looking for duplicate pages Title='$PageTitle' ParentId='$ParentId' SpaceKey='$SpaceKey' KeepNewest=$KeepNewest"

    $TelemetryArgs = @{
        ModuleName    = $MyInvocation.MyCommand.Module.Name
        ModuleVersion = [string]$MyInvocation.MyCommand.Module.Version
        CommandName   = $MyInvocation.MyCommand.Name
        ExecutionID   = [guid]::NewGuid().ToString()
    }
    Invoke-TelemetryCollection @TelemetryArgs -Stage Start -ClearTimer
    $telemetryFailed = $false
    try {
        $all = Get-ConfluencePage -SpaceKey $SpaceKey -ErrorAction Stop
        $pagesCollection = @($all.Results | Where-Object { $null -ne $_ })
        if ($pagesCollection.Count -eq 0) {
            Write-Verbose "No pages returned for space '$SpaceKey'."
            return
        }

        $duplicates = @($pagesCollection | Where-Object { $_.title -eq $PageTitle -and "$($_.parentId)" -eq $ParentId })
        if ($duplicates.Count -le 1) {
            Write-Verbose 'Zero or one page found with that title/parent. Nothing to remove.'
            return ($duplicates | Select-Object -First 1)
        }
        Write-Verbose "Found $($duplicates.Count) pages matching criteria."

        $sortProperties = @(
            @{ Expression = { $number = 0; if ($_.version -and [int]::TryParse("$($_.version.number)", [ref]$number)) { $number } else { 0 } } },
            @{ Expression = { $date = [datetime]::MinValue; $stamp = if ($_.version -and $_.version.createdAt) { $_.version.createdAt } else { $_.lastModified }; if ($stamp -and [datetime]::TryParse("$stamp", [ref]$date)) { $date } else { [datetime]::MinValue } } }
        )
        $sortedPages = @($duplicates | Sort-Object -Property $sortProperties -Descending:$KeepNewest)
        $pageToKeep = $sortedPages[0]
        $pagesToDelete = @($sortedPages | Select-Object -Skip 1)

        $versionNum = if ($pageToKeep.version) { $pageToKeep.version.number } else { 'N/A' }
        Write-Verbose ('Keeping page ID={0} Version={1}' -f $pageToKeep.id, $versionNum)

        foreach ($page in $pagesToDelete) {
            $deleteId = $page.id
            if (-not $PSCmdlet.ShouldProcess("Confluence page $deleteId ('$PageTitle')", 'Remove duplicate')) {
                continue
            }
            try {
                $null = Invoke-ConfluenceRequest -Method DELETE -Resource pages -Id $deleteId -MaxQueryPages 1 -ErrorAction Stop
                Write-Verbose "Deleted page ID=$deleteId"
            }
            catch {
                Write-Error ('Failed to delete page ID {0}. Error: {1}' -f $deleteId, $_)
            }
        }

        return $pageToKeep
    }
    catch {
        $telemetryFailed = $true
        Invoke-TelemetryCollection @TelemetryArgs -Stage End -Failed $true -Exception $_
        Write-Error "Error removing duplicate pages. $_"
    }
    finally {
        if (-not $telemetryFailed) {
            Invoke-TelemetryCollection @TelemetryArgs -Stage End
        }
    }
}