functions/Get-XdrVulnerabilityManagementChangeEvents.ps1

function Get-XdrVulnerabilityManagementChangeEvents {
    <#
    .SYNOPSIS
        Retrieves change events from Vulnerability Management.

    .DESCRIPTION
        Gets change event information from Threat and Vulnerability Management (TVM) in Microsoft Defender XDR.
        This function includes caching support with a 30-minute TTL to reduce API calls.
        By default, returns all change event results with pagination handled automatically.

    .PARAMETER Force
        Bypasses the cache and forces a fresh retrieval from the API.

    .PARAMETER Top
        Limits the number of results returned. Useful for previewing data without fetching all pages.

    .PARAMETER CountOnly
        Returns only the total count of change events (numOfResults).

    .PARAMETER SummaryOnly
        Returns only the change event summary metadata (meta object).

    .EXAMPLE
        Get-XdrVulnerabilityManagementChangeEvents
        Retrieves all change events with full pagination.

    .EXAMPLE
        Get-XdrVulnerabilityManagementChangeEvents -Force
        Forces a fresh retrieval of change events, bypassing the cache.

    .EXAMPLE
        Get-XdrVulnerabilityManagementChangeEvents -Top 10
        Returns only the first 10 change events.

    .EXAMPLE
        Get-XdrVulnerabilityManagementChangeEvents -CountOnly
        Returns only the total number of change events.

    .EXAMPLE
        Get-XdrVulnerabilityManagementChangeEvents -SummaryOnly
        Returns only the change event summary metadata.

    .OUTPUTS
        System.Object[]
        Returns an array of change event objects when using default or -Top parameters.

    .OUTPUTS
        System.Int64
        When -CountOnly is specified, returns the total count as an integer.

    .OUTPUTS
        System.Object[]
        When -SummaryOnly is specified, returns the metadata array.
    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', '')]
    [CmdletBinding(DefaultParameterSetName = 'Default')]
    [OutputType([System.Object[]])]
    [OutputType([System.Int64], ParameterSetName = 'CountOnly')]
    param (
        [Parameter()]
        [switch]$Force,

        [Parameter()]
        [ValidateRange(1, 10000)]
        [int]$Top,

        [Parameter(ParameterSetName = 'CountOnly')]
        [switch]$CountOnly,

        [Parameter(ParameterSetName = 'SummaryOnly')]
        [switch]$SummaryOnly
    )

    begin {
        Update-XdrConnectionSettings

        # Prepare TVM headers
        $tvmHeaders = $script:headers.Clone()
        $tvmHeaders["api-version"] = "1.0"

        # Helper function for paginated requests
        function Invoke-PaginatedRequest {
            param (
                [string]$BaseUri,
                [string]$DisplayName,
                [int]$MaxResults = 0
            )

            $maxPages = 1000
            $pageNum = 1

            try {
                # Get first page
                $uri = "$BaseUri`?pageIndex=$pageNum"
                Write-Verbose "Fetching $DisplayName page 1"
                $response = Invoke-RestMethod -Uri $uri -Method Get -ContentType "application/json" -WebSession $script:session -Headers $tvmHeaders

                $totalResults = $response.numOfResults
                $metaData = $response.meta
                $targetResults = if ($MaxResults -gt 0 -and $MaxResults -lt $totalResults) { $MaxResults } else { $totalResults }
                Write-Information "Total $DisplayName`: $totalResults$(if ($MaxResults -gt 0 -and $MaxResults -lt $totalResults) { " (fetching $targetResults)" })" -InformationAction Continue

                # Collect results with pagination
                $allResults = [System.Collections.Generic.List[object]]::new()
                if ($response.results) { $allResults.AddRange($response.results) }
                $pageNum = 2

                # Show progress for larger result sets (more than one page)
                $showProgress = $targetResults -gt 25

                while ($allResults.Count -lt $targetResults -and $pageNum -le $maxPages) {
                    if ($showProgress) {
                        $percentComplete = [math]::Min(100, [math]::Round(($allResults.Count / $targetResults) * 100))
                        Write-Progress -Activity "Retrieving $DisplayName" -Status "$($allResults.Count) of $targetResults" -PercentComplete $percentComplete
                    }

                    $uri = "$BaseUri`?pageIndex=$pageNum"
                    Write-Verbose "Fetching $DisplayName page $pageNum"
                    $response = Invoke-RestMethod -Uri $uri -Method Get -ContentType "application/json" -WebSession $script:session -Headers $tvmHeaders
                    if ($response.results) { $allResults.AddRange($response.results) }
                    Write-Verbose "Retrieved $($allResults.Count) of $targetResults $DisplayName"
                    $pageNum++
                }

                if ($showProgress) {
                    Write-Progress -Activity "Retrieving $DisplayName" -Completed
                }

                # Trim to MaxResults if specified
                $finalResults = if ($MaxResults -gt 0 -and $allResults.Count -gt $MaxResults) {
                    $allResults.GetRange(0, $MaxResults).ToArray()
                } else {
                    $allResults.ToArray()
                }

                return [PSCustomObject]@{
                    Count   = $totalResults
                    Meta    = $metaData
                    Results = $finalResults
                }
            } catch {
                Write-Progress -Activity "Retrieving $DisplayName" -Completed
                throw "Failed to retrieve $DisplayName`: $_"
            }
        }
    }

    process {
        $cacheKey = "XdrVulnerabilityManagementChangeEvents"
        $baseUri = "https://security.microsoft.com/apiproxy/mtp/tvm/analytics/changeEvents"

        # Check cache first (skip if -Top is specified as we may need subset)
        $useCache = -not $Top -or $Top -eq 0
        $currentCacheValue = Get-XdrCache -CacheKey $cacheKey -ErrorAction SilentlyContinue

        if ($useCache -and -not $Force -and $currentCacheValue.NotValidAfter -gt (Get-Date)) {
            Write-Verbose "Using cached TVM change events"
            Write-Information "Total change events: $($currentCacheValue.Value.numOfResults)" -InformationAction Continue

            if ($SummaryOnly) {
                Write-Information "Change event summary:" -InformationAction Continue
                if ($currentCacheValue.Value.meta) {
                    foreach ($item in $currentCacheValue.Value.meta) {
                        Write-Information "Event Type: $($item.eventType)" -InformationAction Continue
                        Write-Information " CVE Count: $($item.cveCount)" -InformationAction Continue
                        Write-Information " New SCID Count: $($item.newScidCount)" -InformationAction Continue
                    }
                }
                return $currentCacheValue.Value.meta
            }

            if ($CountOnly) {
                return $currentCacheValue.Value.numOfResults
            }

            return $currentCacheValue.Value.results
        }

        if ($Force) {
            Write-Verbose "Force parameter specified, bypassing cache"
            Clear-XdrCache -CacheKey $cacheKey
        } elseif (-not $useCache) {
            Write-Verbose "Top parameter specified, fetching fresh data"
        } else {
            Write-Verbose "TVM change events cache is missing or expired"
        }

        try {
            $paginatedResult = Invoke-PaginatedRequest -BaseUri $baseUri -DisplayName "change events" -MaxResults $Top

            # Display summary info if requested
            if ($SummaryOnly) {
                Write-Information "Change event summary:" -InformationAction Continue
                if ($paginatedResult.Meta) {
                    foreach ($item in $paginatedResult.Meta) {
                        Write-Information "Event Type: $($item.eventType)" -InformationAction Continue
                        Write-Information " CVE Count: $($item.cveCount)" -InformationAction Continue
                        Write-Information " New SCID Count: $($item.newScidCount)" -InformationAction Continue
                    }
                }
            }

            # Cache the full response only if we fetched everything
            if (-not $Top -or $Top -eq 0) {
                $cacheValue = [PSCustomObject]@{
                    numOfResults = $paginatedResult.Count
                    meta         = $paginatedResult.Meta
                    results      = $paginatedResult.Results
                }
                Set-XdrCache -CacheKey $cacheKey -Value $cacheValue -TTLMinutes 30
            }

            # Return based on parameters
            if ($SummaryOnly) {
                # Return empty array if meta is null
                if ($null -eq $paginatedResult.Meta) { return }
                return $paginatedResult.Meta
            }

            if ($CountOnly) {
                return [int64]($(if ($null -ne $paginatedResult.Count) { $paginatedResult.Count } else { 0 }))
            }

            # Return empty array if results are null
            if ($null -eq $paginatedResult.Results -or $paginatedResult.Results.Count -eq 0) {
                return
            }
            return $paginatedResult.Results

        } catch {
            Write-Error "Failed to retrieve TVM change events: $_"
        }
    }

    end {
    }
}