tests/Test-Assessment.61021.ps1

<#
.SYNOPSIS
    Checks whether the Microsoft 365 Copilot data connector is enabled on at least one
    Microsoft Sentinel-onboarded workspace.
 
.DESCRIPTION
    For each Microsoft Sentinel-onboarded Log Analytics workspace, the test checks whether
    the "Microsoft Copilot" content package is installed from the Sentinel Content Hub (Q3).
 
    The tenant passes when at least one workspace has the "Microsoft Copilot" content package
    installed.
 
.NOTES
    Test ID: 61021
    Workshop Task: AI_089
    Pillar: AI
    Category: AI Threat Detection
    Required permissions:
      Microsoft.Resources/subscriptions/read — subscription enumeration (Q1)
      Microsoft.SecurityInsights/contentPackages/read — content package list per workspace (Q3)
#>

function Test-Assessment-61021 {
    [ZtTest(
        Category           = 'AI Threat Detection',
        CompatibleLicense  = ('Microsoft_365_Copilot'),
        ImplementationCost = 'Low',
        Pillar             = 'AI',
        RiskLevel          = 'High',
        Service            = ('Azure'),
        SfiPillar          = 'Monitor and detect cyberthreats',
        TenantType         = ('Workforce'),
        TestId             = 61021,
        Title              = 'Microsoft 365 Copilot data connector is enabled on the Microsoft Sentinel workspace',
        UserImpact         = 'Low'
    )]
    [CmdletBinding()]
    param()

    #region Data Collection

    Write-PSFMessage '🟦 Start' -Tag Test -Level VeryVerbose
    $activity = 'Checking Microsoft 365 Copilot data connector on Sentinel-onboarded workspaces'

    # Q1 (subscriptions) + Q2 (workspaces) + onboarding-state check:
    # Delegate to the shared helper used by 61002. Returns the same sentinel values.
    # $null → unexpected ARG failure (spec: Investigate)
    # 'Forbidden' → 401/403 on Q1 or Q2 ARG query (spec: Investigate)
    # 'NoSubscriptions' → no enabled subscriptions (spec: skip NotApplicable)
    # 'NoWorkspaces' → no Log Analytics workspaces found (spec: skip NotApplicable)
    # array → workspace results (SentinelOnboarded, PermissionError flags)
    $workspaceResults = Get-SentinelWorkspaceData -Activity $activity

    if ($null -eq $workspaceResults) {
        $params = @{
            TestId       = '61021'
            Title        = 'Microsoft 365 Copilot data connector is enabled on the Microsoft Sentinel workspace'
            Status       = $false
            Result       = '⚠️ Azure Resource Graph returned an unexpected error while querying subscriptions or Log Analytics workspaces. This is likely a transient issue, please re-run the assessment.'
            CustomStatus = 'Investigate'
        }
        Add-ZtTestResultDetail @params
        return
    }

    if ($workspaceResults -eq 'Forbidden') {
        $params = @{
            TestId       = '61021'
            Title        = 'Microsoft 365 Copilot data connector is enabled on the Microsoft Sentinel workspace'
            Status       = $false
            Result       = '⚠️ Azure Resource Graph returned insufficient permissions when querying subscriptions or workspaces. Ensure you have at least Reader access to the Azure subscriptions being tested.'
            CustomStatus = 'Investigate'
        }
        Add-ZtTestResultDetail @params
        return
    }

    if ($workspaceResults -in @('NoSubscriptions', 'NoWorkspaces')) {
        Add-ZtTestResultDetail -SkippedBecause NotApplicable
        return
    }

    # Only Sentinel-onboarded workspaces are in scope for connector evaluation.
    # Workspaces where Q3 (onboarding check) returned 401/403 have PermissionError=$true and
    # SentinelOnboarded=$false — exclude them from the onboarded set but track them separately
    # so we can return Investigate instead of a silent NotApplicable skip.
    $permErrorFromHelper = @($workspaceResults | Where-Object { $_.PermissionError })
    $onboardedWorkspaces = @($workspaceResults | Where-Object { $_.SentinelOnboarded })
    if ($onboardedWorkspaces.Count -eq 0) {
        if ($permErrorFromHelper.Count -gt 0) {
            $params = @{
                TestId       = '61021'
                Title        = 'Microsoft 365 Copilot data connector is enabled on the Microsoft Sentinel workspace'
                Status       = $false
                Result       = '⚠️ Some of the queried resources returned status indicating not sufficient permissions. Please make sure you have at least reader access to the Azure Subscriptions being tested.'
                CustomStatus = 'Investigate'
            }
            Add-ZtTestResultDetail @params
            return
        }
        Write-PSFMessage 'No Sentinel-onboarded workspaces found — skipping connector check.' -Tag Test -Level VeryVerbose
        Add-ZtTestResultDetail -SkippedBecause NotApplicable
        return
    }

    # Q3: For each Sentinel-onboarded workspace check for the "Microsoft Copilot" content package.
    $workspaceEvaluations = @()

    foreach ($workspace in $onboardedWorkspaces) {
        Write-ZtProgress -Activity $activity -Status "Checking content packages on workspace '$($workspace.WorkspaceName)'"

        $packages        = $null
        $permissionError = $false
        try {
            $packages = Invoke-ZtAzureRequest `
                -Path "$($workspace.WorkspaceId)/providers/Microsoft.SecurityInsights/contentPackages?api-version=2024-09-01" `
                -ErrorAction Stop
        }
        catch {
            $httpStatusCode = $null
            if ($_.Exception.Message -match 'with status (\d+):') { $httpStatusCode = [int]$Matches[1] }
            if ($httpStatusCode -in @(401, 403)) {
                $permissionError = $true
                Write-PSFMessage "Q3 content packages for workspace '$($workspace.WorkspaceName)' returned $httpStatusCode — insufficient permissions." -Tag Test -Level Warning
            }
            else {
                # 5xx or network error — treat as Investigate per spec
                $permissionError = $true
                Write-PSFMessage "Q3 content packages for workspace '$($workspace.WorkspaceName)' failed (status $httpStatusCode): $_" -Tag Test -Level Warning
            }
        }

        $contentPackageName = $null
        $rowStatus          = 'Fail'
        if ($permissionError) {
            $rowStatus = 'Investigate'
        }
        else {
            $copilotPackage = $packages | Where-Object { $_.properties.displayName -ieq 'Microsoft Copilot' } | Select-Object -First 1
            if ($copilotPackage) {
                $contentPackageName = $copilotPackage.properties.displayName
                $rowStatus          = 'Pass'
            }
        }

        $workspaceEvaluations += [PSCustomObject]@{
            SubscriptionName   = $workspace.SubscriptionName
            WorkspaceName      = $workspace.WorkspaceName
            WorkspaceId        = $workspace.WorkspaceId
            ContentPackageName = $contentPackageName
            RowStatus          = $rowStatus
            PermissionError    = $permissionError
        }
    }

    #endregion Data Collection

    #region Assessment Logic

    $passingWorkspaces = @($workspaceEvaluations | Where-Object { $_.RowStatus -eq 'Pass' })
    $investigateItems  = @($workspaceEvaluations | Where-Object { $_.RowStatus -eq 'Investigate' })

    # All workspaces had permission/server errors and none passed → return Investigate immediately
    if ($investigateItems.Count -gt 0 -and $passingWorkspaces.Count -eq 0 -and
        @($workspaceEvaluations | Where-Object { $_.RowStatus -eq 'Fail' }).Count -eq 0) {
        $params = @{
            TestId       = '61021'
            Title        = 'Microsoft 365 Copilot data connector is enabled on the Microsoft Sentinel workspace'
            Status       = $false
            Result       = '⚠️ Some of the queried resources returned status indicating not sufficient permissions. Please make sure you have at least reader access to the Azure Subscriptions being tested.'
            CustomStatus = 'Investigate'
        }
        Add-ZtTestResultDetail @params
        return
    }

    $passed = $passingWorkspaces.Count -gt 0

    if ($passed) {
        $testResultMarkdown = '✅ The Microsoft 365 Copilot data connector is enabled on at least one Sentinel-onboarded workspace.%TestResult%'
    }
    else {
        $testResultMarkdown = '❌ The Microsoft 365 Copilot data connector is not enabled on any Sentinel-onboarded workspace.%TestResult%'
    }

    #endregion Assessment Logic

    #region Report Generation

    $sentinelPortalUrl = 'https://portal.azure.com/#view/HubsExtension/BrowseResource/resourceType/microsoft.securityinsightsarg%2Fsentinel'

    $formatTemplate = @'
 
### [Sentinel data connectors per workspace]({0})
 
| Subscription | Workspace | Content Package | Status |
| :----------- | :-------- | :-------------- | :----- |
{1}
'@


    $tableRows = ''
    foreach ($evaluation in $workspaceEvaluations) {
        $subscriptionName = Get-SafeMarkdown -Text $evaluation.SubscriptionName
        $workspaceName    = Get-SafeMarkdown -Text $evaluation.WorkspaceName
        $packageDisplay   = if ($evaluation.ContentPackageName) { Get-SafeMarkdown -Text $evaluation.ContentPackageName } else { 'absent' }
        $statusDisplay    = switch ($evaluation.RowStatus) {
            'Pass'        { '✅ Pass' }
            'Investigate' { '⚠️ Investigate' }
            default       { '❌ Fail' }
        }
        $tableRows += "| $subscriptionName | $workspaceName | $packageDisplay | $statusDisplay |`n"
    }

    $partialWarning = ''
    if ($investigateItems.Count -gt 0) {
        $partialWarning = "`n`n> ⚠️ **$($investigateItems.Count) workspace(s) could not be fully checked** due to insufficient permissions or a server error on the content packages endpoint. Ensure Microsoft Sentinel Reader is granted on those workspaces and re-run the assessment."
    }

    $mdInfo = ($formatTemplate -f $sentinelPortalUrl, $tableRows) + $partialWarning
    $testResultMarkdown = $testResultMarkdown -replace '%TestResult%', $mdInfo

    #endregion Report Generation

    $params = @{
        TestId = '61021'
        Title  = 'Microsoft 365 Copilot data connector is enabled on the Microsoft Sentinel workspace'
        Status = $passed
        Result = $testResultMarkdown
    }
    if (-not $passed -and $investigateItems.Count -gt 0) {
        $params.CustomStatus = 'Investigate'
    }

    Add-ZtTestResultDetail @params
}