diff --git a/PiHoleShell/PiHoleShell.psm1 b/PiHoleShell/PiHoleShell.psm1 index 6f5330e..59967d5 100644 --- a/PiHoleShell/PiHoleShell.psm1 +++ b/PiHoleShell/PiHoleShell.psm1 @@ -38,5 +38,7 @@ Export-ModuleMember -Function @( #History 'Get-PiHoleHistory', 'Get-PiHoleHistoryDatabase', 'Get-PiHoleHistoryClient', 'Get-PiHoleHistoryDatabaseClient', ` #Teleporter - 'Get-PiHoleTeleporterDownload' + 'Get-PiHoleTeleporterDownload', ` + #DomainManagement + 'Get-PiHoleDomain', 'New-PiHoleDomain', 'Update-PiHoleDomain', 'Remove-PiHoleDomain' ) \ No newline at end of file diff --git a/PiHoleShell/Public/DomainManagement/Get-PiHoleDomain.ps1 b/PiHoleShell/Public/DomainManagement/Get-PiHoleDomain.ps1 new file mode 100644 index 0000000..c099a2f --- /dev/null +++ b/PiHoleShell/Public/DomainManagement/Get-PiHoleDomain.ps1 @@ -0,0 +1,123 @@ +function Get-PiHoleDomain { + <# +.SYNOPSIS +Get domains + +.DESCRIPTION +Request Pi-hole's per-domain allow/deny list entries (the newer domain-based API, distinct +from the Lists functions which manage whole allow/block list subscriptions). Omit all filters +to get every domain; narrow the results with -Type, -Kind, and -Domain. + +.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 Domain +Only return this domain. Requires -Type and -Kind to also be specified + +.PARAMETER Type +Only return domains of this type (Allow or Deny). Required if -Kind or -Domain is specified + +.PARAMETER Kind +Only return domains of this kind (Exact match or Regex). Requires -Type to also be specified + +.PARAMETER IgnoreSsl +Set to $true to skip SSL certificate validation + +.PARAMETER RawOutput +This will dump the response instead of the formatted object + +.EXAMPLE +Get-PiHoleDomain -PiHoleServer "http://pihole.domain.com:8080" -Password "your-app-password" + +.EXAMPLE +Get-PiHoleDomain -PiHoleServer "http://pihole.domain.com:8080" -Password "your-app-password" -Type Allow -Kind Exact -Domain "example.com" + #> + [CmdletBinding(HelpUri = 'https://ftl.pi-hole.net/master/docs/#get-/domains')] + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "Password")] + param ( + [Parameter(Mandatory = $true)] + [System.URI]$PiHoleServer, + [Parameter(Mandatory = $true)] + [string]$Password, + [string]$Domain, + [ValidateSet("Allow", "Deny")] + [string]$Type, + [ValidateSet("Exact", "Regex")] + [string]$Kind, + [bool]$IgnoreSsl = $false, + [bool]$RawOutput = $false + ) + try { + if ($Kind -and -not $Type) { + throw "-Type must be specified when -Kind is specified" + } + if ($Domain -and (-not $Type -or -not $Kind)) { + throw "-Type and -Kind must both be specified when -Domain is specified" + } + + $Sid = Request-PiHoleAuth -PiHoleServer $PiHoleServer -Password $Password -IgnoreSsl $IgnoreSsl + + $Groups = Get-PiHoleGroup -PiHoleServer $PiHoleServer -Password $Password -IgnoreSsl $IgnoreSsl + + $Uri = "$($PiHoleServer.OriginalString)/api/domains" + if ($Type) { + $Uri += "/$($Type.ToLower())" + } + if ($Kind) { + $Uri += "/$($Kind.ToLower())" + } + if ($Domain) { + $Uri += "/$([System.Uri]::EscapeDataString($Domain))" + } + + $Params = @{ + Headers = @{sid = $($Sid) } + Uri = $Uri + Method = "Get" + SkipCertificateCheck = $IgnoreSsl + ContentType = "application/json" + } + + $Response = Invoke-RestMethod @Params + + if ($RawOutput) { + Write-Output $Response + } + + else { + $ObjectFinal = foreach ($Item in $Response.domains) { + $GroupNames = [System.Collections.ArrayList]@() + foreach ($Group in $Item.groups) { + $GroupNames += ($Groups | Where-Object { $_.Id -eq $Group }).Name + } + + [PSCustomObject]@{ + Domain = $Item.domain + Unicode = $Item.unicode + Type = $Item.type + Kind = $Item.kind + Comment = $Item.comment + Groups = $GroupNames + Enabled = $Item.enabled + Id = $Item.id + DateAdded = (Convert-PiHoleUnixTimeToLocalTime -UnixTime $Item.date_added).LocalTime + DateModified = (Convert-PiHoleUnixTimeToLocalTime -UnixTime $Item.date_modified).LocalTime + } + } + Write-Output $ObjectFinal + } + } + + catch { + Write-Error -Message $_.Exception.Message + } + + finally { + if ($Sid) { + Remove-PiHoleCurrentAuthSession -PiHoleServer $PiHoleServer -Sid $Sid -IgnoreSsl $IgnoreSsl + } + } +} diff --git a/PiHoleShell/Public/DomainManagement/New-PiHoleDomain.ps1 b/PiHoleShell/Public/DomainManagement/New-PiHoleDomain.ps1 new file mode 100644 index 0000000..6086a51 --- /dev/null +++ b/PiHoleShell/Public/DomainManagement/New-PiHoleDomain.ps1 @@ -0,0 +1,139 @@ +function New-PiHoleDomain { + <# +.SYNOPSIS +Add a new domain + +.DESCRIPTION +Adds a domain to Pi-hole's per-domain allow/deny list (the newer domain-based API, distinct +from the Lists functions which manage whole allow/block list subscriptions). Use -Kind Regex +to add a regular expression instead of an exact domain match. + +.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 Domain +The domain (or regular expression, if -Kind is Regex) to add + +.PARAMETER Type +Whether this is an allowed or denied domain + +.PARAMETER Kind +Whether -Domain is an exact match or a regular expression + +.PARAMETER Comment +An optional comment to store alongside the domain + +.PARAMETER Group +The group(s) this domain applies to. Defaults to "Default" + +.PARAMETER Enabled +Whether the domain is enabled immediately. Defaults to $true + +.PARAMETER IgnoreSsl +Set to $true to skip SSL certificate validation + +.PARAMETER RawOutput +This will dump the response instead of the formatted object + +.EXAMPLE +New-PiHoleDomain -PiHoleServer "http://pihole.domain.com:8080" -Password "your-app-password" -Domain "example.com" -Type Allow -Kind Exact + #> + [CmdletBinding(HelpUri = 'https://ftl.pi-hole.net/master/docs/#post-/domains/-type-/-kind-')] + [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)] + [string]$Domain, + [Parameter(Mandatory = $true)] + [ValidateSet("Allow", "Deny")] + [string]$Type, + [Parameter(Mandatory = $true)] + [ValidateSet("Exact", "Regex")] + [string]$Kind, + [string]$Comment = $null, + [string[]]$Group = "Default", + [bool]$Enabled = $true, + [bool]$IgnoreSsl = $false, + [bool]$RawOutput = $false + ) + try { + $FindMatchingDomain = Get-PiHoleDomain -PiHoleServer $PiHoleServer -Password $Password -IgnoreSsl $IgnoreSsl -Type $Type -Kind $Kind -Domain $Domain + + if ($FindMatchingDomain) { + throw "Domain $Domain ($Type/$Kind) already exists on $PiHoleServer! Please use Update-PiHoleDomain to update it" + } + + $AllGroups = Get-PiHoleGroup -PiHoleServer $PiHoleServer -Password $Password -IgnoreSsl $IgnoreSsl + $AllGroupsNames = @() + $AllGroupsIds = @() + foreach ($GroupItem in $Group) { + $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 + + $Body = @{ + domain = $Domain + comment = $Comment + groups = [Object[]]($AllGroupsIds) + enabled = $Enabled + } + + $Params = @{ + Headers = @{sid = $($Sid) } + Uri = "$($PiHoleServer.OriginalString)/api/domains/$($Type.ToLower())/$($Kind.ToLower())" + Method = "Post" + 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.domains) { + [PSCustomObject]@{ + Domain = $Item.domain + Unicode = $Item.unicode + Type = $Item.type.SubString(0, 1).ToUpper() + $Item.type.SubString(1).ToLower() + Kind = $Item.kind.SubString(0, 1).ToUpper() + $Item.kind.SubString(1).ToLower() + 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 + } + } + Write-Output $ObjectFinal + } + } + + catch { + Write-Error -Message $_.Exception.Message + } + + finally { + if ($Sid) { + Remove-PiHoleCurrentAuthSession -PiHoleServer $PiHoleServer -Sid $Sid -IgnoreSsl $IgnoreSsl + } + } +} diff --git a/PiHoleShell/Public/DomainManagement/Remove-PiHoleDomain.ps1 b/PiHoleShell/Public/DomainManagement/Remove-PiHoleDomain.ps1 new file mode 100644 index 0000000..ce6a5c7 --- /dev/null +++ b/PiHoleShell/Public/DomainManagement/Remove-PiHoleDomain.ps1 @@ -0,0 +1,105 @@ +function Remove-PiHoleDomain { + <# +.SYNOPSIS +Remove a domain + +.DESCRIPTION +Removes a domain from Pi-hole's per-domain allow/deny list. The Pi-hole API deletes domains in +a batch, so this sends a single-item batch containing just the domain you specify. + +.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 Domain +The domain to remove + +.PARAMETER Type +Whether this is an allowed or denied domain + +.PARAMETER Kind +Whether -Domain is an exact match or a regular expression + +.PARAMETER IgnoreSsl +Set to $true to skip SSL certificate validation + +.PARAMETER RawOutput +This will dump the response instead of the formatted object + +.EXAMPLE +Remove-PiHoleDomain -PiHoleServer "http://pihole.domain.com:8080" -Password "your-app-password" -Domain "example.com" -Type Allow -Kind Exact + #> + [CmdletBinding(SupportsShouldProcess = $true, HelpUri = 'https://ftl.pi-hole.net/master/docs/#post-/domains-batchDelete')] + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "Password")] + param ( + [Parameter(Mandatory = $true)] + [System.URI]$PiHoleServer, + [Parameter(Mandatory = $true)] + [string]$Password, + [Parameter(Mandatory = $true)] + [string]$Domain, + [Parameter(Mandatory = $true)] + [ValidateSet("Allow", "Deny")] + [string]$Type, + [Parameter(Mandatory = $true)] + [ValidateSet("Exact", "Regex")] + [string]$Kind, + [bool]$IgnoreSsl = $false, + [bool]$RawOutput = $false + ) + try { + $Target = "Pi-Hole domain $Domain of type $Type/$Kind" + if ($PSCmdlet.ShouldProcess($Target, "Remove domain")) { + $Sid = Request-PiHoleAuth -PiHoleServer $PiHoleServer -Password $Password -IgnoreSsl $IgnoreSsl + + $Body = @( + @{ + item = $Domain + type = $Type.ToLower() + kind = $Kind.ToLower() + } + ) + + #For some reason this needs to be here to make it an array + $Body = , $Body + $Params = @{ + Headers = @{sid = $($Sid) } + Uri = "$($PiHoleServer.OriginalString)/api/domains:batchDelete" + Method = "Post" + SkipCertificateCheck = $IgnoreSsl + Body = $Body | ConvertTo-Json -Depth 10 -Compress + ContentType = "application/json" + } + + $Response = Invoke-RestMethod @Params + + if ($RawOutput) { + Write-Output $Response + } + + else { + # A successful delete returns 204 No Content, so there's no response body to + # build a rich object from. + $Object = [PSCustomObject]@{ + Domain = $Domain + Type = $Type + Kind = $Kind + Status = "Removed" + } + 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/DomainManagement/Update-PiHoleDomain.ps1 b/PiHoleShell/Public/DomainManagement/Update-PiHoleDomain.ps1 new file mode 100644 index 0000000..3e05ded --- /dev/null +++ b/PiHoleShell/Public/DomainManagement/Update-PiHoleDomain.ps1 @@ -0,0 +1,158 @@ +function Update-PiHoleDomain { + <# +.SYNOPSIS +Update a domain + +.DESCRIPTION +Updates an existing domain's Comment, Group(s), and/or Enabled state. The underlying Pi-hole +API replaces the entire domain entry on update, so any property you don't pass here is +preserved by first reading the domain's current value and resending it - nothing is silently +cleared just because you only meant to change one property. + +Note: the Pi-hole API documents moving a domain to a different -Type/-Kind by including those +fields in this same PUT request, but real-world testing against a live server showed this +leaves a stale duplicate at the original type/kind rather than actually moving it. This +function only ever updates Comment, Group, and Enabled in place; to change a domain's Type or +Kind, remove it with Remove-PiHoleDomain and add it again with New-PiHoleDomain. + +.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 Domain +The domain to update + +.PARAMETER Type +Whether this is an allowed or denied domain + +.PARAMETER Kind +Whether -Domain is an exact match or a regular expression + +.PARAMETER Comment +The new comment for the domain. Leave unset to keep the domain's current comment + +.PARAMETER Group +The group(s) this domain should apply to. Leave unset to keep the domain's current group(s) + +.PARAMETER Enabled +Whether the domain should be enabled. Leave unset to keep the domain'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-PiHoleDomain -PiHoleServer "http://pihole.domain.com:8080" -Password "your-app-password" -Domain "example.com" -Type Allow -Kind Exact -Enabled $false + #> + [CmdletBinding(HelpUri = 'https://ftl.pi-hole.net/master/docs/#put-/domains/-type-/-kind-/-domain-')] + [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)] + [string]$Domain, + [Parameter(Mandatory = $true)] + [ValidateSet("Allow", "Deny")] + [string]$Type, + [Parameter(Mandatory = $true)] + [ValidateSet("Exact", "Regex")] + [string]$Kind, + [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 $Domain, you must specify the Comment, Group, and/or Enabled parameter" + } + + $ExistingDomain = Get-PiHoleDomain -PiHoleServer $PiHoleServer -Password $Password -IgnoreSsl $IgnoreSsl -Type $Type -Kind $Kind -Domain $Domain + + if (-not $ExistingDomain) { + throw "Cannot find $Domain of type $Type/$Kind on $PiHoleServer! Please use New-PiHoleDomain to create it" + } + + $AllGroups = Get-PiHoleGroup -PiHoleServer $PiHoleServer -Password $Password -IgnoreSsl $IgnoreSsl + + $GroupNamesToResolve = if ($PSBoundParameters.ContainsKey('Group')) { $Group } else { $ExistingDomain.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 domain entry on update, so any property not explicitly + # passed here is resent using the domain's current value to avoid silently clearing it. + # Type/Kind are always resent as-is - see the .DESCRIPTION note on why this never moves + # a domain to a different Type/Kind. + $Body = @{ + type = $Type.ToLower() + kind = $Kind.ToLower() + comment = if ($PSBoundParameters.ContainsKey('Comment')) { $Comment } else { $ExistingDomain.Comment } + groups = [Object[]]($AllGroupsIds) + enabled = if ($PSBoundParameters.ContainsKey('Enabled')) { $Enabled } else { $ExistingDomain.Enabled } + } + + $Params = @{ + Headers = @{sid = $($Sid) } + Uri = "$($PiHoleServer.OriginalString)/api/domains/$($Type.ToLower())/$($Kind.ToLower())/$([System.Uri]::EscapeDataString($Domain))" + 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.domains) { + [PSCustomObject]@{ + Domain = $Item.domain + Unicode = $Item.unicode + Type = $Item.type.SubString(0, 1).ToUpper() + $Item.type.SubString(1).ToLower() + Kind = $Item.kind.SubString(0, 1).ToUpper() + $Item.kind.SubString(1).ToLower() + 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 + } + } + 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 59c247c..37671ed 100644 --- a/README.md +++ b/README.md @@ -121,6 +121,15 @@ See [docs/EXAMPLES.md](docs/EXAMPLES.md) for real, captured output from every fu | `Search-PiHoleListDomain` | _No description yet_ | | `Update-PiHoleList` | Update a list | +### Domain Management + +| Function | Description | +|---|---| +| `Get-PiHoleDomain` | Get domains | +| `New-PiHoleDomain` | Add a new domain | +| `Remove-PiHoleDomain` | Remove a domain | +| `Update-PiHoleDomain` | Update a domain | + ### Metrics | Function | Description | diff --git a/docs/EXAMPLES.md b/docs/EXAMPLES.md index 69b539a..a0deb07 100644 --- a/docs/EXAMPLES.md +++ b/docs/EXAMPLES.md @@ -100,8 +100,8 @@ Name : PiHoleShellDocsExampleGroup Comment : Example group Enabled : True Id : 4 -DateAdded : 9/26/2026 11:55:24 AM -DateModified : 9/26/2026 11:55:24 AM +DateAdded : 9/27/2026 7:45:33 AM +DateModified : 9/27/2026 7:45:33 AM ``` ### Get-PiHoleGroup @@ -137,8 +137,8 @@ Name : PiHoleShellDocsExampleGroup Comment : Example group Enabled : True Id : 4 -DateAdded : 9/26/2026 11:55:24 AM -DateModified : 9/26/2026 11:55:24 AM +DateAdded : 9/27/2026 7:45:33 AM +DateModified : 9/27/2026 7:45:33 AM ``` ### Update-PiHoleGroup @@ -153,8 +153,8 @@ Name : PiHoleShellDocsExampleGroup Comment : Example group Enabled : False Id : 4 -DateAdded : 9/26/2026 11:55:24 AM -DateModified : 9/26/2026 11:55:56 AM +DateAdded : 9/27/2026 7:45:33 AM +DateModified : 9/27/2026 7:45:51 AM ``` ### Remove-PiHoleGroup @@ -184,8 +184,8 @@ Comment : Example list Groups : {Default} Enabled : True Id : 65 -DateAdded : 9/26/2026 11:57:06 AM -DateModified : 9/26/2026 11:57:06 AM +DateAdded : 9/27/2026 7:46:28 AM +DateModified : 9/27/2026 7:46:28 AM Type : Block DateUpdated : Number : 0 @@ -211,10 +211,10 @@ DateAdded : 7/6/2025 2:20:13 AM DateModified : 7/6/2025 2:20:13 AM Type : block DateUpdated : 7/6/2025 2:20:13 AM -Number : 75945 +Number : 74761 InvalidDomains : 1 AbpEntries : 0 -Status : 2 +Status : 1 Address : https://adaway.org/hosts.txt Comment : @@ -288,8 +288,8 @@ Comment : Example list Groups : {Default} Enabled : False Id : 65 -DateAdded : 9/26/2026 11:57:06 AM -DateModified : 9/26/2026 11:58:08 AM +DateAdded : 9/27/2026 7:46:28 AM +DateModified : 9/27/2026 7:47:01 AM Type : Block DateUpdated : Number : 0 @@ -315,11 +315,11 @@ Type : block Groups : {0} DateAdded : 7/6/2025 2:20:13 AM DateModified : 7/6/2025 2:20:13 AM -DateUpdated : 9/25/2026 3:30:51 PM -Number : 75945 +DateUpdated : 9/26/2026 11:28:08 PM +Number : 74761 InvalidDomains : 1 AbpEntries : 0 -Status : 2 +Status : 1 Domain : doubleclick.net Address : https://adaway.org/hosts.txt @@ -397,6 +397,82 @@ Type : Block Status : Removed ``` +## Domain Management + +### New-PiHoleDomain + +```powershell +New-PiHoleDomain -PiHoleServer $PiHoleServer -Password $Password -Domain "piholeshell-docs-example.com" -Type Allow -Kind Exact -Comment "Example domain" +``` + +``` + +Domain : piholeshell-docs-example.com +Unicode : piholeshell-docs-example.com +Type : Allow +Kind : Exact +Comment : Example domain +Groups : {Default} +Enabled : True +Id : 22 +DateAdded : 9/27/2026 7:47:39 AM +DateModified : 9/27/2026 7:47:39 AM +``` + +### Get-PiHoleDomain + +```powershell +Get-PiHoleDomain -PiHoleServer $PiHoleServer -Password $Password +``` + +``` + +Domain : piholeshell-docs-example.com +Unicode : piholeshell-docs-example.com +Type : allow +Kind : exact +Comment : Example domain +Groups : {Default} +Enabled : True +Id : 22 +DateAdded : 9/27/2026 7:47:39 AM +DateModified : 9/27/2026 7:47:39 AM +``` + +### Update-PiHoleDomain + +```powershell +Update-PiHoleDomain -PiHoleServer $PiHoleServer -Password $Password -Domain "piholeshell-docs-example.com" -Type Allow -Kind Exact -Enabled $false +``` + +``` + +Domain : piholeshell-docs-example.com +Unicode : piholeshell-docs-example.com +Type : Allow +Kind : Exact +Comment : Example domain +Groups : {Default} +Enabled : False +Id : 22 +DateAdded : 9/27/2026 7:47:39 AM +DateModified : 9/27/2026 7:48:12 AM +``` + +### Remove-PiHoleDomain + +```powershell +Remove-PiHoleDomain -PiHoleServer $PiHoleServer -Password $Password -Domain "piholeshell-docs-example.com" -Type Allow -Kind Exact +``` + +``` + +Domain : piholeshell-docs-example.com +Type : Allow +Kind : Exact +Status : Removed +``` + ## Metrics ### Get-PiHoleStatsSummary @@ -407,18 +483,18 @@ Get-PiHoleStatsSummary -PiHoleServer $PiHoleServer -Password $Password ``` -Total : 0 -Blocked : 0 -PercentBlocked : 0 -UniqueDomains : 0 -Forwarded : 0 -Cached : 0 +Total : 281 +Blocked : 84 +PercentBlocked : 29.893238067627 +UniqueDomains : 21 +Forwarded : 37 +Cached : 160 Frequency : 0 -Types : @{A=0; AAAA=0; ANY=0; SRV=0; SOA=0; PTR=0; TXT=0; NAPTR=0; MX=0; DS=0; RRSIG=0; DNSKEY=0; NS=0; SVCB=0; HTTPS=0; OTHER=0} -Status : @{Unknown=0; Gravity=0; Forwarded=0; Cache=0; Regex=0; DenyList=0; ExternalBlockedIp=0; ExternalBlockedNull=0; ExternalBlockedNxra=0; GravityCname=0; RegexCname=0; DenyListCname=0; Retired=0; RetiredDnssec=0; InProgress=0; Dbbusy=0; SpecialDomain=0; CacheStale=0; ExternalBlockedEde15=0} -Replies : @{Unknown=0; Nodata=0; Nxdomain=0; Cname=0; Ip=0; Domain=0; Rrname=0; ServFail=0; Refused=0; Notimp=0; Other=0; Dnssec=0; None=0; Blob=0} -Clients : @{Active=0; Total=0} -Gravity : @{DomainsBeingBlocked=500421; LastUpdate=1790441671} +Types : @{A=130; AAAA=130; ANY=0; SRV=0; SOA=0; PTR=21; TXT=0; NAPTR=0; MX=0; DS=0; RRSIG=0; DNSKEY=0; NS=0; SVCB=0; HTTPS=0; OTHER=0} +Status : @{Unknown=0; Gravity=84; Forwarded=37; Cache=139; Regex=0; DenyList=0; ExternalBlockedIp=0; ExternalBlockedNull=0; ExternalBlockedNxra=0; GravityCname=0; RegexCname=0; DenyListCname=0; Retired=0; RetiredDnssec=0; InProgress=0; Dbbusy=0; SpecialDomain=0; CacheStale=21; ExternalBlockedEde15=0} +Replies : @{Unknown=12; Nodata=17; Nxdomain=3; Cname=0; Ip=231; Domain=18; Rrname=0; ServFail=0; Refused=0; Notimp=0; Other=0; Dnssec=0; None=0; Blob=0} +Clients : @{Active=2; Total=2} +Gravity : @{DomainsBeingBlocked=499226; LastUpdate=1790483337} ``` ### Get-PiHoleStatsRecentBlocked @@ -428,7 +504,8 @@ Get-PiHoleStatsRecentBlocked -PiHoleServer $PiHoleServer -Password $Password ``` ``` -(no output) + +Blocked : googleadservices.com ``` ### Get-PiHoleStatsQueryType @@ -440,10 +517,10 @@ Get-PiHoleStatsQueryType -PiHoleServer $PiHoleServer -Password $Password ``` Type : A -Count : 0 +Count : 130 Type : AAAA -Count : 0 +Count : 130 Type : ANY Count : 0 @@ -464,7 +541,23 @@ Get-PiHoleStatsTopDomain -PiHoleServer $PiHoleServer -Password $Password ``` ``` -(no output) + +Domain : wikipedia.org +Count : 20 + +Domain : bbc.com +Count : 18 + +Domain : python.org +Count : 18 + +Domain : example.com +Count : 18 + +Domain : mozilla.org +Count : 16 + +_(showing 5 of 10 results)_ ``` ### Get-PiHoleStatsTopClient @@ -474,7 +567,14 @@ Get-PiHoleStatsTopClient -PiHoleServer $PiHoleServer -Password $Password ``` ``` -(no output) + +IP : 192.168.1.162 +Name : +Count : 260 + +IP : 127.0.0.1 +Name : localhost +Count : 21 ``` ### Get-PiHoleStatsUpstream @@ -485,9 +585,9 @@ Get-PiHoleStatsUpstream -PiHoleServer $PiHoleServer -Password $Password ``` -TotalQueries : 0 -ForwardedQueries : 0 -Upstreams : {@{Ip=blocklist; Name=blocklist; Port=-1; Count=0; ResponseTime=0; Variance=0}, @{Ip=cache; Name=cache; Port=-1; Count=0; ResponseTime=0; Variance=0}} +TotalQueries : 281 +ForwardedQueries : 37 +Upstreams : {@{Ip=blocklist; Name=blocklist; Port=-1; Count=84; ResponseTime=0; Variance=0}, @{Ip=cache; Name=cache; Port=-1; Count=160; ResponseTime=0; Variance=0}, @{Ip=8.8.8.8; Name=dns.google; Port=53; Count=37; ResponseTime=0.024111141761144; Variance=0.00213313327521311}} ``` ### Get-PiHoleStatsQuerySuggestions @@ -498,10 +598,10 @@ Get-PiHoleStatsQuerySuggestions -PiHoleServer $PiHoleServer -Password $Password ``` -Domain : {} -ClientIp : {} -ClientName : {} -Upstream : {blocklist, cache, permitted} +Domain : {wikipedia.org, bbc.com, python.org, example.com…} +ClientIp : {192.168.1.162, 127.0.0.1} +ClientName : {localhost} +Upstream : {blocklist, cache, 8.8.8.8#53 (dns.google), permitted} Type : {A, AAAA, ANY, SRV…} Status : {UNKNOWN, GRAVITY, FORWARDED, CACHE…} Reply : {UNKNOWN, NODATA, NXDOMAIN, CNAME…} @@ -518,10 +618,10 @@ Get-PiHoleStatsDatabaseSummary -PiHoleServer $PiHoleServer -Password $Password ``` -SumQueries : 0 +SumQueries : 8 SumBlocked : 0 PercentBlocked : 0 -TotalClients : 0 +TotalClients : 1 ``` ### Get-PiHoleStatsDatabaseQueryType @@ -547,7 +647,7 @@ Type : SRV Count : 0 Type : SOA -Count : 0 +Count : 8 _(showing 5 of 16 results)_ ``` @@ -561,7 +661,9 @@ Get-PiHoleStatsDatabaseTopDomain -PiHoleServer $PiHoleServer -Password $Password ``` ``` -(no output) + +Domain : 1.0.0.127.in-addr.arpa +Count : 8 ``` ### Get-PiHoleStatsDatabaseTopClient @@ -573,7 +675,10 @@ Get-PiHoleStatsDatabaseTopClient -PiHoleServer $PiHoleServer -Password $Password ``` ``` -(no output) + +IP : 127.0.0.1 +Name : localhost +Count : 8 ``` ### Get-PiHoleStatsDatabaseUpstream @@ -586,9 +691,9 @@ Get-PiHoleStatsDatabaseUpstream -PiHoleServer $PiHoleServer -Password $Password ``` -TotalQueries : 0 +TotalQueries : 8 ForwardedQueries : 0 -Upstreams : {@{Ip=cache; Name=cache; Port=-1; Count=0; ResponseTime=0; Variance=0}, @{Ip=blocklist; Name=blocklist; Port=-1; Count=0; ResponseTime=0; Variance=0}} +Upstreams : {@{Ip=cache; Name=cache; Port=-1; Count=8; ResponseTime=0; Variance=0}, @{Ip=blocklist; Name=blocklist; Port=-1; Count=0; ResponseTime=0; Variance=0}} ``` ## Configuration & Diagnostics @@ -622,22 +727,22 @@ Get-PiHolePadd -PiHoleServer $PiHoleServer -Password $Password CpuPercent : 0 MemoryPercent : 0 -ActiveClients : 0 +ActiveClients : 2 Blocking : enabled -Cache : @{Size=10000; Inserted=0; Evicted=0} +Cache : @{Size=10000; Inserted=96; Evicted=0} Config : @{DhcpActive=False; DhcpStart=; DhcpEnd=; DhcpIpv6=False; DnsDnssec=False; DnsDomain=lan; DnsNumUpstreams=2; DnsPort=53; DnsrevServerAactive=False; PrivacyLevel=0} -GravitySize : 500421 +GravitySize : 499226 HostModel : Raspberry Pi Zero W Rev 1.1 IFace : @{v4=; v6=} NodeName : dns3.localdomain Pid : 389 -Queries : @{Total=0; Blocked=0; PercentBlocked=0; QueryFrequency=0} -RecentBlocked : -Sensors : @{CpuTemp=40.622; HotLimit=60; Unit=C} -System : @{Uptime=558344; Memory=; Procs=75; Cpu=; Ftl=} -TopBlocked : -TopClient : -TopDomain : +Queries : @{Total=281; Blocked=84; PercentBlocked=29.893238067627; QueryFrequency=0} +RecentBlocked : googleadservices.com +Sensors : @{CpuTemp=36.318; HotLimit=60; Unit=C} +System : @{Uptime=629874; Memory=; Procs=70; Cpu=; Ftl=} +TopBlocked : googleadservices.com +TopClient : 192.168.1.162 +TopDomain : wikipedia.org Version : @{Core=; Web=; Ftl=; Docker=} ``` @@ -674,11 +779,11 @@ Get-PiHoleInfoSystem -PiHoleServer $PiHoleServer -Password $Password ``` -Uptime : 558364 +Uptime : 629884 Memory : @{Ram=; Swap=} -Procs : 74 -Cpu : @{NumProcessors=1; PercentCpu=100.800003051758; Load=} -Ftl : @{PercentMemory=2.29744291305542; PercentCpu=49.7000007629395} +Procs : 70 +Cpu : @{NumProcessors=1; PercentCpu=96.8000030517578; Load=} +Ftl : @{PercentMemory=2.40430068969727; PercentCpu=96.3000030517578} ``` ### Get-PiHoleInfoFtl @@ -689,16 +794,16 @@ Get-PiHoleInfoFtl -PiHoleServer $PiHoleServer -Password $Password ``` -Database : @{Gravity=500421; Antigravity=0; Groups=3; Lists=14; Clients=0; Domains=; Regex=} +Database : @{Gravity=499226; Antigravity=0; Groups=3; Lists=14; Clients=0; Domains=; Regex=} PrivacyLevel : 0 QueryFrequency : 0 -Clients : @{Total=0; Active=0} +Clients : @{Total=2; Active=2} Pid : 389 -Uptime : 160835.739884 -PercentMemory : 2.29415488243103 -PercentCpu : 49.7999992370605 +Uptime : 51790377.711423 +PercentMemory : 2.40430068969727 +PercentCpu : 96.3000030517578 AllowDestructive : True -Dnsmasq : @{DnsCacheInserted=0; DnsCacheLiveFreed=0; DnsQueriesForwarded=0; DnsAuthAnswered=0; DnsLocalAnswered=0; DnsStaleAnswered=0; DnsUnanswered=0; DnssecMaxCryptoUse=0; DnssecMaxSigFail=0; DnssecMaxWork=0; Bootp=0; Pxe=0; DhcpAck=0; DhcpDecline=0; DhcpDiscover=0; DhcpInform=0; DhcpNak=0; DhcpOffer=0; DhcpRelease=0; DhcpRequest=0; Noanswer=0; LeasesAllocated4=0; LeasesPruned4=0; LeasesAllocated6=0; LeasesPruned6=0; TcpConnections=0; DhcpLeasequery=0; DhcpLeaseUnassigned=0; DhcpLeaseActve=0; DhcpLeaseUnknown=0} +Dnsmasq : @{DnsCacheInserted=96; DnsCacheLiveFreed=0; DnsQueriesForwarded=58; DnsAuthAnswered=0; DnsLocalAnswered=223; DnsStaleAnswered=21; DnsUnanswered=0; DnssecMaxCryptoUse=0; DnssecMaxSigFail=0; DnssecMaxWork=0; Bootp=0; Pxe=0; DhcpAck=0; DhcpDecline=0; DhcpDiscover=0; DhcpInform=0; DhcpNak=0; DhcpOffer=0; DhcpRelease=0; DhcpRequest=0; Noanswer=0; LeasesAllocated4=0; LeasesPruned4=0; LeasesAllocated6=0; LeasesPruned6=0; TcpConnections=0; DhcpLeasequery=0; DhcpLeaseUnassigned=0; DhcpLeaseActve=0; DhcpLeaseUnknown=0} ``` ### Get-PiHoleInfoSensors @@ -710,7 +815,7 @@ Get-PiHoleInfoSensors -PiHoleServer $PiHoleServer -Password $Password ``` List : {@{Name=cpu_thermal; Path=hwmon0; Source=devices/virtual/thermal/thermal_zone0; Temps=}, @{Name=rpi_volt; Path=hwmon1; Source=devices/platform/soc/soc:firmware/raspberrypi-hwmon; Temps=}} -CpuTemp : 40.084 +CpuTemp : 36.856 HotLimit : 60 Unit : C ``` @@ -727,12 +832,12 @@ Size : 13983744 Type : Regular file Mode : rw-r----- AccessTime : 6/25/2025 12:32:44 AM -ModifiedTime : 9/26/2026 11:50:35 AM -ChangeTime : 9/26/2026 11:50:35 AM +ModifiedTime : 9/27/2026 4:04:00 AM +ChangeTime : 9/27/2026 4:04:00 AM Owner : @{User=; Group=} -Queries : 0 -EarliestTimestamp : 9/25/2026 11:50:36 AM -QueriesDisk : 0 +Queries : 281 +EarliestTimestamp : 9/26/2026 7:40:00 AM +QueriesDisk : 281 EarliestTimestampDisk : SqliteVersion : 3.53.1 ``` @@ -800,12 +905,7 @@ Get-PiHoleInfoMessage -PiHoleServer $PiHoleServer -Password $Password ``` ``` - -Id : 1 -Timestamp : 9/26/2026 11:50:37 AM -Type : LOAD -Plain : Long-term load (15min avg) larger than number of processors: 1.6 > 1 -Html : Long-term load (15min avg) larger than number of processors: 1.6 > 1
This may slow down DNS resolution and can cause bottlenecks. +(no output) ``` ### Get-PiHoleInfoMessageCount @@ -816,7 +916,7 @@ Get-PiHoleInfoMessageCount -PiHoleServer $PiHoleServer -Password $Password ``` -Count : 1 +Count : 0 ``` ### Remove-PiHoleInfoMessage @@ -840,7 +940,7 @@ Get-PiHoleLogWebserver -PiHoleServer $PiHoleServer -Password $Password ``` -Log : @{Timestamp=9/26/2026 11:50:37 AM; Message=Initializing HTTP server on ports "8089,8489s"; Priority=} +Log : @{Timestamp=9/26/2026 5:21:18 PM; Message=Initializing HTTP server on ports "8089,8489s"; Priority=} NextID : 1 Pid : 389 File : /var/log/pihole/webserver.log @@ -870,31 +970,31 @@ Get-PiHoleHistory -PiHoleServer $PiHoleServer -Password $Password ``` -Timestamp : 9/25/2026 12:05:00 PM +Timestamp : 9/26/2026 7:55:00 AM Total : 0 Cached : 0 Blocked : 0 Forwarded : 0 -Timestamp : 9/25/2026 12:15:00 PM +Timestamp : 9/26/2026 8:05:00 AM Total : 0 Cached : 0 Blocked : 0 Forwarded : 0 -Timestamp : 9/25/2026 12:25:00 PM +Timestamp : 9/26/2026 8:15:00 AM Total : 0 Cached : 0 Blocked : 0 Forwarded : 0 -Timestamp : 9/25/2026 12:35:00 PM +Timestamp : 9/26/2026 8:25:00 AM Total : 0 Cached : 0 Blocked : 0 Forwarded : 0 -Timestamp : 9/25/2026 12:45:00 PM +Timestamp : 9/26/2026 8:35:00 AM Total : 0 Cached : 0 Blocked : 0 @@ -911,20 +1011,20 @@ Get-PiHoleHistoryClient -PiHoleServer $PiHoleServer -Password $Password ``` -Timestamp : 9/25/2026 12:05:00 PM -Clients : @{IP=others; Name=; Count=0} +Timestamp : 9/26/2026 7:55:00 AM +Clients : {@{IP=192.168.1.162; Name=; Count=0}, @{IP=others; Name=; Count=0}} -Timestamp : 9/25/2026 12:15:00 PM -Clients : @{IP=others; Name=; Count=0} +Timestamp : 9/26/2026 8:05:00 AM +Clients : {@{IP=192.168.1.162; Name=; Count=0}, @{IP=others; Name=; Count=0}} -Timestamp : 9/25/2026 12:25:00 PM -Clients : @{IP=others; Name=; Count=0} +Timestamp : 9/26/2026 8:15:00 AM +Clients : {@{IP=192.168.1.162; Name=; Count=0}, @{IP=others; Name=; Count=0}} -Timestamp : 9/25/2026 12:35:00 PM -Clients : @{IP=others; Name=; Count=0} +Timestamp : 9/26/2026 8:25:00 AM +Clients : {@{IP=192.168.1.162; Name=; Count=0}, @{IP=others; Name=; Count=0}} -Timestamp : 9/25/2026 12:45:00 PM -Clients : @{IP=others; Name=; Count=0} +Timestamp : 9/26/2026 8:35:00 AM +Clients : {@{IP=192.168.1.162; Name=; Count=0}, @{IP=others; Name=; Count=0}} _(showing 5 of 145 results)_ ``` @@ -938,7 +1038,38 @@ Get-PiHoleHistoryDatabase -PiHoleServer $PiHoleServer -Password $Password ``` ``` -(no output) + +Timestamp : 9/27/2026 12:00:00 AM +Total : 1 +Cached : 1 +Blocked : 0 +Forwarded : 0 + +Timestamp : 9/27/2026 1:00:00 AM +Total : 1 +Cached : 1 +Blocked : 0 +Forwarded : 0 + +Timestamp : 9/27/2026 2:00:00 AM +Total : 1 +Cached : 1 +Blocked : 0 +Forwarded : 0 + +Timestamp : 9/27/2026 3:00:00 AM +Total : 1 +Cached : 1 +Blocked : 0 +Forwarded : 0 + +Timestamp : 9/27/2026 4:00:00 AM +Total : 1 +Cached : 1 +Blocked : 0 +Forwarded : 0 + +_(showing 5 of 8 results)_ ``` ### Get-PiHoleHistoryDatabaseClient @@ -950,7 +1081,23 @@ Get-PiHoleHistoryDatabaseClient -PiHoleServer $PiHoleServer -Password $Password ``` ``` -(no output) + +Timestamp : 9/27/2026 7:00:00 AM +Clients : @{ClientId=3; Count=1} + +Timestamp : 9/27/2026 6:00:00 AM +Clients : @{ClientId=3; Count=1} + +Timestamp : 9/27/2026 5:00:00 AM +Clients : @{ClientId=3; Count=1} + +Timestamp : 9/27/2026 4:00:00 AM +Clients : @{ClientId=3; Count=1} + +Timestamp : 9/27/2026 3:00:00 AM +Clients : @{ClientId=3; Count=1} + +_(showing 5 of 8 results)_ ``` ## Authentication @@ -964,55 +1111,13 @@ Get-PiHoleCurrentAuthSession -PiHoleServer $PiHoleServer -Password $Password ``` Id : 0 -CurrentSession : False -Valid : True -TlsLogin : True -TlsMixed : False -LoginAt : 9/26/2026 11:26:28 AM -LastActive : 9/26/2026 11:26:28 AM -ValidUntil : 9/26/2026 11:56:28 AM -RemoteAddress : 192.168.1.162 -UserAgent : Mozilla/5.0 (Windows NT 10.0; Microsoft Windows 10.0.26200; en-US) PowerShell/7.6.6 -XForwardedFor : -App : True -Cli : False - -Id : 1 -CurrentSession : False -Valid : True -TlsLogin : True -TlsMixed : False -LoginAt : 9/26/2026 11:50:14 AM -LastActive : 9/26/2026 11:50:23 AM -ValidUntil : 9/26/2026 12:20:23 PM -RemoteAddress : 192.168.1.162 -UserAgent : Mozilla/5.0 (Windows NT 10.0; Microsoft Windows 10.0.26200; en-US) PowerShell/7.6.6 -XForwardedFor : -App : True -Cli : False - -Id : 2 -CurrentSession : False -Valid : True -TlsLogin : True -TlsMixed : False -LoginAt : 9/26/2026 11:50:38 AM -LastActive : 9/26/2026 11:50:42 AM -ValidUntil : 9/26/2026 12:20:42 PM -RemoteAddress : 192.168.1.162 -UserAgent : Mozilla/5.0 (Windows NT 10.0; Microsoft Windows 10.0.26200; en-US) PowerShell/7.6.6 -XForwardedFor : -App : True -Cli : False - -Id : 3 CurrentSession : True Valid : True TlsLogin : True TlsMixed : False -LoginAt : 9/26/2026 11:51:30 AM -LastActive : 9/26/2026 11:51:38 AM -ValidUntil : 9/26/2026 12:21:38 PM +LoginAt : 9/27/2026 7:43:32 AM +LastActive : 9/27/2026 7:43:36 AM +ValidUntil : 9/27/2026 8:13:36 AM RemoteAddress : 192.168.1.162 UserAgent : Mozilla/5.0 (Windows NT 10.0; Microsoft Windows 10.0.26200; en-US) PowerShell/7.6.6 XForwardedFor : @@ -1025,13 +1130,13 @@ Cli : False _Deletes a session by its ID (as shown by Get-PiHoleCurrentAuthSession), not the caller's own session._ ```powershell -Remove-PiHoleAuthSession -PiHoleServer $PiHoleServer -Password $Password -Id 3 +Remove-PiHoleAuthSession -PiHoleServer $PiHoleServer -Password $Password -Id 0 ``` ``` -Id : 3 +Id : 0 Status : Removed ``` diff --git a/tests/DomainManagement/Get-PiHoleDomain.Integration.Tests.ps1 b/tests/DomainManagement/Get-PiHoleDomain.Integration.Tests.ps1 new file mode 100644 index 0000000..3699691 --- /dev/null +++ b/tests/DomainManagement/Get-PiHoleDomain.Integration.Tests.ps1 @@ -0,0 +1,70 @@ +# 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-PiHoleDomain (Integration)' -Tag 'Integration' { + BeforeAll { + Import-Module .\PiHoleShell\PiHoleShell.psm1 -Force + + $script:TestDomain = 'piholeshell-test-domain.example.com' + + $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 domain behind + Remove-PiHoleDomain -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl -Domain $script:TestDomain -Type Allow -Kind Exact -Confirm:$false -ErrorAction SilentlyContinue -WarningAction SilentlyContinue | Out-Null + + New-PiHoleDomain -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl -Domain $script:TestDomain -Type Allow -Kind Exact -Comment 'Pester integration test domain' | Out-Null + } + } + + AfterAll { + if ($script:PiHoleServer) { + Remove-PiHoleDomain -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl -Domain $script:TestDomain -Type Allow -Kind Exact -Confirm:$false -ErrorAction SilentlyContinue | Out-Null + } + } + + It 'returns domains as formatted objects' -Skip:(-not $script:ConfigAvailable) { + $result = Get-PiHoleDomain -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 'filters by Type, Kind, and Domain' -Skip:(-not $script:ConfigAvailable) { + $result = Get-PiHoleDomain -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl -Type Allow -Kind Exact -Domain $script:TestDomain + $result | Format-List | Out-String | Write-Host + + $result | Should -Not -BeNullOrEmpty + $result.Domain | Should -Be $script:TestDomain + $result.Type | Should -Be 'allow' + $result.Kind | Should -Be 'exact' + } + + It 'errors when -Domain is specified without -Type and -Kind' -Skip:(-not $script:ConfigAvailable) { + $result = Get-PiHoleDomain -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl -Domain $script:TestDomain -ErrorVariable errOut -ErrorAction SilentlyContinue + + $errOut | Should -Not -BeNullOrEmpty + } + + It 'returns the raw API response when RawOutput is set' -Skip:(-not $script:ConfigAvailable) { + $result = Get-PiHoleDomain -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-PiHoleDomain -PiHoleServer $script:PiHoleServer -Password 'definitely-not-the-real-token' -IgnoreSsl $script:PiHoleIgnoreSsl -ErrorVariable errOut -ErrorAction SilentlyContinue + + $errOut | Should -Not -BeNullOrEmpty + } +} diff --git a/tests/DomainManagement/New-PiHoleDomain.Integration.Tests.ps1 b/tests/DomainManagement/New-PiHoleDomain.Integration.Tests.ps1 new file mode 100644 index 0000000..a48a4a5 --- /dev/null +++ b/tests/DomainManagement/New-PiHoleDomain.Integration.Tests.ps1 @@ -0,0 +1,76 @@ +# 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 'New-PiHoleDomain (Integration)' -Tag 'Integration' { + BeforeAll { + Import-Module .\PiHoleShell\PiHoleShell.psm1 -Force + + $script:TestDomain = 'piholeshell-test-domain.example.com' + + $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 domain behind + Remove-PiHoleDomain -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl -Domain $script:TestDomain -Type Allow -Kind Exact -Confirm:$false -ErrorAction SilentlyContinue -WarningAction SilentlyContinue | Out-Null + } + } + + AfterAll { + if ($script:PiHoleServer) { + # Ensures the test domain is never left behind for other test files to trip over + Remove-PiHoleDomain -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl -Domain $script:TestDomain -Type Allow -Kind Exact -Confirm:$false -ErrorAction SilentlyContinue | Out-Null + } + } + + It 'adds a new domain and returns a formatted object' -Skip:(-not $script:ConfigAvailable) { + $result = New-PiHoleDomain -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl -Domain $script:TestDomain -Type Allow -Kind Exact -Comment 'Pester integration test domain' + $result | Format-List | Out-String | Write-Host + + $result | Should -Not -BeNullOrEmpty + $result.Domain | Should -Be $script:TestDomain + $result.Type | Should -Be 'Allow' + $result.Kind | Should -Be 'Exact' + $result.Enabled | Should -BeTrue + $result.Groups | Should -Contain 'Default' + + # Clean up immediately so the next test starts from a known (domain-absent) state + Remove-PiHoleDomain -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl -Domain $script:TestDomain -Type Allow -Kind Exact -Confirm:$false | Out-Null + } + + It 'returns the raw API response when RawOutput is set' -Skip:(-not $script:ConfigAvailable) { + $result = New-PiHoleDomain -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl -Domain $script:TestDomain -Type Allow -Kind Exact -RawOutput $true + $result | Format-List | Out-String | Write-Host + + $result | Should -Not -BeNullOrEmpty + $result.domains[0].domain | Should -Be $script:TestDomain + + Remove-PiHoleDomain -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl -Domain $script:TestDomain -Type Allow -Kind Exact -Confirm:$false | Out-Null + } + + It 'errors when the domain already exists' -Skip:(-not $script:ConfigAvailable) { + New-PiHoleDomain -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl -Domain $script:TestDomain -Type Allow -Kind Exact | Out-Null + + $result = New-PiHoleDomain -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl -Domain $script:TestDomain -Type Allow -Kind Exact -ErrorVariable errOut -ErrorAction SilentlyContinue + Write-Host "Error ($($errOut.Count) entries, showing last): [$($errOut[-1])]" + + $errOut | Should -Not -BeNullOrEmpty + + Remove-PiHoleDomain -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl -Domain $script:TestDomain -Type Allow -Kind Exact -Confirm:$false | Out-Null + } + + It 'errors when given a bad password' -Skip:(-not $script:ConfigAvailable) { + $result = New-PiHoleDomain -PiHoleServer $script:PiHoleServer -Password 'definitely-not-the-real-token' -IgnoreSsl $script:PiHoleIgnoreSsl -Domain $script:TestDomain -Type Allow -Kind Exact -ErrorVariable errOut -ErrorAction SilentlyContinue + Write-Host "Error ($($errOut.Count) entries, showing last): [$($errOut[-1])]" + + $errOut | Should -Not -BeNullOrEmpty + } +} diff --git a/tests/DomainManagement/Remove-PiHoleDomain.Integration.Tests.ps1 b/tests/DomainManagement/Remove-PiHoleDomain.Integration.Tests.ps1 new file mode 100644 index 0000000..310515e --- /dev/null +++ b/tests/DomainManagement/Remove-PiHoleDomain.Integration.Tests.ps1 @@ -0,0 +1,67 @@ +# 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 'Remove-PiHoleDomain (Integration)' -Tag 'Integration' { + BeforeAll { + Import-Module .\PiHoleShell\PiHoleShell.psm1 -Force + + $script:TestDomain = 'piholeshell-test-domain.example.com' + + $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 domain behind + Remove-PiHoleDomain -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl -Domain $script:TestDomain -Type Allow -Kind Exact -Confirm:$false -ErrorAction SilentlyContinue -WarningAction SilentlyContinue | Out-Null + } + } + + AfterAll { + if ($script:PiHoleServer) { + # Defensive cleanup in case a test left the domain behind + Remove-PiHoleDomain -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl -Domain $script:TestDomain -Type Allow -Kind Exact -Confirm:$false -ErrorAction SilentlyContinue | Out-Null + } + } + + It 'removes an existing domain and returns a formatted object' -Skip:(-not $script:ConfigAvailable) { + New-PiHoleDomain -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl -Domain $script:TestDomain -Type Allow -Kind Exact -Comment 'Pester integration test domain' | Out-Null + + $result = Remove-PiHoleDomain -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl -Domain $script:TestDomain -Type Allow -Kind Exact -Confirm:$false + $result | Format-List | Out-String | Write-Host + + $result | Should -Not -BeNullOrEmpty + $result.Domain | Should -Be $script:TestDomain + $result.Status | Should -Be 'Removed' + + $remaining = Get-PiHoleDomain -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl -Type Allow -Kind Exact -Domain $script:TestDomain + $remaining | Should -BeNullOrEmpty + } + + It 'removes the domain when RawOutput is set, even though the API returns no body' -Skip:(-not $script:ConfigAvailable) { + New-PiHoleDomain -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl -Domain $script:TestDomain -Type Allow -Kind Exact | Out-Null + + # A successful delete is HTTP 204 No Content, so RawOutput is expected to be empty here - + # the domain actually being gone afterward is the real signal of success. + Remove-PiHoleDomain -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl -Domain $script:TestDomain -Type Allow -Kind Exact -RawOutput $true -Confirm:$false + + $remaining = Get-PiHoleDomain -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl -Type Allow -Kind Exact -Domain $script:TestDomain + $remaining | Should -BeNullOrEmpty + } + + It 'errors when given a bad password' -Skip:(-not $script:ConfigAvailable) { + New-PiHoleDomain -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl -Domain $script:TestDomain -Type Allow -Kind Exact | Out-Null + + $result = Remove-PiHoleDomain -PiHoleServer $script:PiHoleServer -Password 'definitely-not-the-real-token' -IgnoreSsl $script:PiHoleIgnoreSsl -Domain $script:TestDomain -Type Allow -Kind Exact -Confirm:$false -ErrorVariable errOut -ErrorAction SilentlyContinue + Write-Host "Error ($($errOut.Count) entries, showing last): [$($errOut[-1])]" + + $errOut | Should -Not -BeNullOrEmpty + } +} diff --git a/tests/DomainManagement/Update-PiHoleDomain.Integration.Tests.ps1 b/tests/DomainManagement/Update-PiHoleDomain.Integration.Tests.ps1 new file mode 100644 index 0000000..4bac383 --- /dev/null +++ b/tests/DomainManagement/Update-PiHoleDomain.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-PiHoleDomain (Integration)' -Tag 'Integration' { + BeforeAll { + Import-Module .\PiHoleShell\PiHoleShell.psm1 -Force + + $script:TestDomain = 'piholeshell-test-domain.example.com' + + $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 domain behind + Remove-PiHoleDomain -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl -Domain $script:TestDomain -Type Allow -Kind Exact -Confirm:$false -ErrorAction SilentlyContinue -WarningAction SilentlyContinue | Out-Null + } + } + + AfterAll { + if ($script:PiHoleServer) { + Remove-PiHoleDomain -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl -Domain $script:TestDomain -Type Allow -Kind Exact -Confirm:$false -ErrorAction SilentlyContinue | Out-Null + } + } + + It 'updates only the comment, preserving Enabled and Group' -Skip:(-not $script:ConfigAvailable) { + New-PiHoleDomain -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl -Domain $script:TestDomain -Type Allow -Kind Exact -Comment 'original comment' -Enabled $true | Out-Null + + $result = Update-PiHoleDomain -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl -Domain $script:TestDomain -Type Allow -Kind Exact -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-PiHoleDomain -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl -Domain $script:TestDomain -Type Allow -Kind Exact -Confirm:$false | Out-Null + } + + It 'updates only Enabled, preserving the current comment' -Skip:(-not $script:ConfigAvailable) { + New-PiHoleDomain -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl -Domain $script:TestDomain -Type Allow -Kind Exact -Comment 'keep this comment' -Enabled $true | Out-Null + + $result = Update-PiHoleDomain -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl -Domain $script:TestDomain -Type Allow -Kind Exact -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-PiHoleDomain -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl -Domain $script:TestDomain -Type Allow -Kind Exact -Confirm:$false | Out-Null + } + + It 'returns the raw API response when RawOutput is set' -Skip:(-not $script:ConfigAvailable) { + New-PiHoleDomain -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl -Domain $script:TestDomain -Type Allow -Kind Exact | Out-Null + + $result = Update-PiHoleDomain -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl -Domain $script:TestDomain -Type Allow -Kind Exact -Comment 'raw output test' -RawOutput $true + $result | Format-List | Out-String | Write-Host + + $result.domains[0].comment | Should -Be 'raw output test' + + Remove-PiHoleDomain -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl -Domain $script:TestDomain -Type Allow -Kind Exact -Confirm:$false | Out-Null + } + + It 'errors when neither Comment, Group, nor Enabled is specified' -Skip:(-not $script:ConfigAvailable) { + New-PiHoleDomain -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl -Domain $script:TestDomain -Type Allow -Kind Exact | Out-Null + + $result = Update-PiHoleDomain -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl -Domain $script:TestDomain -Type Allow -Kind Exact -ErrorVariable errOut -ErrorAction SilentlyContinue + + $errOut | Should -Not -BeNullOrEmpty + + Remove-PiHoleDomain -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl -Domain $script:TestDomain -Type Allow -Kind Exact -Confirm:$false | Out-Null + } + + It 'errors when the domain does not exist' -Skip:(-not $script:ConfigAvailable) { + $result = Update-PiHoleDomain -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl -Domain 'does-not-exist.example.com' -Type Allow -Kind Exact -Comment 'irrelevant' -ErrorVariable errOut -ErrorAction SilentlyContinue + + $errOut | Should -Not -BeNullOrEmpty + } + + It 'errors when given a bad password' -Skip:(-not $script:ConfigAvailable) { + New-PiHoleDomain -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl -Domain $script:TestDomain -Type Allow -Kind Exact | Out-Null + + $result = Update-PiHoleDomain -PiHoleServer $script:PiHoleServer -Password 'definitely-not-the-real-token' -IgnoreSsl $script:PiHoleIgnoreSsl -Domain $script:TestDomain -Type Allow -Kind Exact -Comment 'irrelevant' -ErrorVariable errOut -ErrorAction SilentlyContinue + + $errOut | Should -Not -BeNullOrEmpty + } +} diff --git a/tools/Update-ExampleOutput.ps1 b/tools/Update-ExampleOutput.ps1 index c72e23c..ad26ca4 100644 --- a/tools/Update-ExampleOutput.ps1 +++ b/tools/Update-ExampleOutput.ps1 @@ -51,6 +51,7 @@ $categoryOrder = [ordered]@{ DnsControl = 'DNS Control' GroupManagement = 'Group Management' ListManagement = 'List Management' + DomainManagement = 'Domain Management' Metrics = 'Metrics' Config = 'Configuration & Diagnostics' Authentication = 'Authentication' @@ -258,6 +259,31 @@ Add-Example -Category ListManagement -FunctionName 'Remove-PiHoleList' ` -Result (Remove-PiHoleList -PiHoleServer $PiHoleServer -Password $PiHoleToken -IgnoreSsl $PiHoleIgnoreSsl -Address $docsListAddress -Type Block -Confirm:$false) #endregion +#region DomainManagement +$docsDomain = 'piholeshell-docs-example.com' +Invoke-Quietly { Remove-PiHoleDomain -PiHoleServer $PiHoleServer -Password $PiHoleToken -IgnoreSsl $PiHoleIgnoreSsl -Domain $docsDomain -Type Allow -Kind Exact -Confirm:$false } + +Add-Example -Category DomainManagement -FunctionName 'New-PiHoleDomain' ` + -Invocation "New-PiHoleDomain -PiHoleServer `$PiHoleServer -Password `$Password -Domain `"$docsDomain`" -Type Allow -Kind Exact -Comment `"Example domain`"" ` + -Result (New-PiHoleDomain -PiHoleServer $PiHoleServer -Password $PiHoleToken -IgnoreSsl $PiHoleIgnoreSsl -Domain $docsDomain -Type Allow -Kind Exact -Comment 'Example domain') + +# Same settle-time reasoning as ListManagement above - give the test server a moment before +# relying on the domain just being created. +Start-Sleep -Seconds 3 + +Add-Example -Category DomainManagement -FunctionName 'Get-PiHoleDomain' ` + -Invocation 'Get-PiHoleDomain -PiHoleServer $PiHoleServer -Password $Password' ` + -Result (Get-PiHoleDomain -PiHoleServer $PiHoleServer -Password $PiHoleToken -IgnoreSsl $PiHoleIgnoreSsl) + +Add-Example -Category DomainManagement -FunctionName 'Update-PiHoleDomain' ` + -Invocation "Update-PiHoleDomain -PiHoleServer `$PiHoleServer -Password `$Password -Domain `"$docsDomain`" -Type Allow -Kind Exact -Enabled `$false" ` + -Result (Update-PiHoleDomain -PiHoleServer $PiHoleServer -Password $PiHoleToken -IgnoreSsl $PiHoleIgnoreSsl -Domain $docsDomain -Type Allow -Kind Exact -Enabled $false) + +Add-Example -Category DomainManagement -FunctionName 'Remove-PiHoleDomain' ` + -Invocation "Remove-PiHoleDomain -PiHoleServer `$PiHoleServer -Password `$Password -Domain `"$docsDomain`" -Type Allow -Kind Exact" ` + -Result (Remove-PiHoleDomain -PiHoleServer $PiHoleServer -Password $PiHoleToken -IgnoreSsl $PiHoleIgnoreSsl -Domain $docsDomain -Type Allow -Kind Exact -Confirm:$false) +#endregion + #region Metrics foreach ($fn in 'Get-PiHoleStatsSummary', 'Get-PiHoleStatsRecentBlocked', 'Get-PiHoleStatsQueryType', 'Get-PiHoleStatsTopDomain', 'Get-PiHoleStatsTopClient', 'Get-PiHoleStatsUpstream', 'Get-PiHoleStatsQuerySuggestions') { Add-Example -Category Metrics -FunctionName $fn ` diff --git a/tools/Update-ReadmeCommandReference.ps1 b/tools/Update-ReadmeCommandReference.ps1 index e49f27c..b31b5c0 100644 --- a/tools/Update-ReadmeCommandReference.ps1 +++ b/tools/Update-ReadmeCommandReference.ps1 @@ -23,6 +23,7 @@ $displayNames = [ordered]@{ DnsControl = 'DNS Control' GroupManagement = 'Group Management' ListManagement = 'List Management' + DomainManagement = 'Domain Management' Metrics = 'Metrics' Config = 'Configuration & Diagnostics' Padd = 'Configuration & Diagnostics' @@ -30,7 +31,7 @@ $displayNames = [ordered]@{ Teleporter = 'Configuration & Diagnostics' Authentication = 'Authentication' } -$categoryOrder = @('Actions', 'DnsControl', 'GroupManagement', 'ListManagement', 'Metrics', 'Config', 'Authentication') +$categoryOrder = @('Actions', 'DnsControl', 'GroupManagement', 'ListManagement', 'DomainManagement', 'Metrics', 'Config', 'Authentication') $categoryIntros = @{ Authentication = 'Session handling is automatic for every command above, but these are available for managing sessions directly:' }