Public/Get-ModernDomainScanReport.ps1

# Get-ModernDomainScanReport
# https://dss.globalcyberalliance.org/api/v1/docs#tag/version
# curl https://dss.globalcyberalliance.org/api/v1/scan/example.com

function Get-ModernDomainScanReport {
    <#
    .SYNOPSIS
        Retrieves and optionally displays a domain security scan report from Global Cyber Alliance.
    .DESCRIPTION
        [Global Cyber Alliance](https://github.com/GlobalCyberAlliance/domain-security-scanner)
    .PARAMETER Domain
        The domain name to scan (e.g., example.com).
    .PARAMETER Raw
        If specified, returns the raw 'advice' object instead of formatted output.
    .EXAMPLE
        Get-ModernDomainScanReport -Domain "contoso.com"
    .EXAMPLE
        Get-ModernDomainScanReport -Domain "contoso.com" -Raw
    .LINK
        https://exchangepermissions.alweys.ch/modernmailtools/Get-ModernDomainScanReport
    .INPUTS
        None
    .OUTPUTS
    .NOTES
    #>


    [CmdletBinding(DefaultParameterSetName = 'Formatted')]
    param (
        [Parameter(Mandatory = $true, Position = 0)]
        [ValidateNotNullOrEmpty()]
        [string]$Domain, # $Domain = "globalcyberalliance.org"

        [Parameter(ParameterSetName = 'Advice')]
        [switch]$Advice,

        [Parameter(ParameterSetName = 'Advice')]
        [switch]$Raw, # Legacy alias for -Advice

        [Parameter(ParameterSetName = 'Result')]
        [switch]$Result,

        [Parameter(ParameterSetName = 'Advice')]
        [Parameter(ParameterSetName = 'Result')]
        [ValidateSet('domain', 'bimi', 'dkim', 'dmarc', 'mx', 'ns', 'spf')]
        [string]$Type
    )

    $url = "https://dss.globalcyberalliance.org/api/v1/scan/$Domain"

    try {
        Write-Verbose "Requesting scan report from: $url"
        $response = Invoke-RestMethod -Uri $url -Method Get -ErrorAction Stop

        $hasTypeFilter = $PSBoundParameters.ContainsKey('Type') -and -not [string]::IsNullOrWhiteSpace($Type)

        if ($PSCmdlet.ParameterSetName -eq 'Advice') {
            if ($hasTypeFilter) {
                $prop = $response.advice.PSObject.Properties[$Type]
                if ($null -ne $prop) {
                    return $prop.Value
                }
                return $null
            }

            return $response.advice
        }

        if ($PSCmdlet.ParameterSetName -eq 'Result') {
            # [bool]$response.scanResult.mx
            if ($hasTypeFilter) {
                $prop = $response.scanResult.PSObject.Properties[$Type]
                if ($null -ne $prop) {
                    return $prop.Value
                }
                return $null
            }

            return $response.scanResult
        }

        if ($PSCmdlet.ParameterSetName -eq 'Formatted') {
            Write-Output "`n=== Advice Section ===`n"
            foreach ($key in $response.advice.PSObject.Properties.Name) {
                $value = $response.advice.$key
                if ($value -is [System.Collections.IEnumerable] -and -not ($value -is [string])) {
                    Write-Output "${key}:`n$($value -join "`n")`n"
                } else {
                    Write-Output "${key}: $value`n"
                }
            }

            $adviceHashtable = @{}
            foreach ($property in $response.advice.PSObject.Properties) {
                $adviceHashtable[$property.Name] = $property.Value
            }

            $evaluation = EvaluateAdvice -Advice $adviceHashtable
            Write-Output "`n=== Evaluation Section ===`n"
            Write-Output "Actionable Items:`n$($evaluation.ActionableItems -join "`n")`n"
            Write-Output "Warnings:`n$($evaluation.Warnings -join "`n")`n"
            Write-Output "Confirmations:`n$($evaluation.Confirmations -join "`n")`n"

            Write-Output "`n=== Scan Result Section ===`n"
            foreach ($key in $response.scanResult.PSObject.Properties.Name) {
                $value = $response.scanResult.$key
                if ($value -is [System.Collections.IEnumerable] -and -not ($value -is [string])) {
                    Write-Output "${key}:`n$($value -join "`n")`n"
                } else {
                    Write-Output "${key}: $value`n"
                }
            }
        }
    } catch {
        $err = $_
        $contextMsg = "Failed to retrieve scan report for domain '$Domain' from '$url'."
        try {
            $err.ErrorDetails = [System.Management.Automation.ErrorDetails]::new($contextMsg)
        } catch {
            Write-Verbose "Could not set ErrorDetails: $($_.Exception.Message)"
        }

        $PSCmdlet.ThrowTerminatingError($err)
    }
}

function EvaluateAdvice {
    param (
        [Parameter(Mandatory = $true)]
        [hashtable]$Advice
    )

    $evaluation = @{
        ActionableItems = @()
        Warnings = @()
        Confirmations = @()
    }

    foreach ($key in $Advice.Keys) {
        $value = $Advice[$key]
        if ($value -is [System.Collections.IEnumerable] -and -not ($value -is [string])) {
            foreach ($item in $value) {
                if ($item -match "fix|setup|recommended") {
                    $evaluation.ActionableItems += "${key}: $item"
                } elseif ($item -match "couldn't|failed|not detected") {
                    $evaluation.Warnings += "${key}: $item"
                } elseif ($item -match "is setup|no further action needed") {
                    $evaluation.Confirmations += "${key}: $item"
                } else {
                    $evaluation.Warnings += "${key}: $item" # Default to warnings for ambiguous cases
                }
            }
        } else {
            if ($value -match "fix|setup|recommended") {
                $evaluation.ActionableItems += "${key}: $value"
            } elseif ($value -match "couldn't|failed|not detected") {
                $evaluation.Warnings += "${key}: $value"
            } elseif ($value -match "is setup|no further action needed") {
                $evaluation.Confirmations += "${key}: $value"
            } else {
                $evaluation.Warnings += "${key}: $value" # Default to warnings for ambiguous cases
            }
        }
    }

    return $evaluation
}