Private/Entra/Checks/Invoke-EntraTenantChecks.ps1

# PSGuerrilla - Jim Tyler, Microsoft MVP - CC BY 4.0
# https://github.com/jimrtyler/PSGuerrilla | https://creativecommons.org/licenses/by/4.0/
# AI/LLM use: see AI-USAGE.md for required attribution
function Invoke-EntraTenantChecks {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [hashtable]$AuditData
    )

    $checkDefs = Get-AuditCategoryDefinitions -Category 'EntraTenantChecks'
    $findings = [System.Collections.Generic.List[PSCustomObject]]::new()

    foreach ($check in $checkDefs.checks) {
        $funcName = "Test-Infiltration$($check.id -replace '-', '')"
        if (Get-Command $funcName -ErrorAction SilentlyContinue) {
            try {
                $finding = & $funcName -AuditData $AuditData -CheckDefinition $check
                if ($finding) { $findings.Add($finding) }
            } catch {
                $findings.Add((New-AuditFinding -CheckDefinition $check -Status 'ERROR' `
                    -CurrentValue "Check failed: $_"))
            }
        } else {
            $findings.Add((New-AuditFinding -CheckDefinition $check -Status 'SKIP' `
                -CurrentValue 'Check not yet implemented'))
        }
    }

    return @($findings)
}

# ── EIDTNT-001: Tenant Settings Export ───────────────────────────────────
function Test-InfiltrationEIDTNT001 {
    [CmdletBinding()]
    param([hashtable]$AuditData, [hashtable]$CheckDefinition)

    $org = $AuditData.TenantConfig.Organization
    if (-not $org) {
        return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'WARN' `
            -CurrentValue 'Organization data not available' `
            -Details @{ OrganizationAvailable = $false }
    }

    $tenantId = $org.id
    $displayName = $org.displayName
    $verifiedDomains = @($org.verifiedDomains)
    $technicalContacts = @($org.technicalNotificationMails ?? @())
    $securityComplianceContact = $org.securityComplianceNotificationMails

    return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'PASS' `
        -CurrentValue "Tenant: $displayName ($tenantId), $($verifiedDomains.Count) verified domains" `
        -Details @{
            TenantId                = $tenantId
            DisplayName             = $displayName
            Country                 = $org.countryLetterCode
            PreferredLanguage       = $org.preferredLanguage
            CreatedDateTime         = $org.createdDateTime
            VerifiedDomainCount     = $verifiedDomains.Count
            VerifiedDomains         = @($verifiedDomains | ForEach-Object {
                @{ Name = $_.name; Type = $_.type; IsDefault = $_.isDefault; IsInitial = $_.isInitial }
            })
            TechnicalContacts       = @($technicalContacts)
            OnPremisesSyncEnabled   = $org.onPremisesSyncEnabled
            DirectorySizeQuota      = $org.directorySizeQuota
            AssignedPlans           = @($org.assignedPlans | Select-Object -First 20 | ForEach-Object {
                @{ Service = $_.service; CapabilityStatus = $_.capabilityStatus }
            })
        }
}

