diff --git a/PiHoleShell/PiHoleShell.psm1 b/PiHoleShell/PiHoleShell.psm1 index 001e1ec..a28485d 100644 --- a/PiHoleShell/PiHoleShell.psm1 +++ b/PiHoleShell/PiHoleShell.psm1 @@ -31,9 +31,10 @@ Export-ModuleMember -Function @( 'Get-PiHoleStatsRecentBlocked', 'Get-PiHoleStatsQueryType', 'Get-PiHoleStatsTopDomain', 'Get-PiHoleStatsSummary', 'Get-PiHoleStatsTopClient', 'Get-PiHoleStatsQuerySuggestions', ` 'Get-PiHoleStatsUpstream', 'Get-PiHoleStatsDatabaseUpstream', 'Get-PiHoleStatsDatabaseSummary', 'Get-PiHoleStatsDatabaseTopDomain', 'Get-PiHoleStatsDatabaseTopClient', 'Get-PiHoleStatsDatabaseQueryType' ` #ListManagement - 'Get-PiHoleList', 'Search-PiHoleListDomain', 'Add-PiHoleList', 'Remove-PiHoleList', ` + 'Get-PiHoleList', 'Search-PiHoleListDomain', 'Add-PiHoleList', 'Remove-PiHoleList', 'Update-PiHoleList', ` #FTLInformation - 'Get-PiHoleInfoMessage', 'Get-PiHoleInfoHost', ` + 'Get-PiHoleInfoMessage', 'Get-PiHoleInfoHost', 'Get-PiHoleInfoClient', 'Get-PiHoleInfoLogin', 'Get-PiHoleInfoSystem', 'Get-PiHoleInfoFtl', ` + 'Get-PiHoleInfoSensors', 'Get-PiHoleInfoDatabase', 'Get-PiHoleInfoVersion', 'Get-PiHoleInfoMetrics', 'Get-PiHoleInfoMessageCount', 'Remove-PiHoleInfoMessage', ` #History 'Get-PiHoleHistory', 'Get-PiHoleHistoryDatabase', 'Get-PiHoleHistoryClient', 'Get-PiHoleHistoryDatabaseClient' ) \ No newline at end of file diff --git a/PiHoleShell/Public/FTLInformation/Get-PiHoleInfoClient.ps1 b/PiHoleShell/Public/FTLInformation/Get-PiHoleInfoClient.ps1 new file mode 100644 index 0000000..16b955d --- /dev/null +++ b/PiHoleShell/Public/FTLInformation/Get-PiHoleInfoClient.ps1 @@ -0,0 +1,65 @@ +function Get-PiHoleInfoClient { + <# +.SYNOPSIS +Get information about the requesting client + +.DESCRIPTION +Returns information about how the Pi-hole server sees this request: your remote address, the +HTTP version and method used, and the request headers sent. This endpoint does not require +authentication. + +.PARAMETER PiHoleServer +The URL to the PiHole Server, for example "http://pihole.domain.com:8080", or "http://192.168.1.100" + +.PARAMETER IgnoreSsl +Set to $true to skip SSL certificate validation + +.PARAMETER RawOutput +This will dump the response instead of the formatted object + +.EXAMPLE +Get-PiHoleInfoClient -PiHoleServer "http://pihole.domain.com:8080" + #> + [CmdletBinding(HelpUri = 'https://ftl.pi-hole.net/master/docs/#get-/info/client')] + param ( + [Parameter(Mandatory = $true)] + [System.URI]$PiHoleServer, + [bool]$IgnoreSsl = $false, + [bool]$RawOutput = $false + ) + + try { + $Params = @{ + Uri = "$($PiHoleServer.OriginalString)/api/info/client" + Method = "Get" + SkipCertificateCheck = $IgnoreSsl + ContentType = "application/json" + } + + $Response = Invoke-RestMethod @Params + + if ($RawOutput) { + Write-Output $Response + } + else { + $Headers = foreach ($Item in $Response.headers) { + [PSCustomObject]@{ + Name = $Item.name + Value = $Item.value + } + } + + $Object = [PSCustomObject]@{ + RemoteAddress = $Response.remote_addr + HttpVersion = $Response.http_version + Method = $Response.method + Headers = $Headers + } + Write-Output $Object + } + } + + catch { + Write-Error -Message $_.Exception.Message + } +} diff --git a/PiHoleShell/Public/FTLInformation/Get-PiHoleInfoDatabase.ps1 b/PiHoleShell/Public/FTLInformation/Get-PiHoleInfoDatabase.ps1 new file mode 100644 index 0000000..a5b791a --- /dev/null +++ b/PiHoleShell/Public/FTLInformation/Get-PiHoleInfoDatabase.ps1 @@ -0,0 +1,95 @@ +function Get-PiHoleInfoDatabase { + <# +.SYNOPSIS +Get info about the long-term database + +.DESCRIPTION +Request details about Pi-hole's long-term (on-disk) query database file: its size and +ownership on disk, how many queries are stored in-memory versus on-disk, and the earliest +timestamp in each. + +.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-PiHoleInfoDatabase -PiHoleServer "http://pihole.domain.com:8080" -Password "your-app-password" + #> + [CmdletBinding(HelpUri = 'https://ftl.pi-hole.net/master/docs/#get-/info/database')] + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "Password")] + param ( + [Parameter(Mandatory = $true)] + [System.URI]$PiHoleServer, + [Parameter(Mandatory = $true)] + [string]$Password, + [bool]$IgnoreSsl = $false, + [bool]$RawOutput = $false + ) + + try { + $Sid = Request-PiHoleAuth -PiHoleServer $PiHoleServer -Password $Password -IgnoreSsl $IgnoreSsl + + $Params = @{ + Headers = @{sid = $($Sid) } + Uri = "$($PiHoleServer.OriginalString)/api/info/database" + Method = "Get" + SkipCertificateCheck = $IgnoreSsl + ContentType = "application/json" + } + + $Response = Invoke-RestMethod @Params + + if ($RawOutput) { + Write-Output $Response + } + else { + # The API defaults these to 0.0 when there are no queries stored in-memory/on-disk yet. + $EarliestTimestamp = if ($Response.earliest_timestamp -eq 0) { $null } else { (Convert-PiHoleUnixTimeToLocalTime -UnixTime $Response.earliest_timestamp).LocalTime } + $EarliestTimestampDisk = if ($Response.earliest_timestamp_disk -eq 0) { $null } else { (Convert-PiHoleUnixTimeToLocalTime -UnixTime $Response.earliest_timestamp_disk).LocalTime } + + $Object = [PSCustomObject]@{ + Size = $Response.size + Type = $Response.type + Mode = $Response.mode + AccessTime = (Convert-PiHoleUnixTimeToLocalTime -UnixTime $Response.atime).LocalTime + ModifiedTime = (Convert-PiHoleUnixTimeToLocalTime -UnixTime $Response.mtime).LocalTime + ChangeTime = (Convert-PiHoleUnixTimeToLocalTime -UnixTime $Response.ctime).LocalTime + Owner = [PSCustomObject]@{ + User = [PSCustomObject]@{ + Uid = $Response.owner.user.uid + Name = $Response.owner.user.name + Info = $Response.owner.user.info + } + Group = [PSCustomObject]@{ + Gid = $Response.owner.group.gid + Name = $Response.owner.group.name + } + } + Queries = $Response.queries + EarliestTimestamp = $EarliestTimestamp + QueriesDisk = $Response.queries_disk + EarliestTimestampDisk = $EarliestTimestampDisk + SqliteVersion = $Response.sqlite_version + } + Write-Output $Object + } + } + + catch { + Write-Error -Message $_.Exception.Message + } + + finally { + if ($Sid) { + Remove-PiHoleCurrentAuthSession -PiHoleServer $PiHoleServer -Sid $Sid -IgnoreSsl $IgnoreSsl + } + } +} diff --git a/PiHoleShell/Public/FTLInformation/Get-PiHoleInfoFtl.ps1 b/PiHoleShell/Public/FTLInformation/Get-PiHoleInfoFtl.ps1 new file mode 100644 index 0000000..cfee248 --- /dev/null +++ b/PiHoleShell/Public/FTLInformation/Get-PiHoleInfoFtl.ps1 @@ -0,0 +1,108 @@ +function Get-PiHoleInfoFtl { + <# +.SYNOPSIS +Get info about various FTL parameters + +.DESCRIPTION +Request a collection of FTL process information: gravity database counts, privacy level, +query frequency, client counts, process ID/uptime/resource usage, whether destructive actions +are allowed, and metrics from the embedded dnsmasq resolver. + +.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-PiHoleInfoFtl -PiHoleServer "http://pihole.domain.com:8080" -Password "your-app-password" + #> + [CmdletBinding(HelpUri = 'https://ftl.pi-hole.net/master/docs/#get-/info/ftl')] + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "Password")] + param ( + [Parameter(Mandatory = $true)] + [System.URI]$PiHoleServer, + [Parameter(Mandatory = $true)] + [string]$Password, + [bool]$IgnoreSsl = $false, + [bool]$RawOutput = $false + ) + + try { + $Sid = Request-PiHoleAuth -PiHoleServer $PiHoleServer -Password $Password -IgnoreSsl $IgnoreSsl + + $Params = @{ + Headers = @{sid = $($Sid) } + Uri = "$($PiHoleServer.OriginalString)/api/info/ftl" + Method = "Get" + SkipCertificateCheck = $IgnoreSsl + ContentType = "application/json" + } + + $Response = Invoke-RestMethod @Params + + if ($RawOutput) { + Write-Output $Response + } + else { + $Object = [PSCustomObject]@{ + Database = [PSCustomObject]@{ + Gravity = $Response.ftl.database.gravity + Antigravity = $Response.ftl.database.antigravity + Groups = $Response.ftl.database.groups + Lists = $Response.ftl.database.lists + Clients = $Response.ftl.database.clients + Domains = [PSCustomObject]@{ + Allowed = [PSCustomObject]@{ + Total = $Response.ftl.database.domains.allowed.total + Enabled = $Response.ftl.database.domains.allowed.enabled + } + Denied = [PSCustomObject]@{ + Total = $Response.ftl.database.domains.denied.total + Enabled = $Response.ftl.database.domains.denied.enabled + } + } + Regex = [PSCustomObject]@{ + Allowed = [PSCustomObject]@{ + Total = $Response.ftl.database.regex.allowed.total + Enabled = $Response.ftl.database.regex.allowed.enabled + } + Denied = [PSCustomObject]@{ + Total = $Response.ftl.database.regex.denied.total + Enabled = $Response.ftl.database.regex.denied.enabled + } + } + } + PrivacyLevel = $Response.ftl.privacy_level + QueryFrequency = $Response.ftl.query_frequency + Clients = [PSCustomObject]@{ + Total = $Response.ftl.clients.total + Active = $Response.ftl.clients.active + } + Pid = $Response.ftl.pid + Uptime = $Response.ftl.uptime + PercentMemory = $Response.ftl.'%mem' + PercentCpu = $Response.ftl.'%cpu' + AllowDestructive = $Response.ftl.allow_destructive + Dnsmasq = ConvertTo-PiHolePascalCaseObject -InputObject $Response.ftl.dnsmasq + } + Write-Output $Object + } + } + + catch { + Write-Error -Message $_.Exception.Message + } + + finally { + if ($Sid) { + Remove-PiHoleCurrentAuthSession -PiHoleServer $PiHoleServer -Sid $Sid -IgnoreSsl $IgnoreSsl + } + } +} diff --git a/PiHoleShell/Public/FTLInformation/Get-PiHoleInfoLogin.ps1 b/PiHoleShell/Public/FTLInformation/Get-PiHoleInfoLogin.ps1 new file mode 100644 index 0000000..914c889 --- /dev/null +++ b/PiHoleShell/Public/FTLInformation/Get-PiHoleInfoLogin.ps1 @@ -0,0 +1,56 @@ +function Get-PiHoleInfoLogin { + <# +.SYNOPSIS +Get login page information + +.DESCRIPTION +Returns information used on Pi-hole's login page (the HTTPS port, and whether the DNS server +is currently running). This endpoint does not require authentication, since it's meant to be +usable before logging in. + +.PARAMETER PiHoleServer +The URL to the PiHole Server, for example "http://pihole.domain.com:8080", or "http://192.168.1.100" + +.PARAMETER IgnoreSsl +Set to $true to skip SSL certificate validation + +.PARAMETER RawOutput +This will dump the response instead of the formatted object + +.EXAMPLE +Get-PiHoleInfoLogin -PiHoleServer "http://pihole.domain.com:8080" + #> + [CmdletBinding(HelpUri = 'https://ftl.pi-hole.net/master/docs/#get-/info/login')] + param ( + [Parameter(Mandatory = $true)] + [System.URI]$PiHoleServer, + [bool]$IgnoreSsl = $false, + [bool]$RawOutput = $false + ) + + try { + $Params = @{ + Uri = "$($PiHoleServer.OriginalString)/api/info/login" + Method = "Get" + SkipCertificateCheck = $IgnoreSsl + ContentType = "application/json" + } + + $Response = Invoke-RestMethod @Params + + if ($RawOutput) { + Write-Output $Response + } + else { + $Object = [PSCustomObject]@{ + HttpsPort = $Response.https_port + Dns = $Response.dns + } + Write-Output $Object + } + } + + catch { + Write-Error -Message $_.Exception.Message + } +} diff --git a/PiHoleShell/Public/FTLInformation/Get-PiHoleInfoMessage.ps1 b/PiHoleShell/Public/FTLInformation/Get-PiHoleInfoMessage.ps1 index fcd4f28..c067a3e 100644 --- a/PiHoleShell/Public/FTLInformation/Get-PiHoleInfoMessage.ps1 +++ b/PiHoleShell/Public/FTLInformation/Get-PiHoleInfoMessage.ps1 @@ -2,9 +2,28 @@ function Get-PiHoleInfoMessage { <# .SYNOPSIS Get Pi-hole diagnosis messages -Request Pi-hole diagnosis messages + +.DESCRIPTION +Request Pi-hole's diagnosis messages - warnings FTL has generated about its own configuration +or operation (e.g. rate-limiting a noisy client). See Get-PiHoleInfoMessageCount for just the +count, and Remove-PiHoleInfoMessage to dismiss 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 IgnoreSsl +Set to $true to skip SSL certificate validation + +.PARAMETER RawOutput +This will dump the response instead of the formatted object + +.EXAMPLE +Get-PiHoleInfoMessage -PiHoleServer "http://pihole.domain.com:8080" -Password "your-app-password" #> - [CmdletBinding()] + [CmdletBinding(HelpUri = 'https://ftl.pi-hole.net/master/docs/#get-/info/messages')] [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "Password")] param ( [Parameter(Mandatory = $true)] @@ -58,7 +77,6 @@ Request Pi-hole diagnosis messages catch { Write-Error -Message $_.Exception.Message - break } finally { diff --git a/PiHoleShell/Public/FTLInformation/Get-PiHoleInfoMessageCount.ps1 b/PiHoleShell/Public/FTLInformation/Get-PiHoleInfoMessageCount.ps1 new file mode 100644 index 0000000..b5f98f4 --- /dev/null +++ b/PiHoleShell/Public/FTLInformation/Get-PiHoleInfoMessageCount.ps1 @@ -0,0 +1,69 @@ +function Get-PiHoleInfoMessageCount { + <# +.SYNOPSIS +Get count of Pi-hole diagnosis messages + +.DESCRIPTION +Request the number of Pi-hole diagnosis messages currently outstanding. See +Get-PiHoleInfoMessage for the messages themselves, and Remove-PiHoleInfoMessage to dismiss 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 IgnoreSsl +Set to $true to skip SSL certificate validation + +.PARAMETER RawOutput +This will dump the response instead of the formatted object + +.EXAMPLE +Get-PiHoleInfoMessageCount -PiHoleServer "http://pihole.domain.com:8080" -Password "your-app-password" + #> + [CmdletBinding(HelpUri = 'https://ftl.pi-hole.net/master/docs/#get-/info/messages/count')] + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "Password")] + param ( + [Parameter(Mandatory = $true)] + [System.URI]$PiHoleServer, + [Parameter(Mandatory = $true)] + [string]$Password, + [bool]$IgnoreSsl = $false, + [bool]$RawOutput = $false + ) + + try { + $Sid = Request-PiHoleAuth -PiHoleServer $PiHoleServer -Password $Password -IgnoreSsl $IgnoreSsl + + $Params = @{ + Headers = @{sid = $($Sid) } + Uri = "$($PiHoleServer.OriginalString)/api/info/messages/count" + Method = "Get" + SkipCertificateCheck = $IgnoreSsl + ContentType = "application/json" + } + + $Response = Invoke-RestMethod @Params + + if ($RawOutput) { + Write-Output $Response + } + else { + $Object = [PSCustomObject]@{ + Count = $Response.count + } + Write-Output $Object + } + } + + catch { + Write-Error -Message $_.Exception.Message + } + + finally { + if ($Sid) { + Remove-PiHoleCurrentAuthSession -PiHoleServer $PiHoleServer -Sid $Sid -IgnoreSsl $IgnoreSsl + } + } +} diff --git a/PiHoleShell/Public/FTLInformation/Get-PiHoleInfoMetrics.ps1 b/PiHoleShell/Public/FTLInformation/Get-PiHoleInfoMetrics.ps1 new file mode 100644 index 0000000..22263a5 --- /dev/null +++ b/PiHoleShell/Public/FTLInformation/Get-PiHoleInfoMetrics.ps1 @@ -0,0 +1,117 @@ +function Get-PiHoleInfoMetrics { + <# +.SYNOPSIS +Get metrics info + +.DESCRIPTION +Request live DNS and DHCP metrics: DNS cache statistics (including a per-record-type +breakdown), reply-type counts, and DHCP message/lease counts. + +.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-PiHoleInfoMetrics -PiHoleServer "http://pihole.domain.com:8080" -Password "your-app-password" + #> + [CmdletBinding(HelpUri = 'https://ftl.pi-hole.net/master/docs/#get-/info/metrics')] + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "Password")] + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseSingularNouns", "", Justification = "Metrics matches the Pi-hole API's own endpoint name, /info/metrics")] + param ( + [Parameter(Mandatory = $true)] + [System.URI]$PiHoleServer, + [Parameter(Mandatory = $true)] + [string]$Password, + [bool]$IgnoreSsl = $false, + [bool]$RawOutput = $false + ) + + try { + $Sid = Request-PiHoleAuth -PiHoleServer $PiHoleServer -Password $Password -IgnoreSsl $IgnoreSsl + + $Params = @{ + Headers = @{sid = $($Sid) } + Uri = "$($PiHoleServer.OriginalString)/api/info/metrics" + Method = "Get" + SkipCertificateCheck = $IgnoreSsl + ContentType = "application/json" + } + + $Response = Invoke-RestMethod @Params + + if ($RawOutput) { + Write-Output $Response + } + else { + $CacheContent = foreach ($Item in $Response.metrics.dns.cache.content) { + [PSCustomObject]@{ + Type = $Item.type + Name = $Item.name + Count = [PSCustomObject]@{ + Valid = $Item.count.valid + Stale = $Item.count.stale + } + } + } + + $Object = [PSCustomObject]@{ + Dns = [PSCustomObject]@{ + Cache = [PSCustomObject]@{ + Size = $Response.metrics.dns.cache.size + Inserted = $Response.metrics.dns.cache.inserted + Evicted = $Response.metrics.dns.cache.evicted + Expired = $Response.metrics.dns.cache.expired + Immortal = $Response.metrics.dns.cache.immortal + Content = $CacheContent + } + Replies = [PSCustomObject]@{ + Forwarded = $Response.metrics.dns.replies.forwarded + Unanswered = $Response.metrics.dns.replies.unanswered + Local = $Response.metrics.dns.replies.local + Optimized = $Response.metrics.dns.replies.optimized + Auth = $Response.metrics.dns.replies.auth + Sum = $Response.metrics.dns.replies.sum + } + } + Dhcp = [PSCustomObject]@{ + Ack = $Response.metrics.dhcp.ack + Nak = $Response.metrics.dhcp.nak + Decline = $Response.metrics.dhcp.decline + Offer = $Response.metrics.dhcp.offer + Discover = $Response.metrics.dhcp.discover + Inform = $Response.metrics.dhcp.inform + Request = $Response.metrics.dhcp.request + Release = $Response.metrics.dhcp.release + NoAnswer = $Response.metrics.dhcp.noanswer + Bootp = $Response.metrics.dhcp.bootp + Pxe = $Response.metrics.dhcp.pxe + Leases = [PSCustomObject]@{ + Allocated4 = $Response.metrics.dhcp.leases.allocated_4 + Pruned4 = $Response.metrics.dhcp.leases.pruned_4 + Allocated6 = $Response.metrics.dhcp.leases.allocated_6 + Pruned6 = $Response.metrics.dhcp.leases.pruned_6 + } + } + } + Write-Output $Object + } + } + + catch { + Write-Error -Message $_.Exception.Message + } + + finally { + if ($Sid) { + Remove-PiHoleCurrentAuthSession -PiHoleServer $PiHoleServer -Sid $Sid -IgnoreSsl $IgnoreSsl + } + } +} diff --git a/PiHoleShell/Public/FTLInformation/Get-PiHoleInfoSensors.ps1 b/PiHoleShell/Public/FTLInformation/Get-PiHoleInfoSensors.ps1 new file mode 100644 index 0000000..8d056f7 --- /dev/null +++ b/PiHoleShell/Public/FTLInformation/Get-PiHoleInfoSensors.ps1 @@ -0,0 +1,91 @@ +function Get-PiHoleInfoSensors { + <# +.SYNOPSIS +Get info about various sensors + +.DESCRIPTION +Request temperature sensor information, including Pi-hole's best guess at the CPU +temperature and the configured "hot" limit. + +.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-PiHoleInfoSensors -PiHoleServer "http://pihole.domain.com:8080" -Password "your-app-password" + #> + [CmdletBinding(HelpUri = 'https://ftl.pi-hole.net/master/docs/#get-/info/sensors')] + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "Password")] + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseSingularNouns", "", Justification = "Sensors matches the Pi-hole API's own endpoint name, /info/sensors")] + param ( + [Parameter(Mandatory = $true)] + [System.URI]$PiHoleServer, + [Parameter(Mandatory = $true)] + [string]$Password, + [bool]$IgnoreSsl = $false, + [bool]$RawOutput = $false + ) + + try { + $Sid = Request-PiHoleAuth -PiHoleServer $PiHoleServer -Password $Password -IgnoreSsl $IgnoreSsl + + $Params = @{ + Headers = @{sid = $($Sid) } + Uri = "$($PiHoleServer.OriginalString)/api/info/sensors" + Method = "Get" + SkipCertificateCheck = $IgnoreSsl + ContentType = "application/json" + } + + $Response = Invoke-RestMethod @Params + + if ($RawOutput) { + Write-Output $Response + } + else { + $List = foreach ($Item in $Response.sensors.list) { + $Temps = foreach ($Temp in $Item.temps) { + [PSCustomObject]@{ + Name = $Temp.name + Value = $Temp.value + Max = $Temp.max + Crit = $Temp.crit + Sensor = $Temp.sensor + } + } + [PSCustomObject]@{ + Name = $Item.name + Path = $Item.path + Source = $Item.source + Temps = $Temps + } + } + + $Object = [PSCustomObject]@{ + List = $List + CpuTemp = $Response.sensors.cpu_temp + HotLimit = $Response.sensors.hot_limit + Unit = $Response.sensors.unit + } + Write-Output $Object + } + } + + catch { + Write-Error -Message $_.Exception.Message + } + + finally { + if ($Sid) { + Remove-PiHoleCurrentAuthSession -PiHoleServer $PiHoleServer -Sid $Sid -IgnoreSsl $IgnoreSsl + } + } +} diff --git a/PiHoleShell/Public/FTLInformation/Get-PiHoleInfoSystem.ps1 b/PiHoleShell/Public/FTLInformation/Get-PiHoleInfoSystem.ps1 new file mode 100644 index 0000000..cc148e1 --- /dev/null +++ b/PiHoleShell/Public/FTLInformation/Get-PiHoleInfoSystem.ps1 @@ -0,0 +1,97 @@ +function Get-PiHoleInfoSystem { + <# +.SYNOPSIS +Get info about various system parameters + +.DESCRIPTION +Request system information: uptime, RAM/swap memory usage, process count, and CPU usage/load +averages, including FTL's own share of memory and CPU. + +.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-PiHoleInfoSystem -PiHoleServer "http://pihole.domain.com:8080" -Password "your-app-password" + #> + [CmdletBinding(HelpUri = 'https://ftl.pi-hole.net/master/docs/#get-/info/system')] + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "Password")] + param ( + [Parameter(Mandatory = $true)] + [System.URI]$PiHoleServer, + [Parameter(Mandatory = $true)] + [string]$Password, + [bool]$IgnoreSsl = $false, + [bool]$RawOutput = $false + ) + + try { + $Sid = Request-PiHoleAuth -PiHoleServer $PiHoleServer -Password $Password -IgnoreSsl $IgnoreSsl + + $Params = @{ + Headers = @{sid = $($Sid) } + Uri = "$($PiHoleServer.OriginalString)/api/info/system" + Method = "Get" + SkipCertificateCheck = $IgnoreSsl + ContentType = "application/json" + } + + $Response = Invoke-RestMethod @Params + + if ($RawOutput) { + Write-Output $Response + } + else { + $Object = [PSCustomObject]@{ + Uptime = $Response.system.uptime + Memory = [PSCustomObject]@{ + Ram = [PSCustomObject]@{ + Total = $Response.system.memory.ram.total + Free = $Response.system.memory.ram.free + Used = $Response.system.memory.ram.used + Available = $Response.system.memory.ram.available + PercentUsed = $Response.system.memory.ram.'%used' + } + Swap = [PSCustomObject]@{ + Total = $Response.system.memory.swap.total + Free = $Response.system.memory.swap.free + Used = $Response.system.memory.swap.used + PercentUsed = $Response.system.memory.swap.'%used' + } + } + Procs = $Response.system.procs + Cpu = [PSCustomObject]@{ + NumProcessors = $Response.system.cpu.nprocs + PercentCpu = $Response.system.cpu.'%cpu' + Load = [PSCustomObject]@{ + Raw = $Response.system.cpu.load.raw + Percent = $Response.system.cpu.load.percent + } + } + Ftl = [PSCustomObject]@{ + PercentMemory = $Response.system.ftl.'%mem' + PercentCpu = $Response.system.ftl.'%cpu' + } + } + Write-Output $Object + } + } + + catch { + Write-Error -Message $_.Exception.Message + } + + finally { + if ($Sid) { + Remove-PiHoleCurrentAuthSession -PiHoleServer $PiHoleServer -Sid $Sid -IgnoreSsl $IgnoreSsl + } + } +} diff --git a/PiHoleShell/Public/FTLInformation/Get-PiHoleInfoVersion.ps1 b/PiHoleShell/Public/FTLInformation/Get-PiHoleInfoVersion.ps1 new file mode 100644 index 0000000..f6a6b3b --- /dev/null +++ b/PiHoleShell/Public/FTLInformation/Get-PiHoleInfoVersion.ps1 @@ -0,0 +1,106 @@ +function Get-PiHoleInfoVersion { + <# +.SYNOPSIS +Get Pi-hole version + +.DESCRIPTION +Request the local and remote (latest available) versions of each Pi-hole component: Core, +Web, FTL, and the Docker image (if running in Docker). + +.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-PiHoleInfoVersion -PiHoleServer "http://pihole.domain.com:8080" -Password "your-app-password" + #> + [CmdletBinding(HelpUri = 'https://ftl.pi-hole.net/master/docs/#get-/info/version')] + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "Password")] + param ( + [Parameter(Mandatory = $true)] + [System.URI]$PiHoleServer, + [Parameter(Mandatory = $true)] + [string]$Password, + [bool]$IgnoreSsl = $false, + [bool]$RawOutput = $false + ) + + try { + $Sid = Request-PiHoleAuth -PiHoleServer $PiHoleServer -Password $Password -IgnoreSsl $IgnoreSsl + + $Params = @{ + Headers = @{sid = $($Sid) } + Uri = "$($PiHoleServer.OriginalString)/api/info/version" + Method = "Get" + SkipCertificateCheck = $IgnoreSsl + ContentType = "application/json" + } + + $Response = Invoke-RestMethod @Params + + if ($RawOutput) { + Write-Output $Response + } + else { + $Object = [PSCustomObject]@{ + Core = [PSCustomObject]@{ + Local = [PSCustomObject]@{ + Branch = $Response.version.core.local.branch + Version = $Response.version.core.local.version + Hash = $Response.version.core.local.hash + } + Remote = [PSCustomObject]@{ + Version = $Response.version.core.remote.version + Hash = $Response.version.core.remote.hash + } + } + Web = [PSCustomObject]@{ + Local = [PSCustomObject]@{ + Branch = $Response.version.web.local.branch + Version = $Response.version.web.local.version + Hash = $Response.version.web.local.hash + } + Remote = [PSCustomObject]@{ + Version = $Response.version.web.remote.version + Hash = $Response.version.web.remote.hash + } + } + Ftl = [PSCustomObject]@{ + Local = [PSCustomObject]@{ + Branch = $Response.version.ftl.local.branch + Version = $Response.version.ftl.local.version + Hash = $Response.version.ftl.local.hash + Date = $Response.version.ftl.local.date + } + Remote = [PSCustomObject]@{ + Version = $Response.version.ftl.remote.version + Hash = $Response.version.ftl.remote.hash + } + } + Docker = [PSCustomObject]@{ + Local = $Response.version.docker.local + Remote = $Response.version.docker.remote + } + } + Write-Output $Object + } + } + + catch { + Write-Error -Message $_.Exception.Message + } + + finally { + if ($Sid) { + Remove-PiHoleCurrentAuthSession -PiHoleServer $PiHoleServer -Sid $Sid -IgnoreSsl $IgnoreSsl + } + } +} diff --git a/PiHoleShell/Public/FTLInformation/Remove-PiHoleInfoMessage.ps1 b/PiHoleShell/Public/FTLInformation/Remove-PiHoleInfoMessage.ps1 new file mode 100644 index 0000000..53b498f --- /dev/null +++ b/PiHoleShell/Public/FTLInformation/Remove-PiHoleInfoMessage.ps1 @@ -0,0 +1,83 @@ +function Remove-PiHoleInfoMessage { + <# +.SYNOPSIS +Delete a Pi-hole diagnosis message + +.DESCRIPTION +Dismisses one or more Pi-hole diagnosis messages by ID. See Get-PiHoleInfoMessage to list +messages and find their IDs. + +.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 MessageId +The ID (or IDs) of the message(s) to delete + +.PARAMETER IgnoreSsl +Set to $true to skip SSL certificate validation + +.PARAMETER RawOutput +This will dump the response instead of the formatted object + +.EXAMPLE +Remove-PiHoleInfoMessage -PiHoleServer "http://pihole.domain.com:8080" -Password "your-app-password" -MessageId 3 + +.EXAMPLE +Remove-PiHoleInfoMessage -PiHoleServer "http://pihole.domain.com:8080" -Password "your-app-password" -MessageId 1,2,3 + #> + [CmdletBinding(SupportsShouldProcess = $true, HelpUri = 'https://ftl.pi-hole.net/master/docs/#delete-/info/messages/-message_id-')] + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "Password")] + param ( + [Parameter(Mandatory = $true)] + [System.URI]$PiHoleServer, + [Parameter(Mandatory = $true)] + [string]$Password, + [Parameter(Mandatory = $true)] + [int[]]$MessageId, + [bool]$IgnoreSsl = $false, + [bool]$RawOutput = $false + ) + + try { + $MessageIdPath = $MessageId -join ',' + + if ($PSCmdlet.ShouldProcess("Pi-Hole diagnosis message(s) $MessageIdPath on $PiHoleServer", "Delete")) { + $Sid = Request-PiHoleAuth -PiHoleServer $PiHoleServer -Password $Password -IgnoreSsl $IgnoreSsl + + $Params = @{ + Headers = @{sid = $($Sid) } + Uri = "$($PiHoleServer.OriginalString)/api/info/messages/$MessageIdPath" + Method = "Delete" + SkipCertificateCheck = $IgnoreSsl + ContentType = "application/json" + } + + $Response = Invoke-RestMethod @Params + + if ($RawOutput) { + Write-Output $Response + } + else { + # A successful delete is HTTP 204 No Content, so there's no response body to parse. + $Object = [PSCustomObject]@{ + MessageId = $MessageId + Status = "Deleted" + } + Write-Output $Object + } + } + } + + catch { + Write-Error -Message $_.Exception.Message + } + + finally { + if ($Sid) { + Remove-PiHoleCurrentAuthSession -PiHoleServer $PiHoleServer -Sid $Sid -IgnoreSsl $IgnoreSsl + } + } +} diff --git a/PiHoleShell/Public/ListManagement/Update-PiHoleList.ps1 b/PiHoleShell/Public/ListManagement/Update-PiHoleList.ps1 new file mode 100644 index 0000000..d6789fe --- /dev/null +++ b/PiHoleShell/Public/ListManagement/Update-PiHoleList.ps1 @@ -0,0 +1,152 @@ +function Update-PiHoleList { + <# +.SYNOPSIS +Update a list + +.DESCRIPTION +Updates an existing list's Comment, Group(s), and/or Enabled state. The underlying Pi-hole API +replaces the entire list on update, so any property you don't pass here is preserved by first +reading the list's current value and resending it - nothing is silently cleared just because +you only meant to change one property. + +.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 Address +The URL of the list to update + +.PARAMETER Type +Whether this is an Allow list or a Block list + +.PARAMETER Comment +The new comment for the list. Leave unset to keep the list's current comment + +.PARAMETER Group +The group(s) this list should apply to. Leave unset to keep the list's current group(s) + +.PARAMETER Enabled +Whether the list should be enabled. Leave unset to keep the list's current state + +.PARAMETER IgnoreSsl +Set to $true to skip SSL certificate validation + +.PARAMETER RawOutput +This will dump the response instead of the formatted object + +.EXAMPLE +Update-PiHoleList -PiHoleServer "http://pihole.domain.com:8080" -Password "your-app-password" -Address "https://hosts-file.net/ad_servers.txt" -Type Block -Enabled $false + #> + [CmdletBinding(HelpUri = 'https://ftl.pi-hole.net/master/docs/#put-/lists/-list-')] + [Diagnostics.CodeAnalysis.SuppressMessage("PSUseShouldProcessForStateChangingFunctions", "", Justification = "Ignoring for now")] + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "Password")] + param ( + [Parameter(Mandatory = $true)] + [System.URI]$PiHoleServer, + [Parameter(Mandatory = $true)] + [string]$Password, + [Parameter(Mandatory = $true)] + [System.Uri]$Address, + [Parameter(Mandatory = $true)] + [ValidateSet("Allow", "Block")] + [string]$Type, + [string]$Comment, + [string[]]$Group, + [Nullable[bool]]$Enabled, + [bool]$IgnoreSsl = $false, + [bool]$RawOutput = $false + ) + + try { + if (-not $PSBoundParameters.ContainsKey('Comment') -and -not $PSBoundParameters.ContainsKey('Group') -and -not $PSBoundParameters.ContainsKey('Enabled')) { + throw "To update $Address, you must specify the Comment, Group, and/or Enabled parameter" + } + + $ExistingList = Get-PiHoleList -PiHoleServer $PiHoleServer -Password $Password -IgnoreSsl $IgnoreSsl -List $Address | Where-Object { $_.Type -eq $Type } + + if (-not $ExistingList) { + throw "Cannot find $Address of type $Type on $PiHoleServer! Please use Add-PiHoleList to create it" + } + + $AllGroups = Get-PiHoleGroup -PiHoleServer $PiHoleServer -Password $Password -IgnoreSsl $IgnoreSsl + + $GroupNamesToResolve = if ($PSBoundParameters.ContainsKey('Group')) { $Group } else { $ExistingList.Groups } + + $AllGroupsNames = @() + $AllGroupsIds = @() + foreach ($GroupItem in $GroupNamesToResolve) { + $FoundGroup = $AllGroups | Where-Object { $_.Name -eq $GroupItem } + if ($FoundGroup) { + $AllGroupsNames += $FoundGroup.Name + $AllGroupsIds += $FoundGroup.Id + } + else { + throw "Cannot find $GroupItem on $PiHoleServer! Please use Get-PiHoleGroup to list all groups" + } + } + + $Sid = Request-PiHoleAuth -PiHoleServer $PiHoleServer -Password $Password -IgnoreSsl $IgnoreSsl + + # The API replaces the whole list on update, so any property not explicitly passed here + # is resent using the list's current value to avoid silently clearing it. + $Body = @{ + comment = if ($PSBoundParameters.ContainsKey('Comment')) { $Comment } else { $ExistingList.Comment } + groups = [Object[]]($AllGroupsIds) + enabled = if ($PSBoundParameters.ContainsKey('Enabled')) { $Enabled } else { $ExistingList.Enabled } + } + + $Params = @{ + Headers = @{sid = $($Sid) } + Uri = "$($PiHoleServer.OriginalString)/api/lists/$Address`?type=$($Type.ToLower())" + Method = "Put" + SkipCertificateCheck = $IgnoreSsl + Body = $Body | ConvertTo-Json -Depth 10 + ContentType = "application/json" + } + + $Response = Invoke-RestMethod @Params + + if ($RawOutput) { + Write-Output $Response + } + else { + $ObjectFinal = foreach ($Item in $Response.lists) { + if ($Item.date_updated -eq 0) { + $DateUpdated = $null + } + else { + $DateUpdated = (Convert-PiHoleUnixTimeToLocalTime -UnixTime $Item.date_modified).LocalTime + } + + [PSCustomObject]@{ + Address = $Item.address + Comment = $Item.comment + Groups = $AllGroupsNames + Enabled = $Item.enabled + Id = $Item.id + DateAdded = (Convert-PiHoleUnixTimeToLocalTime -UnixTime $Item.date_added).LocalTime + DateModified = (Convert-PiHoleUnixTimeToLocalTime -UnixTime $Item.date_modified).LocalTime + Type = $Item.type.SubString(0, 1).ToUpper() + $Item.type.SubString(1).ToLower() + DateUpdated = $DateUpdated + Number = $Item.number + InvalidDomains = $Item.invalid_domains + AbpEntries = $Item.abp_entries + Status = $Item.status + } + } + Write-Output $ObjectFinal + } + } + + catch { + Write-Error -Message $_.Exception.Message + } + + finally { + if ($Sid) { + Remove-PiHoleCurrentAuthSession -PiHoleServer $PiHoleServer -Sid $Sid -IgnoreSsl $IgnoreSsl + } + } +} diff --git a/README.md b/README.md index 2a7c11a..b4711ea 100644 --- a/README.md +++ b/README.md @@ -116,6 +116,7 @@ Functions marked 🚧 are still under active development — signatures and outp | `Get-PiHoleList` 🚧 | Get lists | | `Remove-PiHoleList` | Remove a list | | `Search-PiHoleListDomain` | _No description yet_ | +| `Update-PiHoleList` | Update a list | ### Metrics @@ -143,9 +144,19 @@ Functions marked 🚧 are still under active development — signatures and outp | `Get-PiHoleHistoryClient` | Get per-client activity graph data | | `Get-PiHoleHistoryDatabase` | Get activity graph data (long-term data) | | `Get-PiHoleHistoryDatabaseClient` | Get per-client activity graph data (long-term data) | +| `Get-PiHoleInfoClient` | Get information about the requesting client | +| `Get-PiHoleInfoDatabase` | Get info about the long-term database | +| `Get-PiHoleInfoFtl` | Get info about various FTL parameters | | `Get-PiHoleInfoHost` | Get information about the host system | -| `Get-PiHoleInfoMessage` | Get Pi-hole diagnosis messages Request Pi-hole diagnosis messages | +| `Get-PiHoleInfoLogin` | Get login page information | +| `Get-PiHoleInfoMessage` | Get Pi-hole diagnosis messages | +| `Get-PiHoleInfoMessageCount` | Get count of Pi-hole diagnosis messages | +| `Get-PiHoleInfoMetrics` | Get metrics info | +| `Get-PiHoleInfoSensors` | Get info about various sensors | +| `Get-PiHoleInfoSystem` | Get info about various system parameters | +| `Get-PiHoleInfoVersion` | Get Pi-hole version | | `Get-PiHolePadd` | Get summarized data for PADD | +| `Remove-PiHoleInfoMessage` | Delete a Pi-hole diagnosis message | ### Authentication diff --git a/tests/FTLInformation/Get-PiHoleInfoClient.Integration.Tests.ps1 b/tests/FTLInformation/Get-PiHoleInfoClient.Integration.Tests.ps1 new file mode 100644 index 0000000..add9b14 --- /dev/null +++ b/tests/FTLInformation/Get-PiHoleInfoClient.Integration.Tests.ps1 @@ -0,0 +1,36 @@ +# 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-PiHoleInfoClient (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:PiHoleIgnoreSsl = $PiHoleIgnoreSsl + } + } + + It 'returns client info as a formatted object without needing a password' -Skip:(-not $script:ConfigAvailable) { + $result = Get-PiHoleInfoClient -PiHoleServer $script:PiHoleServer -IgnoreSsl $script:PiHoleIgnoreSsl + $result | Format-List | Out-String | Write-Host + + $result | Should -Not -BeNullOrEmpty + $result.RemoteAddress | Should -Not -BeNullOrEmpty + $result.Headers | Should -Not -BeNullOrEmpty + } + + It 'returns the raw API response when RawOutput is set' -Skip:(-not $script:ConfigAvailable) { + $result = Get-PiHoleInfoClient -PiHoleServer $script:PiHoleServer -IgnoreSsl $script:PiHoleIgnoreSsl -RawOutput $true + $result | Format-List | Out-String | Write-Host + + $result.remote_addr | Should -Not -BeNullOrEmpty + } +} diff --git a/tests/FTLInformation/Get-PiHoleInfoDatabase.Integration.Tests.ps1 b/tests/FTLInformation/Get-PiHoleInfoDatabase.Integration.Tests.ps1 new file mode 100644 index 0000000..3ef996a --- /dev/null +++ b/tests/FTLInformation/Get-PiHoleInfoDatabase.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 (Split-Path $PSScriptRoot -Parent) 'IntegrationConfig.local.ps1') + +Describe 'Get-PiHoleInfoDatabase (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 database info as a formatted object' -Skip:(-not $script:ConfigAvailable) { + $result = Get-PiHoleInfoDatabase -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl + $result | Format-List | Out-String | Write-Host + + $result | Should -Not -BeNullOrEmpty + $result.Size | Should -BeGreaterThan 0 + $result.SqliteVersion | Should -Not -BeNullOrEmpty + } + + It 'returns the raw API response when RawOutput is set' -Skip:(-not $script:ConfigAvailable) { + $result = Get-PiHoleInfoDatabase -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl -RawOutput $true + $result | Format-List | Out-String | Write-Host + + $result.sqlite_version | Should -Not -BeNullOrEmpty + } + + It 'errors when given a bad password' -Skip:(-not $script:ConfigAvailable) { + $result = Get-PiHoleInfoDatabase -PiHoleServer $script:PiHoleServer -Password 'definitely-not-the-real-token' -IgnoreSsl $script:PiHoleIgnoreSsl -ErrorVariable errOut -ErrorAction SilentlyContinue + + $errOut | Should -Not -BeNullOrEmpty + } +} diff --git a/tests/FTLInformation/Get-PiHoleInfoFtl.Integration.Tests.ps1 b/tests/FTLInformation/Get-PiHoleInfoFtl.Integration.Tests.ps1 new file mode 100644 index 0000000..7b4e2c3 --- /dev/null +++ b/tests/FTLInformation/Get-PiHoleInfoFtl.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 (Split-Path $PSScriptRoot -Parent) 'IntegrationConfig.local.ps1') + +Describe 'Get-PiHoleInfoFtl (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 FTL info as a formatted object' -Skip:(-not $script:ConfigAvailable) { + $result = Get-PiHoleInfoFtl -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl + $result | Format-List | Out-String | Write-Host + + $result | Should -Not -BeNullOrEmpty + $result.Pid | Should -BeGreaterThan 0 + $result.Database.Gravity | Should -BeGreaterOrEqual 0 + $result.Dnsmasq | Should -Not -BeNullOrEmpty + } + + It 'returns the raw API response when RawOutput is set' -Skip:(-not $script:ConfigAvailable) { + $result = Get-PiHoleInfoFtl -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl -RawOutput $true + $result | Format-List | Out-String | Write-Host + + $result.ftl | Should -Not -BeNullOrEmpty + } + + It 'errors when given a bad password' -Skip:(-not $script:ConfigAvailable) { + $result = Get-PiHoleInfoFtl -PiHoleServer $script:PiHoleServer -Password 'definitely-not-the-real-token' -IgnoreSsl $script:PiHoleIgnoreSsl -ErrorVariable errOut -ErrorAction SilentlyContinue + + $errOut | Should -Not -BeNullOrEmpty + } +} diff --git a/tests/FTLInformation/Get-PiHoleInfoLogin.Integration.Tests.ps1 b/tests/FTLInformation/Get-PiHoleInfoLogin.Integration.Tests.ps1 new file mode 100644 index 0000000..92a5eaa --- /dev/null +++ b/tests/FTLInformation/Get-PiHoleInfoLogin.Integration.Tests.ps1 @@ -0,0 +1,35 @@ +# 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-PiHoleInfoLogin (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:PiHoleIgnoreSsl = $PiHoleIgnoreSsl + } + } + + It 'returns login page info as a formatted object without needing a password' -Skip:(-not $script:ConfigAvailable) { + $result = Get-PiHoleInfoLogin -PiHoleServer $script:PiHoleServer -IgnoreSsl $script:PiHoleIgnoreSsl + $result | Format-List | Out-String | Write-Host + + $result | Should -Not -BeNullOrEmpty + $result.HttpsPort | Should -BeGreaterThan 0 + } + + It 'returns the raw API response when RawOutput is set' -Skip:(-not $script:ConfigAvailable) { + $result = Get-PiHoleInfoLogin -PiHoleServer $script:PiHoleServer -IgnoreSsl $script:PiHoleIgnoreSsl -RawOutput $true + $result | Format-List | Out-String | Write-Host + + $result.https_port | Should -BeGreaterThan 0 + } +} diff --git a/tests/FTLInformation/Get-PiHoleInfoMessageCount.Integration.Tests.ps1 b/tests/FTLInformation/Get-PiHoleInfoMessageCount.Integration.Tests.ps1 new file mode 100644 index 0000000..c2fb490 --- /dev/null +++ b/tests/FTLInformation/Get-PiHoleInfoMessageCount.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 (Split-Path $PSScriptRoot -Parent) 'IntegrationConfig.local.ps1') + +Describe 'Get-PiHoleInfoMessageCount (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 the message count as a formatted object' -Skip:(-not $script:ConfigAvailable) { + $result = Get-PiHoleInfoMessageCount -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl + $result | Format-List | Out-String | Write-Host + + $result | Should -Not -BeNullOrEmpty + $result.Count | Should -BeGreaterOrEqual 0 + } + + It 'returns the raw API response when RawOutput is set' -Skip:(-not $script:ConfigAvailable) { + $result = Get-PiHoleInfoMessageCount -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl -RawOutput $true + $result | Format-List | Out-String | Write-Host + + $result.count | Should -BeGreaterOrEqual 0 + } + + It 'errors when given a bad password' -Skip:(-not $script:ConfigAvailable) { + $result = Get-PiHoleInfoMessageCount -PiHoleServer $script:PiHoleServer -Password 'definitely-not-the-real-token' -IgnoreSsl $script:PiHoleIgnoreSsl -ErrorVariable errOut -ErrorAction SilentlyContinue + + $errOut | Should -Not -BeNullOrEmpty + } +} diff --git a/tests/FTLInformation/Get-PiHoleInfoMetrics.Integration.Tests.ps1 b/tests/FTLInformation/Get-PiHoleInfoMetrics.Integration.Tests.ps1 new file mode 100644 index 0000000..eaa2e06 --- /dev/null +++ b/tests/FTLInformation/Get-PiHoleInfoMetrics.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 (Split-Path $PSScriptRoot -Parent) 'IntegrationConfig.local.ps1') + +Describe 'Get-PiHoleInfoMetrics (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 metrics as a formatted object' -Skip:(-not $script:ConfigAvailable) { + $result = Get-PiHoleInfoMetrics -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl + $result | Format-List | Out-String | Write-Host + + $result | Should -Not -BeNullOrEmpty + $result.Dns.Cache | Should -Not -BeNullOrEmpty + $result.Dhcp | Should -Not -BeNullOrEmpty + } + + It 'returns the raw API response when RawOutput is set' -Skip:(-not $script:ConfigAvailable) { + $result = Get-PiHoleInfoMetrics -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl -RawOutput $true + $result | Format-List | Out-String | Write-Host + + $result.metrics | Should -Not -BeNullOrEmpty + } + + It 'errors when given a bad password' -Skip:(-not $script:ConfigAvailable) { + $result = Get-PiHoleInfoMetrics -PiHoleServer $script:PiHoleServer -Password 'definitely-not-the-real-token' -IgnoreSsl $script:PiHoleIgnoreSsl -ErrorVariable errOut -ErrorAction SilentlyContinue + + $errOut | Should -Not -BeNullOrEmpty + } +} diff --git a/tests/FTLInformation/Get-PiHoleInfoSensors.Integration.Tests.ps1 b/tests/FTLInformation/Get-PiHoleInfoSensors.Integration.Tests.ps1 new file mode 100644 index 0000000..c85b638 --- /dev/null +++ b/tests/FTLInformation/Get-PiHoleInfoSensors.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 (Split-Path $PSScriptRoot -Parent) 'IntegrationConfig.local.ps1') + +Describe 'Get-PiHoleInfoSensors (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 sensor info as a formatted object' -Skip:(-not $script:ConfigAvailable) { + $result = Get-PiHoleInfoSensors -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl + $result | Format-List | Out-String | Write-Host + + $result | Should -Not -BeNullOrEmpty + $result.List | Should -Not -BeNullOrEmpty + $result.Unit | Should -Not -BeNullOrEmpty + } + + It 'returns the raw API response when RawOutput is set' -Skip:(-not $script:ConfigAvailable) { + $result = Get-PiHoleInfoSensors -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl -RawOutput $true + $result | Format-List | Out-String | Write-Host + + $result.sensors | Should -Not -BeNullOrEmpty + } + + It 'errors when given a bad password' -Skip:(-not $script:ConfigAvailable) { + $result = Get-PiHoleInfoSensors -PiHoleServer $script:PiHoleServer -Password 'definitely-not-the-real-token' -IgnoreSsl $script:PiHoleIgnoreSsl -ErrorVariable errOut -ErrorAction SilentlyContinue + + $errOut | Should -Not -BeNullOrEmpty + } +} diff --git a/tests/FTLInformation/Get-PiHoleInfoSystem.Integration.Tests.ps1 b/tests/FTLInformation/Get-PiHoleInfoSystem.Integration.Tests.ps1 new file mode 100644 index 0000000..ec12516 --- /dev/null +++ b/tests/FTLInformation/Get-PiHoleInfoSystem.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 (Split-Path $PSScriptRoot -Parent) 'IntegrationConfig.local.ps1') + +Describe 'Get-PiHoleInfoSystem (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 system info as a formatted object' -Skip:(-not $script:ConfigAvailable) { + $result = Get-PiHoleInfoSystem -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl + $result | Format-List | Out-String | Write-Host + + $result | Should -Not -BeNullOrEmpty + $result.Uptime | Should -BeGreaterThan 0 + $result.Memory.Ram.Total | Should -BeGreaterThan 0 + $result.Cpu.Load | Should -Not -BeNullOrEmpty + } + + It 'returns the raw API response when RawOutput is set' -Skip:(-not $script:ConfigAvailable) { + $result = Get-PiHoleInfoSystem -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl -RawOutput $true + $result | Format-List | Out-String | Write-Host + + $result.system | Should -Not -BeNullOrEmpty + } + + It 'errors when given a bad password' -Skip:(-not $script:ConfigAvailable) { + $result = Get-PiHoleInfoSystem -PiHoleServer $script:PiHoleServer -Password 'definitely-not-the-real-token' -IgnoreSsl $script:PiHoleIgnoreSsl -ErrorVariable errOut -ErrorAction SilentlyContinue + + $errOut | Should -Not -BeNullOrEmpty + } +} diff --git a/tests/FTLInformation/Get-PiHoleInfoVersion.Integration.Tests.ps1 b/tests/FTLInformation/Get-PiHoleInfoVersion.Integration.Tests.ps1 new file mode 100644 index 0000000..ea98313 --- /dev/null +++ b/tests/FTLInformation/Get-PiHoleInfoVersion.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 (Split-Path $PSScriptRoot -Parent) 'IntegrationConfig.local.ps1') + +Describe 'Get-PiHoleInfoVersion (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 version info as a formatted object' -Skip:(-not $script:ConfigAvailable) { + $result = Get-PiHoleInfoVersion -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl + $result | Format-List | Out-String | Write-Host + + $result | Should -Not -BeNullOrEmpty + $result.Core.Local.Version | Should -Not -BeNullOrEmpty + $result.Ftl.Local.Version | Should -Not -BeNullOrEmpty + } + + It 'returns the raw API response when RawOutput is set' -Skip:(-not $script:ConfigAvailable) { + $result = Get-PiHoleInfoVersion -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl -RawOutput $true + $result | Format-List | Out-String | Write-Host + + $result.version | Should -Not -BeNullOrEmpty + } + + It 'errors when given a bad password' -Skip:(-not $script:ConfigAvailable) { + $result = Get-PiHoleInfoVersion -PiHoleServer $script:PiHoleServer -Password 'definitely-not-the-real-token' -IgnoreSsl $script:PiHoleIgnoreSsl -ErrorVariable errOut -ErrorAction SilentlyContinue + + $errOut | Should -Not -BeNullOrEmpty + } +} diff --git a/tests/FTLInformation/Remove-PiHoleInfoMessage.Integration.Tests.ps1 b/tests/FTLInformation/Remove-PiHoleInfoMessage.Integration.Tests.ps1 new file mode 100644 index 0000000..c541a48 --- /dev/null +++ b/tests/FTLInformation/Remove-PiHoleInfoMessage.Integration.Tests.ps1 @@ -0,0 +1,37 @@ +# 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. +# +# NOTE: Diagnosis messages arise from real FTL warnings (e.g. rate-limiting a client) and can't +# be manufactured on demand, so there's no reliable way to test a genuine successful delete here. +# These tests instead verify the error paths, which still exercise the real request/auth flow. + +$script:ConfigAvailable = Test-Path (Join-Path (Split-Path $PSScriptRoot -Parent) 'IntegrationConfig.local.ps1') + +Describe 'Remove-PiHoleInfoMessage (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 'errors when the message does not exist' -Skip:(-not $script:ConfigAvailable) { + $result = Remove-PiHoleInfoMessage -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl -MessageId 999999 -Confirm:$false -ErrorVariable errOut -ErrorAction SilentlyContinue + + $errOut | Should -Not -BeNullOrEmpty + } + + It 'errors when given a bad password' -Skip:(-not $script:ConfigAvailable) { + $result = Remove-PiHoleInfoMessage -PiHoleServer $script:PiHoleServer -Password 'definitely-not-the-real-token' -IgnoreSsl $script:PiHoleIgnoreSsl -MessageId 1 -Confirm:$false -ErrorVariable errOut -ErrorAction SilentlyContinue + + $errOut | Should -Not -BeNullOrEmpty + } +} diff --git a/tests/ListManagement/Update-PiHoleList.Integration.Tests.ps1 b/tests/ListManagement/Update-PiHoleList.Integration.Tests.ps1 new file mode 100644 index 0000000..61d73bd --- /dev/null +++ b/tests/ListManagement/Update-PiHoleList.Integration.Tests.ps1 @@ -0,0 +1,94 @@ +# 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 'Update-PiHoleList (Integration)' -Tag 'Integration' { + BeforeAll { + Import-Module .\PiHoleShell\PiHoleShell.psm1 -Force + + $script:TestListAddress = 'https://blocklistproject.github.io/Lists/alt-version/ransomware-nl.txt' + + $configPath = Join-Path (Split-Path $PSScriptRoot -Parent) 'IntegrationConfig.local.ps1' + if (Test-Path $configPath) { + . $configPath + $script:PiHoleServer = $PiHoleServer + $script:PiHoleToken = $PiHoleToken + $script:PiHoleIgnoreSsl = $PiHoleIgnoreSsl + + # Defensive cleanup in case a previous failed run left the test list behind + Remove-PiHoleList -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl -Address $script:TestListAddress -Type Block -Confirm:$false -ErrorAction SilentlyContinue | Out-Null + } + } + + AfterAll { + if ($script:PiHoleServer) { + Remove-PiHoleList -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl -Address $script:TestListAddress -Type Block -Confirm:$false -ErrorAction SilentlyContinue | Out-Null + } + } + + It 'updates only the comment, preserving Enabled and Group' -Skip:(-not $script:ConfigAvailable) { + Add-PiHoleList -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl -Address $script:TestListAddress -Type Block -Comment 'original comment' -Enabled $true | Out-Null + + $result = Update-PiHoleList -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl -Address $script:TestListAddress -Type Block -Comment 'updated comment' + $result | Format-List | Out-String | Write-Host + + $result | Should -Not -BeNullOrEmpty + $result.Comment | Should -Be 'updated comment' + $result.Enabled | Should -BeTrue + $result.Groups | Should -Contain 'Default' + + Remove-PiHoleList -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl -Address $script:TestListAddress -Type Block -Confirm:$false | Out-Null + } + + It 'updates only Enabled, preserving the current comment' -Skip:(-not $script:ConfigAvailable) { + Add-PiHoleList -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl -Address $script:TestListAddress -Type Block -Comment 'keep this comment' -Enabled $true | Out-Null + + $result = Update-PiHoleList -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl -Address $script:TestListAddress -Type Block -Enabled $false + $result | Format-List | Out-String | Write-Host + + $result | Should -Not -BeNullOrEmpty + $result.Comment | Should -Be 'keep this comment' + $result.Enabled | Should -BeFalse + + Remove-PiHoleList -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl -Address $script:TestListAddress -Type Block -Confirm:$false | Out-Null + } + + It 'returns the raw API response when RawOutput is set' -Skip:(-not $script:ConfigAvailable) { + Add-PiHoleList -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl -Address $script:TestListAddress -Type Block | Out-Null + + $result = Update-PiHoleList -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl -Address $script:TestListAddress -Type Block -Comment 'raw output test' -RawOutput $true + $result | Format-List | Out-String | Write-Host + + $result.lists[0].comment | Should -Be 'raw output test' + + Remove-PiHoleList -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl -Address $script:TestListAddress -Type Block -Confirm:$false | Out-Null + } + + It 'errors when neither Comment, Group, nor Enabled is specified' -Skip:(-not $script:ConfigAvailable) { + Add-PiHoleList -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl -Address $script:TestListAddress -Type Block | Out-Null + + $result = Update-PiHoleList -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl -Address $script:TestListAddress -Type Block -ErrorVariable errOut -ErrorAction SilentlyContinue + + $errOut | Should -Not -BeNullOrEmpty + + Remove-PiHoleList -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl -Address $script:TestListAddress -Type Block -Confirm:$false | Out-Null + } + + It 'errors when the list does not exist' -Skip:(-not $script:ConfigAvailable) { + $result = Update-PiHoleList -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl -Address 'https://example.com/does-not-exist.txt' -Type Block -Comment 'irrelevant' -ErrorVariable errOut -ErrorAction SilentlyContinue + + $errOut | Should -Not -BeNullOrEmpty + } + + It 'errors when given a bad password' -Skip:(-not $script:ConfigAvailable) { + Add-PiHoleList -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl -Address $script:TestListAddress -Type Block | Out-Null + + $result = Update-PiHoleList -PiHoleServer $script:PiHoleServer -Password 'definitely-not-the-real-token' -IgnoreSsl $script:PiHoleIgnoreSsl -Address $script:TestListAddress -Type Block -Comment 'irrelevant' -ErrorVariable errOut -ErrorAction SilentlyContinue + + $errOut | Should -Not -BeNullOrEmpty + } +}