Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions PiHoleShell/PiHoleShell.psm1
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@ Export-ModuleMember -Function @(
'Get-PiHoleList', 'Search-PiHoleListDomain', 'Add-PiHoleList', 'Remove-PiHoleList', 'Update-PiHoleList', `
#FTLInformation
'Get-PiHoleInfoMessage', 'Get-PiHoleInfoHost', 'Get-PiHoleInfoClient', 'Get-PiHoleInfoLogin', 'Get-PiHoleInfoSystem', 'Get-PiHoleInfoFtl', `
'Get-PiHoleInfoSensors', 'Get-PiHoleInfoDatabase', 'Get-PiHoleInfoVersion', 'Get-PiHoleInfoMetrics', 'Get-PiHoleInfoMessageCount', 'Remove-PiHoleInfoMessage', `
'Get-PiHoleInfoSensors', 'Get-PiHoleInfoDatabase', 'Get-PiHoleInfoVersion', 'Get-PiHoleInfoMetrics', 'Get-PiHoleInfoMessageCount', 'Remove-PiHoleInfoMessage', 'Get-PiHoleLogWebserver', `
#History
'Get-PiHoleHistory', 'Get-PiHoleHistoryDatabase', 'Get-PiHoleHistoryClient', 'Get-PiHoleHistoryDatabaseClient'
'Get-PiHoleHistory', 'Get-PiHoleHistoryDatabase', 'Get-PiHoleHistoryClient', 'Get-PiHoleHistoryDatabaseClient', `
#Teleporter
'Get-PiHoleTeleporterDownload'
)
57 changes: 44 additions & 13 deletions PiHoleShell/Public/FTLInformation/Get-PiHoleLogWebserver.ps1
Original file line number Diff line number Diff line change
@@ -1,31 +1,49 @@

function Get-PiHoleLogWebserver {
<#
.SYNOPSIS
Get info about logs for webserver
Get webserver log content

.DESCRIPTION
Request content from the log of the embedded CivetWeb HTTP server. Every response includes a
NextID; pass it back as -NextID on your next call to only get lines added since then, making
periodic polling for new log lines easy without checking for duplicates.

.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 NextID
Only return log lines added after this ID (returned as NextID on a previous call). Omit to
get the full available log

.PARAMETER IgnoreSsl
Set to $true to skip SSL certificate validation

.PARAMETER RawOutput
This will dump the response instead of the formatted object

.EXAMPLE
Get-PiHoleLogWebserver -PiHoleServer "http://pihole.domain.com:8080" -Password "your-app-password"
#>
#Work In Progress
[CmdletBinding(HelpUri = 'https://ftl.pi-hole.net/master/docs/#get-/logs/webserver')]
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "Password")]
param (
[Parameter(Mandatory = $true)]
[System.URI]$PiHoleServer,
[Parameter(Mandatory = $true)]
[string]$Password,
[int]$NextID,
[Nullable[int]]$NextID,
[bool]$IgnoreSsl = $false,
[bool]$RawOutput = $false
)
try {
$Sid = Request-PiHoleAuth -PiHoleServer $PiHoleServer -Password $Password -IgnoreSsl $IgnoreSsl

if ($NextID) {
$Uri = "$($PiHoleServer.OriginalString)/api/logs/webserver?nextId=$NextId"

}
else {
$Uri = "$($PiHoleServer.OriginalString)/api/logs/webserver"
$Uri = "$($PiHoleServer.OriginalString)/api/logs/webserver"
if ($PSBoundParameters.ContainsKey('NextID')) {
$Uri += "?nextID=$NextID"
}

$Params = @{
Expand All @@ -43,18 +61,31 @@ Get info about logs for webserver
}

else {
#$ObjectFinal = @()
$Log = foreach ($Item in $Response.log) {
[PSCustomObject]@{
Timestamp = (Convert-PiHoleUnixTimeToLocalTime -UnixTime $Item.timestamp).LocalTime
Message = $Item.message
Priority = $Item.prio
}
}

$Object = [PSCustomObject]@{
Log = $Log
NextID = $Response.nextID
Pid = $Response.pid
File = $Response.file
}
Write-Output $Object
}
}

catch {
Write-Error -Message $_.Exception.Message
break
}

finally {
if ($Sid) {
Remove-PiHoleCurrentAuthSession -PiHoleServer $PiHoleServer -Sid $Sid -IgnoreSsl $IgnoreSsl
}
}
}
}
13 changes: 12 additions & 1 deletion PiHoleShell/Public/ListManagement/Get-PiHoleList.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,31 @@ function Get-PiHoleList {
.SYNOPSIS
Get lists

.DESCRIPTION
Request Pi-hole's subscribed allow/block lists. Omit -List to get every list; specify it to
get just that one.

.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 List
The URL of a specific list to return. Omit to return every list

.PARAMETER IgnoreSsl
Set to $true to skip SSL certificate validation

.PARAMETER RawOutput
This will dump the response instead of the formatted object

.EXAMPLE
Get-PiHoleList -PiHoleServer "http://pihole.domain.com:8080" -Password "your-app-password"

.EXAMPLE
Get-PiHoleList -PiHoleServer "http://pihole.domain.com:8080" -Password "your-app-password" -List "https://hosts-file.net/ad_servers.txt"
#>
#Work In Progress
[CmdletBinding(HelpUri = 'https://ftl.pi-hole.net/master/docs/#get-/lists/-list-')]
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "Password")]
param (
Expand Down
63 changes: 37 additions & 26 deletions PiHoleShell/Public/Teleporter/Get-PiHoleTeleporterDownload.ps1
Original file line number Diff line number Diff line change
@@ -1,11 +1,32 @@
function Get-PiHoleTeleporterDownload {
<#
.SYNOPSIS
Get info about logs for webserver
Export Pi-hole settings

.DESCRIPTION
Downloads an archived copy of Pi-hole's current configuration (a Teleporter backup) to disk.
The API always returns a binary application/zip archive, not JSON, so there's no -RawOutput
option here - the downloaded file is the only output.

.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 FolderPath
The folder to save the backup file into. Must already exist

.PARAMETER FileName
The name to give the backup file, without an extension - ".zip" is appended automatically

.PARAMETER IgnoreSsl
Set to $true to skip SSL certificate validation

.EXAMPLE
Get-PiHoleTeleporterDownload -PiHoleServer "http://pihole.domain.com:8080" -Password "your-app-password" -FolderPath "C:\Backups" -FileName "pihole-backup"
#>
#Work In Progress
[CmdletBinding()]
[CmdletBinding(HelpUri = 'https://ftl.pi-hole.net/master/docs/#get-/teleporter')]
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "Password")]
param (
[Parameter(Mandatory = $true)]
Expand All @@ -16,56 +37,46 @@ Get info about logs for webserver
[System.IO.DirectoryInfo]$FolderPath,
[Parameter(Mandatory = $true)]
[string]$FileName,
[bool]$IgnoreSsl = $false,
[bool]$RawOutput = $false
[bool]$IgnoreSsl = $false
)
try {
$Sid = Request-PiHoleAuth -PiHoleServer $PiHoleServer -Password $Password -IgnoreSsl $IgnoreSsl

if (!(Test-Path -Path $FolderPath)) {
throw "$FolderPath does not exist!"
}
$FileName = "$FileName.tar.gz"
$FileName = "$FileName.zip"
$OutFile = "$FolderPath\$FileName"
if (Test-Path -Path $OutFile) {
throw "$OutFile already exists!"
}

$Sid = Request-PiHoleAuth -PiHoleServer $PiHoleServer -Password $Password -IgnoreSsl $IgnoreSsl

$Params = @{
Headers = @{sid = $($Sid) }
Uri = "$($PiHoleServer.OriginalString)/api/teleporter"
Method = "Get"
SkipCertificateCheck = $IgnoreSsl
ContentType = "application/json"
OutFile = $OutFile
}

$Response = Invoke-RestMethod @Params -OutFile $OutFile

if ($RawOutput) {
Write-Output $Response
}
Invoke-RestMethod @Params

else {
$ObjectFinal = @()
$Object = [PSCustomObject]@{
FileName = $FileName
FilePath = $OutFile
RootFolder = $FolderPath
FileSizeKB = [math]::Ceiling((Get-Item $OutFile).Length / 1KB)
}
$ObjectFinal += $Object
Write-Output $ObjectFinal
$Object = [PSCustomObject]@{
FileName = $FileName
FilePath = $OutFile
RootFolder = $FolderPath
FileSizeKB = [math]::Ceiling((Get-Item $OutFile).Length / 1KB)
}
Write-Output $Object
}

catch {
Write-Error -Message $_.Exception.Message
break
}

finally {
if ($Sid) {
Remove-PiHoleCurrentAuthSession -PiHoleServer $PiHoleServer -Sid $Sid -IgnoreSsl $IgnoreSsl
}
}
}
}
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ Functions marked 🚧 are still under active development — signatures and outp
| Function | Description |
|---|---|
| `Add-PiHoleList` | Add a new list |
| `Get-PiHoleList` 🚧 | Get lists |
| `Get-PiHoleList` | Get lists |
| `Remove-PiHoleList` | Remove a list |
| `Search-PiHoleListDomain` | _No description yet_ |
| `Update-PiHoleList` | Update a list |
Expand Down Expand Up @@ -155,7 +155,9 @@ Functions marked 🚧 are still under active development — signatures and outp
| `Get-PiHoleInfoSensors` | Get info about various sensors |
| `Get-PiHoleInfoSystem` | Get info about various system parameters |
| `Get-PiHoleInfoVersion` | Get Pi-hole version |
| `Get-PiHoleLogWebserver` | Get webserver log content |
| `Get-PiHolePadd` | Get summarized data for PADD |
| `Get-PiHoleTeleporterDownload` | Export Pi-hole settings |
| `Remove-PiHoleInfoMessage` | Delete a Pi-hole diagnosis message |

### Authentication
Expand Down
55 changes: 55 additions & 0 deletions tests/FTLInformation/Get-PiHoleLogWebserver.Integration.Tests.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# 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 (Split-Path $PSScriptRoot -Parent) 'IntegrationConfig.local.ps1')

Describe 'Get-PiHoleLogWebserver (Integration)' -Tag 'Integration' {
BeforeAll {
Import-Module .\PiHoleShell\PiHoleShell.psm1 -Force

$configPath = Join-Path (Split-Path $PSScriptRoot -Parent) 'IntegrationConfig.local.ps1'
if (Test-Path $configPath) {
. $configPath
$script:PiHoleServer = $PiHoleServer
$script:PiHoleToken = $PiHoleToken
$script:PiHoleIgnoreSsl = $PiHoleIgnoreSsl
}
}

It 'returns webserver log content as a formatted object' -Skip:(-not $script:ConfigAvailable) {
$result = Get-PiHoleLogWebserver -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl
$result | Format-List | Out-String | Write-Host

$result | Should -Not -BeNullOrEmpty
$result.NextID | Should -BeGreaterOrEqual 0
$result.File | Should -Not -BeNullOrEmpty
}

It 'only returns lines added after NextID on a follow-up call' -Skip:(-not $script:ConfigAvailable) {
$first = Get-PiHoleLogWebserver -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl

$second = Get-PiHoleLogWebserver -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl -NextID $first.NextID
$second | Format-List | Out-String | Write-Host

# No guarantee new webserver log lines were generated between the two calls, so this
# just confirms the call succeeds and NextID never goes backwards.
$second.NextID | Should -BeGreaterOrEqual $first.NextID
}

It 'returns the raw API response when RawOutput is set' -Skip:(-not $script:ConfigAvailable) {
$result = Get-PiHoleLogWebserver -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl -RawOutput $true
$result | Format-List | Out-String | Write-Host

$result.PSObject.Properties.Name | Should -Contain 'log'
$result.PSObject.Properties.Name | Should -Contain 'nextID'
}

It 'errors when given a bad password' -Skip:(-not $script:ConfigAvailable) {
$result = Get-PiHoleLogWebserver -PiHoleServer $script:PiHoleServer -Password 'definitely-not-the-real-token' -IgnoreSsl $script:PiHoleIgnoreSsl -ErrorVariable errOut -ErrorAction SilentlyContinue

$errOut | Should -Not -BeNullOrEmpty
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# 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 (Split-Path $PSScriptRoot -Parent) 'IntegrationConfig.local.ps1')

Describe 'Get-PiHoleTeleporterDownload (Integration)' -Tag 'Integration' {
BeforeAll {
Import-Module .\PiHoleShell\PiHoleShell.psm1 -Force

$configPath = Join-Path (Split-Path $PSScriptRoot -Parent) 'IntegrationConfig.local.ps1'
if (Test-Path $configPath) {
. $configPath
$script:PiHoleServer = $PiHoleServer
$script:PiHoleToken = $PiHoleToken
$script:PiHoleIgnoreSsl = $PiHoleIgnoreSsl
}

$script:TestFolder = Join-Path ([System.IO.Path]::GetTempPath()) "PiHoleShellPesterTeleporter"
if (Test-Path $script:TestFolder) {
Remove-Item -Path $script:TestFolder -Recurse -Force
}
New-Item -ItemType Directory -Path $script:TestFolder | Out-Null
}

AfterAll {
if (Test-Path $script:TestFolder) {
Remove-Item -Path $script:TestFolder -Recurse -Force
}
}

It 'downloads a real ZIP backup and returns a formatted object' -Skip:(-not $script:ConfigAvailable) {
$result = Get-PiHoleTeleporterDownload -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl -FolderPath $script:TestFolder -FileName 'pester-backup'
$result | Format-List | Out-String | Write-Host

$result | Should -Not -BeNullOrEmpty
$result.FileName | Should -Be 'pester-backup.zip'
$result.FileSizeKB | Should -BeGreaterThan 0
Test-Path $result.FilePath | Should -BeTrue

# Confirm it's a genuine, openable ZIP archive, not just a file with a .zip name.
$zip = [System.IO.Compression.ZipFile]::OpenRead($result.FilePath)
try {
$zip.Entries.Count | Should -BeGreaterThan 0
}
finally {
$zip.Dispose()
}
}

It 'errors when the destination file already exists' -Skip:(-not $script:ConfigAvailable) {
Get-PiHoleTeleporterDownload -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl -FolderPath $script:TestFolder -FileName 'duplicate-backup' | Out-Null

$result = Get-PiHoleTeleporterDownload -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl -FolderPath $script:TestFolder -FileName 'duplicate-backup' -ErrorVariable errOut -ErrorAction SilentlyContinue

$errOut | Should -Not -BeNullOrEmpty
}

It 'errors when the destination folder does not exist' -Skip:(-not $script:ConfigAvailable) {
$result = Get-PiHoleTeleporterDownload -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl -FolderPath (Join-Path $script:TestFolder 'does-not-exist') -FileName 'x' -ErrorVariable errOut -ErrorAction SilentlyContinue

$errOut | Should -Not -BeNullOrEmpty
}

It 'errors when given a bad password' -Skip:(-not $script:ConfigAvailable) {
$result = Get-PiHoleTeleporterDownload -PiHoleServer $script:PiHoleServer -Password 'definitely-not-the-real-token' -IgnoreSsl $script:PiHoleIgnoreSsl -FolderPath $script:TestFolder -FileName 'bad-password-backup' -ErrorVariable errOut -ErrorAction SilentlyContinue

$errOut | Should -Not -BeNullOrEmpty
}
}
Loading