Domeneshop.psm1

[CmdletBinding()]
param()
$baseName = [System.IO.Path]::GetFileNameWithoutExtension($PSCommandPath)
$script:PSModuleInfo = Import-PowerShellDataFile -Path "$PSScriptRoot\$baseName.psd1"
$script:PSModuleInfo | Format-List | Out-String -Stream | ForEach-Object { Write-Debug $_ }
$scriptName = $script:PSModuleInfo.Name
Write-Debug "[$scriptName] - Importing module"

#region [functions] - [private]
Write-Debug "[$scriptName] - [functions] - [private] - Processing folder"
#region [functions] - [private] - [Get-DomeneshopApiBaseUri]
Write-Debug "[$scriptName] - [functions] - [private] - [Get-DomeneshopApiBaseUri] - Importing"
function Get-DomeneshopApiBaseUri {
    <#
        .SYNOPSIS
        Get the API base URI from a resolved Domeneshop context.

        .DESCRIPTION
        Validate and normalize the API base URI stored on a resolved Domeneshop context.

        .EXAMPLE
        Get-DomeneshopApiBaseUri -Context $resolvedContext

        Get the normalized API base URI for a resolved context.

        .INPUTS
        None

        You can't pipe objects to Get-DomeneshopApiBaseUri.

        .OUTPUTS
        System.String

        The normalized Domeneshop API base URI.

        .NOTES
        The caller must resolve and validate the context before invoking this helper.

        .LINK
        https://api.domeneshop.no/docs/
    #>

    [OutputType([string])]
    [CmdletBinding()]
    param(
        # The resolved Domeneshop context containing an API base URI.
        [Parameter(Mandatory)]
        [ValidateNotNull()]
        [object] $Context
    )

    $apiBaseUri = [string] $Context.ApiBaseUri
    if ([string]::IsNullOrWhiteSpace($apiBaseUri)) {
        throw "The Domeneshop context [$($Context.ID)] does not define an API base URI."
    }

    $parsedUri = $null
    if (-not [uri]::TryCreate($apiBaseUri, [System.UriKind]::Absolute, [ref] $parsedUri)) {
        throw "The Domeneshop context [$($Context.ID)] contains an invalid API base URI."
    }

    $parsedUri.AbsoluteUri.TrimEnd('/')
}
Write-Debug "[$scriptName] - [functions] - [private] - [Get-DomeneshopApiBaseUri] - Done"
#endregion [functions] - [private] - [Get-DomeneshopApiBaseUri]
#region [functions] - [private] - [Invoke-DomeneshopApiRequest]
Write-Debug "[$scriptName] - [functions] - [private] - [Invoke-DomeneshopApiRequest] - Importing"
function Invoke-DomeneshopApiRequest {
    <#
        .SYNOPSIS
        Send an authenticated request to the Domeneshop API.

        .DESCRIPTION
        Build an HTTP Basic credential from a resolved Domeneshop context and send a REST request, optionally with a JSON body.

        .EXAMPLE
        Invoke-DomeneshopApiRequest -Method Get -Uri $uri -Context $resolvedContext

        Send an authenticated GET request.

        .EXAMPLE
        Invoke-DomeneshopApiRequest -Method Post -Uri $uri -Context $resolvedContext -Body $body

        Send an authenticated POST request with a JSON body.

        .INPUTS
        None

        You can't pipe objects to Invoke-DomeneshopApiRequest.

        .OUTPUTS
        System.Object

        The response returned by the Domeneshop API.

        .NOTES
        Transport errors are terminating so public callers fail fast.

        .LINK
        https://api.domeneshop.no/docs/
    #>

    [OutputType([object])]
    [CmdletBinding()]
    param(
        # The HTTP method accepted by the Domeneshop API.
        [Parameter(Mandatory)]
        [ValidateSet('Get', 'Post', 'Put', 'Delete')]
        [string] $Method,

        # The absolute Domeneshop API endpoint URI.
        [Parameter(Mandatory)]
        [ValidateNotNull()]
        [uri] $Uri,

        # The resolved Domeneshop context containing valid credentials.
        [Parameter(Mandatory)]
        [ValidateNotNull()]
        [object] $Context,

        # The request payload to serialize as JSON.
        [Parameter()]
        [AllowNull()]
        [object] $Body
    )

    $credential = [pscredential]::new(
        [string] $Context.Token,
        [securestring] $Context.Secret
    )

    $params = @{
        Method         = $Method
        Uri            = $Uri
        Authentication = 'Basic'
        Credential     = $credential
        ErrorAction    = 'Stop'
    }

    if ($PSBoundParameters.ContainsKey('Body')) {
        $params['ContentType'] = 'application/json'
        $params['Body'] = ($Body | ConvertTo-Json -Depth 100)
    }

    Write-Debug "Sending $Method request to [$Uri]."
    Invoke-RestMethod @params
}
Write-Debug "[$scriptName] - [functions] - [private] - [Invoke-DomeneshopApiRequest] - Done"
#endregion [functions] - [private] - [Invoke-DomeneshopApiRequest]
#region [functions] - [private] - [Auth]
Write-Debug "[$scriptName] - [functions] - [private] - [Auth] - Processing folder"
#region [functions] - [private] - [Auth] - [Get-DomeneshopConfig]
Write-Debug "[$scriptName] - [functions] - [private] - [Auth] - [Get-DomeneshopConfig] - Importing"
#Requires -Modules @{ ModuleName = 'Context'; ModuleVersion = '8.1.6' }

