private/tests-shared/Get-SentinelWorkspaceData.ps1

# Enumerates all Log Analytics workspaces across accessible subscriptions using a single
# Azure Resource Graph query (spec Q1+Q2), then checks the Sentinel onboarding state for
# each workspace (spec Q3) via the Microsoft.SecurityInsights/onboardingStates/default endpoint.
#
# Returns an array of [PSCustomObject] (SubscriptionName, WorkspaceName, ResourceGroup,
# WorkspaceId, SentinelOnboarded).
# Returns $null when the ARG query fails so the caller can issue a skip.
function Get-SentinelWorkspaceData {
    [CmdletBinding()]
    param(
        [string]$Activity = 'Fetching Sentinel workspace data'
    )

    # Q1 + Q2: Single ARG query joins subscription names onto workspace records so one
    # round-trip covers both steps. ARG respects caller RBAC automatically.
    $argQuery = @"
resources
| where type =~ 'microsoft.operationalinsights/workspaces'
| join kind=leftouter (
    resourcecontainers
    | where type =~ 'microsoft.resources/subscriptions'
    | where tostring(properties.state) =~ 'Enabled'
    | project subscriptionName=name, subscriptionId
) on subscriptionId
| project
    workspaceName=name,
    workspaceId=id,
    resourceGroup,
    subscriptionId,
    subscriptionName
| order by subscriptionName asc, workspaceName asc
"@


    Write-ZtProgress -Activity $Activity -Status 'Enumerating Log Analytics workspaces via Resource Graph'
    $allWorkspaces = @()
    try {
        $allWorkspaces = @(Invoke-ZtAzureResourceGraphRequest -Query $argQuery)
        Write-PSFMessage "ARG query returned $($allWorkspaces.Count) Log Analytics workspace(s)." -Tag Test -Level VeryVerbose
    }
    catch {
        $httpStatusCode = $null
        if ($_.Exception.Message -match 'with status (\d+):') {
            $httpStatusCode = [int]$Matches[1]
        }
        elseif ($_.Exception.Response) {
            $httpStatusCode = [int]$_.Exception.Response.StatusCode
        }

        if ($httpStatusCode -in @(401, 403)) {
            Write-PSFMessage "Azure Resource Graph query returned $httpStatusCode — insufficient permissions to enumerate Log Analytics workspaces." -Tag Test -Level Warning
            return 'Forbidden'
        }
        Write-PSFMessage "Azure Resource Graph query failed: $($_.Exception.Message)" -Tag Test -Level Warning
        return $null
    }

    # Q3: For each workspace query the Sentinel onboarding state.
    # HTTP 200 = onboarded; HTTP 404 = not onboarded; HTTP 401/403 = permission error.
    # -FullResponse prevents non-2xx responses from throwing so the status code can be
    # inspected explicitly. Anything other than 200/404 is treated as an error.
    $results = @()
    foreach ($workspace in $allWorkspaces) {
        Write-ZtProgress -Activity $Activity -Status "Checking Sentinel onboarding on workspace '$($workspace.workspaceName)'"

        $sentinelOnboarded = $false
        try {
            $sentinelPath = "$($workspace.workspaceId)/providers/Microsoft.SecurityInsights/onboardingStates/default?api-version=2024-03-01"
            $response = Invoke-ZtAzureRequest -Path $sentinelPath -FullResponse -ErrorAction Stop

            switch ([int]$response.StatusCode) {
                200 { $sentinelOnboarded = $true }
                404 { $sentinelOnboarded = $false }
                { $_ -in @(401, 403) } {
                    Write-PSFMessage "Sentinel onboarding check for workspace '$($workspace.workspaceName)' returned $($response.StatusCode) — insufficient permissions." -Tag Test -Level Warning
                    return 'Forbidden'
                }
                default {
                    Write-PSFMessage "Sentinel onboarding check for workspace '$($workspace.workspaceName)' returned unexpected status $($response.StatusCode)." -Tag Test -Level Warning
                }
            }
        }
        catch {
            Write-PSFMessage "Error checking Sentinel onboarding for workspace '$($workspace.workspaceName)': $_" -Tag Test -Level Warning
        }

        $results += [PSCustomObject]@{
            SubscriptionName  = $workspace.subscriptionName
            WorkspaceName     = $workspace.workspaceName
            ResourceGroup     = $workspace.resourceGroup
            WorkspaceId       = $workspace.workspaceId
            SentinelOnboarded = $sentinelOnboarded
        }
    }

    # Use the unary comma operator so an empty array is preserved as an array
    # (not collapsed to $null by the pipeline) and the caller can distinguish
    # "no workspaces found" from an ARG failure ($null return above).
    return , $results
}