# ── EIDTNT-002: User Settings ────────────────────────────────────────────
function Test-InfiltrationEIDTNT002 {
    [CmdletBinding()]
    param([hashtable]$AuditData, [hashtable]$CheckDefinition)

    $authzPolicy = $AuditData.TenantConfig.AuthorizationPolicy
    if (-not $authzPolicy) {
        return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'SKIP' `
            -CurrentValue 'Authorization policy not available'
    }

    $defaultPerms = $authzPolicy.defaultUserRolePermissions
    $allowCreateApps = $defaultPerms.allowedToCreateApps ?? $true
    $allowCreateGroups = $defaultPerms.allowedToCreateSecurityGroups ?? $true
    $allowReadOtherUsers = $defaultPerms.allowedToReadOtherUsers ?? $true
    $allowCreateTenants = $defaultPerms.allowedToCreateTenants ?? $true

    $issues = [System.Collections.Generic.List[string]]::new()
    if ($allowCreateApps) { $issues.Add('Users can create app registrations') }
    if ($allowCreateGroups) { $issues.Add('Users can create security groups') }
    if ($allowCreateTenants) { $issues.Add('Users can create tenants') }

    $status = if ($issues.Count -eq 0) { 'PASS' }
              elseif ($issues.Count -le 1) { 'WARN' }
              else { 'FAIL' }

    return New-AuditFinding -CheckDefinition $CheckDefinition -Status $status `
        -CurrentValue "Default user permissions: create apps=$allowCreateApps, create groups=$allowCreateGroups, read users=$allowReadOtherUsers, create tenants=$allowCreateTenants" `
        -Details @{
            AllowedToCreateApps           = $allowCreateApps
            AllowedToCreateSecurityGroups = $allowCreateGroups
            AllowedToReadOtherUsers       = $allowReadOtherUsers
            AllowedToCreateTenants        = $allowCreateTenants
            Issues                        = @($issues)
        }
}

# ── EIDTNT-003: Guest Access Restrictions ────────────────────────────────
function Test-InfiltrationEIDTNT003 {
    [CmdletBinding()]
    param([hashtable]$AuditData, [hashtable]$CheckDefinition)

    $authzPolicy = $AuditData.TenantConfig.AuthorizationPolicy
    if (-not $authzPolicy) {
        return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'SKIP' `
            -CurrentValue 'Authorization policy not available'
    }

    $guestUserRoleId = $authzPolicy.guestUserRoleId

    # Guest role GUIDs:
    # a0b1b346-4d3e-4e8b-98f8-753987be4970 = Same as member users (most permissive)
    # 10dae51f-b6af-4016-8d66-8c2a99b929b3 = Limited access (default)
    # 2af84b1e-32c8-42b7-82bc-daa82404023b = Restricted access (most restrictive)

    $roleMapping = @{
        'a0b1b346-4d3e-4e8b-98f8-753987be4970' = @{ Name = 'Same as member users'; Risk = 'High' }
        '10dae51f-b6af-4016-8d66-8c2a99b929b3' = @{ Name = 'Limited access (default)'; Risk = 'Medium' }
        '2af84b1e-32c8-42b7-82bc-daa82404023b' = @{ Name = 'Restricted access'; Risk = 'Low' }
    }

    $roleInfo = $roleMapping[$guestUserRoleId]
    if (-not $roleInfo) {
        $roleInfo = @{ Name = "Unknown ($guestUserRoleId)"; Risk = 'Unknown' }
    }

    $status = switch ($roleInfo.Risk) {
        'Low'    { 'PASS' }
        'Medium' { 'WARN' }
        'High'   { 'FAIL' }
        default  { 'WARN' }
    }

    return New-AuditFinding -CheckDefinition $CheckDefinition -Status $status `
        -CurrentValue "Guest user access level: $($roleInfo.Name)" `
        -Details @{
            GuestUserRoleId   = $guestUserRoleId
            AccessLevel       = $roleInfo.Name
            RiskLevel         = $roleInfo.Risk
        }
}