function Get-DomeneshopConfig {
    <#
        .SYNOPSIS
        Get the Domeneshop module configuration.

        .DESCRIPTION
        Read the module configuration from the Context vault or return an in-memory default when no configuration has been stored.

        .EXAMPLE
        Get-DomeneshopConfig

        Get the current Domeneshop module configuration.

        .INPUTS
        None

        You can't pipe objects to Get-DomeneshopConfig.

        .OUTPUTS
        System.Object

        The stored or default Domeneshop module configuration.

        .NOTES
        This read helper does not create or update Context vault entries.

        .LINK
        https://psmodule.io/Domeneshop/Functions/Auth/Get-DomeneshopContext

        .LINK
        https://psmodule.io/Context/
    #>

    [OutputType([object])]
    [CmdletBinding()]
    param()

    $vault = 'Domeneshop'
    $id = '__Domeneshop.Config'
    $config = Get-Context -ID $id -Vault $vault

    if (-not $config) {
        $config = [pscustomobject][ordered]@{
            DefaultContext = $null
            ApiBaseUri     = 'https://api.domeneshop.no/v0'
        }
    }

    $config
}
Write-Debug "[$scriptName] - [functions] - [private] - [Auth] - [Get-DomeneshopConfig] - Done"
#endregion [functions] - [private] - [Auth] - [Get-DomeneshopConfig]
#region [functions] - [private] - [Auth] - [Resolve-DomeneshopContext]
Write-Debug "[$scriptName] - [functions] - [private] - [Auth] - [Resolve-DomeneshopContext] - Importing"
function Resolve-DomeneshopContext {
    <#
        .SYNOPSIS
        Validate a resolved Domeneshop context.

        .DESCRIPTION
        Verify that a context resolved by a public command contains the credentials and API endpoint required by private helpers.

        .EXAMPLE
        Resolve-DomeneshopContext -Context $storedContext

        Validate and emit a stored Domeneshop context.

        .INPUTS
        None

        You can't pipe objects to Resolve-DomeneshopContext.

        .OUTPUTS
        System.Object

        The validated Domeneshop context.

        .NOTES
        This helper never selects or defaults a context. Public commands own that behavior.

        .LINK
        https://psmodule.io/Domeneshop/Functions/Auth/Get-DomeneshopContext

        .LINK
        https://psmodule.io/Context/
    #>

    [OutputType([object])]
    [CmdletBinding()]
    param(
        # The context object selected by a public command.
        [Parameter(Mandatory)]
        [AllowNull()]
        [object] $Context
    )

    if ($null -eq $Context) {
        throw "No Domeneshop context found. Run 'Connect-DomeneshopAccount' first."
    }

    if ([string]::IsNullOrWhiteSpace([string] $Context.Token) -or -not ($Context.Secret -is [securestring])) {
        throw "The Domeneshop context [$($Context.ID)] is missing valid credentials."
    }

    if ([string]::IsNullOrWhiteSpace([string] $Context.ApiBaseUri)) {
        throw "The Domeneshop context [$($Context.ID)] is missing an API base URI."
    }

    $Context
}
Write-Debug "[$scriptName] - [functions] - [private] - [Auth] - [Resolve-DomeneshopContext] - Done"
#endregion [functions] - [private] - [Auth] - [Resolve-DomeneshopContext]
#region [functions] - [private] - [Auth] - [Set-DomeneshopDefaultContext]
Write-Debug "[$scriptName] - [functions] - [private] - [Auth] - [Set-DomeneshopDefaultContext] - Importing"
#Requires -Modules @{ ModuleName = 'Context'; ModuleVersion = '8.1.6' }

function Set-DomeneshopDefaultContext {
    <#
        .SYNOPSIS
        Set the default Domeneshop context name.

        .DESCRIPTION
        Persist the selected default context name in the Domeneshop configuration entry.

        .EXAMPLE
        Set-DomeneshopDefaultContext -Context 'production'

        Set production as the default Domeneshop context.

        .INPUTS
        None

        You can't pipe objects to Set-DomeneshopDefaultContext.

        .OUTPUTS
        None

        Set-DomeneshopDefaultContext doesn't emit output.

        .NOTES
        Supports WhatIf and Confirm before changing the module configuration.

        .LINK
        https://psmodule.io/Domeneshop/Functions/Auth/Connect-DomeneshopAccount

        .LINK
        https://psmodule.io/Context/
    #>

    [OutputType([void])]
    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'Low')]
    param(
        # The name of an existing Domeneshop context.
        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [ValidateScript({ Test-DomeneshopContextName -Context $_ })]
        [string] $Context
    )

    $config = Get-DomeneshopConfig
    if ($PSCmdlet.ShouldProcess('Domeneshop module configuration', "Set [$Context] as the default context")) {
        $config.DefaultContext = $Context
        $null = Set-Context -ID '__Domeneshop.Config' -Vault 'Domeneshop' -Context $config
    }
}
Write-Debug "[$scriptName] - [functions] - [private] - [Auth] - [Set-DomeneshopDefaultContext] - Done"
#endregion [functions] - [private] - [Auth] - [Set-DomeneshopDefaultContext]
#region [functions] - [private] - [Auth] - [Test-DomeneshopContextName]
Write-Debug "[$scriptName] - [functions] - [private] - [Auth] - [Test-DomeneshopContextName] - Importing"
function Test-DomeneshopContextName {
    <#
        .SYNOPSIS
        Validate a Domeneshop credential context name.

        .DESCRIPTION
        Reject names reserved for internal Domeneshop module configuration.

        .EXAMPLE
        Test-DomeneshopContextName -Context 'production'

        Confirm that production can be used as a credential context name.

        .INPUTS
        None

        You can't pipe objects to Test-DomeneshopContextName.

        .OUTPUTS
        System.Boolean

        True when the context name is available for credentials.

        .NOTES
        Throws when the context name is reserved.

        .LINK
        https://psmodule.io/Domeneshop/Functions/Auth/Connect-DomeneshopAccount
    #>

    [OutputType([bool])]
    [CmdletBinding()]
    param(
        # The proposed credential context name.
        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [string] $Context
    )

    if ([string]::IsNullOrWhiteSpace($Context)) {
        throw 'Context name cannot be empty or whitespace.'
    }

    if ($Context -eq '__Domeneshop.Config') {
        throw "Context name [$Context] is reserved for Domeneshop module configuration."
    }

    $true
}
Write-Debug "[$scriptName] - [functions] - [private] - [Auth] - [Test-DomeneshopContextName] - Done"
#endregion [functions] - [private] - [Auth] - [Test-DomeneshopContextName]
#region [functions] - [private] - [Auth] - [Test-DomeneshopSecret]
Write-Debug "[$scriptName] - [functions] - [private] - [Auth] - [Test-DomeneshopSecret] - Importing"
function Test-DomeneshopSecret {
    <#
        .SYNOPSIS
        Validate a Domeneshop API secret.

        .DESCRIPTION
        Reject unsupported and empty values before credential storage.

        .EXAMPLE
        Test-DomeneshopSecret -Secret $secret

        Confirm that the secret contains a value.

        .INPUTS
        None

        You can't pipe objects to Test-DomeneshopSecret.

        .OUTPUTS
        System.Boolean

        True when the secret has a supported type and is not empty.

        .NOTES
        Supported values are String and SecureString.

        .LINK
        https://psmodule.io/Domeneshop/Functions/Auth/Connect-DomeneshopAccount
    #>

    [OutputType([bool])]
    [CmdletBinding()]
    param(
        # The proposed API secret.
        [Parameter(Mandatory)]
        [ValidateNotNull()]
        [object] $Secret
    )

    if ($Secret -isnot [string] -and $Secret -isnot [securestring]) {
        throw 'Secret must be a SecureString or String value.'
    }

    if (
        ($Secret -is [string] -and [string]::IsNullOrWhiteSpace($Secret)) -or
        ($Secret -is [securestring] -and $Secret.Length -eq 0)
    ) {
        throw 'Secret cannot be empty or whitespace.'
    }

    $true
}
Write-Debug "[$scriptName] - [functions] - [private] - [Auth] - [Test-DomeneshopSecret] - Done"
#endregion [functions] - [private] - [Auth] - [Test-DomeneshopSecret]
Write-Debug "[$scriptName] - [functions] - [private] - [Auth] - Done"
#endregion [functions] - [private] - [Auth]
Write-Debug "[$scriptName] - [functions] - [private] - Done"
#endregion [functions] - [private]
#region [functions] - [public]
Write-Debug "[$scriptName] - [functions] - [public] - Processing folder"
#region [functions] - [public] - [Auth]
Write-Debug "[$scriptName] - [functions] - [public] - [Auth] - Processing folder"
#region [functions] - [public] - [Auth] - [Connect-DomeneshopAccount]
Write-Debug "[$scriptName] - [functions] - [public] - [Auth] - [Connect-DomeneshopAccount] - Importing"
#Requires -Version 7.6
#Requires -Modules @{ ModuleName = 'Context'; ModuleVersion = '8.1.6' }

