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:'
}