diff --git a/.github/workflows/CreateRelease.yml b/.github/workflows/CreateRelease.yml index b5d58cf..7ee89de 100644 --- a/.github/workflows/CreateRelease.yml +++ b/.github/workflows/CreateRelease.yml @@ -51,6 +51,16 @@ jobs: git tag ${{ steps.bump.outputs.new_tag }} git push origin ${{ steps.bump.outputs.new_tag }} + - name: Generate release notes + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh api "repos/${{ github.repository }}/releases/generate-notes" \ + -f tag_name="${{ steps.bump.outputs.new_tag }}" \ + -f previous_tag_name="${{ steps.get_tag.outputs.latest_tag }}" \ + --jq .body > release-notes.md + cat release-notes.md + - name: Copy README into module folder shell: pwsh run: | @@ -82,7 +92,7 @@ jobs: with: tag_name: ${{ steps.bump.outputs.new_tag }} name: "Release ${{ steps.bump.outputs.new_tag }}" - generate_release_notes: true + body_path: release-notes.md files: output/release.zip env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -94,14 +104,19 @@ jobs: $modulePath = "PiHoleShell" # Change to your actual module folder $manifest = Get-ChildItem -Path $modulePath -Filter PiHoleShell.psd1 -Recurse | Select-Object -First 1 - (Get-Content $($manifest.fullname)) -replace '0.0.0', ($ENV:TAG -replace "v", "") | Out-File $manifest.fullname - Write-Host "Replacing 0.0.0 with $ENV:NEW_TAG" - if (-not $manifest) { throw "No module manifest (*.psd1) found in $modulePath" } - + + # Surgical replace rather than Update-ModuleManifest, which rewrites and reformats + # the entire file (drops comments, changes array literal styles, etc.). The source + # manifest always has this exact static placeholder, since the version/notes bump + # here is never committed back to the repo. + $releaseNotes = (Get-Content -Path release-notes.md -Raw).Trim() + $releaseNotesEscaped = $releaseNotes -replace "'", "''" + $manifestContent = Get-Content -Path $manifest.FullName -Raw + $manifestContent = $manifestContent.Replace("'Initial release targeting PowerShell 7+'", "'$releaseNotesEscaped'") + Set-Content -Path $manifest.FullName -Value $manifestContent -NoNewline + Write-Host "Publishing module: $($manifest.FullName)" - Publish-Module -Path $manifest.DirectoryName -NuGetApiKey $apiKey -Verbose - env: - NEW_TAG: ${{ steps.bump.outputs.new_tag }} \ No newline at end of file + Publish-Module -Path $manifest.DirectoryName -NuGetApiKey $apiKey -Verbose \ No newline at end of file diff --git a/.github/workflows/SyncReadmeCommandReference.yml b/.github/workflows/SyncReadmeCommandReference.yml index c409d63..3b9c3f1 100644 --- a/.github/workflows/SyncReadmeCommandReference.yml +++ b/.github/workflows/SyncReadmeCommandReference.yml @@ -6,7 +6,7 @@ on: branches: [ "main" ] permissions: - contents: write + contents: read jobs: sync-readme: @@ -20,18 +20,10 @@ jobs: ref: ${{ github.head_ref }} fetch-depth: 0 - - name: Regenerate README command reference + # develop requires PRs for all changes, so this can't auto-commit/push a fix directly to + # develop (that used to work before branch protection was added, and now fails every time + # there's real drift to fix). Instead this just fails the check with instructions, the same + # way Invoke-ScriptAnalyzer already gates PSScriptAnalyzer.yml. + - name: Check README command reference is up to date shell: pwsh - run: ./tools/Update-ReadmeCommandReference.ps1 - - - name: Commit changes if needed - run: | - if [ -n "$(git status --porcelain README.md)" ]; then - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" - git add README.md - git commit -m "docs: sync README command reference [skip ci]" - git push origin HEAD:${{ github.head_ref }} - else - echo "README.md command reference already up to date." - fi + run: ./tools/Update-ReadmeCommandReference.ps1 -Check diff --git a/PiHoleShell/Private/Misc.ps1 b/PiHoleShell/Private/Misc.ps1 index c9c50cc..a537078 100644 --- a/PiHoleShell/Private/Misc.ps1 +++ b/PiHoleShell/Private/Misc.ps1 @@ -47,6 +47,57 @@ function Convert-LocalTimeToPiHoleUnixTime { 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()] @@ -68,6 +119,10 @@ function Remove-PiHoleCurrentAuthSession { } catch { - Write-Error -Message $_.Exception.Message + # 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)" } } \ No newline at end of file diff --git a/PiHoleShell/Public/Actions/Invoke-PiHoleFlushNetwork.ps1 b/PiHoleShell/Public/Actions/Invoke-PiHoleFlushNetwork.ps1 index e1b6a8c..2c47c34 100644 --- a/PiHoleShell/Public/Actions/Invoke-PiHoleFlushNetwork.ps1 +++ b/PiHoleShell/Public/Actions/Invoke-PiHoleFlushNetwork.ps1 @@ -19,7 +19,7 @@ Set to $true to skip SSL certificate validation This will dump the response instead of the formatted object .EXAMPLE -Invoke-PiHoleFlushNetwork -PiHoleServer "http://pihole.domain.com:8080" -Password "fjdsjfldsjfkldjslafjskdl" +Invoke-PiHoleFlushNetwork -PiHoleServer "http://pihole.domain.com:8080" -Password "your-app-password" #> [CmdletBinding(HelpUri = 'https://ftl.pi-hole.net/master/docs/#post-/action/flush/network')] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Justification = 'Flushes PiHole logs')] diff --git a/PiHoleShell/Public/Actions/Restart-PiHoleDnsService.ps1 b/PiHoleShell/Public/Actions/Restart-PiHoleDnsService.ps1 index 2257393..60d8c7d 100644 --- a/PiHoleShell/Public/Actions/Restart-PiHoleDnsService.ps1 +++ b/PiHoleShell/Public/Actions/Restart-PiHoleDnsService.ps1 @@ -19,7 +19,7 @@ Set to $true to skip SSL certificate validation This will dump the response instead of the formatted object .EXAMPLE -Invoke-PiHoleRestartDns -PiHoleServer "http://pihole.domain.com:8080" -Password "fjdsjfldsjfkldjslafjskdl" +Invoke-PiHoleRestartDns -PiHoleServer "http://pihole.domain.com:8080" -Password "your-app-password" #> [CmdletBinding(HelpUri = 'https://ftl.pi-hole.net/master/docs/#post-/action/restartdns')] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Justification = 'Restarts PiHole DNS')] diff --git a/PiHoleShell/Public/Authentication/Get-PiHoleCurrentAuthSession.ps1 b/PiHoleShell/Public/Authentication/Get-PiHoleCurrentAuthSession.ps1 index 2ae3603..fdce234 100644 --- a/PiHoleShell/Public/Authentication/Get-PiHoleCurrentAuthSession.ps1 +++ b/PiHoleShell/Public/Authentication/Get-PiHoleCurrentAuthSession.ps1 @@ -16,7 +16,7 @@ Ignore SSL when interacting with the PiHole API This will dump the response instead of the formatted object .EXAMPLE -Get-PiHoleCurrentAuthSession -PiHoleServer "http://pihole.domain.com:8080" -Password "fjdsjfldsjfkldjslafjskdl" +Get-PiHoleCurrentAuthSession -PiHoleServer "http://pihole.domain.com:8080" -Password "your-app-password" #> [CmdletBinding(HelpUri = 'https://ftl.pi-hole.net/master/docs/#get-/auth/sessions')] [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "Password")] diff --git a/PiHoleShell/Public/Authentication/Remove-PiHoleAuthSession.ps1 b/PiHoleShell/Public/Authentication/Remove-PiHoleAuthSession.ps1 index c448ad7..e34fff4 100644 --- a/PiHoleShell/Public/Authentication/Remove-PiHoleAuthSession.ps1 +++ b/PiHoleShell/Public/Authentication/Remove-PiHoleAuthSession.ps1 @@ -13,7 +13,7 @@ The API Password you generated from your PiHole server Ignore SSL when interacting with the PiHole API .EXAMPLE -Get-PiHoleCurrentAuthSession -PiHoleServer "http://pihole.domain.com:8080" -Password "fjdsjfldsjfkldjslafjskdl" +Get-PiHoleCurrentAuthSession -PiHoleServer "http://pihole.domain.com:8080" -Password "your-app-password" #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Justification = 'Does not change state')] [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "Password")] diff --git a/PiHoleShell/Public/Config/Get-PiHoleConfig.ps1 b/PiHoleShell/Public/Config/Get-PiHoleConfig.ps1 index 5a2ace8..12cb171 100644 --- a/PiHoleShell/Public/Config/Get-PiHoleConfig.ps1 +++ b/PiHoleShell/Public/Config/Get-PiHoleConfig.ps1 @@ -1,11 +1,33 @@ function Get-PiHoleConfig { <# .SYNOPSIS -https://ftl.pi-hole.net/master/docs/#get-/config +Get current configuration of Pi-hole +.DESCRIPTION +Request Pi-hole's full configuration tree (dns, dhcp, ntp, resolver, database, webserver, +files, misc, and debug settings). The formatted output mirrors the API response as nested +objects with PascalCase property names, so the entire configuration is available for +inspection rather than a hand-picked subset. + +.PARAMETER PiHoleServer +The URL to the PiHole Server, for example "http://pihole.domain.com:8080", or "http://192.168.1.100" + +.PARAMETER Password +The API Password you generated from your PiHole server + +.PARAMETER IgnoreSsl +Set to $true to skip SSL certificate validation + +.PARAMETER RawOutput +This will dump the response instead of the formatted object + +.EXAMPLE +Get-PiHoleConfig -PiHoleServer "http://pihole.domain.com:8080" -Password "your-app-password" + +.EXAMPLE +(Get-PiHoleConfig -PiHoleServer "http://pihole.domain.com:8080" -Password "your-app-password").Dns.Upstreams #> - #Work In Progress - [CmdletBinding()] + [CmdletBinding(HelpUri = 'https://ftl.pi-hole.net/master/docs/#get-/config')] [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "Password")] param ( [Parameter(Mandatory = $true)] @@ -15,6 +37,7 @@ https://ftl.pi-hole.net/master/docs/#get-/config [bool]$IgnoreSsl = $false, [bool]$RawOutput = $false ) + try { $Sid = Request-PiHoleAuth -PiHoleServer $PiHoleServer -Password $Password -IgnoreSsl $IgnoreSsl $Params = @{ @@ -31,41 +54,13 @@ https://ftl.pi-hole.net/master/docs/#get-/config Write-Output $Response } else { - $ObjectFinal = @() - $Dns = [PSCustomObject]@{ - Upstreams = $Response.config.dns.upstreams - } - - $Dhcp = [PSCustomObject]@{ - Active = $Response.config.dhcp.active - Start = $Response.config.dhcp.start - End = $Response.config.dhcp.end - Hosts = $Response.config.dhcp.hosts - IgnoreUnknownClients = $Response.config.dhcp.ignoreUnknownClients - Ipv6 = $Response.config.dhcp.ipv6 - LeaseTime = $Response.config.dhcp.leaseTime - Logging = $Response.config.dhcp.logging - MultiDNS = $Response.config.dhcp.multiDNS - Netmask = $Response.config.dhcp.netmask - RapidCommit = $Response.config.dhcp.rapidCommit - Router = $Response.config.dhcp.router - } - - $Object = [PSCustomObject]@{ - Dns = $Dns - Dhcp = $Dhcp - } - - if ($Object) { - $ObjectFinal += $Object - } - Write-Output $ObjectFinal + $Object = ConvertTo-PiHolePascalCaseObject -InputObject $Response.config + Write-Output $Object } } catch { Write-Error -Message $_.Exception.Message - break } finally { @@ -73,4 +68,4 @@ https://ftl.pi-hole.net/master/docs/#get-/config Remove-PiHoleCurrentAuthSession -PiHoleServer $PiHoleServer -Sid $Sid -IgnoreSsl $IgnoreSsl } } -} \ No newline at end of file +} diff --git a/PiHoleShell/Public/DnsControl/Get-PiHoleDnsBlockingStatus.ps1 b/PiHoleShell/Public/DnsControl/Get-PiHoleDnsBlockingStatus.ps1 index e27a504..804b3e7 100644 --- a/PiHoleShell/Public/DnsControl/Get-PiHoleDnsBlockingStatus.ps1 +++ b/PiHoleShell/Public/DnsControl/Get-PiHoleDnsBlockingStatus.ps1 @@ -16,7 +16,7 @@ Ignore SSL when interacting with the PiHole API This will dump the response instead of the formatted object .EXAMPLE -Get-PiHoleDnsBlockingStatus -PiHoleServer "http://pihole.domain.com:8080" -Password "fjdsjfldsjfkldjslafjskdl" +Get-PiHoleDnsBlockingStatus -PiHoleServer "http://pihole.domain.com:8080" -Password "your-app-password" #> [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "Password")] param ( diff --git a/PiHoleShell/Public/DnsControl/Set-PiHoleDnsBlocking.ps1 b/PiHoleShell/Public/DnsControl/Set-PiHoleDnsBlocking.ps1 index ab64125..de140d2 100644 --- a/PiHoleShell/Public/DnsControl/Set-PiHoleDnsBlocking.ps1 +++ b/PiHoleShell/Public/DnsControl/Set-PiHoleDnsBlocking.ps1 @@ -13,13 +13,13 @@ The API Password you generated from your PiHole server True or False, if you set it to False when Blocking was set to true, it will disable blocking .PARAMETER TimeInSeconds -How long should the opposite setting last, if you do not set a time, it will be set forever until you change it +How long the opposite setting should last, in seconds .PARAMETER RawOutput This will dump the response instead of the formatted object .EXAMPLE -Set-PiHoleDnsBlocking -PiHoleServer "http://pihole.domain.com:8080" -Password "fjdsjfldsjfkldjslafjskdl" -Blocking $false -TimeInSeconds 60 +Set-PiHoleDnsBlocking -PiHoleServer "http://pihole.domain.com:8080" -Password "your-app-password" -Blocking $false -TimeInSeconds 60 #> [CmdletBinding()] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Justification = 'Does not change state')] @@ -31,7 +31,8 @@ Set-PiHoleDnsBlocking -PiHoleServer "http://pihole.domain.com:8080" -Password "f [string]$Password, [ValidateSet("True", "False")] $Blocking, - [int]$TimeInSeconds = $null, + [Parameter(Mandatory = $true)] + [int]$TimeInSeconds, [bool]$IgnoreSsl = $false, [bool]$RawOutput = $false ) diff --git a/PiHoleShell/Public/ListManagement/Search-PiHoleListDomain.ps1 b/PiHoleShell/Public/ListManagement/Search-PiHoleListDomain.ps1 index 87374c3..4d8c7de 100644 --- a/PiHoleShell/Public/ListManagement/Search-PiHoleListDomain.ps1 +++ b/PiHoleShell/Public/ListManagement/Search-PiHoleListDomain.ps1 @@ -25,7 +25,7 @@ Set to $true to skip SSL certificate validation This will dump the response instead of the formatted object .EXAMPLE -Search-PiHoleListDomain -PiHoleServer "http://pihole.domain.com:8080" -Password "fjdsjfldsjfkldjslafjskdl" -Domain "doubleclick.net" +Search-PiHoleListDomain -PiHoleServer "http://pihole.domain.com:8080" -Password "your-app-password" -Domain "doubleclick.net" #> [CmdletBinding(HelpUri = 'https://ftl.pi-hole.net/master/docs/#get-/search/-domain-')] [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "Password")] diff --git a/PiHoleShell/Public/Metrics/Get-PiHoleStatsDatabaseQueryType.ps1 b/PiHoleShell/Public/Metrics/Get-PiHoleStatsDatabaseQueryType.ps1 index ab1f931..5350d52 100644 --- a/PiHoleShell/Public/Metrics/Get-PiHoleStatsDatabaseQueryType.ps1 +++ b/PiHoleShell/Public/Metrics/Get-PiHoleStatsDatabaseQueryType.ps1 @@ -14,10 +14,10 @@ The URL to the PiHole Server, for example "http://pihole.domain.com:8080", or "h The API Password you generated from your PiHole server .PARAMETER From -Unix timestamp from when the data should be requested +Local date/time from when the data should be requested. Defaults to 8 hours ago. .PARAMETER Until -Unix timestamp until when the data should be requested +Local date/time until when the data should be requested. Defaults to now. .PARAMETER IgnoreSsl Set to $true to skip SSL certificate validation @@ -26,7 +26,10 @@ Set to $true to skip SSL certificate validation This will dump the response instead of the formatted object .EXAMPLE -Get-PiHoleStatsDatabaseQueryType -PiHoleServer "http://pihole.domain.com:8080" -Password "fjdsjfldsjfkldjslafjskdl" -From 1672580025 -Until 1672666425 +Get-PiHoleStatsDatabaseQueryType -PiHoleServer "http://pihole.domain.com:8080" -Password "your-app-password" + +.EXAMPLE +Get-PiHoleStatsDatabaseQueryType -PiHoleServer "http://pihole.domain.com:8080" -Password "your-app-password" -From (Get-Date).AddDays(-7) -Until (Get-Date) #> [CmdletBinding(HelpUri = 'https://ftl.pi-hole.net/master/docs/#get-/stats/database/query_types')] [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "Password")] @@ -35,10 +38,8 @@ Get-PiHoleStatsDatabaseQueryType -PiHoleServer "http://pihole.domain.com:8080" - [System.URI]$PiHoleServer, [Parameter(Mandatory = $true)] [string]$Password, - [Parameter(Mandatory = $true)] - [int]$From, - [Parameter(Mandatory = $true)] - [int]$Until, + [datetime]$From = (Get-Date).AddHours(-8), + [datetime]$Until = (Get-Date), [bool]$IgnoreSsl = $false, [bool]$RawOutput = $false ) @@ -46,9 +47,12 @@ Get-PiHoleStatsDatabaseQueryType -PiHoleServer "http://pihole.domain.com:8080" - try { $Sid = Request-PiHoleAuth -PiHoleServer $PiHoleServer -Password $Password -IgnoreSsl $IgnoreSsl + $FromUnixTime = (Convert-LocalTimeToPiHoleUnixTime -Date $From).UnixTime + $UntilUnixTime = (Convert-LocalTimeToPiHoleUnixTime -Date $Until).UnixTime + $Params = @{ Headers = @{sid = $($Sid) } - Uri = "$($PiHoleServer.OriginalString)/api/stats/database/query_types?from=$From&until=$Until" + Uri = "$($PiHoleServer.OriginalString)/api/stats/database/query_types?from=$FromUnixTime&until=$UntilUnixTime" Method = "Get" SkipCertificateCheck = $IgnoreSsl ContentType = "application/json" @@ -60,25 +64,14 @@ Get-PiHoleStatsDatabaseQueryType -PiHoleServer "http://pihole.domain.com:8080" - Write-Output $Response } else { - $Object = [PSCustomObject]@{ - A = $Response.types.A - AAAA = $Response.types.AAAA - ANY = $Response.types.ANY - SRV = $Response.types.SRV - SOA = $Response.types.SOA - PTR = $Response.types.PTR - TXT = $Response.types.TXT - NAPTR = $Response.types.NAPTR - MX = $Response.types.MX - DS = $Response.types.DS - RRSIG = $Response.types.RRSIG - DNSKEY = $Response.types.DNSKEY - NS = $Response.types.NS - SVCB = $Response.types.SVCB - HTTPS = $Response.types.HTTPS - OTHER = $Response.types.OTHER + $QueryTypes = @('A', 'AAAA', 'ANY', 'SRV', 'SOA', 'PTR', 'TXT', 'NAPTR', 'MX', 'DS', 'RRSIG', 'DNSKEY', 'NS', 'SVCB', 'HTTPS', 'OTHER') + $ObjectFinal = foreach ($Type in $QueryTypes) { + [PSCustomObject]@{ + Type = $Type + Count = $Response.types.$Type + } } - Write-Output $Object + Write-Output $ObjectFinal } } diff --git a/PiHoleShell/Public/Metrics/Get-PiHoleStatsDatabaseSummary.ps1 b/PiHoleShell/Public/Metrics/Get-PiHoleStatsDatabaseSummary.ps1 index 0b5c003..54bc13c 100644 --- a/PiHoleShell/Public/Metrics/Get-PiHoleStatsDatabaseSummary.ps1 +++ b/PiHoleShell/Public/Metrics/Get-PiHoleStatsDatabaseSummary.ps1 @@ -14,10 +14,10 @@ The URL to the PiHole Server, for example "http://pihole.domain.com:8080", or "h The API Password you generated from your PiHole server .PARAMETER From -Unix timestamp from when the data should be requested +Local date/time from when the data should be requested. Defaults to 8 hours ago. .PARAMETER Until -Unix timestamp until when the data should be requested +Local date/time until when the data should be requested. Defaults to now. .PARAMETER IgnoreSsl Set to $true to skip SSL certificate validation @@ -26,7 +26,10 @@ Set to $true to skip SSL certificate validation This will dump the response instead of the formatted object .EXAMPLE -Get-PiHoleStatsDatabaseSummary -PiHoleServer "http://pihole.domain.com:8080" -Password "fjdsjfldsjfkldjslafjskdl" -From 1672580025 -Until 1672666425 +Get-PiHoleStatsDatabaseSummary -PiHoleServer "http://pihole.domain.com:8080" -Password "your-app-password" + +.EXAMPLE +Get-PiHoleStatsDatabaseSummary -PiHoleServer "http://pihole.domain.com:8080" -Password "your-app-password" -From (Get-Date).AddDays(-7) -Until (Get-Date) #> [CmdletBinding(HelpUri = 'https://ftl.pi-hole.net/master/docs/#get-/stats/database/summary')] [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "Password")] @@ -35,10 +38,8 @@ Get-PiHoleStatsDatabaseSummary -PiHoleServer "http://pihole.domain.com:8080" -Pa [System.URI]$PiHoleServer, [Parameter(Mandatory = $true)] [string]$Password, - [Parameter(Mandatory = $true)] - [int]$From, - [Parameter(Mandatory = $true)] - [int]$Until, + [datetime]$From = (Get-Date).AddHours(-8), + [datetime]$Until = (Get-Date), [bool]$IgnoreSsl = $false, [bool]$RawOutput = $false ) @@ -46,9 +47,12 @@ Get-PiHoleStatsDatabaseSummary -PiHoleServer "http://pihole.domain.com:8080" -Pa try { $Sid = Request-PiHoleAuth -PiHoleServer $PiHoleServer -Password $Password -IgnoreSsl $IgnoreSsl + $FromUnixTime = (Convert-LocalTimeToPiHoleUnixTime -Date $From).UnixTime + $UntilUnixTime = (Convert-LocalTimeToPiHoleUnixTime -Date $Until).UnixTime + $Params = @{ Headers = @{sid = $($Sid) } - Uri = "$($PiHoleServer.OriginalString)/api/stats/database/summary?from=$From&until=$Until" + Uri = "$($PiHoleServer.OriginalString)/api/stats/database/summary?from=$FromUnixTime&until=$UntilUnixTime" Method = "Get" SkipCertificateCheck = $IgnoreSsl ContentType = "application/json" diff --git a/PiHoleShell/Public/Metrics/Get-PiHoleStatsDatabaseTopClient.ps1 b/PiHoleShell/Public/Metrics/Get-PiHoleStatsDatabaseTopClient.ps1 index 4110f2c..4125f56 100644 --- a/PiHoleShell/Public/Metrics/Get-PiHoleStatsDatabaseTopClient.ps1 +++ b/PiHoleShell/Public/Metrics/Get-PiHoleStatsDatabaseTopClient.ps1 @@ -14,10 +14,10 @@ The URL to the PiHole Server, for example "http://pihole.domain.com:8080", or "h The API Password you generated from your PiHole server .PARAMETER From -Unix timestamp from when the data should be requested +Local date/time from when the data should be requested. Defaults to 8 hours ago. .PARAMETER Until -Unix timestamp until when the data should be requested +Local date/time until when the data should be requested. Defaults to now. .PARAMETER MaxResult How many results should be returned @@ -32,7 +32,10 @@ Set to $true to skip SSL certificate validation This will dump the response instead of the formatted object .EXAMPLE -Get-PiHoleStatsDatabaseTopClient -PiHoleServer "http://pihole.domain.com:8080" -Password "fjdsjfldsjfkldjslafjskdl" -From 1672580025 -Until 1672666425 -MaxResult 10 +Get-PiHoleStatsDatabaseTopClient -PiHoleServer "http://pihole.domain.com:8080" -Password "your-app-password" -MaxResult 10 + +.EXAMPLE +Get-PiHoleStatsDatabaseTopClient -PiHoleServer "http://pihole.domain.com:8080" -Password "your-app-password" -From (Get-Date).AddDays(-7) -Until (Get-Date) -MaxResult 10 #> [CmdletBinding(HelpUri = 'https://ftl.pi-hole.net/master/docs/#get-/stats/database/top_clients')] [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "Password")] @@ -41,10 +44,8 @@ Get-PiHoleStatsDatabaseTopClient -PiHoleServer "http://pihole.domain.com:8080" - [System.URI]$PiHoleServer, [Parameter(Mandatory = $true)] [string]$Password, - [Parameter(Mandatory = $true)] - [int]$From, - [Parameter(Mandatory = $true)] - [int]$Until, + [datetime]$From = (Get-Date).AddHours(-8), + [datetime]$Until = (Get-Date), [int]$MaxResult = 10, [bool]$Blocked = $false, [bool]$IgnoreSsl = $false, @@ -62,9 +63,12 @@ Get-PiHoleStatsDatabaseTopClient -PiHoleServer "http://pihole.domain.com:8080" - Write-Verbose "Blocked: $BlockedParam" Write-Verbose "MaxResult: $MaxResult" + $FromUnixTime = (Convert-LocalTimeToPiHoleUnixTime -Date $From).UnixTime + $UntilUnixTime = (Convert-LocalTimeToPiHoleUnixTime -Date $Until).UnixTime + $Params = @{ Headers = @{sid = $($Sid) } - Uri = "$($PiHoleServer.OriginalString)/api/stats/database/top_clients?from=$From&until=$Until&blocked=$BlockedParam&count=$MaxResult" + Uri = "$($PiHoleServer.OriginalString)/api/stats/database/top_clients?from=$FromUnixTime&until=$UntilUnixTime&blocked=$BlockedParam&count=$MaxResult" Method = "Get" SkipCertificateCheck = $IgnoreSsl ContentType = "application/json" diff --git a/PiHoleShell/Public/Metrics/Get-PiHoleStatsDatabaseTopDomain.ps1 b/PiHoleShell/Public/Metrics/Get-PiHoleStatsDatabaseTopDomain.ps1 index 214ec85..67c4dcd 100644 --- a/PiHoleShell/Public/Metrics/Get-PiHoleStatsDatabaseTopDomain.ps1 +++ b/PiHoleShell/Public/Metrics/Get-PiHoleStatsDatabaseTopDomain.ps1 @@ -14,10 +14,10 @@ The URL to the PiHole Server, for example "http://pihole.domain.com:8080", or "h The API Password you generated from your PiHole server .PARAMETER From -Unix timestamp from when the data should be requested +Local date/time from when the data should be requested. Defaults to 8 hours ago. .PARAMETER Until -Unix timestamp until when the data should be requested +Local date/time until when the data should be requested. Defaults to now. .PARAMETER MaxResult How many results should be returned @@ -32,7 +32,10 @@ Set to $true to skip SSL certificate validation This will dump the response instead of the formatted object .EXAMPLE -Get-PiHoleStatsDatabaseTopDomain -PiHoleServer "http://pihole.domain.com:8080" -Password "fjdsjfldsjfkldjslafjskdl" -From 1672580025 -Until 1672666425 -MaxResult 10 +Get-PiHoleStatsDatabaseTopDomain -PiHoleServer "http://pihole.domain.com:8080" -Password "your-app-password" -MaxResult 10 + +.EXAMPLE +Get-PiHoleStatsDatabaseTopDomain -PiHoleServer "http://pihole.domain.com:8080" -Password "your-app-password" -From (Get-Date).AddDays(-7) -Until (Get-Date) -MaxResult 10 #> [CmdletBinding(HelpUri = 'https://ftl.pi-hole.net/master/docs/#get-/stats/database/top_domains')] [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "Password")] @@ -41,10 +44,8 @@ Get-PiHoleStatsDatabaseTopDomain -PiHoleServer "http://pihole.domain.com:8080" - [System.URI]$PiHoleServer, [Parameter(Mandatory = $true)] [string]$Password, - [Parameter(Mandatory = $true)] - [int]$From, - [Parameter(Mandatory = $true)] - [int]$Until, + [datetime]$From = (Get-Date).AddHours(-8), + [datetime]$Until = (Get-Date), [int]$MaxResult = 10, [bool]$Blocked = $false, [bool]$IgnoreSsl = $false, @@ -62,9 +63,12 @@ Get-PiHoleStatsDatabaseTopDomain -PiHoleServer "http://pihole.domain.com:8080" - Write-Verbose "Blocked: $BlockedParam" Write-Verbose "MaxResult: $MaxResult" + $FromUnixTime = (Convert-LocalTimeToPiHoleUnixTime -Date $From).UnixTime + $UntilUnixTime = (Convert-LocalTimeToPiHoleUnixTime -Date $Until).UnixTime + $Params = @{ Headers = @{sid = $($Sid) } - Uri = "$($PiHoleServer.OriginalString)/api/stats/database/top_domains?from=$From&until=$Until&blocked=$BlockedParam&count=$MaxResult" + Uri = "$($PiHoleServer.OriginalString)/api/stats/database/top_domains?from=$FromUnixTime&until=$UntilUnixTime&blocked=$BlockedParam&count=$MaxResult" Method = "Get" SkipCertificateCheck = $IgnoreSsl ContentType = "application/json" diff --git a/PiHoleShell/Public/Metrics/Get-PiHoleStatsDatabaseUpstream.ps1 b/PiHoleShell/Public/Metrics/Get-PiHoleStatsDatabaseUpstream.ps1 index 8cb0d44..0d35e26 100644 --- a/PiHoleShell/Public/Metrics/Get-PiHoleStatsDatabaseUpstream.ps1 +++ b/PiHoleShell/Public/Metrics/Get-PiHoleStatsDatabaseUpstream.ps1 @@ -14,10 +14,10 @@ The URL to the PiHole Server, for example "http://pihole.domain.com:8080", or "h The API Password you generated from your PiHole server .PARAMETER From -Unix timestamp from when the data should be requested +Local date/time from when the data should be requested. Defaults to 8 hours ago. .PARAMETER Until -Unix timestamp until when the data should be requested +Local date/time until when the data should be requested. Defaults to now. .PARAMETER IgnoreSsl Set to $true to skip SSL certificate validation @@ -26,7 +26,10 @@ Set to $true to skip SSL certificate validation This will dump the response instead of the formatted object .EXAMPLE -Get-PiHoleStatsDatabaseUpstream -PiHoleServer "http://pihole.domain.com:8080" -Password "fjdsjfldsjfkldjslafjskdl" -From 1672580025 -Until 1672666425 +Get-PiHoleStatsDatabaseUpstream -PiHoleServer "http://pihole.domain.com:8080" -Password "your-app-password" + +.EXAMPLE +Get-PiHoleStatsDatabaseUpstream -PiHoleServer "http://pihole.domain.com:8080" -Password "your-app-password" -From (Get-Date).AddDays(-7) -Until (Get-Date) #> [CmdletBinding(HelpUri = 'https://ftl.pi-hole.net/master/docs/#get-/stats/database/upstreams')] [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "Password")] @@ -35,10 +38,8 @@ Get-PiHoleStatsDatabaseUpstream -PiHoleServer "http://pihole.domain.com:8080" -P [System.URI]$PiHoleServer, [Parameter(Mandatory = $true)] [string]$Password, - [Parameter(Mandatory = $true)] - [int]$From, - [Parameter(Mandatory = $true)] - [int]$Until, + [datetime]$From = (Get-Date).AddHours(-8), + [datetime]$Until = (Get-Date), [bool]$IgnoreSsl = $false, [bool]$RawOutput = $false ) @@ -46,9 +47,12 @@ Get-PiHoleStatsDatabaseUpstream -PiHoleServer "http://pihole.domain.com:8080" -P try { $Sid = Request-PiHoleAuth -PiHoleServer $PiHoleServer -Password $Password -IgnoreSsl $IgnoreSsl + $FromUnixTime = (Convert-LocalTimeToPiHoleUnixTime -Date $From).UnixTime + $UntilUnixTime = (Convert-LocalTimeToPiHoleUnixTime -Date $Until).UnixTime + $Params = @{ Headers = @{sid = $($Sid) } - Uri = "$($PiHoleServer.OriginalString)/api/stats/database/upstreams?from=$From&until=$Until" + Uri = "$($PiHoleServer.OriginalString)/api/stats/database/upstreams?from=$FromUnixTime&until=$UntilUnixTime" Method = "Get" SkipCertificateCheck = $IgnoreSsl ContentType = "application/json" diff --git a/PiHoleShell/Public/Metrics/Get-PiHoleStatsQuerySuggestions.ps1 b/PiHoleShell/Public/Metrics/Get-PiHoleStatsQuerySuggestions.ps1 index 245bdb4..30421bf 100644 --- a/PiHoleShell/Public/Metrics/Get-PiHoleStatsQuerySuggestions.ps1 +++ b/PiHoleShell/Public/Metrics/Get-PiHoleStatsQuerySuggestions.ps1 @@ -20,7 +20,7 @@ Set to $true to skip SSL certificate validation This will dump the response instead of the formatted object .EXAMPLE -Get-PiHoleStatsQuerySuggestions -PiHoleServer "http://pihole.domain.com:8080" -Password "fjdsjfldsjfkldjslafjskdl" +Get-PiHoleStatsQuerySuggestions -PiHoleServer "http://pihole.domain.com:8080" -Password "your-app-password" #> [CmdletBinding(HelpUri = 'https://ftl.pi-hole.net/master/docs/#get-/queries/suggestions')] [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "Password")] diff --git a/PiHoleShell/Public/Metrics/Get-PiHoleStatsQueryType.ps1 b/PiHoleShell/Public/Metrics/Get-PiHoleStatsQueryType.ps1 index 2cc78e7..1117f7e 100644 --- a/PiHoleShell/Public/Metrics/Get-PiHoleStatsQueryType.ps1 +++ b/PiHoleShell/Public/Metrics/Get-PiHoleStatsQueryType.ps1 @@ -1,7 +1,23 @@ function Get-PiHoleStatsQueryType { <# .SYNOPSIS -https://TODOFINDNEWAPILINK +Get query types +Request a breakdown of query types (A, AAAA, ...) + +.PARAMETER PiHoleServer +The URL to the PiHole Server, for example "http://pihole.domain.com:8080", or "http://192.168.1.100" + +.PARAMETER Password +The API Password you generated from your PiHole server + +.PARAMETER IgnoreSsl +Set to $true to skip SSL certificate validation + +.PARAMETER RawOutput +This will dump the response instead of the formatted object + +.EXAMPLE +Get-PiHoleStatsQueryType -PiHoleServer "http://pihole.domain.com:8080" -Password "your-app-password" #> [CmdletBinding()] [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "Password")] @@ -14,41 +30,41 @@ https://TODOFINDNEWAPILINK [bool]$RawOutput = $false ) - $Sid = Request-PiHoleAuth -PiHoleServer $PiHoleServer -Password $Password -IgnoreSsl $IgnoreSsl - Write-Verbose -Message "MaxResults - $MaxResult" - $Params = @{ - Headers = @{sid = $($Sid) } - Uri = "$($PiHoleServer.OriginalString)/api/stats/query_types" - Method = "Get" - SkipCertificateCheck = $IgnoreSsl - ContentType = "application/json" - } + try { + $Sid = Request-PiHoleAuth -PiHoleServer $PiHoleServer -Password $Password -IgnoreSsl $IgnoreSsl - $Response = Invoke-RestMethod @Params + $Params = @{ + Headers = @{sid = $($Sid) } + Uri = "$($PiHoleServer.OriginalString)/api/stats/query_types" + Method = "Get" + SkipCertificateCheck = $IgnoreSsl + ContentType = "application/json" + } - if ($RawOutput) { - Write-Output $Response + $Response = Invoke-RestMethod @Params + + if ($RawOutput) { + Write-Output $Response + } + else { + $QueryTypes = @('A', 'AAAA', 'ANY', 'SRV', 'SOA', 'PTR', 'TXT', 'NAPTR', 'MX', 'DS', 'RRSIG', 'DNSKEY', 'NS', 'SVCB', 'HTTPS', 'OTHER') + $ObjectFinal = foreach ($Type in $QueryTypes) { + [PSCustomObject]@{ + Type = $Type + Count = $Response.types.$Type + } + } + Write-Output $ObjectFinal + } } - else { - $Object = [PSCustomObject]@{ - A = $Response.types.A - AAAA = $Response.types.AAAA - ANY = $Response.types.ANY - SRV = $Response.types.SRV - SOA = $Response.types.SOA - PTR = $Response.types.PTR - TXT = $Response.types.TXT - NAPTR = $Response.types.NAPTR - MX = $Response.types.MX - DS = $Response.types.DS - RRSIG = $Response.types.RRSIG - DNSKEY = $Response.types.DNSKEY - NS = $Response.types.NS - SVCB = $Response.types.SVCB - HTTPS = $Response.types.HTTPS - OTHER = $Response.types.OTHER + + catch { + Write-Error -Message $_.Exception.Message + } + + finally { + if ($Sid) { + Remove-PiHoleCurrentAuthSession -PiHoleServer $PiHoleServer -Sid $Sid -IgnoreSsl $IgnoreSsl } - $ObjectFinal += $Object - Write-Output $ObjectFinal } -} \ No newline at end of file +} diff --git a/PiHoleShell/Public/Metrics/Get-PiHoleStatsRecentBlocked.ps1 b/PiHoleShell/Public/Metrics/Get-PiHoleStatsRecentBlocked.ps1 index b390fcd..e8104fc 100644 --- a/PiHoleShell/Public/Metrics/Get-PiHoleStatsRecentBlocked.ps1 +++ b/PiHoleShell/Public/Metrics/Get-PiHoleStatsRecentBlocked.ps1 @@ -16,7 +16,7 @@ How many results should be returned This will dump the response instead of the formatted object .EXAMPLE -Get-PiHoleStatsRecentBlocked -PiHoleServer "http://pihole.domain.com:8080" -Password "fjdsjfldsjfkldjslafjskdl" -MaxResult 20 +Get-PiHoleStatsRecentBlocked -PiHoleServer "http://pihole.domain.com:8080" -Password "your-app-password" -MaxResult 20 #> [CmdletBinding(HelpUri = 'https://ftl.pi-hole.net/master/docs/#get-/stats/recent_blocked')] [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "Password")] @@ -27,7 +27,7 @@ Get-PiHoleStatsRecentBlocked -PiHoleServer "http://pihole.domain.com:8080" -Pass [string]$Password, [int]$MaxResult = 1, [bool]$IgnoreSsl = $false, - [bool]$RawOutput + [bool]$RawOutput = $false ) try { $Sid = Request-PiHoleAuth -PiHoleServer $PiHoleServer -Password $Password -IgnoreSsl $IgnoreSsl diff --git a/PiHoleShell/Public/Metrics/Get-PiHoleStatsSummary.ps1 b/PiHoleShell/Public/Metrics/Get-PiHoleStatsSummary.ps1 index 17c8274..18c1fbb 100644 --- a/PiHoleShell/Public/Metrics/Get-PiHoleStatsSummary.ps1 +++ b/PiHoleShell/Public/Metrics/Get-PiHoleStatsSummary.ps1 @@ -17,7 +17,7 @@ This will dump the response instead of the formatted object This will dump the response instead of the formatted object .EXAMPLE -Get-PiHoleStatsSummary -PiHoleServer "http://pihole.domain.com:8080" -Password "fjdsjfldsjfkldjslafjskdl" +Get-PiHoleStatsSummary -PiHoleServer "http://pihole.domain.com:8080" -Password "your-app-password" #> [CmdletBinding()] [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "Password")] @@ -27,7 +27,7 @@ Get-PiHoleStatsSummary -PiHoleServer "http://pihole.domain.com:8080" -Password " [Parameter(Mandatory = $true)] [string]$Password, [bool]$IgnoreSsl = $false, - [bool]$RawOutput + [bool]$RawOutput = $false ) try { $Sid = Request-PiHoleAuth -PiHoleServer $PiHoleServer -Password $Password -IgnoreSsl $IgnoreSsl diff --git a/PiHoleShell/Public/Metrics/Get-PiHoleStatsTopClient.ps1 b/PiHoleShell/Public/Metrics/Get-PiHoleStatsTopClient.ps1 index eba3963..79a43e5 100644 --- a/PiHoleShell/Public/Metrics/Get-PiHoleStatsTopClient.ps1 +++ b/PiHoleShell/Public/Metrics/Get-PiHoleStatsTopClient.ps1 @@ -20,7 +20,7 @@ If true, returns top clients by blocked queries instead of total queries This will dump the response instead of the formatted object .EXAMPLE -Get-PiHoleStatsTopClient -PiHoleServer "http://pihole.domain.com:8080" -Password "fjdsjfldsjfkldjslafjskdl" -MaxResult 10 +Get-PiHoleStatsTopClient -PiHoleServer "http://pihole.domain.com:8080" -Password "your-app-password" -MaxResult 10 #> [CmdletBinding()] [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "Password")] diff --git a/PiHoleShell/Public/Metrics/Get-PiHoleStatsTopDomain.ps1 b/PiHoleShell/Public/Metrics/Get-PiHoleStatsTopDomain.ps1 index 17b78c8..81031ef 100644 --- a/PiHoleShell/Public/Metrics/Get-PiHoleStatsTopDomain.ps1 +++ b/PiHoleShell/Public/Metrics/Get-PiHoleStatsTopDomain.ps1 @@ -1,7 +1,26 @@ function Get-PiHoleStatsTopDomain { <# .SYNOPSIS -https://TODOFINDNEWAPILINK +Get top domains +Request the top domains (by query count) + +.PARAMETER PiHoleServer +The URL to the PiHole Server, for example "http://pihole.domain.com:8080", or "http://192.168.1.100" + +.PARAMETER Password +The API Password you generated from your PiHole server + +.PARAMETER MaxResult +How many results should be returned + +.PARAMETER Blocked +If true, returns top domains by blocked queries instead of total queries + +.PARAMETER RawOutput +This will dump the response instead of the formatted object + +.EXAMPLE +Get-PiHoleStatsTopDomain -PiHoleServer "http://pihole.domain.com:8080" -Password "your-app-password" -MaxResult 10 #> [CmdletBinding()] [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "Password")] @@ -15,37 +34,51 @@ https://TODOFINDNEWAPILINK [bool]$IgnoreSsl = $false, [bool]$RawOutput = $false ) + try { + $Sid = Request-PiHoleAuth -PiHoleServer $PiHoleServer -Password $Password -IgnoreSsl $IgnoreSsl - $Sid = Request-PiHoleAuth -PiHoleServer $PiHoleServer -Password $Password -IgnoreSsl $IgnoreSsl + switch ($Blocked) { + $false { $BlockedParam = "false" } + $true { $BlockedParam = "true" } + Default { throw "ERROR" } + } + Write-Verbose "Blocked: $BlockedParam" + Write-Verbose "MaxResult: $MaxResult" - switch ($Blocked) { - $false { - $Blocked = "false" + $Params = @{ + Headers = @{sid = $($Sid) } + Uri = "$($PiHoleServer.OriginalString)/api/stats/top_domains?blocked=$BlockedParam&count=$MaxResult" + Method = "Get" + SkipCertificateCheck = $IgnoreSsl + ContentType = "application/json" } - $true { - $Blocked = "true" + + $Response = Invoke-RestMethod @Params + + if ($RawOutput) { + Write-Output $Response } - Default { - throw "ERROR" + else { + $ObjectFinal = @() + foreach ($Item in $Response.domains) { + $Object = [PSCustomObject]@{ + Domain = $Item.domain + Count = $Item.count + } + Write-Verbose -Message "Domain - $($Item.domain): $($Item.count)" + $ObjectFinal += $Object + } + Write-Output $ObjectFinal } } - Write-Verbose "Blocked: $Blocked" - - $Params = @{ - Headers = @{sid = $($Sid) } - Uri = "$($PiHoleServer.OriginalString)/api/stats/top_domains?blocked=$Blocked&count=$MaxResult" - Method = "Get" - SkipCertificateCheck = $IgnoreSsl - ContentType = "application/json" - } - - $Response = Invoke-RestMethod @Params - if ($RawOutput) { - Write-Output $Response + catch { + Write-Error -Message $_.Exception.Message } - if ($Sid) { - Remove-PiHoleCurrentAuthSession -PiHoleServer $PiHoleServer -Sid $Sid -IgnoreSsl $IgnoreSsl + finally { + if ($Sid) { + Remove-PiHoleCurrentAuthSession -PiHoleServer $PiHoleServer -Sid $Sid -IgnoreSsl $IgnoreSsl + } } -} \ No newline at end of file +} diff --git a/PiHoleShell/Public/Metrics/Get-PiHoleStatsUpstream.ps1 b/PiHoleShell/Public/Metrics/Get-PiHoleStatsUpstream.ps1 index 4ec0e36..52ede7b 100644 --- a/PiHoleShell/Public/Metrics/Get-PiHoleStatsUpstream.ps1 +++ b/PiHoleShell/Public/Metrics/Get-PiHoleStatsUpstream.ps1 @@ -20,7 +20,7 @@ Set to $true to skip SSL certificate validation This will dump the response instead of the formatted object .EXAMPLE -Get-PiHoleStatsUpstream -PiHoleServer "http://pihole.domain.com:8080" -Password "fjdsjfldsjfkldjslafjskdl" +Get-PiHoleStatsUpstream -PiHoleServer "http://pihole.domain.com:8080" -Password "your-app-password" #> [CmdletBinding(HelpUri = 'https://ftl.pi-hole.net/master/docs/#get-/stats/upstreams')] [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "Password")] diff --git a/PiHoleShell/Public/Padd/Get-PiHolePadd.ps1 b/PiHoleShell/Public/Padd/Get-PiHolePadd.ps1 index 5186fca..662266f 100644 --- a/PiHoleShell/Public/Padd/Get-PiHolePadd.ps1 +++ b/PiHoleShell/Public/Padd/Get-PiHolePadd.ps1 @@ -1,11 +1,29 @@ function Get-PiHolePadd { <# .SYNOPSIS -https://TODO +Get summarized data for PADD +.DESCRIPTION +Request the summarized dashboard data used to power PADD (the Pi-hole ASCII dashboard): +current CPU/memory load, blocking status, gravity size, network interfaces, query totals, +sensors, and top clients/domains/blocked entries. + +.PARAMETER PiHoleServer +The URL to the PiHole Server, for example "http://pihole.domain.com:8080", or "http://192.168.1.100" + +.PARAMETER Password +The API Password you generated from your PiHole server + +.PARAMETER IgnoreSsl +Set to $true to skip SSL certificate validation + +.PARAMETER RawOutput +This will dump the response instead of the formatted object + +.EXAMPLE +Get-PiHolePadd -PiHoleServer "http://pihole.domain.com:8080" -Password "your-app-password" #> - #Work In Progress - [CmdletBinding()] + [CmdletBinding(HelpUri = 'https://ftl.pi-hole.net/master/docs/#get-/padd')] [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "Password")] param ( [Parameter(Mandatory = $true)] @@ -74,7 +92,7 @@ https://TODO $Cache = [PSCustomObject]@{ Size = $Response.cache.size Inserted = $Response.cache.inserted - Evicted = $Reponse.cache.evicted + Evicted = $Response.cache.evicted } $Object = [PSCustomObject]@{ diff --git a/README.md b/README.md index 21fc6e6..d14d073 100644 --- a/README.md +++ b/README.md @@ -126,21 +126,21 @@ Functions marked 🚧 are still under active development — signatures and outp | `Get-PiHoleStatsDatabaseTopDomain` | Get top domains (long-term database) | | `Get-PiHoleStatsDatabaseUpstream` | Get metrics about Pi-hole's upstream destinations (long-term database) | | `Get-PiHoleStatsQuerySuggestions` | Get query filter suggestions | -| `Get-PiHoleStatsQueryType` | _No description yet_ | +| `Get-PiHoleStatsQueryType` | Get query types Request a breakdown of query types (A, AAAA, ...) | | `Get-PiHoleStatsRecentBlocked` | Request most recently blocked domain | | `Get-PiHoleStatsSummary` | Get overview of Pi-hole activity Request various query, system, and FTL properties | | `Get-PiHoleStatsTopClient` | Get top clients Request the top clients (by query count) | -| `Get-PiHoleStatsTopDomain` | _No description yet_ | +| `Get-PiHoleStatsTopDomain` | Get top domains Request the top domains (by query count) | | `Get-PiHoleStatsUpstream` | Get metrics about Pi-hole's upstream destinations | ### Configuration & Diagnostics | Function | Description | |---|---| -| `Get-PiHoleConfig` 🚧 | _No description yet_ | +| `Get-PiHoleConfig` | Get current configuration of Pi-hole | | `Get-PiHoleInfoHost` 🚧 | Get info about various host parameters This API hook returns a collection of host infos. | | `Get-PiHoleInfoMessage` | Get Pi-hole diagnosis messages Request Pi-hole diagnosis messages | -| `Get-PiHolePadd` 🚧 | _No description yet_ | +| `Get-PiHolePadd` | Get summarized data for PADD | ### Authentication diff --git a/tests/Get-PiHoleConfig.Integration.Tests.ps1 b/tests/Get-PiHoleConfig.Integration.Tests.ps1 new file mode 100644 index 0000000..0f6dc74 --- /dev/null +++ b/tests/Get-PiHoleConfig.Integration.Tests.ps1 @@ -0,0 +1,43 @@ +# Requires -Module Pester +# +# Integration tests that call a REAL Pi-hole server. Configure tests/IntegrationConfig.local.ps1 +# (copy it from IntegrationConfig.example.ps1) before running. Tests are skipped automatically +# if that file is missing. + +$script:ConfigAvailable = Test-Path (Join-Path $PSScriptRoot 'IntegrationConfig.local.ps1') + +Describe 'Get-PiHoleConfig (Integration)' -Tag 'Integration' { + BeforeAll { + Import-Module .\PiHoleShell\PiHoleShell.psm1 -Force + + $configPath = Join-Path $PSScriptRoot 'IntegrationConfig.local.ps1' + if (Test-Path $configPath) { + . $configPath + $script:PiHoleServer = $PiHoleServer + $script:PiHoleToken = $PiHoleToken + $script:PiHoleIgnoreSsl = $PiHoleIgnoreSsl + } + } + + It 'returns config as a formatted object' -Skip:(-not $script:ConfigAvailable) { + $result = Get-PiHoleConfig -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl + $result | Format-List | Out-String | Write-Host + + $result | Should -Not -BeNullOrEmpty + $result.Dns | Should -Not -BeNullOrEmpty + $result.Dhcp | Should -Not -BeNullOrEmpty + } + + It 'returns the raw API response when RawOutput is set' -Skip:(-not $script:ConfigAvailable) { + $result = Get-PiHoleConfig -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl -RawOutput $true + $result | Format-List | Out-String | Write-Host + + $result.config | Should -Not -BeNullOrEmpty + } + + It 'errors when given a bad password' -Skip:(-not $script:ConfigAvailable) { + $result = Get-PiHoleConfig -PiHoleServer $script:PiHoleServer -Password 'definitely-not-the-real-token' -IgnoreSsl $script:PiHoleIgnoreSsl -ErrorVariable errOut -ErrorAction SilentlyContinue + + $errOut | Should -Not -BeNullOrEmpty + } +} diff --git a/tests/Get-PiHoleCurrentAuthSession.Integration.Tests.ps1 b/tests/Get-PiHoleCurrentAuthSession.Integration.Tests.ps1 new file mode 100644 index 0000000..64f29d4 --- /dev/null +++ b/tests/Get-PiHoleCurrentAuthSession.Integration.Tests.ps1 @@ -0,0 +1,41 @@ +# Requires -Module Pester +# +# Integration tests that call a REAL Pi-hole server. Configure tests/IntegrationConfig.local.ps1 +# (copy it from IntegrationConfig.example.ps1) before running. Tests are skipped automatically +# if that file is missing. + +$script:ConfigAvailable = Test-Path (Join-Path $PSScriptRoot 'IntegrationConfig.local.ps1') + +Describe 'Get-PiHoleCurrentAuthSession (Integration)' -Tag 'Integration' { + BeforeAll { + Import-Module .\PiHoleShell\PiHoleShell.psm1 -Force + + $configPath = Join-Path $PSScriptRoot 'IntegrationConfig.local.ps1' + if (Test-Path $configPath) { + . $configPath + $script:PiHoleServer = $PiHoleServer + $script:PiHoleToken = $PiHoleToken + $script:PiHoleIgnoreSsl = $PiHoleIgnoreSsl + } + } + + It 'returns active sessions as formatted objects' -Skip:(-not $script:ConfigAvailable) { + $result = Get-PiHoleCurrentAuthSession -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl + $result | Format-Table | Out-String | Write-Host + + $result | Should -Not -BeNullOrEmpty + } + + It 'returns the raw API response when RawOutput is set' -Skip:(-not $script:ConfigAvailable) { + $result = Get-PiHoleCurrentAuthSession -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl -RawOutput $true + $result | Format-List | Out-String | Write-Host + + $result | Should -Not -BeNullOrEmpty + } + + It 'errors when given a bad password' -Skip:(-not $script:ConfigAvailable) { + $result = Get-PiHoleCurrentAuthSession -PiHoleServer $script:PiHoleServer -Password 'definitely-not-the-real-token' -IgnoreSsl $script:PiHoleIgnoreSsl -ErrorVariable errOut -ErrorAction SilentlyContinue + + $errOut | Should -Not -BeNullOrEmpty + } +} diff --git a/tests/Get-PiHoleDnsBlockingStatus.Integration.Tests.ps1 b/tests/Get-PiHoleDnsBlockingStatus.Integration.Tests.ps1 new file mode 100644 index 0000000..23592c1 --- /dev/null +++ b/tests/Get-PiHoleDnsBlockingStatus.Integration.Tests.ps1 @@ -0,0 +1,42 @@ +# Requires -Module Pester +# +# Integration tests that call a REAL Pi-hole server. Configure tests/IntegrationConfig.local.ps1 +# (copy it from IntegrationConfig.example.ps1) before running. Tests are skipped automatically +# if that file is missing. + +$script:ConfigAvailable = Test-Path (Join-Path $PSScriptRoot 'IntegrationConfig.local.ps1') + +Describe 'Get-PiHoleDnsBlockingStatus (Integration)' -Tag 'Integration' { + BeforeAll { + Import-Module .\PiHoleShell\PiHoleShell.psm1 -Force + + $configPath = Join-Path $PSScriptRoot 'IntegrationConfig.local.ps1' + if (Test-Path $configPath) { + . $configPath + $script:PiHoleServer = $PiHoleServer + $script:PiHoleToken = $PiHoleToken + $script:PiHoleIgnoreSsl = $PiHoleIgnoreSsl + } + } + + It 'returns blocking status as a formatted object' -Skip:(-not $script:ConfigAvailable) { + $result = Get-PiHoleDnsBlockingStatus -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl + $result | Format-List | Out-String | Write-Host + + $result | Should -Not -BeNullOrEmpty + $result.Blocking | Should -Not -BeNullOrEmpty + } + + It 'returns the raw API response when RawOutput is set' -Skip:(-not $script:ConfigAvailable) { + $result = Get-PiHoleDnsBlockingStatus -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl -RawOutput $true + $result | Format-List | Out-String | Write-Host + + $result.blocking | Should -Not -BeNullOrEmpty + } + + It 'errors when given a bad password' -Skip:(-not $script:ConfigAvailable) { + $result = Get-PiHoleDnsBlockingStatus -PiHoleServer $script:PiHoleServer -Password 'definitely-not-the-real-token' -IgnoreSsl $script:PiHoleIgnoreSsl -ErrorVariable errOut -ErrorAction SilentlyContinue + + $errOut | Should -Not -BeNullOrEmpty + } +} diff --git a/tests/Get-PiHoleGroup.Integration.Tests.ps1 b/tests/Get-PiHoleGroup.Integration.Tests.ps1 new file mode 100644 index 0000000..89c564f --- /dev/null +++ b/tests/Get-PiHoleGroup.Integration.Tests.ps1 @@ -0,0 +1,42 @@ +# Requires -Module Pester +# +# Integration tests that call a REAL Pi-hole server. Configure tests/IntegrationConfig.local.ps1 +# (copy it from IntegrationConfig.example.ps1) before running. Tests are skipped automatically +# if that file is missing. + +$script:ConfigAvailable = Test-Path (Join-Path $PSScriptRoot 'IntegrationConfig.local.ps1') + +Describe 'Get-PiHoleGroup (Integration)' -Tag 'Integration' { + BeforeAll { + Import-Module .\PiHoleShell\PiHoleShell.psm1 -Force + + $configPath = Join-Path $PSScriptRoot 'IntegrationConfig.local.ps1' + if (Test-Path $configPath) { + . $configPath + $script:PiHoleServer = $PiHoleServer + $script:PiHoleToken = $PiHoleToken + $script:PiHoleIgnoreSsl = $PiHoleIgnoreSsl + } + } + + It 'returns groups as formatted objects' -Skip:(-not $script:ConfigAvailable) { + $result = Get-PiHoleGroup -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl + $result | Format-Table | Out-String | Write-Host + + $result | Should -Not -BeNullOrEmpty + ($result | Where-Object Name -EQ 'Default') | Should -Not -BeNullOrEmpty + } + + It 'returns the raw API response when RawOutput is set' -Skip:(-not $script:ConfigAvailable) { + $result = Get-PiHoleGroup -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl -RawOutput $true + $result | Format-List | Out-String | Write-Host + + $result | Should -Not -BeNullOrEmpty + } + + It 'errors when given a bad password' -Skip:(-not $script:ConfigAvailable) { + $result = Get-PiHoleGroup -PiHoleServer $script:PiHoleServer -Password 'definitely-not-the-real-token' -IgnoreSsl $script:PiHoleIgnoreSsl -ErrorVariable errOut -ErrorAction SilentlyContinue + + $errOut | Should -Not -BeNullOrEmpty + } +} diff --git a/tests/Get-PiHoleInfoHost.Integration.Tests.ps1 b/tests/Get-PiHoleInfoHost.Integration.Tests.ps1 new file mode 100644 index 0000000..0aa7b03 --- /dev/null +++ b/tests/Get-PiHoleInfoHost.Integration.Tests.ps1 @@ -0,0 +1,42 @@ +# Requires -Module Pester +# +# Integration tests that call a REAL Pi-hole server. Configure tests/IntegrationConfig.local.ps1 +# (copy it from IntegrationConfig.example.ps1) before running. Tests are skipped automatically +# if that file is missing. + +$script:ConfigAvailable = Test-Path (Join-Path $PSScriptRoot 'IntegrationConfig.local.ps1') + +Describe 'Get-PiHoleInfoHost (Integration)' -Tag 'Integration' { + BeforeAll { + Import-Module .\PiHoleShell\PiHoleShell.psm1 -Force + + $configPath = Join-Path $PSScriptRoot 'IntegrationConfig.local.ps1' + if (Test-Path $configPath) { + . $configPath + $script:PiHoleServer = $PiHoleServer + $script:PiHoleToken = $PiHoleToken + $script:PiHoleIgnoreSsl = $PiHoleIgnoreSsl + } + } + + It 'returns host info as a formatted object' -Skip:(-not $script:ConfigAvailable) { + $result = Get-PiHoleInfoHost -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl + $result | Format-List | Out-String | Write-Host + + $result | Should -Not -BeNullOrEmpty + $result.NodeName | Should -Not -BeNullOrEmpty + } + + It 'returns the raw API response when RawOutput is set' -Skip:(-not $script:ConfigAvailable) { + $result = Get-PiHoleInfoHost -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl -RawOutput $true + $result | Format-List | Out-String | Write-Host + + $result | Should -Not -BeNullOrEmpty + } + + It 'errors when given a bad password' -Skip:(-not $script:ConfigAvailable) { + $result = Get-PiHoleInfoHost -PiHoleServer $script:PiHoleServer -Password 'definitely-not-the-real-token' -IgnoreSsl $script:PiHoleIgnoreSsl -ErrorVariable errOut -ErrorAction SilentlyContinue + + $errOut | Should -Not -BeNullOrEmpty + } +} diff --git a/tests/Get-PiHoleInfoMessage.Integration.Tests.ps1 b/tests/Get-PiHoleInfoMessage.Integration.Tests.ps1 new file mode 100644 index 0000000..4acc18f --- /dev/null +++ b/tests/Get-PiHoleInfoMessage.Integration.Tests.ps1 @@ -0,0 +1,41 @@ +# Requires -Module Pester +# +# Integration tests that call a REAL Pi-hole server. Configure tests/IntegrationConfig.local.ps1 +# (copy it from IntegrationConfig.example.ps1) before running. Tests are skipped automatically +# if that file is missing. + +$script:ConfigAvailable = Test-Path (Join-Path $PSScriptRoot 'IntegrationConfig.local.ps1') + +Describe 'Get-PiHoleInfoMessage (Integration)' -Tag 'Integration' { + BeforeAll { + Import-Module .\PiHoleShell\PiHoleShell.psm1 -Force + + $configPath = Join-Path $PSScriptRoot 'IntegrationConfig.local.ps1' + if (Test-Path $configPath) { + . $configPath + $script:PiHoleServer = $PiHoleServer + $script:PiHoleToken = $PiHoleToken + $script:PiHoleIgnoreSsl = $PiHoleIgnoreSsl + } + } + + # Diagnosis messages may legitimately be empty on a healthy server, so this only asserts + # that the call succeeds, not that any messages exist. + It 'returns diagnosis messages without error' -Skip:(-not $script:ConfigAvailable) { + $result = Get-PiHoleInfoMessage -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl + $result | Format-Table | Out-String | Write-Host + } + + It 'returns the raw API response when RawOutput is set' -Skip:(-not $script:ConfigAvailable) { + $result = Get-PiHoleInfoMessage -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl -RawOutput $true + $result | Format-List | Out-String | Write-Host + + $result | Should -Not -BeNullOrEmpty + } + + It 'errors when given a bad password' -Skip:(-not $script:ConfigAvailable) { + $result = Get-PiHoleInfoMessage -PiHoleServer $script:PiHoleServer -Password 'definitely-not-the-real-token' -IgnoreSsl $script:PiHoleIgnoreSsl -ErrorVariable errOut -ErrorAction SilentlyContinue + + $errOut | Should -Not -BeNullOrEmpty + } +} diff --git a/tests/Get-PiHoleList.Integration.Tests.ps1 b/tests/Get-PiHoleList.Integration.Tests.ps1 new file mode 100644 index 0000000..cfe219f --- /dev/null +++ b/tests/Get-PiHoleList.Integration.Tests.ps1 @@ -0,0 +1,41 @@ +# Requires -Module Pester +# +# Integration tests that call a REAL Pi-hole server. Configure tests/IntegrationConfig.local.ps1 +# (copy it from IntegrationConfig.example.ps1) before running. Tests are skipped automatically +# if that file is missing. + +$script:ConfigAvailable = Test-Path (Join-Path $PSScriptRoot 'IntegrationConfig.local.ps1') + +Describe 'Get-PiHoleList (Integration)' -Tag 'Integration' { + BeforeAll { + Import-Module .\PiHoleShell\PiHoleShell.psm1 -Force + + $configPath = Join-Path $PSScriptRoot 'IntegrationConfig.local.ps1' + if (Test-Path $configPath) { + . $configPath + $script:PiHoleServer = $PiHoleServer + $script:PiHoleToken = $PiHoleToken + $script:PiHoleIgnoreSsl = $PiHoleIgnoreSsl + } + } + + It 'returns lists as formatted objects' -Skip:(-not $script:ConfigAvailable) { + $result = Get-PiHoleList -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl + $result | Select-Object -First 5 | Format-Table | Out-String | Write-Host + + $result | Should -Not -BeNullOrEmpty + } + + It 'returns the raw API response when RawOutput is set' -Skip:(-not $script:ConfigAvailable) { + $result = Get-PiHoleList -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl -RawOutput $true + $result | Format-List | Out-String | Write-Host + + $result | Should -Not -BeNullOrEmpty + } + + It 'errors when given a bad password' -Skip:(-not $script:ConfigAvailable) { + $result = Get-PiHoleList -PiHoleServer $script:PiHoleServer -Password 'definitely-not-the-real-token' -IgnoreSsl $script:PiHoleIgnoreSsl -ErrorVariable errOut -ErrorAction SilentlyContinue + + $errOut | Should -Not -BeNullOrEmpty + } +} diff --git a/tests/Get-PiHolePadd.Integration.Tests.ps1 b/tests/Get-PiHolePadd.Integration.Tests.ps1 new file mode 100644 index 0000000..c03db0d --- /dev/null +++ b/tests/Get-PiHolePadd.Integration.Tests.ps1 @@ -0,0 +1,46 @@ +# Requires -Module Pester +# +# Integration tests that call a REAL Pi-hole server. Configure tests/IntegrationConfig.local.ps1 +# (copy it from IntegrationConfig.example.ps1) before running. Tests are skipped automatically +# if that file is missing. + +$script:ConfigAvailable = Test-Path (Join-Path $PSScriptRoot 'IntegrationConfig.local.ps1') + +Describe 'Get-PiHolePadd (Integration)' -Tag 'Integration' { + BeforeAll { + Import-Module .\PiHoleShell\PiHoleShell.psm1 -Force + + $configPath = Join-Path $PSScriptRoot 'IntegrationConfig.local.ps1' + if (Test-Path $configPath) { + . $configPath + $script:PiHoleServer = $PiHoleServer + $script:PiHoleToken = $PiHoleToken + $script:PiHoleIgnoreSsl = $PiHoleIgnoreSsl + + # Generates some real query traffic so live stats aren't all zero/empty. + & (Join-Path $PSScriptRoot 'Initialize-PiHoleTestData.ps1') -DnsServer $PiHoleServer.Host + } + } + + It 'returns the PADD dashboard data as a formatted object' -Skip:(-not $script:ConfigAvailable) { + $result = Get-PiHolePadd -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl + $result | Format-List | Out-String | Write-Host + + $result | Should -Not -BeNullOrEmpty + $result.NodeName | Should -Not -BeNullOrEmpty + $result.Queries | Should -Not -BeNullOrEmpty + } + + It 'returns the raw API response when RawOutput is set' -Skip:(-not $script:ConfigAvailable) { + $result = Get-PiHolePadd -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl -RawOutput $true + $result | Format-List | Out-String | Write-Host + + $result | Should -Not -BeNullOrEmpty + } + + It 'errors when given a bad password' -Skip:(-not $script:ConfigAvailable) { + $result = Get-PiHolePadd -PiHoleServer $script:PiHoleServer -Password 'definitely-not-the-real-token' -IgnoreSsl $script:PiHoleIgnoreSsl -ErrorVariable errOut -ErrorAction SilentlyContinue + + $errOut | Should -Not -BeNullOrEmpty + } +} diff --git a/tests/Get-PiHoleStatsDatabaseQueryType.Integration.Tests.ps1 b/tests/Get-PiHoleStatsDatabaseQueryType.Integration.Tests.ps1 index fe70c55..6bcc515 100644 --- a/tests/Get-PiHoleStatsDatabaseQueryType.Integration.Tests.ps1 +++ b/tests/Get-PiHoleStatsDatabaseQueryType.Integration.Tests.ps1 @@ -16,23 +16,30 @@ Describe 'Get-PiHoleStatsDatabaseQueryType (Integration)' -Tag 'Integration' { $script:PiHoleServer = $PiHoleServer $script:PiHoleToken = $PiHoleToken $script:PiHoleIgnoreSsl = $PiHoleIgnoreSsl + + # Generates some real query traffic. Live stats reflect it immediately; the on-disk + # database stats this file tests only reflect it once FTL's periodic flush runs, so + # this mainly helps build up real history across repeated runs, not this run's own data. + & (Join-Path $PSScriptRoot 'Initialize-PiHoleTestData.ps1') -DnsServer $PiHoleServer.Host } - # from=0 is rejected by the API with a 400; use a wide-but-valid recent window instead. - $script:Until = [DateTimeOffset]::UtcNow.ToUnixTimeSeconds() - $script:From = $script:Until - (30 * 86400) + # The API rejects from=0 (epoch) with a 400, so use a recent, valid window instead. + $script:Until = Get-Date + $script:From = $script:Until.AddDays(-30) } - It 'returns a formatted object with all query type properties' -Skip:(-not $script:ConfigAvailable) { + It 'returns an array of Type/Count rows for every query type' -Skip:(-not $script:ConfigAvailable) { $result = Get-PiHoleStatsDatabaseQueryType -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -From $script:From -Until $script:Until -IgnoreSsl $script:PiHoleIgnoreSsl + $result | Format-Table | Out-String | Write-Host $result | Should -Not -BeNullOrEmpty - $result.PSObject.Properties.Name | Should -Contain 'A' - $result.PSObject.Properties.Name | Should -Contain 'AAAA' + ($result | Where-Object Type -EQ 'A') | Should -Not -BeNullOrEmpty + ($result | Where-Object Type -EQ 'AAAA') | Should -Not -BeNullOrEmpty } It 'returns the raw API response when RawOutput is set' -Skip:(-not $script:ConfigAvailable) { $result = Get-PiHoleStatsDatabaseQueryType -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -From $script:From -Until $script:Until -IgnoreSsl $script:PiHoleIgnoreSsl -RawOutput $true + $result | Format-List | Out-String | Write-Host $result.PSObject.Properties.Name | Should -Contain 'types' } @@ -42,4 +49,11 @@ Describe 'Get-PiHoleStatsDatabaseQueryType (Integration)' -Tag 'Integration' { $errOut | Should -Not -BeNullOrEmpty } + + It 'defaults to the last 8 hours when From/Until are omitted' -Skip:(-not $script:ConfigAvailable) { + $result = Get-PiHoleStatsDatabaseQueryType -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl + $result | Format-Table | Out-String | Write-Host + + $result | Should -Not -BeNullOrEmpty + } } diff --git a/tests/Get-PiHoleStatsDatabaseSummary.Integration.Tests.ps1 b/tests/Get-PiHoleStatsDatabaseSummary.Integration.Tests.ps1 index 8f2dbdd..4027ca6 100644 --- a/tests/Get-PiHoleStatsDatabaseSummary.Integration.Tests.ps1 +++ b/tests/Get-PiHoleStatsDatabaseSummary.Integration.Tests.ps1 @@ -16,15 +16,21 @@ Describe 'Get-PiHoleStatsDatabaseSummary (Integration)' -Tag 'Integration' { $script:PiHoleServer = $PiHoleServer $script:PiHoleToken = $PiHoleToken $script:PiHoleIgnoreSsl = $PiHoleIgnoreSsl + + # Generates some real query traffic. Live stats reflect it immediately; the on-disk + # database stats this file tests only reflect it once FTL's periodic flush runs, so + # this mainly helps build up real history across repeated runs, not this run's own data. + & (Join-Path $PSScriptRoot 'Initialize-PiHoleTestData.ps1') -DnsServer $PiHoleServer.Host } - # from=0 is rejected by the API with a 400; use a wide-but-valid recent window instead. - $script:Until = [DateTimeOffset]::UtcNow.ToUnixTimeSeconds() - $script:From = $script:Until - (30 * 86400) + # The API rejects from=0 (epoch) with a 400, so use a recent, valid window instead. + $script:Until = Get-Date + $script:From = $script:Until.AddDays(-30) } It 'returns database summary as a formatted object' -Skip:(-not $script:ConfigAvailable) { $result = Get-PiHoleStatsDatabaseSummary -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -From $script:From -Until $script:Until -IgnoreSsl $script:PiHoleIgnoreSsl + $result | Format-List | Out-String | Write-Host $result | Should -Not -BeNullOrEmpty $result.SumQueries | Should -BeGreaterOrEqual 0 @@ -33,6 +39,7 @@ Describe 'Get-PiHoleStatsDatabaseSummary (Integration)' -Tag 'Integration' { It 'returns the raw API response when RawOutput is set' -Skip:(-not $script:ConfigAvailable) { $result = Get-PiHoleStatsDatabaseSummary -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -From $script:From -Until $script:Until -IgnoreSsl $script:PiHoleIgnoreSsl -RawOutput $true + $result | Format-List | Out-String | Write-Host $result.PSObject.Properties.Name | Should -Contain 'sum_queries' } @@ -42,4 +49,11 @@ Describe 'Get-PiHoleStatsDatabaseSummary (Integration)' -Tag 'Integration' { $errOut | Should -Not -BeNullOrEmpty } + + It 'defaults to the last 8 hours when From/Until are omitted' -Skip:(-not $script:ConfigAvailable) { + $result = Get-PiHoleStatsDatabaseSummary -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl + $result | Format-List | Out-String | Write-Host + + $result | Should -Not -BeNullOrEmpty + } } diff --git a/tests/Get-PiHoleStatsDatabaseTopClient.Integration.Tests.ps1 b/tests/Get-PiHoleStatsDatabaseTopClient.Integration.Tests.ps1 index f62aa5c..9d7120f 100644 --- a/tests/Get-PiHoleStatsDatabaseTopClient.Integration.Tests.ps1 +++ b/tests/Get-PiHoleStatsDatabaseTopClient.Integration.Tests.ps1 @@ -16,20 +16,26 @@ Describe 'Get-PiHoleStatsDatabaseTopClient (Integration)' -Tag 'Integration' { $script:PiHoleServer = $PiHoleServer $script:PiHoleToken = $PiHoleToken $script:PiHoleIgnoreSsl = $PiHoleIgnoreSsl + + # Generates some real query traffic. Live stats reflect it immediately; the on-disk + # database stats this file tests only reflect it once FTL's periodic flush runs, so + # this mainly helps build up real history across repeated runs, not this run's own data. + & (Join-Path $PSScriptRoot 'Initialize-PiHoleTestData.ps1') -DnsServer $PiHoleServer.Host } - # from=0 is rejected by the API with a 400; use a wide-but-valid recent window instead. - $script:Until = [DateTimeOffset]::UtcNow.ToUnixTimeSeconds() - $script:From = $script:Until - (30 * 86400) + # The API rejects from=0 (epoch) with a 400, so use a recent, valid window instead. + $script:Until = Get-Date + $script:From = $script:Until.AddDays(-30) } It 'returns top clients without error' -Skip:(-not $script:ConfigAvailable) { - { Get-PiHoleStatsDatabaseTopClient -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -From $script:From -Until $script:Until -IgnoreSsl $script:PiHoleIgnoreSsl -MaxResult 5 } | - Should -Not -Throw + $result = Get-PiHoleStatsDatabaseTopClient -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -From $script:From -Until $script:Until -IgnoreSsl $script:PiHoleIgnoreSsl -MaxResult 5 + $result | Format-Table | Out-String | Write-Host } It 'returns the raw API response when RawOutput is set' -Skip:(-not $script:ConfigAvailable) { $result = Get-PiHoleStatsDatabaseTopClient -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -From $script:From -Until $script:Until -IgnoreSsl $script:PiHoleIgnoreSsl -RawOutput $true + $result | Format-List | Out-String | Write-Host $result.PSObject.Properties.Name | Should -Contain 'clients' } @@ -39,4 +45,9 @@ Describe 'Get-PiHoleStatsDatabaseTopClient (Integration)' -Tag 'Integration' { $errOut | Should -Not -BeNullOrEmpty } + + It 'defaults to the last 8 hours when From/Until are omitted' -Skip:(-not $script:ConfigAvailable) { + $result = Get-PiHoleStatsDatabaseTopClient -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl -MaxResult 5 + $result | Format-Table | Out-String | Write-Host + } } diff --git a/tests/Get-PiHoleStatsDatabaseTopDomain.Integration.Tests.ps1 b/tests/Get-PiHoleStatsDatabaseTopDomain.Integration.Tests.ps1 index 0821b53..42b34ee 100644 --- a/tests/Get-PiHoleStatsDatabaseTopDomain.Integration.Tests.ps1 +++ b/tests/Get-PiHoleStatsDatabaseTopDomain.Integration.Tests.ps1 @@ -16,20 +16,26 @@ Describe 'Get-PiHoleStatsDatabaseTopDomain (Integration)' -Tag 'Integration' { $script:PiHoleServer = $PiHoleServer $script:PiHoleToken = $PiHoleToken $script:PiHoleIgnoreSsl = $PiHoleIgnoreSsl + + # Generates some real query traffic. Live stats reflect it immediately; the on-disk + # database stats this file tests only reflect it once FTL's periodic flush runs, so + # this mainly helps build up real history across repeated runs, not this run's own data. + & (Join-Path $PSScriptRoot 'Initialize-PiHoleTestData.ps1') -DnsServer $PiHoleServer.Host } - # from=0 is rejected by the API with a 400; use a wide-but-valid recent window instead. - $script:Until = [DateTimeOffset]::UtcNow.ToUnixTimeSeconds() - $script:From = $script:Until - (30 * 86400) + # The API rejects from=0 (epoch) with a 400, so use a recent, valid window instead. + $script:Until = Get-Date + $script:From = $script:Until.AddDays(-30) } It 'returns top domains without error' -Skip:(-not $script:ConfigAvailable) { - { Get-PiHoleStatsDatabaseTopDomain -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -From $script:From -Until $script:Until -IgnoreSsl $script:PiHoleIgnoreSsl -MaxResult 5 } | - Should -Not -Throw + $result = Get-PiHoleStatsDatabaseTopDomain -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -From $script:From -Until $script:Until -IgnoreSsl $script:PiHoleIgnoreSsl -MaxResult 5 + $result | Format-Table | Out-String | Write-Host } It 'returns the raw API response when RawOutput is set' -Skip:(-not $script:ConfigAvailable) { $result = Get-PiHoleStatsDatabaseTopDomain -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -From $script:From -Until $script:Until -IgnoreSsl $script:PiHoleIgnoreSsl -RawOutput $true + $result | Format-List | Out-String | Write-Host $result.PSObject.Properties.Name | Should -Contain 'domains' } @@ -39,4 +45,9 @@ Describe 'Get-PiHoleStatsDatabaseTopDomain (Integration)' -Tag 'Integration' { $errOut | Should -Not -BeNullOrEmpty } + + It 'defaults to the last 8 hours when From/Until are omitted' -Skip:(-not $script:ConfigAvailable) { + $result = Get-PiHoleStatsDatabaseTopDomain -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl -MaxResult 5 + $result | Format-Table | Out-String | Write-Host + } } diff --git a/tests/Get-PiHoleStatsDatabaseUpstream.Integration.Tests.ps1 b/tests/Get-PiHoleStatsDatabaseUpstream.Integration.Tests.ps1 index 5130c86..83ba3ac 100644 --- a/tests/Get-PiHoleStatsDatabaseUpstream.Integration.Tests.ps1 +++ b/tests/Get-PiHoleStatsDatabaseUpstream.Integration.Tests.ps1 @@ -16,15 +16,22 @@ Describe 'Get-PiHoleStatsDatabaseUpstream (Integration)' -Tag 'Integration' { $script:PiHoleServer = $PiHoleServer $script:PiHoleToken = $PiHoleToken $script:PiHoleIgnoreSsl = $PiHoleIgnoreSsl + + # Generates some real query traffic. Live stats reflect it immediately; the on-disk + # database stats this file tests only reflect it once FTL's periodic flush runs, so + # this mainly helps build up real history across repeated runs, not this run's own data. + & (Join-Path $PSScriptRoot 'Initialize-PiHoleTestData.ps1') -DnsServer $PiHoleServer.Host } - # from=0 is rejected by the API with a 400; use a wide-but-valid recent window instead. - $script:Until = [DateTimeOffset]::UtcNow.ToUnixTimeSeconds() - $script:From = $script:Until - (30 * 86400) + # The API rejects from=0 (epoch) with a 400, so use a recent, valid window instead. + $script:Until = Get-Date + $script:From = $script:Until.AddDays(-30) } It 'returns upstream metrics as a formatted object' -Skip:(-not $script:ConfigAvailable) { $result = Get-PiHoleStatsDatabaseUpstream -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -From $script:From -Until $script:Until -IgnoreSsl $script:PiHoleIgnoreSsl + $result | Format-List | Out-String | Write-Host + $result.Upstreams | Format-Table | Out-String | Write-Host $result | Should -Not -BeNullOrEmpty $result.TotalQueries | Should -BeGreaterOrEqual 0 @@ -32,6 +39,7 @@ Describe 'Get-PiHoleStatsDatabaseUpstream (Integration)' -Tag 'Integration' { It 'returns the raw API response when RawOutput is set' -Skip:(-not $script:ConfigAvailable) { $result = Get-PiHoleStatsDatabaseUpstream -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -From $script:From -Until $script:Until -IgnoreSsl $script:PiHoleIgnoreSsl -RawOutput $true + $result | Format-List | Out-String | Write-Host $result.total_queries | Should -Not -BeNullOrEmpty } @@ -41,4 +49,11 @@ Describe 'Get-PiHoleStatsDatabaseUpstream (Integration)' -Tag 'Integration' { $errOut | Should -Not -BeNullOrEmpty } + + It 'defaults to the last 8 hours when From/Until are omitted' -Skip:(-not $script:ConfigAvailable) { + $result = Get-PiHoleStatsDatabaseUpstream -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl + $result | Format-List | Out-String | Write-Host + + $result | Should -Not -BeNullOrEmpty + } } diff --git a/tests/Get-PiHoleStatsQuerySuggestions.Integration.Tests.ps1 b/tests/Get-PiHoleStatsQuerySuggestions.Integration.Tests.ps1 index d74623a..7ae2124 100644 --- a/tests/Get-PiHoleStatsQuerySuggestions.Integration.Tests.ps1 +++ b/tests/Get-PiHoleStatsQuerySuggestions.Integration.Tests.ps1 @@ -20,11 +20,15 @@ Describe 'Get-PiHoleStatsQuerySuggestions (Integration)' -Tag 'Integration' { $script:PiHoleServer = $PiHoleServer $script:PiHoleToken = $PiHoleToken $script:PiHoleIgnoreSsl = $PiHoleIgnoreSsl + + # Generates some real query traffic so live stats aren't all zero/empty. + & (Join-Path $PSScriptRoot 'Initialize-PiHoleTestData.ps1') -DnsServer $PiHoleServer.Host } } It 'returns filter suggestions as a formatted object' -Skip:(-not $script:ConfigAvailable) { $result = Get-PiHoleStatsQuerySuggestions -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl + $result | Format-List | Out-String | Write-Host $result | Should -Not -BeNullOrEmpty $result.Type | Should -Not -BeNullOrEmpty @@ -35,6 +39,7 @@ Describe 'Get-PiHoleStatsQuerySuggestions (Integration)' -Tag 'Integration' { It 'returns the raw API response when RawOutput is set' -Skip:(-not $script:ConfigAvailable) { $result = Get-PiHoleStatsQuerySuggestions -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl -RawOutput $true + $result | Format-List | Out-String | Write-Host $result.suggestions | Should -Not -BeNullOrEmpty } diff --git a/tests/Get-PiHoleStatsQueryType.Integration.Tests.ps1 b/tests/Get-PiHoleStatsQueryType.Integration.Tests.ps1 new file mode 100644 index 0000000..07cbbf4 --- /dev/null +++ b/tests/Get-PiHoleStatsQueryType.Integration.Tests.ps1 @@ -0,0 +1,46 @@ +# Requires -Module Pester +# +# Integration tests that call a REAL Pi-hole server. Configure tests/IntegrationConfig.local.ps1 +# (copy it from IntegrationConfig.example.ps1) before running. Tests are skipped automatically +# if that file is missing. + +$script:ConfigAvailable = Test-Path (Join-Path $PSScriptRoot 'IntegrationConfig.local.ps1') + +Describe 'Get-PiHoleStatsQueryType (Integration)' -Tag 'Integration' { + BeforeAll { + Import-Module .\PiHoleShell\PiHoleShell.psm1 -Force + + $configPath = Join-Path $PSScriptRoot 'IntegrationConfig.local.ps1' + if (Test-Path $configPath) { + . $configPath + $script:PiHoleServer = $PiHoleServer + $script:PiHoleToken = $PiHoleToken + $script:PiHoleIgnoreSsl = $PiHoleIgnoreSsl + + # Generates some real query traffic so live stats aren't all zero/empty. + & (Join-Path $PSScriptRoot 'Initialize-PiHoleTestData.ps1') -DnsServer $PiHoleServer.Host + } + } + + It 'returns an array of Type/Count rows for every query type' -Skip:(-not $script:ConfigAvailable) { + $result = Get-PiHoleStatsQueryType -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl + $result | Format-Table | Out-String | Write-Host + + $result | Should -Not -BeNullOrEmpty + ($result | Where-Object Type -EQ 'A') | Should -Not -BeNullOrEmpty + ($result | Where-Object Type -EQ 'AAAA') | Should -Not -BeNullOrEmpty + } + + It 'returns the raw API response when RawOutput is set' -Skip:(-not $script:ConfigAvailable) { + $result = Get-PiHoleStatsQueryType -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl -RawOutput $true + $result | Format-List | Out-String | Write-Host + + $result.types | Should -Not -BeNullOrEmpty + } + + It 'errors when given a bad password' -Skip:(-not $script:ConfigAvailable) { + $result = Get-PiHoleStatsQueryType -PiHoleServer $script:PiHoleServer -Password 'definitely-not-the-real-token' -IgnoreSsl $script:PiHoleIgnoreSsl -ErrorVariable errOut -ErrorAction SilentlyContinue + + $errOut | Should -Not -BeNullOrEmpty + } +} diff --git a/tests/Get-PiHoleStatsRecentBlocked.Integration.Tests.ps1 b/tests/Get-PiHoleStatsRecentBlocked.Integration.Tests.ps1 new file mode 100644 index 0000000..7e9958a --- /dev/null +++ b/tests/Get-PiHoleStatsRecentBlocked.Integration.Tests.ps1 @@ -0,0 +1,45 @@ +# Requires -Module Pester +# +# Integration tests that call a REAL Pi-hole server. Configure tests/IntegrationConfig.local.ps1 +# (copy it from IntegrationConfig.example.ps1) before running. Tests are skipped automatically +# if that file is missing. + +$script:ConfigAvailable = Test-Path (Join-Path $PSScriptRoot 'IntegrationConfig.local.ps1') + +Describe 'Get-PiHoleStatsRecentBlocked (Integration)' -Tag 'Integration' { + BeforeAll { + Import-Module .\PiHoleShell\PiHoleShell.psm1 -Force + + $configPath = Join-Path $PSScriptRoot 'IntegrationConfig.local.ps1' + if (Test-Path $configPath) { + . $configPath + $script:PiHoleServer = $PiHoleServer + $script:PiHoleToken = $PiHoleToken + $script:PiHoleIgnoreSsl = $PiHoleIgnoreSsl + + # Generates some real query traffic (including known ad/tracker domains that get + # blocked) so this function has something recent to report. + & (Join-Path $PSScriptRoot 'Initialize-PiHoleTestData.ps1') -DnsServer $PiHoleServer.Host + } + } + + It 'returns recently blocked domains as formatted objects' -Skip:(-not $script:ConfigAvailable) { + $result = Get-PiHoleStatsRecentBlocked -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl -MaxResult 5 + $result | Format-Table | Out-String | Write-Host + + $result | Should -Not -BeNullOrEmpty + } + + It 'returns the raw API response when RawOutput is set' -Skip:(-not $script:ConfigAvailable) { + $result = Get-PiHoleStatsRecentBlocked -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl -RawOutput $true + $result | Format-List | Out-String | Write-Host + + $result.blocked | Should -Not -BeNullOrEmpty + } + + It 'errors when given a bad password' -Skip:(-not $script:ConfigAvailable) { + $result = Get-PiHoleStatsRecentBlocked -PiHoleServer $script:PiHoleServer -Password 'definitely-not-the-real-token' -IgnoreSsl $script:PiHoleIgnoreSsl -ErrorVariable errOut -ErrorAction SilentlyContinue + + $errOut | Should -Not -BeNullOrEmpty + } +} diff --git a/tests/Get-PiHoleStatsSummary.Integration.Tests.ps1 b/tests/Get-PiHoleStatsSummary.Integration.Tests.ps1 new file mode 100644 index 0000000..00aa057 --- /dev/null +++ b/tests/Get-PiHoleStatsSummary.Integration.Tests.ps1 @@ -0,0 +1,48 @@ +# Requires -Module Pester +# +# Integration tests that call a REAL Pi-hole server. Configure tests/IntegrationConfig.local.ps1 +# (copy it from IntegrationConfig.example.ps1) before running. Tests are skipped automatically +# if that file is missing. + +$script:ConfigAvailable = Test-Path (Join-Path $PSScriptRoot 'IntegrationConfig.local.ps1') + +Describe 'Get-PiHoleStatsSummary (Integration)' -Tag 'Integration' { + BeforeAll { + Import-Module .\PiHoleShell\PiHoleShell.psm1 -Force + + $configPath = Join-Path $PSScriptRoot 'IntegrationConfig.local.ps1' + if (Test-Path $configPath) { + . $configPath + $script:PiHoleServer = $PiHoleServer + $script:PiHoleToken = $PiHoleToken + $script:PiHoleIgnoreSsl = $PiHoleIgnoreSsl + + # Generates some real query traffic so live stats aren't all zero/empty. + & (Join-Path $PSScriptRoot 'Initialize-PiHoleTestData.ps1') -DnsServer $PiHoleServer.Host + } + } + + It 'returns a summary as a formatted object' -Skip:(-not $script:ConfigAvailable) { + $result = Get-PiHoleStatsSummary -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl + $result | Format-List | Out-String | Write-Host + + $result | Should -Not -BeNullOrEmpty + $result.Total | Should -BeGreaterOrEqual 0 + $result.Types | Should -Not -BeNullOrEmpty + $result.Status | Should -Not -BeNullOrEmpty + $result.Replies | Should -Not -BeNullOrEmpty + } + + It 'returns the raw API response when RawOutput is set' -Skip:(-not $script:ConfigAvailable) { + $result = Get-PiHoleStatsSummary -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl -RawOutput $true + $result | Format-List | Out-String | Write-Host + + $result.queries | Should -Not -BeNullOrEmpty + } + + It 'errors when given a bad password' -Skip:(-not $script:ConfigAvailable) { + $result = Get-PiHoleStatsSummary -PiHoleServer $script:PiHoleServer -Password 'definitely-not-the-real-token' -IgnoreSsl $script:PiHoleIgnoreSsl -ErrorVariable errOut -ErrorAction SilentlyContinue + + $errOut | Should -Not -BeNullOrEmpty + } +} diff --git a/tests/Get-PiHoleStatsTopClient.Integration.Tests.ps1 b/tests/Get-PiHoleStatsTopClient.Integration.Tests.ps1 new file mode 100644 index 0000000..7f8fc67 --- /dev/null +++ b/tests/Get-PiHoleStatsTopClient.Integration.Tests.ps1 @@ -0,0 +1,44 @@ +# Requires -Module Pester +# +# Integration tests that call a REAL Pi-hole server. Configure tests/IntegrationConfig.local.ps1 +# (copy it from IntegrationConfig.example.ps1) before running. Tests are skipped automatically +# if that file is missing. + +$script:ConfigAvailable = Test-Path (Join-Path $PSScriptRoot 'IntegrationConfig.local.ps1') + +Describe 'Get-PiHoleStatsTopClient (Integration)' -Tag 'Integration' { + BeforeAll { + Import-Module .\PiHoleShell\PiHoleShell.psm1 -Force + + $configPath = Join-Path $PSScriptRoot 'IntegrationConfig.local.ps1' + if (Test-Path $configPath) { + . $configPath + $script:PiHoleServer = $PiHoleServer + $script:PiHoleToken = $PiHoleToken + $script:PiHoleIgnoreSsl = $PiHoleIgnoreSsl + + # Generates some real query traffic so live stats aren't all zero/empty. + & (Join-Path $PSScriptRoot 'Initialize-PiHoleTestData.ps1') -DnsServer $PiHoleServer.Host + } + } + + It 'returns top clients as formatted objects' -Skip:(-not $script:ConfigAvailable) { + $result = Get-PiHoleStatsTopClient -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl -MaxResult 5 + $result | Format-Table | Out-String | Write-Host + + $result | Should -Not -BeNullOrEmpty + } + + It 'returns the raw API response when RawOutput is set' -Skip:(-not $script:ConfigAvailable) { + $result = Get-PiHoleStatsTopClient -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl -RawOutput $true + $result | Format-List | Out-String | Write-Host + + $result.clients | Should -Not -BeNullOrEmpty + } + + It 'errors when given a bad password' -Skip:(-not $script:ConfigAvailable) { + $result = Get-PiHoleStatsTopClient -PiHoleServer $script:PiHoleServer -Password 'definitely-not-the-real-token' -IgnoreSsl $script:PiHoleIgnoreSsl -ErrorVariable errOut -ErrorAction SilentlyContinue + + $errOut | Should -Not -BeNullOrEmpty + } +} diff --git a/tests/Get-PiHoleStatsTopDomain.Integration.Tests.ps1 b/tests/Get-PiHoleStatsTopDomain.Integration.Tests.ps1 new file mode 100644 index 0000000..e09aa23 --- /dev/null +++ b/tests/Get-PiHoleStatsTopDomain.Integration.Tests.ps1 @@ -0,0 +1,44 @@ +# Requires -Module Pester +# +# Integration tests that call a REAL Pi-hole server. Configure tests/IntegrationConfig.local.ps1 +# (copy it from IntegrationConfig.example.ps1) before running. Tests are skipped automatically +# if that file is missing. + +$script:ConfigAvailable = Test-Path (Join-Path $PSScriptRoot 'IntegrationConfig.local.ps1') + +Describe 'Get-PiHoleStatsTopDomain (Integration)' -Tag 'Integration' { + BeforeAll { + Import-Module .\PiHoleShell\PiHoleShell.psm1 -Force + + $configPath = Join-Path $PSScriptRoot 'IntegrationConfig.local.ps1' + if (Test-Path $configPath) { + . $configPath + $script:PiHoleServer = $PiHoleServer + $script:PiHoleToken = $PiHoleToken + $script:PiHoleIgnoreSsl = $PiHoleIgnoreSsl + + # Generates some real query traffic so live stats aren't all zero/empty. + & (Join-Path $PSScriptRoot 'Initialize-PiHoleTestData.ps1') -DnsServer $PiHoleServer.Host + } + } + + It 'returns top domains as formatted objects' -Skip:(-not $script:ConfigAvailable) { + $result = Get-PiHoleStatsTopDomain -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl -MaxResult 5 + $result | Format-Table | Out-String | Write-Host + + $result | Should -Not -BeNullOrEmpty + } + + It 'returns the raw API response when RawOutput is set' -Skip:(-not $script:ConfigAvailable) { + $result = Get-PiHoleStatsTopDomain -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl -RawOutput $true + $result | Format-List | Out-String | Write-Host + + $result.domains | Should -Not -BeNullOrEmpty + } + + It 'errors when given a bad password' -Skip:(-not $script:ConfigAvailable) { + $result = Get-PiHoleStatsTopDomain -PiHoleServer $script:PiHoleServer -Password 'definitely-not-the-real-token' -IgnoreSsl $script:PiHoleIgnoreSsl -ErrorVariable errOut -ErrorAction SilentlyContinue + + $errOut | Should -Not -BeNullOrEmpty + } +} diff --git a/tests/Get-PiHoleStatsUpstream.Integration.Tests.ps1 b/tests/Get-PiHoleStatsUpstream.Integration.Tests.ps1 index 1f501c5..5b415be 100644 --- a/tests/Get-PiHoleStatsUpstream.Integration.Tests.ps1 +++ b/tests/Get-PiHoleStatsUpstream.Integration.Tests.ps1 @@ -16,11 +16,16 @@ Describe 'Get-PiHoleStatsUpstream (Integration)' -Tag 'Integration' { $script:PiHoleServer = $PiHoleServer $script:PiHoleToken = $PiHoleToken $script:PiHoleIgnoreSsl = $PiHoleIgnoreSsl + + # Generates some real query traffic so live stats aren't all zero/empty. + & (Join-Path $PSScriptRoot 'Initialize-PiHoleTestData.ps1') -DnsServer $PiHoleServer.Host } } It 'returns upstream metrics as a formatted object' -Skip:(-not $script:ConfigAvailable) { $result = Get-PiHoleStatsUpstream -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl + $result | Format-List | Out-String | Write-Host + $result.Upstreams | Format-Table | Out-String | Write-Host $result | Should -Not -BeNullOrEmpty $result.Upstreams | Should -Not -BeNullOrEmpty @@ -29,6 +34,7 @@ Describe 'Get-PiHoleStatsUpstream (Integration)' -Tag 'Integration' { It 'returns the raw API response when RawOutput is set' -Skip:(-not $script:ConfigAvailable) { $result = Get-PiHoleStatsUpstream -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl -RawOutput $true + $result | Format-List | Out-String | Write-Host $result.upstreams | Should -Not -BeNullOrEmpty } diff --git a/tests/Initialize-PiHoleTestData.ps1 b/tests/Initialize-PiHoleTestData.ps1 new file mode 100644 index 0000000..d593a1f --- /dev/null +++ b/tests/Initialize-PiHoleTestData.ps1 @@ -0,0 +1,52 @@ +<# +.SYNOPSIS +Generates some real DNS query traffic against a Pi-hole server so its stats aren't all zero. + +.DESCRIPTION +Resolves a random mix of well-known domains (for forwarded/cached queries) and known +ad/tracker domains (for blocked queries) directly against the Pi-hole's DNS resolver, using +the same hostname as the API server. Live stats (Get-PiHoleStats*) reflect this immediately; +the on-disk "database" stats (Get-PiHoleStatsDatabase*) only reflect it once FTL's periodic +flush to the long-term database runs, so don't expect seeded data to appear there within the +same test run - this is still useful for building up real history across repeated runs. + +Windows-only: relies on the Resolve-DnsName cmdlet. + +.PARAMETER DnsServer +Hostname or IP of the Pi-hole's DNS resolver (typically the same host as the API, without the +scheme or port - e.g. $PiHoleServer.Host). + +.PARAMETER Count +How many domains to query this run. +#> +[CmdletBinding()] +param ( + [Parameter(Mandatory = $true)] + [string]$DnsServer, + [int]$Count = 8 +) + +$permittedDomains = @( + 'example.com', 'wikipedia.org', 'github.com', 'microsoft.com', 'apple.com', + 'cloudflare.com', 'stackoverflow.com', 'reddit.com', 'nytimes.com', 'bbc.com', + 'mozilla.org', 'python.org' +) +$blockedDomains = @( + 'doubleclick.net', 'googlesyndication.com', 'googleadservices.com', + 'adservice.google.com', 'ads.pubmatic.com', 'analytics.google.com' +) + +$pool = $permittedDomains + $blockedDomains +$selected = $pool | Get-Random -Count ([Math]::Min($Count, $pool.Count)) + +foreach ($domain in $selected) { + try { + Resolve-DnsName -Name $domain -Server $DnsServer -ErrorAction Stop | Out-Null + Write-Verbose "Queried $domain" + } + catch { + # A blocked or non-existent domain can still throw here depending on how it's blocked; + # either way, the query itself already reached Pi-hole and was logged. + Write-Verbose "Query for $domain errored (likely blocked): $($_.Exception.Message)" + } +} diff --git a/tests/Invoke-PiHoleFlushNetwork.Integration.Tests.ps1 b/tests/Invoke-PiHoleFlushNetwork.Integration.Tests.ps1 index 16ff84e..c461e9a 100644 --- a/tests/Invoke-PiHoleFlushNetwork.Integration.Tests.ps1 +++ b/tests/Invoke-PiHoleFlushNetwork.Integration.Tests.ps1 @@ -25,14 +25,15 @@ Describe 'Invoke-PiHoleFlushNetwork (Integration)' -Tag 'Integration' { It 'flushes the network table and returns a formatted status' -Skip:(-not $script:ConfigAvailable) { $result = Invoke-PiHoleFlushNetwork -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl + $result | Format-List | Out-String | Write-Host $result | Should -Not -BeNullOrEmpty $result.Status | Should -Be 'Flushed' } It 'returns the raw API response when RawOutput is set' -Skip:(-not $script:ConfigAvailable) { - { Invoke-PiHoleFlushNetwork -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl -RawOutput $true } | - Should -Not -Throw + $result = Invoke-PiHoleFlushNetwork -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl -RawOutput $true + Write-Host "RawOutput: [$result]" } It 'errors when given a bad password' -Skip:(-not $script:ConfigAvailable) { diff --git a/tests/Restart-PiHoleDnsService.Integration.Tests.ps1 b/tests/Restart-PiHoleDnsService.Integration.Tests.ps1 index 31da77c..90f4a3f 100644 --- a/tests/Restart-PiHoleDnsService.Integration.Tests.ps1 +++ b/tests/Restart-PiHoleDnsService.Integration.Tests.ps1 @@ -28,6 +28,7 @@ Describe 'Restart-PiHoleDnsService (Integration)' -Tag 'Integration' { It 'restarts the DNS service and returns a formatted status' -Skip:(-not $script:ConfigAvailable) { $result = Restart-PiHoleDnsService -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl + $result | Format-List | Out-String | Write-Host $result | Should -Not -BeNullOrEmpty $result.Status | Should -Be 'Restarted' @@ -38,8 +39,8 @@ Describe 'Restart-PiHoleDnsService (Integration)' -Tag 'Integration' { # restarting it again, or this occasionally hits a transient connection failure. Start-Sleep -Seconds 5 - { Restart-PiHoleDnsService -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl -RawOutput $true } | - Should -Not -Throw + $result = Restart-PiHoleDnsService -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl -RawOutput $true + Write-Host "RawOutput: [$result]" } It 'errors when given a bad password' -Skip:(-not $script:ConfigAvailable) { diff --git a/tests/Update-PiHoleActionsGravity.Integration.Tests.ps1 b/tests/Update-PiHoleActionsGravity.Integration.Tests.ps1 index 35a9fc9..a9a64bc 100644 --- a/tests/Update-PiHoleActionsGravity.Integration.Tests.ps1 +++ b/tests/Update-PiHoleActionsGravity.Integration.Tests.ps1 @@ -28,6 +28,7 @@ Describe 'Update-PiHoleActionsGravity (Integration)' -Tag 'Integration' { It 'runs a gravity update and returns the raw API response' -Skip:(-not $script:ConfigAvailable) { $result = Update-PiHoleActionsGravity -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl -RawOutput $true -Confirm:$false + Write-Host "RawOutput: [$result]" $result | Should -Not -BeNullOrEmpty }