# ── EIDTNT-004: Guest Invitation Restrictions ───────────────────────────
function Test-InfiltrationEIDTNT004 {
    [CmdletBinding()]
    param([hashtable]$AuditData, [hashtable]$CheckDefinition)

    $authzPolicy = $AuditData.TenantConfig.AuthorizationPolicy
    if (-not $authzPolicy) {
        return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'SKIP' `
            -CurrentValue 'Authorization policy not available'
    }

    $allowInvitesFrom = $authzPolicy.allowInvitesFrom

    # allowInvitesFrom values:
    # none = No one can invite (most restrictive)
    # adminsAndGuestInviters = Only admins and guest inviter role
    # adminsGuestInvitersAndAllMembers = Admins, guest inviters, and all members
    # everyone = Anyone including guests can invite (least restrictive)

    $inviteMapping = @{
        'none'                                   = @{ Description = 'No one can invite guests'; Risk = 'Low' }
        'adminsAndGuestInviters'                  = @{ Description = 'Only admins and users in Guest Inviter role'; Risk = 'Low' }
        'adminsGuestInvitersAndAllMembers'        = @{ Description = 'Admins, Guest Inviters, and all member users'; Risk = 'Medium' }
        'everyone'                                = @{ Description = 'Everyone including guests can invite'; Risk = 'High' }
    }

    $inviteInfo = $inviteMapping[$allowInvitesFrom]
    if (-not $inviteInfo) {
        $inviteInfo = @{ Description = "Unknown ($allowInvitesFrom)"; Risk = 'Unknown' }
    }

    $status = switch ($inviteInfo.Risk) {
        'Low'    { 'PASS' }
        'Medium' { 'WARN' }
        'High'   { 'FAIL' }
        default  { 'WARN' }
    }

    return New-AuditFinding -CheckDefinition $CheckDefinition -Status $status `
        -CurrentValue "Guest invitation setting: $($inviteInfo.Description)" `
        -Details @{
            AllowInvitesFrom = $allowInvitesFrom
            Description      = $inviteInfo.Description
            RiskLevel        = $inviteInfo.Risk
        }
}

# ── EIDTNT-005: External Collaboration Settings ─────────────────────────
function Test-InfiltrationEIDTNT005 {
    [CmdletBinding()]
    param([hashtable]$AuditData, [hashtable]$CheckDefinition)

    $crossTenantAccess = $AuditData.TenantConfig.CrossTenantAccess
    if (-not $crossTenantAccess) {
        return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'SKIP' `
            -CurrentValue 'Cross-tenant access policy not available'
    }

    $defaultPolicy = $crossTenantAccess.default ?? $crossTenantAccess

    $inboundDefault = $defaultPolicy.b2bCollaborationInbound
    $outboundDefault = $defaultPolicy.b2bCollaborationOutbound
    $inboundDirectConnect = $defaultPolicy.b2bDirectConnectInbound
    $outboundDirectConnect = $defaultPolicy.b2bDirectConnectOutbound

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

    # Check if inbound is overly permissive
    if ($inboundDefault.usersAndGroups.accessType -eq 'allowed' -and
        ($inboundDefault.usersAndGroups.targets | Where-Object { $_.target -eq 'AllUsers' })) {
        $issues.Add('Inbound B2B collaboration allows all external users by default')
    }

    # Check if outbound is overly permissive
    if ($outboundDefault.usersAndGroups.accessType -eq 'allowed' -and
        ($outboundDefault.usersAndGroups.targets | Where-Object { $_.target -eq 'AllUsers' })) {
        $issues.Add('Outbound B2B collaboration allows all users to access external tenants')
    }

    $status = if ($issues.Count -eq 0) { 'PASS' }
              elseif ($issues.Count -eq 1) { 'WARN' }
              else { 'FAIL' }

    return New-AuditFinding -CheckDefinition $CheckDefinition -Status $status `
        -CurrentValue "Cross-tenant access default policy reviewed, $($issues.Count) issue(s) found" `
        -Details @{
            IssueCount               = $issues.Count
            Issues                   = @($issues)
            InboundB2BCollaboration  = $inboundDefault
            OutboundB2BCollaboration = $outboundDefault
            InboundDirectConnect     = $inboundDirectConnect
            OutboundDirectConnect    = $outboundDirectConnect
        }
}

# ── EIDTNT-006: B2B Cross-Tenant Access Partners ────────────────────────
function Test-InfiltrationEIDTNT006 {
    [CmdletBinding()]
    param([hashtable]$AuditData, [hashtable]$CheckDefinition)

    $partners = $AuditData.TenantConfig.CrossTenantPartners
    if (-not $partners -or $partners.Count -eq 0) {
        return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'PASS' `
            -CurrentValue 'No cross-tenant access partner configurations — default policy applies to all external tenants' `
            -Details @{ PartnerCount = 0 }
    }

    $partnerDetails = @($partners | ForEach-Object {
        @{
            TenantId                 = $_.tenantId
            IsServiceProvider        = $_.isServiceProvider
            InboundTrust             = $_.inboundTrust
            B2BCollaborationInbound  = $_.b2bCollaborationInbound
            B2BCollaborationOutbound = $_.b2bCollaborationOutbound
            B2BDirectConnectInbound  = $_.b2bDirectConnectInbound
            B2BDirectConnectOutbound = $_.b2bDirectConnectOutbound
        }
    })

    # Warn if there are many partner configurations as they increase attack surface
    $status = if ($partners.Count -le 5) { 'PASS' }
              elseif ($partners.Count -le 20) { 'WARN' }
              else { 'FAIL' }

    return New-AuditFinding -CheckDefinition $CheckDefinition -Status $status `
        -CurrentValue "$($partners.Count) cross-tenant partner configurations — review for necessity and trust level" `
        -Details @{
            PartnerCount = $partners.Count
            Partners     = @($partnerDetails | Select-Object -First 50)
        }
}

