Public/Export-PDHostedSPF.ps1

function Export-PDHostedSPF {
    <#
    .SYNOPSIS
        Exports Hosted SPF (PowerSPF) configuration to one JSON file per domain.

    .DESCRIPTION
        Calls Get-PDHostedSPF for each domain ID and writes every returned record to its own JSON
        file under -Path, named after the domain. Modeled after the per-object JSON export
        pattern used by Microsoft's EntraExporter (https://github.com/microsoft/EntraExporter):
        one file per object rather than one combined dump, with a guard against exceeding the
        Windows 260-character MAX_PATH.

    .PARAMETER DomainId
        The PowerDMARC domain ID(s) to export. Accepts pipeline input, including by property name
        (e.g. piped from Get-PDDomain). If omitted (and nothing is piped in), every domain visible
        to Get-PDDomain is enumerated and exported -- in Reseller mode that means the selected
        account only, unless -All is also used.

    .PARAMETER All
        Reseller mode only. Passed through to Get-PDDomain: exports domains across every customer
        account instead of just the selected one. Ignored if -DomainId is also given.

    .PARAMETER Path
        Directory to write the exported JSON files to. Created if it doesn't already exist.

    .PARAMETER EnabledOnly
        Only export entries where is_enabled is true. Passed through to Get-PDHostedSPF.

    .PARAMETER Depth
        JSON serialization depth passed to ConvertTo-Json. Defaults to 10.

    .EXAMPLE
        Export-PDHostedSPF -DomainId 12345 -Path C:\Export

    .EXAMPLE
        Export-PDHostedSPF -Path C:\Export -EnabledOnly
        # Exports every domain under the selected account (Reseller mode requires an AccountId
        # via Select-PDAccount or Connect-PowerDMARC -AccountId).

    .EXAMPLE
        Export-PDHostedSPF -All -Path C:\Export -EnabledOnly
        # Exports every domain across every customer account (Reseller mode).

    .EXAMPLE
        Get-PDDomain | Export-PDHostedSPF -Path C:\Export -EnabledOnly

    .LINK
        https://github.com/microsoft/EntraExporter
    #>

    [CmdletBinding()]
    [OutputType([System.IO.FileInfo])]
    param(
        [Parameter(Position = 0, ValueFromPipeline, ValueFromPipelineByPropertyName)]
        [Alias('Id')]
        [int[]]$DomainId,

        [Parameter()]
        [switch]$All,

        [Parameter(Mandatory)]
        [string]$Path,

        [Parameter()]
        [switch]$EnabledOnly,

        [Parameter()]
        [int]$Depth = 10
    )

    begin {
        if (-not (Test-Path -LiteralPath $Path)) {
            $null = New-Item -Path $Path -ItemType Directory -Force
        }
        $resolvedPath = (Resolve-Path -LiteralPath $Path).ProviderPath
    }

    process {
        $ids = if ($PSBoundParameters.ContainsKey('DomainId') -and $DomainId) {
            $DomainId
        } elseif ($All) {
            (Get-PDDomain -All).id
        } else {
            (Get-PDDomain).id
        }

        foreach ($id in $ids) {
            $getParams = @{ DomainId = $id }
            if ($EnabledOnly) { $getParams['EnabledOnly'] = $true }

            foreach ($record in (Get-PDHostedSPF @getParams)) {
                $domainLabel = if ($record.domain) { $record.domain } else { "domain-$($record.DomainId)" }
                $fileName = "$($domainLabel -replace '[\\/:*?"<>|]', '_').json"
                $filePath = Join-Path -Path $resolvedPath -ChildPath $fileName

                if ($filePath.Length -ge 260) {
                    Write-Error "Skipping '$domainLabel': resulting path exceeds 260 characters ('$filePath')."
                    continue
                }

                $record | ConvertTo-Json -Depth $Depth | Out-File -LiteralPath $filePath -Encoding utf8 -Force
                Get-Item -LiteralPath $filePath
            }
        }
    }
}