Public/Get-PiHoleDbTopClients.ps1

function Get-PiHoleDbTopClients {
    <#
    .SYNOPSIS
    Retrieves top clients data from the Pi-hole long-term database.
 
    .DESCRIPTION
    This function authenticates to the Pi-hole API using Connect-PiHole, retrieves top clients data from the long-term database, and then disconnects the session using Disconnect-PiHole.
 
    .PARAMETER BaseUrl
    The base URL of the Pi-hole instance (e.g., http://pi.hole or https://pi.hole).
 
    .PARAMETER Credential
    A PSCredential object containing the Pi-hole password.
 
    .PARAMETER From
    DateTime from when the data should be requested. Accepts DateTime objects or date strings like '5-7-2025 3:34PM'.
 
    .PARAMETER Until
    DateTime until when the data should be requested. Accepts DateTime objects or date strings like '5-7-2025 3:34PM'.
 
    .PARAMETER Blocked
    Switch parameter to return information about blocked queries instead of permitted queries.
 
    .PARAMETER Count
    Number of requested items. Default is 10.
 
    .PARAMETER SkipCertificateCheck
    Skip SSL certificate validation. Useful for self-signed certificates.
 
    .OUTPUTS
    A PSCustomObject containing the top clients data, total queries, blocked queries, and processing time.
 
    .EXAMPLE
    $cred = Get-Credential
    $from = Get-Date "2023-01-01 12:00:00"
    $until = Get-Date "2023-01-02 12:00:00"
    Get-PiHoleDbTopClients -BaseUrl 'https://pi.hole' -Credential $cred -From $from -Until $until -SkipCertificateCheck
 
    .EXAMPLE
    # Get top 20 blocked clients using string dates
    $cred = Get-Credential
    Get-PiHoleDbTopClients -BaseUrl 'http://pi.hole' -Credential $cred -From '5-7-2025 3:34PM' -Until '5-7-2025 11:59PM' -Blocked -Count 20
 
    .EXAMPLE
    # Get top 5 permitted clients for the last 24 hours
    $cred = Get-Credential
    $until = Get-Date
    $from = $until.AddDays(-1)
    Get-PiHoleDbTopClients -BaseUrl 'http://pi.hole' -Credential $cred -From $from -Until $until -Count 5
    #>


    param (
        [Parameter(Mandatory = $true)]
        [string]$BaseUrl,

        [Parameter(Mandatory = $true)]
        [System.Management.Automation.PSCredential]$Credential,

        [Parameter(Mandatory = $true)]
        [DateTime]$From,

        [Parameter(Mandatory = $true)]
        [DateTime]$Until,

        [Parameter()]
        [switch]$Blocked,

        [Parameter()]
        [int]$Count = 10,

        [switch]$SkipCertificateCheck
    )

    begin {
        # Validate DateTime parameters
        if ($From -gt $Until) {
            throw "Error: From DateTime cannot be greater than Until DateTime."
        }

        # Validate Count parameter
        if ($Count -lt 1) {
            throw "Error: Count must be greater than 0."
        }
    }

    process {
        $sessionData = $null
        try {
            Write-Verbose "Starting authentication to Pi-hole at $BaseUrl"
            # Authenticate and get session data
            $sessionData = Connect-PiHole -BaseUrl $BaseUrl -Credential $Credential -SkipCertificateCheck:$SkipCertificateCheck
            Write-Verbose "Authentication successful. Session ID: $($sessionData.ID), SID: $($sessionData.SID)"

            # Convert DateTime to Unix timestamps for API call
            $fromUnix = [DateTimeOffset]::new($From).ToUnixTimeSeconds()
            $untilUnix = [DateTimeOffset]::new($Until).ToUnixTimeSeconds()
            Write-Verbose "Converted dates - From: $From ($fromUnix), Until: $Until ($untilUnix)"

            # Prepare API request to get top clients data
            $blockedValue = if ($Blocked) { "1" } else { "0" }
            $url = "$BaseUrl/api/stats/database/top_clients?from=$fromUnix&until=$untilUnix&blocked=$blockedValue&count=$Count"
            Write-Verbose "Making API request to: $url"
            $headers = @{ 'X-FTL-SID' = $sessionData.SID }

            $invokeParams = @{
                Uri         = $url
                Method      = 'Get'
                Headers     = $headers
                ErrorAction = 'Stop'
            }

            if ($SkipCertificateCheck) {
                $invokeParams.SkipCertificateCheck = $true
            }

            $response = Invoke-RestMethod @invokeParams
            Write-Verbose "API request successful. Retrieved $($response.clients.Count) top clients"

            # Return the response as-is
            Write-Verbose "Successfully retrieved top clients data"
            return $response
        }
        catch {
            Write-Error "Error occurred: $($_.Exception.Message)"
            Write-Verbose "Full error details: $($_ | Out-String)"
            
            # Try to disconnect if we have session data
            if ($sessionData -and $sessionData.ID -and $sessionData.SID) {
                Write-Verbose "Attempting to disconnect session due to error..."
                try {
                    Disconnect-PiHole -BaseUrl $BaseUrl -Id $sessionData.ID -SID $sessionData.SID -SkipCertificateCheck:$SkipCertificateCheck
                    Write-Verbose "Session disconnected successfully"
                }
                catch {
                    Write-Warning "Failed to disconnect session: $($_.Exception.Message)"
                }
            }
            throw
        }
        finally {
            # Clean disconnect in finally block
            if ($sessionData -and $sessionData.ID -and $sessionData.SID) {
                Write-Verbose "Attempting final session cleanup..."
                try {
                    Disconnect-PiHole -BaseUrl $BaseUrl -Id $sessionData.ID -SID $sessionData.SID -SkipCertificateCheck:$SkipCertificateCheck
                    Write-Verbose "Final session cleanup completed successfully"
                }
                catch {
                    Write-Warning "Failed to clean up session in finally block: $($_.Exception.Message)"
                }
            }
        }
    }

    end {
        # Nothing to clean up in the end block
    }
}