# ── EIDTNT-007: Security Defaults ────────────────────────────────────────
function Test-InfiltrationEIDTNT007 {
    [CmdletBinding()]
    param([hashtable]$AuditData, [hashtable]$CheckDefinition)

    $securityDefaults = $AuditData.TenantConfig.SecurityDefaults
    if (-not $securityDefaults) {
        return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'WARN' `
            -CurrentValue 'Security defaults policy not available' `
            -Details @{ PolicyAvailable = $false }
    }

    $isEnabled = $securityDefaults.isEnabled

    if ($isEnabled) {
        return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'PASS' `
            -CurrentValue 'Security defaults are enabled — baseline protections active' `
            -Details @{
                IsEnabled     = $true
                Description   = $securityDefaults.description
            }
    }

    # Security defaults disabled — check if CA policies exist as a replacement
    $caPolicies = $AuditData.ConditionalAccess.Policies
    $hasCAPolicies = $caPolicies -and $caPolicies.Count -gt 0
    $enabledCAPolicies = if ($hasCAPolicies) {
        @($caPolicies | Where-Object { $_.state -eq 'enabled' }).Count
    } else { 0 }

    if ($enabledCAPolicies -gt 0) {
        return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'PASS' `
            -CurrentValue "Security defaults disabled but $enabledCAPolicies CA policies are active as replacement" `
            -Details @{
                IsEnabled         = $false
                CAReplacementCount = $enabledCAPolicies
            }
    }

    return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'FAIL' `
        -CurrentValue 'Security defaults are disabled with no Conditional Access policies as replacement — tenant has no baseline protections' `
        -Details @{
            IsEnabled         = $false
            CAReplacementCount = 0
        }
}

# ── EIDTNT-008: License Inventory ────────────────────────────────────────
function Test-InfiltrationEIDTNT008 {
    [CmdletBinding()]
    param([hashtable]$AuditData, [hashtable]$CheckDefinition)

    $skus = $AuditData.TenantConfig.SubscribedSkus
    if (-not $skus -or $skus.Count -eq 0) {
        return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'WARN' `
            -CurrentValue 'No subscribed SKU data available' `
            -Details @{ SkuCount = 0 }
    }

    $enabledSkus = @($skus | Where-Object { $_.capabilityStatus -eq 'Enabled' })
    $suspendedSkus = @($skus | Where-Object { $_.capabilityStatus -eq 'Suspended' })
    $warningSkus = @($skus | Where-Object { $_.capabilityStatus -eq 'Warning' })

    $totalConsumed = ($enabledSkus | ForEach-Object { $_.consumedUnits } | Measure-Object -Sum).Sum
    $totalPrepaid = ($enabledSkus | ForEach-Object { $_.prepaidUnits.enabled } | Measure-Object -Sum).Sum

    # Check for premium security SKUs
    $premiumSkuPartNumbers = @('AAD_PREMIUM', 'AAD_PREMIUM_P2', 'IDENTITY_THREAT_PROTECTION',
        'EMSPREMIUM', 'EMS_E5', 'M365_E5', 'SPE_E5', 'MICROSOFT_365_E5_SECURITY')
    $hasPremiumSecurity = @($enabledSkus | Where-Object {
        $_.skuPartNumber -in $premiumSkuPartNumbers
    }).Count -gt 0

    return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'PASS' `
        -CurrentValue "$($enabledSkus.Count) active license SKUs, $totalConsumed consumed of $totalPrepaid total. Premium security: $hasPremiumSecurity" `
        -Details @{
            TotalSkus         = $skus.Count
            EnabledSkus       = $enabledSkus.Count
            SuspendedSkus     = $suspendedSkus.Count
            WarningSkus       = $warningSkus.Count
            TotalConsumed     = $totalConsumed
            TotalPrepaid      = $totalPrepaid
            HasPremiumSecurity = $hasPremiumSecurity
            Licenses          = @($enabledSkus | ForEach-Object {
                @{
                    SkuId            = $_.skuId
                    SkuPartNumber    = $_.skuPartNumber
                    CapabilityStatus = $_.capabilityStatus
                    ConsumedUnits    = $_.consumedUnits
                    PrepaidEnabled   = $_.prepaidUnits.enabled
                    PrepaidSuspended = $_.prepaidUnits.suspended
                    PrepaidWarning   = $_.prepaidUnits.warning
                }
            })
        }
}

