Public/Get-CustomDetectionIds.ps1

function Get-CustomDetectionIds {
    <#
    .SYNOPSIS
        Lists detection rule IDs with their detector IDs and description tags.
 
    .DESCRIPTION
        Queries Microsoft Graph API to retrieve all detection rules and returns
        their detection rule ID, detector ID, and the UUID from the description
        tag (if present). Results are cached for the duration specified by
        CacheTtlMinutes (default: 60 minutes).
 
    .PARAMETER CacheTtlMinutes
        How long (in minutes) to keep the cached result before re-querying the API.
        Defaults to 60 minutes.
 
    .PARAMETER Force
        Bypass the cache and force a fresh API call.
 
    .EXAMPLE
        Get-CustomDetectionIds
 
        Returns a list of detection rule IDs and detector IDs (cached for 60 min).
 
    .EXAMPLE
        Get-CustomDetectionIds -Force
 
        Forces a fresh API call, ignoring any cached data.
 
    .EXAMPLE
        Get-CustomDetectionIds -CacheTtlMinutes 10
 
        Returns the list, caching results for 10 minutes.
 
    .NOTES
        Requires the Microsoft.Graph.Authentication module and an active Graph API session.
        Use Connect-MgGraph before calling this function.
    #>

    [CmdletBinding()]
    [OutputType([PSCustomObject])]
    param (
        [Parameter()]
        [ValidateRange(0, [int]::MaxValue)]
        [int]$CacheTtlMinutes = 60,

        [Parameter()]
        [switch]$Force
    )

    begin {
        Assert-MgGraphConnection
    }

    process {
        # Return cached data if still valid
        $cacheValid = (-not $Force) -and
        ($null -ne $script:DetectionIdsCache.Data) -and
        ([datetime]::UtcNow -lt $script:DetectionIdsCache.ExpiresAt)

        if ($cacheValid) {
            Write-Verbose 'Returning cached detection IDs (use -Force to refresh).'
            return $script:DetectionIdsCache.Data
        }

        try {
            # Query the Microsoft Graph API with pagination support
            $uri = "https://graph.microsoft.com/beta/security/rules/detectionRules?`$select=id,detectorId,detectionAction"
            $allValues = [System.Collections.Generic.List[object]]::new()

            do {
                $response = Invoke-MgGraphRequestWithRetry -Method GET -Uri $uri
                if ($response.value) {
                    $allValues.AddRange([object[]]$response.value)
                }
                $uri = $response.'@odata.nextLink'
            } while ($uri)

            if ($allValues.Count -eq 0) {
                $result = @()
            } else {
                $uuidPattern = '[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}'
                $tagPattern = "\[(?:[^:\]]*:)?($uuidPattern)\]"

                $result = $allValues | ForEach-Object {
                    $descriptionTag = $null
                    $desc = $_.detectionAction.alertTemplate.description
                    if ($desc -and $desc -match $tagPattern) {
                        $descriptionTag = $Matches[1]
                    }

                    [PSCustomObject]@{
                        Id             = $_.id
                        DetectorId     = $_.detectorId
                        DescriptionTag = $descriptionTag
                    }
                }
            }

            # Update the cache
            $script:DetectionIdsCache.Data = $result
            $script:DetectionIdsCache.ExpiresAt = [datetime]::UtcNow.AddMinutes($CacheTtlMinutes)

            return $result
        } catch {
            Write-Error "Error querying Microsoft Graph API: $($_.Exception.Message)"
            throw
        }
    }
}