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 { } } |