Private/Misc.ps1

function Format-PiHoleSecond {
    param (
        [int]$TimeInSeconds
    )

    $Data = [math]::Round($TimeInSeconds)

    $Object = [PSCustomObject]@{
        TimeInSeconds = $Data
    }

    $ObjectFinal = $Object
    Write-Output $ObjectFinal
}

function Convert-PiHoleUnixTimeToLocalTime {
    param (
        [int]$UnixTime
    )

    $ConvertedTime = [timezone]::CurrentTimeZone.ToLocalTime(([datetime]'1/1/1970').AddSeconds($UnixTime))

    $ObjectFinal = @()

    $Object = [PSCustomObject]@{
        LocalTime = $ConvertedTime
        UnixTime  = $UnixTime
    }

    $ObjectFinal = $Object
    Write-Output $ObjectFinal
}

function Convert-LocalTimeToPiHoleUnixTime {
    param (
        [datetime]$Date
    )

    $ConvertedTime = [int64](Get-Date $Date -UFormat %s)

    $Object = [PSCustomObject]@{
        LocalTime = $Date
        UnixTime  = $ConvertedTime
    }

    $ObjectFinal = $Object
    Write-Output $ObjectFinal
}

function ConvertTo-PiHolePascalCase {
    #INTERNAL FUNCTION
    param (
        [string]$Name
    )

    if ([string]::IsNullOrEmpty($Name)) {
        return $Name
    }

    $Segments = $Name -split '_' | Where-Object { $_.Length -gt 0 }
    $PascalSegments = foreach ($Segment in $Segments) {
        $Segment.Substring(0, 1).ToUpper() + $Segment.Substring(1)
    }
    return ($PascalSegments -join '')
}

function ConvertTo-PiHolePascalCaseObject {
    #INTERNAL FUNCTION
    #
    # Recursively rebuilds an API response as nested PSCustomObjects/arrays with PascalCase
    # property names (e.g. EXTERNAL_BLOCKED_IP / app_pwhash -> ExternalBlockedIp / AppPwhash),
    # so deep/wide response trees don't need every field hardcoded by hand to be PowerShell
    # object friendly - and so newly added API fields show up automatically instead of being
    # silently dropped.
    param (
        [Parameter(ValueFromPipeline = $true)]
        $InputObject
    )
    process {
        if ($null -eq $InputObject) {
            return $null
        }

        if ($InputObject -is [System.Management.Automation.PSCustomObject]) {
            $Result = [ordered]@{}
            foreach ($Prop in $InputObject.PSObject.Properties) {
                $Key = ConvertTo-PiHolePascalCase -Name $Prop.Name
                $Result[$Key] = ConvertTo-PiHolePascalCaseObject -InputObject $Prop.Value
            }
            return [PSCustomObject]$Result
        }

        if (($InputObject -is [System.Collections.IEnumerable]) -and ($InputObject -isnot [string])) {
            return @($InputObject | ForEach-Object { ConvertTo-PiHolePascalCaseObject -InputObject $_ })
        }

        return $InputObject
    }
}

function Remove-PiHoleCurrentAuthSession {
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "", Justification = "It removes sessions from PiHole only")]
    [CmdletBinding()]
    param (
        [System.URI]$PiHoleServer,
        [string]$Sid,
        [bool]$IgnoreSsl = $false
    )
    $Params = @{
        Headers              = @{sid = $($Sid) }
        Uri                  = "$($PiHoleServer.OriginalString)/api/auth"
        Method               = "Delete"
        SkipCertificateCheck = $IgnoreSsl
        ContentType          = "application/json"
    }

    try {
        $null = Invoke-RestMethod @Params
    }

    catch {
        # Best-effort logout, called from every public function's finally block - a transient
        # failure here (e.g. the server briefly unreachable right after a restart) must never
        # fail the caller. Write-Error would do exactly that under $ErrorActionPreference =
        # 'Stop', which Azure Pipelines' pwsh task sets by default.
        Write-Warning -Message "Failed to close Pi-hole session: $($_.Exception.Message)"
    }
}