Checks/EXO/MET-EXO022-SharingPolicy.ps1

try {
    $policies = @(Get-SharingPolicy -ErrorAction Stop)
}
catch {
    New-METCheckResult -CheckId 'MET-EXO022' -Category EXO -Name 'Calendar and Contact Sharing Policies' `
        -Result Fail -Severity Medium -AffectedObject 'Sharing Policies' `
        -Finding 'Unable to retrieve sharing policies' `
        -Recommendation 'Ensure the account has Exchange View-Only Configuration or higher permissions.' `
        -ReferenceUrl 'https://learn.microsoft.com/en-us/exchange/sharing/sharing-policies/sharing-policies' -ErrorMessage $_.ToString()
    return
}

if ($policies.Count -eq 0) {
    New-METCheckResult -CheckId 'MET-EXO022' -Category EXO -Name 'Calendar and Contact Sharing Policies' `
        -Result Info -Severity Medium -AffectedObject 'Sharing Policies' `
        -Finding 'No sharing policies are configured in this tenant, so no organization-wide calendar or contact sharing relationship is defined' `
        -ReferenceUrl 'https://learn.microsoft.com/en-us/exchange/sharing/sharing-policies/sharing-policies'
    return
}

foreach ($policy in $policies) {
    $policyName = [string]$policy.Name
    if ([string]::IsNullOrWhiteSpace($policyName)) {
        $policyName = 'Unnamed sharing policy'
    }

    $affectedObject = $policyName
    if ($policy.Default -eq $true) {
        $affectedObject = "$policyName (default)"
    }

    if ($policy.Enabled -ne $true) {
        New-METCheckResult -CheckId 'MET-EXO022' -Category EXO -Name 'Calendar and Contact Sharing Policies' `
            -Result Info -Severity Medium -AffectedObject $affectedObject `
            -Finding 'This sharing policy exists but is disabled, so none of its sharing rules currently apply to any mailbox' `
            -ReferenceUrl 'https://learn.microsoft.com/en-us/exchange/sharing/sharing-policies/sharing-policies'
        continue
    }

    $domainEntries = @(@($policy.Domains) | Where-Object { -not [string]::IsNullOrWhiteSpace([string]$_) })

    $permissiveEntries = [System.Collections.Generic.List[string]]::new()
    $freeBusyOnlyEntries = [System.Collections.Generic.List[string]]::new()

    foreach ($entry in $domainEntries) {
        $entryText = ([string]$entry).Trim()
        $separatorIndex = $entryText.IndexOf(':')
        if ($separatorIndex -lt 0) {
            $domainPart = $entryText
            $actionPart = ''
        }
        else {
            $domainPart = $entryText.Substring(0, $separatorIndex).Trim()
            $actionPart = $entryText.Substring($separatorIndex + 1).Trim()
        }

        if ($domainPart -ne '*' -and $domainPart -ne 'Anonymous') { continue }

        if ($actionPart -match 'Reviewer|Detail|ContactsSharing') {
            $permissiveEntries.Add($entryText)
        }
        else {
            $freeBusyOnlyEntries.Add($entryText)
        }
    }

    $recommendation = "Scope sharing to named partner domains with: Set-SharingPolicy -Identity '$policyName' -Domains 'partner.example:CalendarSharingFreeBusySimple'. Where a wildcard or anonymous entry is genuinely required, downgrade it to CalendarSharingFreeBusySimple rather than a Reviewer, Detail, or ContactsSharing level."

    if ($permissiveEntries.Count -gt 0) {
        New-METCheckResult -CheckId 'MET-EXO022' -Category EXO -Name 'Calendar and Contact Sharing Policies' `
            -Result Warning -Severity Medium -AffectedObject $affectedObject `
            -Finding "This enabled sharing policy shares more than simple free/busy with every domain or anonymously: $($permissiveEntries -join ', ') - calendar detail and contact sharing at this scope hands an attacker an org chart, meeting subjects, attendee lists and internal addresses, which is exactly the reconnaissance an internal-impersonation phish is built from" `
            -Recommendation $recommendation `
            -ReferenceUrl 'https://learn.microsoft.com/en-us/exchange/sharing/sharing-policies/sharing-policies' `
            -Metadata @{ DomainEntryCount = $domainEntries.Count; PermissiveEntryCount = $permissiveEntries.Count }
    }
    elseif ($freeBusyOnlyEntries.Count -gt 0) {
        New-METCheckResult -CheckId 'MET-EXO022' -Category EXO -Name 'Calendar and Contact Sharing Policies' `
            -Result Pass -Severity Medium -AffectedObject $affectedObject `
            -Finding "This enabled sharing policy has wildcard or anonymous entries, but they share simple free/busy only: $($freeBusyOnlyEntries -join ', ') - no calendar detail, attendee list, or contact data is exposed" `
            -ReferenceUrl 'https://learn.microsoft.com/en-us/exchange/sharing/sharing-policies/sharing-policies' `
            -Metadata @{ DomainEntryCount = $domainEntries.Count; PermissiveEntryCount = 0 }
    }
    elseif ($domainEntries.Count -eq 0) {
        New-METCheckResult -CheckId 'MET-EXO022' -Category EXO -Name 'Calendar and Contact Sharing Policies' `
            -Result Pass -Severity Medium -AffectedObject $affectedObject `
            -Finding 'This enabled sharing policy defines no sharing domain entries, so it grants no external calendar or contact sharing' `
            -ReferenceUrl 'https://learn.microsoft.com/en-us/exchange/sharing/sharing-policies/sharing-policies' `
            -Metadata @{ DomainEntryCount = 0; PermissiveEntryCount = 0 }
    }
    else {
        New-METCheckResult -CheckId 'MET-EXO022' -Category EXO -Name 'Calendar and Contact Sharing Policies' `
            -Result Pass -Severity Medium -AffectedObject $affectedObject `
            -Finding "This enabled sharing policy has no wildcard or anonymous entries - all $($domainEntries.Count) sharing relationship(s) are scoped to named domains" `
            -ReferenceUrl 'https://learn.microsoft.com/en-us/exchange/sharing/sharing-policies/sharing-policies' `
            -Metadata @{ DomainEntryCount = $domainEntries.Count; PermissiveEntryCount = 0 }
    }
}