Security/Get-DefenderPolicyReport.ps1

<#
.SYNOPSIS
    Reports Safe Links and Safe Attachments policies from Microsoft Defender for Office 365.
.DESCRIPTION
    Retrieves all Safe Links and Safe Attachments policies configured in the tenant
    and produces a unified report showing policy type, enabled state, priority, and
    key configuration settings. Useful for security assessments, baseline reviews,
    and verifying Defender for Office 365 protection is properly configured.
 
    Handles tenants where Defender for Office 365 is not licensed by skipping
    unavailable cmdlets gracefully.
 
    Requires ExchangeOnlineManagement module and an active Exchange Online connection.
.PARAMETER OutputPath
    Optional path to export results as CSV. If not specified, results are returned
    to the pipeline.
.EXAMPLE
    PS> . .\Common\Connect-Service.ps1
    PS> Connect-Service -Service ExchangeOnline
    PS> .\Security\Get-DefenderPolicyReport.ps1
 
    Displays all Safe Links and Safe Attachments policies in the tenant.
.EXAMPLE
    PS> .\Security\Get-DefenderPolicyReport.ps1 -OutputPath '.\defender-policies.csv'
 
    Exports all Defender for Office 365 policies to CSV.
.EXAMPLE
    PS> .\Security\Get-DefenderPolicyReport.ps1 -Verbose
 
    Displays policies with verbose processing details, including skip messages for
    unlicensed features.
#>

[CmdletBinding()]
param(
    [Parameter()]
    [string]$OutputPath
)

$ErrorActionPreference = 'Stop'

# Verify Exchange Online connection
try {
    $null = Get-OrganizationConfig -ErrorAction Stop
}
catch {
    Write-Error "Not connected to Exchange Online. Run Connect-Service -Service ExchangeOnline first."
    return
}

$results = [System.Collections.Generic.List[PSCustomObject]]::new()

# --- Safe Links Policies ---
$safeLinksAvailable = $true
try {
    $null = Get-Command -Name Get-SafeLinksPolicy -ErrorAction Stop
}
catch {
    $safeLinksAvailable = $false
    Write-Warning "Get-SafeLinksPolicy cmdlet not found. Defender for Office 365 (Safe Links) may not be licensed for this tenant. Skipping Safe Links policies."
}

if ($safeLinksAvailable) {
    Write-Verbose "Retrieving Safe Links policies..."
    try {
        $safeLinksPolices = Get-SafeLinksPolicy -ErrorAction Stop

        if (-not $safeLinksPolices -or @($safeLinksPolices).Count -eq 0) {
            Write-Verbose "No Safe Links policies found."
        }
        else {
            foreach ($policy in @($safeLinksPolices)) {
                $keySettings = @(
                    "IsEnabled=$($policy.IsEnabled)"
                    "DoNotTrackUserClicks=$($policy.DoNotTrackUserClicks)"
                    "ScanUrls=$($policy.ScanUrls)"
                    "EnableForInternalSenders=$($policy.EnableForInternalSenders)"
                ) -join '; '

                $results.Add([PSCustomObject]@{
                    PolicyType  = 'SafeLinks'
                    Name        = $policy.Name
                    Enabled     = $policy.IsEnabled
                    Priority    = if ($null -ne $policy.Priority) { $policy.Priority } else { 'N/A' }
                    KeySettings = $keySettings
                })
            }
            Write-Verbose "Found $(@($safeLinksPolices).Count) Safe Links policies."
        }
    }
    catch {
        Write-Warning "Failed to retrieve Safe Links policies: $_"
    }
}

# --- Safe Attachments Policies ---
$safeAttachmentsAvailable = $true
try {
    $null = Get-Command -Name Get-SafeAttachmentPolicy -ErrorAction Stop
}
catch {
    $safeAttachmentsAvailable = $false
    Write-Warning "Get-SafeAttachmentPolicy cmdlet not found. Defender for Office 365 (Safe Attachments) may not be licensed for this tenant. Skipping Safe Attachments policies."
}

if ($safeAttachmentsAvailable) {
    Write-Verbose "Retrieving Safe Attachments policies..."
    try {
        $safeAttachmentPolicies = Get-SafeAttachmentPolicy -ErrorAction Stop

        if (-not $safeAttachmentPolicies -or @($safeAttachmentPolicies).Count -eq 0) {
            Write-Verbose "No Safe Attachments policies found."
        }
        else {
            foreach ($policy in @($safeAttachmentPolicies)) {
                $keySettings = @(
                    "Enable=$($policy.Enable)"
                    "Action=$($policy.Action)"
                    "Redirect=$($policy.Redirect)"
                    "RedirectAddress=$($policy.RedirectAddress)"
                ) -join '; '

                $results.Add([PSCustomObject]@{
                    PolicyType  = 'SafeAttachments'
                    Name        = $policy.Name
                    Enabled     = $policy.Enable
                    Priority    = if ($null -ne $policy.Priority) { $policy.Priority } else { 'N/A' }
                    KeySettings = $keySettings
                })
            }
            Write-Verbose "Found $(@($safeAttachmentPolicies).Count) Safe Attachments policies."
        }
    }
    catch {
        Write-Warning "Failed to retrieve Safe Attachments policies: $_"
    }
}

# Output results
if ($results.Count -eq 0) {
    Write-Warning "No Defender for Office 365 policies found. Verify that the tenant is licensed for Microsoft Defender for Office 365."
    return
}

Write-Verbose "Total policies found: $($results.Count)"

if ($OutputPath) {
    $results | Export-Csv -Path $OutputPath -NoTypeInformation -Encoding UTF8
    Write-Output "Exported $($results.Count) Defender for Office 365 policies to $OutputPath"
}
else {
    Write-Output $results
}