# ── EIDTNT-009: Administrative Units ─────────────────────────────────────
function Test-InfiltrationEIDTNT009 {
    [CmdletBinding()]
    param([hashtable]$AuditData, [hashtable]$CheckDefinition)

    $adminUnits = $AuditData.TenantConfig.AdminUnits
    if (-not $adminUnits -or $adminUnits.Count -eq 0) {
        return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'PASS' `
            -CurrentValue 'No administrative units configured' `
            -Details @{ AdminUnitCount = 0 }
    }

    $restrictedMgmt = @($adminUnits | Where-Object {
        $_.isMemberManagementRestricted -eq $true
    })

    return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'PASS' `
        -CurrentValue "$($adminUnits.Count) administrative units configured ($($restrictedMgmt.Count) with restricted management)" `
        -Details @{
            AdminUnitCount         = $adminUnits.Count
            RestrictedMgmtCount    = $restrictedMgmt.Count
            AdminUnits             = @($adminUnits | ForEach-Object {
                @{
                    Id                            = $_.id
                    DisplayName                   = $_.displayName
                    Description                   = $_.description
                    IsMemberManagementRestricted  = $_.isMemberManagementRestricted
                    Visibility                    = $_.visibility
                }
            })
        }
}

# ── EIDTNT-010: Custom Domains ───────────────────────────────────────────
function Test-InfiltrationEIDTNT010 {
    [CmdletBinding()]
    param([hashtable]$AuditData, [hashtable]$CheckDefinition)

    $domains = $AuditData.TenantConfig.Domains
    if (-not $domains -or $domains.Count -eq 0) {
        return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'WARN' `
            -CurrentValue 'No domain data available' `
            -Details @{ DomainCount = 0 }
    }

    $verified = @($domains | Where-Object { $_.isVerified -eq $true })
    $unverified = @($domains | Where-Object { $_.isVerified -ne $true })
    $defaultDomain = @($domains | Where-Object { $_.isDefault -eq $true })
    $initialDomains = @($domains | Where-Object { $_.isInitial -eq $true })
    $customDomains = @($domains | Where-Object { $_.isInitial -ne $true })

    $status = if ($unverified.Count -gt 0) { 'WARN' } else { 'PASS' }

    return New-AuditFinding -CheckDefinition $CheckDefinition -Status $status `
        -CurrentValue "$($domains.Count) domains: $($customDomains.Count) custom, $($verified.Count) verified, $($unverified.Count) unverified" `
        -Details @{
            TotalDomains     = $domains.Count
            CustomDomains    = $customDomains.Count
            VerifiedCount    = $verified.Count
            UnverifiedCount  = $unverified.Count
            DefaultDomain    = if ($defaultDomain.Count -gt 0) { $defaultDomain[0].id } else { 'None' }
            UnverifiedDomains = @($unverified | ForEach-Object { $_.id })
            Domains          = @($domains | ForEach-Object {
                @{
                    Id                 = $_.id
                    AuthenticationType = $_.authenticationType
                    IsVerified         = $_.isVerified
                    IsDefault          = $_.isDefault
                    IsInitial          = $_.isInitial
                    IsAdminManaged     = $_.isAdminManaged
                    SupportedServices  = @($_.supportedServices ?? @())
                }
            })
        }
}