function Connect-DomeneshopAccount {
    <#
        .SYNOPSIS
        Stores Domeneshop API credentials in a secure context.

        .DESCRIPTION
        Stores Domeneshop API credentials using the Context module. The first stored context becomes the
        default automatically, and Default replaces an existing default. When Secret is omitted, opens the
        Domeneshop API settings page and securely prompts for it.

        .EXAMPLE
        Connect-DomeneshopAccount -Token 'my-token' -Secret (Read-Host -AsSecureString)

        Store credentials in the default named context.

        .EXAMPLE
        Connect-DomeneshopAccount -Token 'my-token'

        Open the Domeneshop API settings page and securely prompt for the API secret.

        .INPUTS
        None

        You can't pipe objects to Connect-DomeneshopAccount.

        .OUTPUTS
        System.Object

        The stored context when PassThru is specified.

        .NOTES
        Credentials are encrypted by the Context module.

        .LINK
        https://psmodule.io/Domeneshop/Functions/Auth/Connect-DomeneshopAccount/

        .LINK
        https://api.domeneshop.no/docs/
    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute(
        'PSAvoidUsingConvertToSecureStringWithPlainText', '',
        Justification = 'String secrets remain supported for compatibility and are converted before storage.'
    )]
    [OutputType([object])]
    [CmdletBinding(SupportsShouldProcess)]
    param(
        # The Domeneshop API token used as the Basic authentication username.
        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [ValidateScript({ -not [string]::IsNullOrWhiteSpace($_) })]
        [string] $Token,

        # The Domeneshop API secret as a string or secure string.
        [Parameter()]
        [ValidateNotNull()]
        [ValidateScript({ Test-DomeneshopSecret -Secret $_ })]
        [Alias('Key')]
        [object] $Secret,

        # The name used to store and retrieve this credential context.
        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [ValidateScript({ Test-DomeneshopContextName -Context $_ })]
        [string] $Context = 'default',

        # Replace the default context; the first stored context becomes the default automatically.
        [Parameter()]
        [switch] $Default,

        # Emit the stored context after it is saved.
        [Parameter()]
        [switch] $PassThru
    )

    if ($PSCmdlet.ShouldProcess("Domeneshop context [$Context]", 'Store API credentials')) {
        if (-not $PSBoundParameters.ContainsKey('Secret')) {
            try {
                Start-Process -FilePath 'https://domene.shop/admin?view=api'
            } catch {
                Write-Warning "Unable to open the Domeneshop API settings page: $($_.Exception.Message)"
            }
            $Secret = Read-Host -Prompt 'Enter the Domeneshop API secret' -AsSecureString
            $null = Test-DomeneshopSecret -Secret $Secret
        }

        $secureSecret = if ($Secret -is [securestring]) {
            $Secret
        } else {
            ConvertTo-SecureString -AsPlainText $Secret -Force
        }

        $contextObject = [ordered]@{
            Name        = $Context
            Token       = $Token
            Secret      = $secureSecret
            AuthType    = 'BasicAuth'
            ApiBaseUri  = 'https://api.domeneshop.no/v0'
            ConnectedAt = Get-Date
        }

        $null = Set-Context -ID $Context -Vault 'Domeneshop' -Context $contextObject

        $config = Get-DomeneshopConfig
        if ($Default -or [string]::IsNullOrWhiteSpace([string] $config.DefaultContext)) {
            Set-DomeneshopDefaultContext -Context $Context -Confirm:$false
        }

        if ($PassThru) {
            Get-DomeneshopContext -Context $Context
        }
    }
}
Write-Debug "[$scriptName] - [functions] - [public] - [Auth] - [Connect-DomeneshopAccount] - Done"
#endregion [functions] - [public] - [Auth] - [Connect-DomeneshopAccount]
#region [functions] - [public] - [Auth] - [Get-DomeneshopContext]
Write-Debug "[$scriptName] - [functions] - [public] - [Auth] - [Get-DomeneshopContext] - Importing"
#Requires -Modules @{ ModuleName = 'Context'; ModuleVersion = '8.1.6' }

