Private/Export-PluginArgs.ps1

function Export-PluginArgs {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory,Position=0,ValueFromPipeline,ValueFromPipelineByPropertyName)]
        [string]$MainDomain,
        [Parameter(Mandatory,Position=1)]
        [string[]]$Plugin,
        [Parameter(Mandatory,Position=2)]
        [hashtable]$PluginArgs,
        [switch]$IgnoreExisting
    )

    # In this function, we're trying to merge the specified plugin args with the existing set
    # of saved plugin arg data on disk. But some plugins have parameter sets that can
    # end up causing AmbiguousParameterSet errors if we just naively merge all new args.
    # So essentially what we're going to do is this for each specified plugin:
    # - query all supported args
    # - if any $PluginArgs match
    # - check for saved plugin args that match and remove them
    # - add the new args to the saved data
    #
    # This should allow you to do something like add names to an existing cert where the new names
    # utilize a different plugin than the previous ones and only need to specify the new plugin's
    # parameters in $PluginArgs.

    Begin {
        # Make sure we have an account configured
        if (-not ($acct = Get-PAAccount)) {
            throw "No ACME account configured. Run Set-PAAccount or New-PAAccount first."
        }
    }

    Process {
        trap { $PSCmdlet.ThrowTerminatingError($PSItem) }

        Write-Debug "Exporting plugin args for $MainDomain with plugins $(($Plugin -join ','))"

        # throw an error if an order isn't found matching MainDomain
        if (-not ($order = Get-PAOrder $MainDomain)) {
            throw "No ACME order found for $MainDomain."
        }

        $pData = @{}
        if (-not $IgnoreExisting) {
            $pData = Get-PAPluginArgs $order.MainDomain
        }

        # define the set of parameter names to ignore
        $ignoreParams = @('RecordName','TxtValue','Url','Body') +
            [Management.Automation.PSCmdlet]::CommonParameters +
            [Management.Automation.PSCmdlet]::OptionalCommonParameters

        # $Plugin will most often come with duplicates after being called from Submit-ChallengeValidation
        # So grab just the unique set.
        $uniquePlugins = @($Plugin | Sort-Object -Unique)

        # Get all of the plugin specific parameter names for the current plugin list
        $paramNames = foreach ($p in $uniquePlugins) {

            Write-Debug "Attempting to load plugin $p"

            $pluginDetail = $script:Plugins.$p
            if (-not $pluginDetail) {
                Write-Error "$p plugin not found or was invalid."
                continue
            }

            # dot source the plugin file
            . $pluginDetail.Path

            # grab a reference to the appropriate Add command
            if ('dns-01' -eq $pluginDetail.ChallengeType) {
                $cmd = Get-Command Add-DnsTxt
            } else {
                $cmd = Get-Command Add-HttpChallenge
            }

            # return the set of non-common param names
            $cmd.Parameters.Keys | Where-Object {
                ($_ -notin $ignoreParams) -and
                ($true -notin $cmd.Parameters[$_].Attributes.ValueFromRemainingArguments)
            }
        }

        # Remove any old args that may conflict with the new ones
        foreach ($key in @($pData.Keys)) {
            if ($key -in $paramNames) {
                Write-Debug "Removing old value for $key"
                $pData.Remove($key)
            }
        }

        # Add new args to the old data
        foreach ($key in ($PluginArgs.Keys | Where-Object { $_ -in $paramNames })) {
            Write-Debug "Adding new value for $key"
            $pData.$key = $PluginArgs.$key
        }

        # Now we need to export the merged object as JSON
        # but we have to pre-serialize things like SecureString and PSCredential
        # first because ConvertTo-Json can't deal with those natively.

        # determine whether we're using a custom key
        $encParam = @{}
        if (-not [String]::IsNullOrEmpty($acct.sskey)) {
            $encParam.Key = $acct.sskey | ConvertFrom-Base64Url -AsByteArray
        }

        $pDataSafe = @{}
        foreach ($key in $pData.Keys) {
            if ($pData.$key -is [securestring]) {
                $pDataSafe.$key = [pscustomobject]@{
                    origType = 'securestring'
                    value = $pData.$key | ConvertFrom-SecureString @encParam
                }
            }
            elseif ($pData.$key -is [pscredential]) {
                $pDataSafe.$key = [pscustomobject]@{
                    origType = 'pscredential'
                    user = $pData.$key.Username
                    pass = $pData.$key.Password | ConvertFrom-SecureString @encParam
                }
            }
            else {
                # for now, assume everything else is safe to auto serialize
                $pDataSafe.$key = $pData.$key
            }
        }

        # build the path to the existing plugin data file and export it
        $pDataFile = Join-Path $order.Folder 'pluginargs.json'
        $pDataSafe | ConvertTo-Json -Depth 10 | Out-File $pDataFile -Encoding utf8
    }

}