# ── EIDTNT-011: Diagnostic Settings ─────────────────────────────────────
function Test-InfiltrationEIDTNT011 {
    [CmdletBinding()]
    param([hashtable]$AuditData, [hashtable]$CheckDefinition)

    # Diagnostic settings (log export to Log Analytics, Event Hub, Storage Account)
    # are configured at the Azure subscription level via ARM, not directly queryable
    # from Microsoft Graph. Flag as a review item.

    return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'WARN' `
        -CurrentValue 'Diagnostic settings verification required — ensure Entra ID sign-in and audit logs are exported to SIEM or Log Analytics' `
        -Details @{
            Note               = 'Diagnostic settings for Entra ID logs are configured via Azure Monitor (ARM). Verify through Azure Portal > Entra ID > Diagnostic settings.'
            RecommendedExports = @(
                'SignInLogs'
                'AuditLogs'
                'NonInteractiveUserSignInLogs'
                'ServicePrincipalSignInLogs'
                'ManagedIdentitySignInLogs'
                'ProvisioningLogs'
                'ADFSSignInLogs'
                'RiskyUsers'
                'UserRiskEvents'
            )
        }
}

# ── EIDTNT-012: Audit Log Retention ──────────────────────────────────────
function Test-InfiltrationEIDTNT012 {
    [CmdletBinding()]
    param([hashtable]$AuditData, [hashtable]$CheckDefinition)

    # Audit log retention depends on the license tier:
    # - Free/P1: 7 days via Graph API, 30 days in Azure Portal
    # - P2: 30 days via Graph API
    # - Long-term retention requires export to Log Analytics or storage

    $skus = $AuditData.TenantConfig.SubscribedSkus
    $premiumP2SkuParts = @('AAD_PREMIUM_P2', 'EMS_E5', 'M365_E5', 'SPE_E5',
        'IDENTITY_THREAT_PROTECTION', 'MICROSOFT_365_E5_SECURITY')

    $hasP2 = $false
    if ($skus) {
        $hasP2 = @($skus | Where-Object {
            $_.capabilityStatus -eq 'Enabled' -and $_.skuPartNumber -in $premiumP2SkuParts
        }).Count -gt 0
    }

    $retentionDays = if ($hasP2) { 30 } else { 7 }

    return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'WARN' `
        -CurrentValue "Default Entra ID audit log retention: $retentionDays days (P2: $hasP2) — verify long-term export is configured" `
        -Details @{
            HasP2License         = $hasP2
            DefaultRetentionDays = $retentionDays
            Note                 = 'For compliance and incident response, configure Diagnostic Settings to export logs to Log Analytics (90+ day retention) or Azure Storage (long-term).'
        }
}

# ── EIDTNT-013: Notification Settings ────────────────────────────────────
function Test-InfiltrationEIDTNT013 {
    [CmdletBinding()]
    param([hashtable]$AuditData, [hashtable]$CheckDefinition)

    # Notification settings for Entra ID (security alerts, PIM notifications, etc.)
    # are not directly accessible through a single Graph API endpoint.
    # Check what we can: technical notification contacts from Organization object.

    $org = $AuditData.TenantConfig.Organization
    if (-not $org) {
        return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'SKIP' `
            -CurrentValue 'Organization data not available for notification settings check'
    }

    $technicalContacts = @($org.technicalNotificationMails ?? @())
    $securityContacts = @($org.securityComplianceNotificationMails ?? @())
    $privacyProfile = $org.privacyProfile

    $hasTechnical = $technicalContacts.Count -gt 0
    $hasSecurity = $securityContacts.Count -gt 0

    if (-not $hasTechnical -and -not $hasSecurity) {
        return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'WARN' `
            -CurrentValue 'No technical or security notification contacts configured' `
            -Details @{
                TechnicalContacts = @()
                SecurityContacts  = @()
            }
    }

    return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'PASS' `
        -CurrentValue "Notification contacts: $($technicalContacts.Count) technical, $($securityContacts.Count) security" `
        -Details @{
            TechnicalContacts      = @($technicalContacts)
            SecurityContacts       = @($securityContacts)
            HasPrivacyProfile      = $null -ne $privacyProfile
            Note                   = 'Additional notification settings (PIM, Identity Protection alerts) should be verified in their respective configurations.'
        }
}