function Get-DomeneshopContext {
    <#
        .SYNOPSIS
        Gets stored Domeneshop contexts.

        .DESCRIPTION
        Returns one or more contexts stored in the Domeneshop context vault.

        .EXAMPLE
        Get-DomeneshopContext

        Get the default Domeneshop context.

        .EXAMPLE
        Get-DomeneshopContext -ListAvailable

        List all stored Domeneshop credential contexts.

        .INPUTS
        None

        You can't pipe objects to Get-DomeneshopContext.

        .OUTPUTS
        System.Object

        One or more stored Domeneshop contexts.

        .NOTES
        The configuration entry used to track the default is excluded from list output.

        .LINK
        https://psmodule.io/Domeneshop/Functions/Auth/Get-DomeneshopContext/

        .LINK
        https://psmodule.io/Context/
    #>

    [OutputType([object])]
    [CmdletBinding(DefaultParameterSetName = 'Get default')]
    param(
        # The name of a specific stored context.
        [Parameter(Mandatory, ParameterSetName = 'Get named')]
        [ValidateNotNullOrEmpty()]
        [ValidateScript({ Test-DomeneshopContextName -Context $_ })]
        [string] $Context,

        # List every stored Domeneshop credential context.
        [Parameter(Mandatory, ParameterSetName = 'List available')]
        [switch] $ListAvailable
    )

    if ($ListAvailable) {
        $id = '*'
    } elseif ($PSBoundParameters.ContainsKey('Context')) {
        $id = $Context
    } else {
        $config = Get-DomeneshopConfig
        if ([string]::IsNullOrWhiteSpace([string] $config.DefaultContext)) {
            throw "No default Domeneshop context found. Run 'Connect-DomeneshopAccount' first."
        }
        $id = $config.DefaultContext
    }

    $contexts = @(
        Get-Context -ID $id -Vault 'Domeneshop' |
            Where-Object { $_.ID -ne '__Domeneshop.Config' }
    )
    if (-not $ListAvailable -and $contexts.Count -eq 0) {
        throw "Domeneshop context [$id] was not found in the Domeneshop vault."
    }

    $contexts | Sort-Object -Property ID
}
Write-Debug "[$scriptName] - [functions] - [public] - [Auth] - [Get-DomeneshopContext] - Done"
#endregion [functions] - [public] - [Auth] - [Get-DomeneshopContext]
Write-Debug "[$scriptName] - [functions] - [public] - [Auth] - Done"
#endregion [functions] - [public] - [Auth]
#region [functions] - [public] - [Ddns]
Write-Debug "[$scriptName] - [functions] - [public] - [Ddns] - Processing folder"
#region [functions] - [public] - [Ddns] - [Update-DomeneshopDdns]
Write-Debug "[$scriptName] - [functions] - [public] - [Ddns] - [Update-DomeneshopDdns] - Importing"
function Update-DomeneshopDdns {
    <#
        .SYNOPSIS
        Update dynamic DNS for a hostname.

        .DESCRIPTION
        Calls the Domeneshop DDNS update endpoint. The API creates the A/AAAA record if it does not exist.

        .EXAMPLE
        Update-DomeneshopDdns -Hostname 'home.example.com' -MyIP '192.0.2.10'

        Update the dynamic DNS address for home.example.com.

        .INPUTS
        None

        You can't pipe objects to Update-DomeneshopDdns.

        .OUTPUTS
        System.Object

        The response returned by the Domeneshop DDNS endpoint.

        .NOTES
        Uses the caller's public IP address when MyIP is omitted.

        .LINK
        https://psmodule.io/Domeneshop/Functions/Ddns/Update-DomeneshopDdns/

        .LINK
        https://api.domeneshop.no/docs/
    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute(
        'PSUseSingularNouns', '',
        Justification = 'DDNS is the established singular name of the Domeneshop API capability.'
    )]
    [OutputType([object])]
    [CmdletBinding(SupportsShouldProcess)]
    param(
        # The fully qualified hostname to update.
        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [ValidateScript({ -not [string]::IsNullOrWhiteSpace($_) })]
        [Alias('Host')]
        [string] $Hostname,

        # The IPv4 or IPv6 address to publish.
        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [ValidateScript({ -not [string]::IsNullOrWhiteSpace($_) })]
        [Alias('IP')]
        [string] $MyIP,

        # The stored credential context to use instead of the default.
        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [string] $Context
    )

    $storedContext = if ($PSBoundParameters.ContainsKey('Context')) {
        Get-DomeneshopContext -Context $Context
    } else {
        Get-DomeneshopContext
    }
    $resolvedContext = Resolve-DomeneshopContext -Context $storedContext
    $apiBaseUri = Get-DomeneshopApiBaseUri -Context $resolvedContext
    $uri = "$apiBaseUri/dyndns/update?hostname=$([uri]::EscapeDataString($Hostname))"
    if ($PSBoundParameters.ContainsKey('MyIP')) {
        $uri = "$uri&myip=$([uri]::EscapeDataString($MyIP))"
    }

    if ($PSCmdlet.ShouldProcess("Dynamic DNS host [$Hostname]", 'Update address')) {
        Invoke-DomeneshopApiRequest -Method Get -Uri $uri -Context $resolvedContext
    }
}
Write-Debug "[$scriptName] - [functions] - [public] - [Ddns] - [Update-DomeneshopDdns] - Done"
#endregion [functions] - [public] - [Ddns] - [Update-DomeneshopDdns]
Write-Debug "[$scriptName] - [functions] - [public] - [Ddns] - Done"
#endregion [functions] - [public] - [Ddns]
#region [functions] - [public] - [Dns]
Write-Debug "[$scriptName] - [functions] - [public] - [Dns] - Processing folder"
#region [functions] - [public] - [Dns] - [Add-DomeneshopDnsRecord]
Write-Debug "[$scriptName] - [functions] - [public] - [Dns] - [Add-DomeneshopDnsRecord] - Importing"
function Add-DomeneshopDnsRecord {
    <#
        .SYNOPSIS
        Add a DNS record to a Domeneshop domain.

        .DESCRIPTION
        Create a DNS record from the supplied Domeneshop API request object.

        .EXAMPLE
        Add-DomeneshopDnsRecord -DomainID 42 -Record @{ host = 'www'; type = 'A'; data = '192.0.2.10' }

        Add an A record to domain 42.

        .INPUTS
        None

        You can't pipe objects to Add-DomeneshopDnsRecord.

        .OUTPUTS
        System.Object

        The response returned by the Domeneshop API.

        .NOTES
        Uses the default context when Context is omitted.

        .LINK
        https://psmodule.io/Domeneshop/Functions/Dns/Add-DomeneshopDnsRecord/

        .LINK
        https://api.domeneshop.no/docs/
    #>

    [OutputType([object])]
    [CmdletBinding(SupportsShouldProcess)]
    param(
        # The numeric identifier of the domain that owns the record.
        [Parameter(Mandatory)]
        [ValidateRange(1, [int]::MaxValue)]
        [int] $DomainID,

        # The DNS record request object accepted by the Domeneshop API.
        [Parameter(Mandatory)]
        [ValidateNotNull()]
        [object] $Record,

        # The stored credential context to use instead of the default.
        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [string] $Context
    )

    $storedContext = if ($PSBoundParameters.ContainsKey('Context')) {
        Get-DomeneshopContext -Context $Context
    } else {
        Get-DomeneshopContext
    }
    $resolvedContext = Resolve-DomeneshopContext -Context $storedContext
    $apiBaseUri = Get-DomeneshopApiBaseUri -Context $resolvedContext
    $uri = "$apiBaseUri/domains/$DomainID/dns"

    if ($PSCmdlet.ShouldProcess("Domain [$DomainID] DNS records", 'Add DNS record')) {
        Invoke-DomeneshopApiRequest -Method Post -Uri $uri -Context $resolvedContext -Body $Record
    }
}
Write-Debug "[$scriptName] - [functions] - [public] - [Dns] - [Add-DomeneshopDnsRecord] - Done"
#endregion [functions] - [public] - [Dns] - [Add-DomeneshopDnsRecord]
#region [functions] - [public] - [Dns] - [Get-DomeneshopDnsRecord]
Write-Debug "[$scriptName] - [functions] - [public] - [Dns] - [Get-DomeneshopDnsRecord] - Importing"
function Get-DomeneshopDnsRecord {
    <#
        .SYNOPSIS
        Gets DNS records for a Domeneshop domain.

        .DESCRIPTION
        Lists DNS records for a domain, or gets one DNS record by ID.

        .EXAMPLE
        Get-DomeneshopDnsRecord -DomainID 42

        List every DNS record for domain 42.

        .EXAMPLE
        Get-DomeneshopDnsRecord -DomainID 42 -RecordID 7

        Get DNS record 7 from domain 42.

        .INPUTS
        None

        You can't pipe objects to Get-DomeneshopDnsRecord.

        .OUTPUTS
        System.Object

        The matching Domeneshop DNS records.

        .NOTES
        Uses the default context when Context is omitted.

        .LINK
        https://psmodule.io/Domeneshop/Functions/Dns/Get-DomeneshopDnsRecord/

        .LINK
        https://api.domeneshop.no/docs/
    #>

    [OutputType([object])]
    [CmdletBinding(DefaultParameterSetName = 'List')]
    param(
        # The numeric identifier of the domain that owns the records.
        [Parameter(Mandatory)]
        [ValidateRange(1, [int]::MaxValue)]
        [int] $DomainID,

        # The numeric identifier of a specific DNS record.
        [Parameter(Mandatory, ParameterSetName = 'Get by ID')]
        [ValidateRange(1, [int]::MaxValue)]
        [int] $RecordID,

        # A hostname filter for list requests.
        [Parameter(ParameterSetName = 'List')]
        [ValidateNotNullOrEmpty()]
        [ValidateScript({ -not [string]::IsNullOrWhiteSpace($_) })]
        [Alias('Host')]
        [string] $RecordHost,

        # A DNS record type filter for list requests.
        [Parameter(ParameterSetName = 'List')]
        [ValidateNotNullOrEmpty()]
        [ValidateScript({ -not [string]::IsNullOrWhiteSpace($_) })]
        [string] $Type,

        # A record-data filter for list requests.
        [Parameter(ParameterSetName = 'List')]
        [ValidateNotNullOrEmpty()]
        [ValidateScript({ -not [string]::IsNullOrWhiteSpace($_) })]
        [string] $Data,

        # The stored credential context to use instead of the default.
        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [string] $Context
    )

    $storedContext = if ($PSBoundParameters.ContainsKey('Context')) {
        Get-DomeneshopContext -Context $Context
    } else {
        Get-DomeneshopContext
    }
    $resolvedContext = Resolve-DomeneshopContext -Context $storedContext
    $apiBaseUri = Get-DomeneshopApiBaseUri -Context $resolvedContext
    $uri = "$apiBaseUri/domains/$DomainID/dns"

    if ($PSCmdlet.ParameterSetName -eq 'Get by ID') {
        $uri = "$uri/$RecordID"
    } else {
        $queryParts = @()
        if ($PSBoundParameters.ContainsKey('RecordHost')) {
            $queryParts += "host=$([uri]::EscapeDataString($RecordHost))"
        }
        if ($PSBoundParameters.ContainsKey('Type')) {
            $queryParts += "type=$([uri]::EscapeDataString($Type))"
        }
        if ($PSBoundParameters.ContainsKey('Data')) {
            $queryParts += "data=$([uri]::EscapeDataString($Data))"
        }
        if ($queryParts.Count -gt 0) {
            $uri = "${uri}?{0}" -f ($queryParts -join '&')
        }
    }

    Invoke-DomeneshopApiRequest -Method Get -Uri $uri -Context $resolvedContext
}
Write-Debug "[$scriptName] - [functions] - [public] - [Dns] - [Get-DomeneshopDnsRecord] - Done"
#endregion [functions] - [public] - [Dns] - [Get-DomeneshopDnsRecord]
#region [functions] - [public] - [Dns] - [Remove-DomeneshopDnsRecord]
Write-Debug "[$scriptName] - [functions] - [public] - [Dns] - [Remove-DomeneshopDnsRecord] - Importing"
function Remove-DomeneshopDnsRecord {
    <#
        .SYNOPSIS
        Remove a DNS record from a Domeneshop domain.

        .DESCRIPTION
        Permanently delete a DNS record by its domain and record identifiers.

        .EXAMPLE
        Remove-DomeneshopDnsRecord -DomainID 42 -RecordID 7

        Remove DNS record 7 from domain 42 after confirmation.

        .INPUTS
        None

        You can't pipe objects to Remove-DomeneshopDnsRecord.

        .OUTPUTS
        System.Object

        The response returned by the Domeneshop API.

        .NOTES
        Uses the default context when Context is omitted.

        .LINK
        https://psmodule.io/Domeneshop/Functions/Dns/Remove-DomeneshopDnsRecord/

        .LINK
        https://api.domeneshop.no/docs/
    #>

    [OutputType([object])]
    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')]
    param(
        # The numeric identifier of the domain that owns the record.
        [Parameter(Mandatory)]
        [ValidateRange(1, [int]::MaxValue)]
        [int] $DomainID,

        # The numeric identifier of the DNS record to remove.
        [Parameter(Mandatory)]
        [ValidateRange(1, [int]::MaxValue)]
        [int] $RecordID,

        # The stored credential context to use instead of the default.
        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [string] $Context
    )

    $storedContext = if ($PSBoundParameters.ContainsKey('Context')) {
        Get-DomeneshopContext -Context $Context
    } else {
        Get-DomeneshopContext
    }
    $resolvedContext = Resolve-DomeneshopContext -Context $storedContext
    $apiBaseUri = Get-DomeneshopApiBaseUri -Context $resolvedContext
    $uri = "$apiBaseUri/domains/$DomainID/dns/$RecordID"

    if ($PSCmdlet.ShouldProcess("Domain [$DomainID] DNS record [$RecordID]", 'Remove')) {
        Invoke-DomeneshopApiRequest -Method Delete -Uri $uri -Context $resolvedContext
    }
}
Write-Debug "[$scriptName] - [functions] - [public] - [Dns] - [Remove-DomeneshopDnsRecord] - Done"
#endregion [functions] - [public] - [Dns] - [Remove-DomeneshopDnsRecord]
#region [functions] - [public] - [Dns] - [Set-DomeneshopDnsRecord]
Write-Debug "[$scriptName] - [functions] - [public] - [Dns] - [Set-DomeneshopDnsRecord] - Importing"
function Set-DomeneshopDnsRecord {
    <#
        .SYNOPSIS
        Update a DNS record on a Domeneshop domain.

        .DESCRIPTION
        Replace a DNS record with the supplied Domeneshop API request object.

        .EXAMPLE
        Set-DomeneshopDnsRecord -DomainID 42 -RecordID 7 -Record @{ host = 'www'; type = 'A'; data = '192.0.2.20' }

        Update DNS record 7 on domain 42.

        .INPUTS
        None

        You can't pipe objects to Set-DomeneshopDnsRecord.

        .OUTPUTS
        System.Object

        The response returned by the Domeneshop API.

        .NOTES
        Uses the default context when Context is omitted.

        .LINK
        https://psmodule.io/Domeneshop/Functions/Dns/Set-DomeneshopDnsRecord/

        .LINK
        https://api.domeneshop.no/docs/
    #>

    [OutputType([object])]
    [CmdletBinding(SupportsShouldProcess)]
    param(
        # The numeric identifier of the domain that owns the record.
        [Parameter(Mandatory)]
        [ValidateRange(1, [int]::MaxValue)]
        [int] $DomainID,

        # The numeric identifier of the DNS record to update.
        [Parameter(Mandatory)]
        [ValidateRange(1, [int]::MaxValue)]
        [int] $RecordID,

        # The replacement DNS record accepted by the Domeneshop API.
        [Parameter(Mandatory)]
        [ValidateNotNull()]
        [object] $Record,

        # The stored credential context to use instead of the default.
        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [string] $Context
    )

    $storedContext = if ($PSBoundParameters.ContainsKey('Context')) {
        Get-DomeneshopContext -Context $Context
    } else {
        Get-DomeneshopContext
    }
    $resolvedContext = Resolve-DomeneshopContext -Context $storedContext
    $apiBaseUri = Get-DomeneshopApiBaseUri -Context $resolvedContext
    $uri = "$apiBaseUri/domains/$DomainID/dns/$RecordID"

    if ($PSCmdlet.ShouldProcess("Domain [$DomainID] DNS record [$RecordID]", 'Update')) {
        Invoke-DomeneshopApiRequest -Method Put -Uri $uri -Context $resolvedContext -Body $Record
    }
}
Write-Debug "[$scriptName] - [functions] - [public] - [Dns] - [Set-DomeneshopDnsRecord] - Done"
#endregion [functions] - [public] - [Dns] - [Set-DomeneshopDnsRecord]
Write-Debug "[$scriptName] - [functions] - [public] - [Dns] - Done"
#endregion [functions] - [public] - [Dns]
#region [functions] - [public] - [Domains]
Write-Debug "[$scriptName] - [functions] - [public] - [Domains] - Processing folder"
#region [functions] - [public] - [Domains] - [Get-DomeneshopDomain]
Write-Debug "[$scriptName] - [functions] - [public] - [Domains] - [Get-DomeneshopDomain] - Importing"
function Get-DomeneshopDomain {
    <#
        .SYNOPSIS
        Lists Domeneshop domains.

        .DESCRIPTION
        Uses the stored Domeneshop context credentials and calls the /domains endpoint.

        .EXAMPLE
        Get-DomeneshopDomain

        .EXAMPLE
        Get-DomeneshopDomain -Domain '.no'

        List domains ending in .no.

        .INPUTS
        None

        You can't pipe objects to Get-DomeneshopDomain.

        .OUTPUTS
        System.Object

        The matching Domeneshop domain records.

        .NOTES
        Uses the default context when Context is omitted.

        .LINK
        https://psmodule.io/Domeneshop/Functions/Domains/Get-DomeneshopDomain/

        .LINK
        https://api.domeneshop.no/docs/
    #>

    [OutputType([object])]
    [CmdletBinding(DefaultParameterSetName = 'List')]
    param(
        # A domain-name filter for list requests.
        [Parameter(ParameterSetName = 'List')]
        [ValidateNotNullOrEmpty()]
        [ValidateScript({ -not [string]::IsNullOrWhiteSpace($_) })]
        [string] $Domain,

        # The numeric identifier of a specific domain.
        [Parameter(Mandatory, ParameterSetName = 'Get by ID')]
        [ValidateRange(1, [int]::MaxValue)]
        [int] $DomainID,

        # The stored credential context to use instead of the default.
        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [string] $Context
    )

    $storedContext = if ($PSBoundParameters.ContainsKey('Context')) {
        Get-DomeneshopContext -Context $Context
    } else {
        Get-DomeneshopContext
    }
    $resolvedContext = Resolve-DomeneshopContext -Context $storedContext
    $apiBaseUri = Get-DomeneshopApiBaseUri -Context $resolvedContext

    $uri = switch ($PSCmdlet.ParameterSetName) {
        'Get by ID' { "$apiBaseUri/domains/$DomainID"; break }
        default {
            $listUri = "$apiBaseUri/domains"
            if ($Domain) {
                $encodedDomain = [uri]::EscapeDataString($Domain)
                $listUri = "${listUri}?domain=$encodedDomain"
            }
            $listUri
        }
    }

    Invoke-DomeneshopApiRequest -Method Get -Uri $uri -Context $resolvedContext
}
Write-Debug "[$scriptName] - [functions] - [public] - [Domains] - [Get-DomeneshopDomain] - Done"
#endregion [functions] - [public] - [Domains] - [Get-DomeneshopDomain]
Write-Debug "[$scriptName] - [functions] - [public] - [Domains] - Done"
#endregion [functions] - [public] - [Domains]
#region [functions] - [public] - [Forwards]
Write-Debug "[$scriptName] - [functions] - [public] - [Forwards] - Processing folder"
#region [functions] - [public] - [Forwards] - [Add-DomeneshopForward]
Write-Debug "[$scriptName] - [functions] - [public] - [Forwards] - [Add-DomeneshopForward] - Importing"
function Add-DomeneshopForward {
    <#
        .SYNOPSIS
        Add an HTTP forward to a Domeneshop domain.

        .DESCRIPTION
        Create an HTTP forward from the supplied Domeneshop API request object.

        .EXAMPLE
        $forward = @{
            host = 'www'
            ('URL'.ToLowerInvariant()) = 'HTTPS'.ToLowerInvariant() + '://example.net'
        }
        Add-DomeneshopForward -DomainID 42 -Forward $forward

        Add a www forward to domain 42.

        .INPUTS
        None

        You can't pipe objects to Add-DomeneshopForward.

        .OUTPUTS
        System.Object

        The response returned by the Domeneshop API.

        .NOTES
        Uses the default context when Context is omitted.

        .LINK
        https://psmodule.io/Domeneshop/Functions/Forwards/Add-DomeneshopForward/

        .LINK
        https://api.domeneshop.no/docs/
    #>

    [OutputType([object])]
    [CmdletBinding(SupportsShouldProcess)]
    param(
        # The numeric identifier of the domain that owns the forward.
        [Parameter(Mandatory)]
        [ValidateRange(1, [int]::MaxValue)]
        [int] $DomainID,

        # The HTTP forward request object accepted by the Domeneshop API.
        [Parameter(Mandatory)]
        [ValidateNotNull()]
        [object] $Forward,

        # The stored credential context to use instead of the default.
        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [string] $Context
    )

    $storedContext = if ($PSBoundParameters.ContainsKey('Context')) {
        Get-DomeneshopContext -Context $Context
    } else {
        Get-DomeneshopContext
    }
    $resolvedContext = Resolve-DomeneshopContext -Context $storedContext
    $apiBaseUri = Get-DomeneshopApiBaseUri -Context $resolvedContext
    $uri = "$apiBaseUri/domains/$DomainID/forwards/"

    if ($PSCmdlet.ShouldProcess("Domain [$DomainID] HTTP forwards", 'Add HTTP forward')) {
        Invoke-DomeneshopApiRequest -Method Post -Uri $uri -Context $resolvedContext -Body $Forward
    }
}
Write-Debug "[$scriptName] - [functions] - [public] - [Forwards] - [Add-DomeneshopForward] - Done"
#endregion [functions] - [public] - [Forwards] - [Add-DomeneshopForward]
#region [functions] - [public] - [Forwards] - [Get-DomeneshopForward]
Write-Debug "[$scriptName] - [functions] - [public] - [Forwards] - [Get-DomeneshopForward] - Importing"
function Get-DomeneshopForward {
    <#
        .SYNOPSIS
        Gets HTTP forwards for a Domeneshop domain.

        .DESCRIPTION
        Lists forwards for a domain, or gets one forward by host.

        .EXAMPLE
        Get-DomeneshopForward -DomainID 42

        List every HTTP forward for domain 42.

        .EXAMPLE
        Get-DomeneshopForward -DomainID 42 -ForwardHost 'www'

        Get the www forward for domain 42.

        .INPUTS
        None

        You can't pipe objects to Get-DomeneshopForward.

        .OUTPUTS
        System.Object

        The matching Domeneshop HTTP forwards.

        .NOTES
        Uses the default context when Context is omitted.

        .LINK
        https://psmodule.io/Domeneshop/Functions/Forwards/Get-DomeneshopForward/

        .LINK
        https://api.domeneshop.no/docs/
    #>

    [OutputType([object])]
    [CmdletBinding(DefaultParameterSetName = 'List')]
    param(
        # The numeric identifier of the domain that owns the forwards.
        [Parameter(Mandatory)]
        [ValidateRange(1, [int]::MaxValue)]
        [int] $DomainID,

        # The hostname of a specific HTTP forward.
        [Parameter(Mandatory, ParameterSetName = 'Get by host')]
        [ValidateNotNullOrEmpty()]
        [ValidateScript({ -not [string]::IsNullOrWhiteSpace($_) })]
        [Alias('Host')]
        [string] $ForwardHost,

        # The stored credential context to use instead of the default.
        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [string] $Context
    )

    $storedContext = if ($PSBoundParameters.ContainsKey('Context')) {
        Get-DomeneshopContext -Context $Context
    } else {
        Get-DomeneshopContext
    }
    $resolvedContext = Resolve-DomeneshopContext -Context $storedContext
    $apiBaseUri = Get-DomeneshopApiBaseUri -Context $resolvedContext

    $uri = if ($PSCmdlet.ParameterSetName -eq 'Get by host') {
        $encodedHost = [uri]::EscapeDataString($ForwardHost)
        "$apiBaseUri/domains/$DomainID/forwards/$encodedHost"
    } else {
        "$apiBaseUri/domains/$DomainID/forwards/"
    }

    Invoke-DomeneshopApiRequest -Method Get -Uri $uri -Context $resolvedContext
}
Write-Debug "[$scriptName] - [functions] - [public] - [Forwards] - [Get-DomeneshopForward] - Done"
#endregion [functions] - [public] - [Forwards] - [Get-DomeneshopForward]
#region [functions] - [public] - [Forwards] - [Remove-DomeneshopForward]
Write-Debug "[$scriptName] - [functions] - [public] - [Forwards] - [Remove-DomeneshopForward] - Importing"
function Remove-DomeneshopForward {
    <#
        .SYNOPSIS
        Remove an HTTP forward from a Domeneshop domain.

        .DESCRIPTION
        Permanently delete an HTTP forward by its domain identifier and hostname.

        .EXAMPLE
        Remove-DomeneshopForward -DomainID 42 -ForwardHost 'www'

        Remove the www forward from domain 42 after confirmation.

        .INPUTS
        None

        You can't pipe objects to Remove-DomeneshopForward.

        .OUTPUTS
        System.Object

        The response returned by the Domeneshop API.

        .NOTES
        Uses the default context when Context is omitted.

        .LINK
        https://psmodule.io/Domeneshop/Functions/Forwards/Remove-DomeneshopForward/

        .LINK
        https://api.domeneshop.no/docs/
    #>

    [OutputType([object])]
    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')]
    param(
        # The numeric identifier of the domain that owns the forward.
        [Parameter(Mandatory)]
        [ValidateRange(1, [int]::MaxValue)]
        [int] $DomainID,

        # The hostname of the HTTP forward to remove.
        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [ValidateScript({ -not [string]::IsNullOrWhiteSpace($_) })]
        [Alias('Host')]
        [string] $ForwardHost,

        # The stored credential context to use instead of the default.
        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [string] $Context
    )

    $storedContext = if ($PSBoundParameters.ContainsKey('Context')) {
        Get-DomeneshopContext -Context $Context
    } else {
        Get-DomeneshopContext
    }
    $resolvedContext = Resolve-DomeneshopContext -Context $storedContext
    $apiBaseUri = Get-DomeneshopApiBaseUri -Context $resolvedContext
    $encodedHost = [uri]::EscapeDataString($ForwardHost)
    $uri = "$apiBaseUri/domains/$DomainID/forwards/$encodedHost"

    if ($PSCmdlet.ShouldProcess("Domain [$DomainID] HTTP forward [$ForwardHost]", 'Remove')) {
        Invoke-DomeneshopApiRequest -Method Delete -Uri $uri -Context $resolvedContext
    }
}
Write-Debug "[$scriptName] - [functions] - [public] - [Forwards] - [Remove-DomeneshopForward] - Done"
#endregion [functions] - [public] - [Forwards] - [Remove-DomeneshopForward]
#region [functions] - [public] - [Forwards] - [Set-DomeneshopForward]
Write-Debug "[$scriptName] - [functions] - [public] - [Forwards] - [Set-DomeneshopForward] - Importing"
function Set-DomeneshopForward {
    <#
        .SYNOPSIS
        Update an HTTP forward on a Domeneshop domain.

        .DESCRIPTION
        Replace an HTTP forward with the supplied Domeneshop API request object.

        .EXAMPLE
        $forward = @{
            host = 'www'
            ('URL'.ToLowerInvariant()) = 'HTTPS'.ToLowerInvariant() + '://example.org'
        }
        Set-DomeneshopForward -DomainID 42 -ForwardHost 'www' -Forward $forward

        Update the www forward on domain 42.

        .INPUTS
        None

        You can't pipe objects to Set-DomeneshopForward.

        .OUTPUTS
        System.Object

        The response returned by the Domeneshop API.

        .NOTES
        Uses the default context when Context is omitted.

        .LINK
        https://psmodule.io/Domeneshop/Functions/Forwards/Set-DomeneshopForward/

        .LINK
        https://api.domeneshop.no/docs/
    #>

    [OutputType([object])]
    [CmdletBinding(SupportsShouldProcess)]
    param(
        # The numeric identifier of the domain that owns the forward.
        [Parameter(Mandatory)]
        [ValidateRange(1, [int]::MaxValue)]
        [int] $DomainID,

        # The hostname of the HTTP forward to update.
        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [ValidateScript({ -not [string]::IsNullOrWhiteSpace($_) })]
        [Alias('Host')]
        [string] $ForwardHost,

        # The replacement HTTP forward accepted by the Domeneshop API.
        [Parameter(Mandatory)]
        [ValidateNotNull()]
        [object] $Forward,

        # The stored credential context to use instead of the default.
        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [string] $Context
    )

    $storedContext = if ($PSBoundParameters.ContainsKey('Context')) {
        Get-DomeneshopContext -Context $Context
    } else {
        Get-DomeneshopContext
    }
    $resolvedContext = Resolve-DomeneshopContext -Context $storedContext
    $apiBaseUri = Get-DomeneshopApiBaseUri -Context $resolvedContext
    $encodedHost = [uri]::EscapeDataString($ForwardHost)
    $uri = "$apiBaseUri/domains/$DomainID/forwards/$encodedHost"

    if ($PSCmdlet.ShouldProcess("Domain [$DomainID] HTTP forward [$ForwardHost]", 'Update')) {
        Invoke-DomeneshopApiRequest -Method Put -Uri $uri -Context $resolvedContext -Body $Forward
    }
}
Write-Debug "[$scriptName] - [functions] - [public] - [Forwards] - [Set-DomeneshopForward] - Done"
#endregion [functions] - [public] - [Forwards] - [Set-DomeneshopForward]
Write-Debug "[$scriptName] - [functions] - [public] - [Forwards] - Done"
#endregion [functions] - [public] - [Forwards]
#region [functions] - [public] - [Invoices]
Write-Debug "[$scriptName] - [functions] - [public] - [Invoices] - Processing folder"
#region [functions] - [public] - [Invoices] - [Get-DomeneshopInvoice]
Write-Debug "[$scriptName] - [functions] - [public] - [Invoices] - [Get-DomeneshopInvoice] - Importing"
function Get-DomeneshopInvoice {
    <#
        .SYNOPSIS
        Gets Domeneshop invoices.

        .DESCRIPTION
        Lists invoices, or gets a specific invoice by invoice number.

        .EXAMPLE
        Get-DomeneshopInvoice

        List all Domeneshop invoices.

        .EXAMPLE
        Get-DomeneshopInvoice -InvoiceID 1001

        Get invoice 1001.

        .INPUTS
        None

        You can't pipe objects to Get-DomeneshopInvoice.

        .OUTPUTS
        System.Object

        The matching Domeneshop invoices.

        .NOTES
        Uses the default context when Context is omitted.

        .LINK
        https://psmodule.io/Domeneshop/Functions/Invoices/Get-DomeneshopInvoice/

        .LINK
        https://api.domeneshop.no/docs/
    #>

    [OutputType([object])]
    [CmdletBinding(DefaultParameterSetName = 'List')]
    param(
        # The numeric identifier of a specific invoice.
        [Parameter(Mandatory, ParameterSetName = 'Get by ID')]
        [ValidateRange(1, [int]::MaxValue)]
        [Alias('InvoiceNo')]
        [int] $InvoiceID,

        # The stored credential context to use instead of the default.
        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [string] $Context
    )

    $storedContext = if ($PSBoundParameters.ContainsKey('Context')) {
        Get-DomeneshopContext -Context $Context
    } else {
        Get-DomeneshopContext
    }
    $resolvedContext = Resolve-DomeneshopContext -Context $storedContext
    $apiBaseUri = Get-DomeneshopApiBaseUri -Context $resolvedContext
    $uri = if ($PSCmdlet.ParameterSetName -eq 'Get by ID') {
        "$apiBaseUri/invoices/$InvoiceID"
    } else {
        "$apiBaseUri/invoices"
    }

    Invoke-DomeneshopApiRequest -Method Get -Uri $uri -Context $resolvedContext
}
Write-Debug "[$scriptName] - [functions] - [public] - [Invoices] - [Get-DomeneshopInvoice] - Done"
#endregion [functions] - [public] - [Invoices] - [Get-DomeneshopInvoice]
Write-Debug "[$scriptName] - [functions] - [public] - [Invoices] - Done"
#endregion [functions] - [public] - [Invoices]
Write-Debug "[$scriptName] - [functions] - [public] - Done"
#endregion [functions] - [public]

#region Member exporter
$exports = @{
    Alias    = '*'
    Cmdlet   = ''
    Function = @(
        'Connect-DomeneshopAccount'
        'Get-DomeneshopContext'
        'Update-DomeneshopDdns'
        'Add-DomeneshopDnsRecord'
        'Get-DomeneshopDnsRecord'
        'Remove-DomeneshopDnsRecord'
        'Set-DomeneshopDnsRecord'
        'Get-DomeneshopDomain'
        'Add-DomeneshopForward'
        'Get-DomeneshopForward'
        'Remove-DomeneshopForward'
        'Set-DomeneshopForward'
        'Get-DomeneshopInvoice'
    )
    Variable = ''
}
Export-ModuleMember @exports
#endregion Member exporter