Public/Unpublish-DnsPersistChallenge.ps1

function Unpublish-DnsPersistChallenge {
    [CmdletBinding(DefaultParameterSetName='FromOrder')]
    param(
        [Parameter(Mandatory,ParameterSetName='FromOrder',Position=0,ValueFromPipeline)]
        [PSTypeName('PoshACME.PAOrder')]$Order,
        [Parameter(Mandatory,ParameterSetName='Standalone',Position=0,ValueFromPipeline)]
        [string[]]$Domain,
        [Parameter(Mandatory,ParameterSetName='Standalone',Position=1)]
        [string]$AccountUri,
        [Parameter(Mandatory,ParameterSetName='Standalone',Position=2)]
        [string]$IssuerDomainName,
        [Parameter(Mandatory,ParameterSetName='Standalone')]
        [ValidateScript({Test-ValidPlugin $_ -ThrowOnFail})]
        [string]$Plugin,
        [Parameter(ParameterSetName='Standalone')]
        [hashtable]$PluginArgs,
        [switch]$AllowWildcard,
        [switch]$UseAllDomains,
        [DateTimeOffset]$PersistUntil
    )

    Begin {
        # Make sure we have an account if we're running in the FromOrder parameter set.
        if ('FromOrder' -eq $PSCmdlet.ParameterSetName) {
             try {
                if (-not ($Account = Get-PAAccount)) {
                    throw "No current account selected. Try running Set-PAAccount first."
                }
            }
            catch { $PSCmdlet.ThrowTerminatingError($_) }
        }

        # initialize a deferred collection object so we can build up the list of challenges
        # to publish as we process the orders and then publish them all at once at the end.
        $chalCollection = [Collections.Generic.List[pscustomobject]]::new()
    }

    Process {

        if ('FromOrder' -eq $PSCmdlet.ParameterSetName) {

            # extract the required parameters from the order object
            $auths = $Order | Get-PAAuthorization
            $pArgs = $Order | Get-PAPluginArgs

            # loop through the auths by index so we can correlate them to the associated plugin on the order
            for ($i=0; $i -lt $auths.Count; $i++) {
                $fqdn = $auths[$i].fqdn

                if ($fqdn.StartsWith('*.')) {
                    # Ignore wildcards unless -AllowWildcard is specified.
                    if (-not $AllowWildcard) {
                        Write-Warning "Skipping dns-persist-01 for $fqdn because -AllowWildcard was not specified."
                        continue
                    }
                    # strip the wildcard characters for the rest of the processing since the validation record doesn't need them.
                    $fqdn = $fqdn.Substring(2)
                }

                $challenge = $auths[$i].challenges | Where-Object { $_.type -eq 'dns-persist-01' }
                if (-not $challenge) {
                    Write-Warning "Authz contains no dns-persist-01 challenge for $fqdn. Skipping."
                    continue
                }
                $issuers = $challenge.'issuer-domain-names'

                # Sanity check issuer-domain-names.
                # "Clients MUST consider a challenge malformed if the issuer-domain-names array is empty or if it contains
                # more than 10 entries, and MUST reject such challenges."
                # https://www.ietf.org/archive/id/draft-ietf-acme-dns-persist-01.html#section-3.1
                if (-not $issuers -or $issuers.Length -eq 0) {
                    Write-Warning "dns-persist-01 challenge for $fqdn has no issuer domain names. Skipping."
                    continue
                }
                if ($issuers.Length -gt 10) {
                    Write-Warning "dns-persist-01 challenge for $fqdn has more than 10 issuer domain names. Clients must reject this. Skipping."
                    continue
                }

                # "The order of names in the array has no significance."
                # https://www.ietf.org/archive/id/draft-ietf-acme-dns-persist-01.html#section-7.6
                # So sort them to make it more likely that we get the same value for each challenge on each run.
                $issuers = @($issuers | Sort-Object)

                # correlate the plugin args to the auth by index or use the last one available.
                if ($Order.Plugin.Count -gt $i) {
                    $plugin = $Order.Plugin[$i]
                } else {
                    $plugin = $Order.Plugin[-1]
                }

                # Sanitize the account URI for draft-00 challenges until Pebble supports the newer draft and includes it.
                if (-not $challenge.accounturi) {
                    Write-Warning "dns-persist-01 challenge for $fqdn is missing accounturi. Might be based on draft-00. Using account URI from account object instead."
                    $challenge | Add-Member accounturi $Account.location -Force
                }

                $chalCollection.Add([pscustomobject]@{
                    fqdn = $fqdn
                    accounturi = $challenge.accounturi
                    issuer = $issuers[0]
                    plugin = $plugin
                    pArgs = $pArgs
                })
            }

        } else {

            foreach ($d in $Domain) {
                # sanitize the domain if it was passed in as a wildcard on accident
                if ($d -and $d.StartsWith('*.')) {
                    Write-Warning "Stripping wildcard characters from $d. Not required for publishing."
                    $d = $d.Substring(2)
                }

                $chalCollection.Add([pscustomobject]@{
                    fqdn = $d
                    accounturi = $AccountUri
                    issuer = $IssuerDomainName
                    plugin = $Plugin
                    pArgs = $PluginArgs
                })

            }

        }

    }

    End {

        # Sort FQDNs by reverse label order so the most generic (example.com) comes first
        # and filter duplicate fqdns due to wildcard trimming.
        $chals = $chalCollection.ToArray() | Sort-Object {$a=$_.fqdn.Split('.'); [array]::Reverse($a); $a -join '.'} -Unique

        # filter out any challenges that would be covered by a previous record if wildcards are allowed
        # and -UseAllDomains was not specified
        $lastFqdn = $null
        $chals = foreach ($chal in $chals) {
            if ($chal.fqdn -like "*.$lastFqdn" -and $AllowWildcard -and -not $UseAllDomains) {
                Write-Verbose "Skipping $($chal.fqdn) because it's a wildcard match for $lastFqdn and should be covered by the same TXT record."
                continue
            }
            Write-Output $chal
            $lastFqdn = $chal.fqdn
        }

        # process what's left by plugin
        $chals | Group-Object plugin | ForEach-Object {

            # dot source the plugin file
            $pluginDetail = $script:Plugins.($_.Name)
            Write-Verbose "Loading plugin $($pluginDetail.Name)"
            . $pluginDetail.Path

            # process the group by unique pArgs
            $_.Group | Group-Object pArgs | ForEach-Object {
                $pArgs = $_.Group[0].pArgs

                foreach ($chal in $_.Group) {

                    Write-Verbose "Unpublishing dns-persist-01 challenge for $($chal.fqdn) using Plugin $($chal.plugin)."

                    $recordName = "_validation-persist.$($chal.fqdn)".TrimEnd('.')

                    # build the TXT value based on the input parameters
                    $txtValue = '{0}; accounturi={1}' -f $chal.issuer, $chal.accounturi
                    if ($AllowWildcard) {
                        $txtValue += '; policy=wildcard'
                    }
                    if ($PersistUntil) {
                        $txtValue += '; persistUntil={0}' -f $PersistUntil.ToUnixTimeSeconds()
                    }
                    $txtValue = '"{0}"' -f $txtValue

                    # call the function with the required parameters and splatting the rest
                    Write-Debug "Calling $($chal.plugin) plugin to remove $recordName TXT with value $txtValue"
                    Remove-DnsTxt -RecordName $recordName -TxtValue $txtValue @pArgs
                }

                # Save the changes for this plugin and pArgs combination
                Write-Verbose "Finalizing record changes for plugin $($pluginDetail.Name)."
                Save-DnsTxt @pArgs
            }

        }

    }

}