functions/Get-XdrVulnerabilityManagementProducts.ps1
|
function Get-XdrVulnerabilityManagementProducts { <# .SYNOPSIS Retrieves products from Vulnerability Management. .DESCRIPTION Gets product 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 the products array. .PARAMETER Top Limits the number of results returned. Valid range is 1-10000. .PARAMETER Force Bypasses the cache and forces a fresh retrieval from the API. .PARAMETER CountOnly Returns only the total count of products (numOfResults). .PARAMETER HardwareVendorsInventoryCount Retrieves hardware vendors inventory count. .PARAMETER BiosVendorsInventoryCount Retrieves BIOS vendors inventory count. .PARAMETER ProcessorVendorsInventoryCount Retrieves processor vendors inventory count. .EXAMPLE Get-XdrVulnerabilityManagementProducts Retrieves all products using cached data if available. .EXAMPLE Get-XdrVulnerabilityManagementProducts -Top 100 Retrieves the first 100 products. .EXAMPLE Get-XdrVulnerabilityManagementProducts -Force Forces a fresh retrieval of products, bypassing the cache. .EXAMPLE Get-XdrVulnerabilityManagementProducts -CountOnly Returns only the total number of products. .EXAMPLE Get-XdrVulnerabilityManagementProducts -HardwareVendorsInventoryCount Retrieves hardware vendors inventory count. .OUTPUTS System.Object[] Returns an array of product objects from TVM. .OUTPUTS System.Int64 When -CountOnly is specified, returns the total count as an integer. .OUTPUTS System.Management.Automation.PSCustomObject When vendor inventory parameters are specified, returns a summary object. #> # Suppress false positive: Switch parameters are used via $PSCmdlet.ParameterSetName, not direct reference [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', '')] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', '')] [CmdletBinding(DefaultParameterSetName = 'Default')] [OutputType([System.Object[]])] [OutputType([System.Int64], ParameterSetName = 'CountOnly')] [OutputType([System.Management.Automation.PSCustomObject], ParameterSetName = 'HardwareVendors')] [OutputType([System.Management.Automation.PSCustomObject], ParameterSetName = 'BiosVendors')] [OutputType([System.Management.Automation.PSCustomObject], ParameterSetName = 'ProcessorVendors')] param ( [Parameter(ParameterSetName = 'Default')] [ValidateRange(1, 10000)] [int]$Top, [Parameter()] [switch]$Force, [Parameter(ParameterSetName = 'CountOnly')] [switch]$CountOnly, [Parameter(ParameterSetName = 'HardwareVendors')] [switch]$HardwareVendorsInventoryCount, [Parameter(ParameterSetName = 'BiosVendors')] [switch]$BiosVendorsInventoryCount, [Parameter(ParameterSetName = 'ProcessorVendors')] [switch]$ProcessorVendorsInventoryCount ) begin { Update-XdrConnectionSettings # Helper function for paginated requests function Invoke-PaginatedRequest { param( [string]$BaseUri, [hashtable]$Headers, [int]$MaxResults = 0, [string]$DisplayName = "items" ) $allResults = [System.Collections.Generic.List[object]]::new() $pageIndex = 1 $totalResults = 0 $maxPages = 1000 # Safety limit do { $uri = "$BaseUri`?pageIndex=$pageIndex" Write-Verbose "Fetching page $pageIndex" try { $response = Invoke-RestMethod -Uri $uri -Method Get -ContentType "application/json" -WebSession $script:session -Headers $Headers } catch { Write-Error "Failed to retrieve $DisplayName (page $pageIndex): $_" break } if ($pageIndex -eq 1) { $totalResults = $response.numOfResults $targetCount = if ($MaxResults -gt 0 -and $MaxResults -lt $totalResults) { $MaxResults } else { $totalResults } Write-Information "Total $DisplayName available: $totalResults$(if ($MaxResults -gt 0 -and $MaxResults -lt $totalResults) { " (retrieving top $MaxResults)" })" -InformationAction Continue } if ($response.results) { foreach ($item in $response.results) { $allResults.Add($item) if ($MaxResults -gt 0 -and $allResults.Count -ge $MaxResults) { break } } } # Progress bar for large result sets if ($targetCount -gt 25) { $percentComplete = [math]::Min(100, [math]::Round(($allResults.Count / $targetCount) * 100)) Write-Progress -Activity "Retrieving $DisplayName" -Status "$($allResults.Count) of $targetCount" -PercentComplete $percentComplete } $pageIndex++ } while ($allResults.Count -lt $targetCount -and $response.results.Count -gt 0 -and $pageIndex -le $maxPages) if ($targetCount -gt 25) { Write-Progress -Activity "Retrieving $DisplayName" -Completed } return @{ Results = $allResults.ToArray() TotalCount = $totalResults } } } process { # Configuration for each parameter set $config = switch ($PSCmdlet.ParameterSetName) { 'HardwareVendors' { @{ CacheKey = "XdrVulnerabilityManagementProducts_HardwareVendors" Endpoint = "/HardwareVendorsInventoryCount" DisplayName = "hardware vendors inventory" Simple = $true } } 'BiosVendors' { @{ CacheKey = "XdrVulnerabilityManagementProducts_BiosVendors" Endpoint = "/biosVendorsInventoryCount" DisplayName = "BIOS vendors inventory" Simple = $true } } 'ProcessorVendors' { @{ CacheKey = "XdrVulnerabilityManagementProducts_ProcessorVendors" Endpoint = "/ProcessorVendorsInventoryCount" DisplayName = "processor vendors inventory" Simple = $true } } 'CountOnly' { @{ CacheKey = "XdrVulnerabilityManagementProducts" DisplayName = "products" Simple = $false } } default { @{ CacheKey = "XdrVulnerabilityManagementProducts" DisplayName = "products" Simple = $false } } } $BaseUri = "https://security.microsoft.com/apiproxy/mtp/tvm/analytics/products" # Create TVM headers $tvmHeaders = $script:headers.Clone() $tvmHeaders["api-version"] = "1.0" # Handle simple (non-paginated) vendor endpoints if ($config.Simple) { $currentCacheValue = Get-XdrCache -CacheKey $config.CacheKey -ErrorAction SilentlyContinue if (-not $Force -and $currentCacheValue.NotValidAfter -gt (Get-Date)) { Write-Verbose "Using cached $($config.DisplayName) data" return $currentCacheValue.Value } elseif ($Force) { Write-Verbose "Force parameter specified, bypassing cache" Clear-XdrCache -CacheKey $config.CacheKey } $Uri = $BaseUri + $config.Endpoint Write-Verbose "Retrieving $($config.DisplayName) from: $Uri" try { $result = Invoke-RestMethod -Uri $Uri -Method Get -ContentType "application/json" -WebSession $script:session -Headers $tvmHeaders Set-XdrCache -CacheKey $config.CacheKey -Value $result -TTLMinutes 30 return $result } catch { Write-Error "Failed to retrieve $($config.DisplayName): $_" return } } # Handle paginated products endpoint $currentCacheValue = Get-XdrCache -CacheKey $config.CacheKey -ErrorAction SilentlyContinue if (-not $Force -and $currentCacheValue.NotValidAfter -gt (Get-Date)) { Write-Verbose "Using cached $($config.DisplayName)" if ($CountOnly) { Write-Information "Total $($config.DisplayName): $($currentCacheValue.Value.numOfResults)" -InformationAction Continue return [int64]$currentCacheValue.Value.numOfResults } $results = $currentCacheValue.Value.results if ($Top -gt 0 -and $Top -lt $results.Count) { $results = $results | Select-Object -First $Top } Write-Information "Retrieved $($results.Count) $($config.DisplayName) from cache" -InformationAction Continue return $results } elseif ($Force) { Write-Verbose "Force parameter specified, bypassing cache" Clear-XdrCache -CacheKey $config.CacheKey } Write-Verbose "Retrieving $($config.DisplayName)" # For CountOnly, just fetch first page to get the count if ($CountOnly) { try { $Uri = "$BaseUri`?pageIndex=1" $response = Invoke-RestMethod -Uri $Uri -Method Get -ContentType "application/json" -WebSession $script:session -Headers $tvmHeaders Write-Information "Total $($config.DisplayName): $($response.numOfResults)" -InformationAction Continue return [int64]$response.numOfResults } catch { Write-Error "Failed to retrieve $($config.DisplayName) count: $_" return } } # Fetch paginated results $paginatedResult = Invoke-PaginatedRequest -BaseUri $BaseUri -Headers $tvmHeaders -MaxResults $Top -DisplayName $config.DisplayName # Cache complete results (without Top limit) if we fetched everything if ($Top -eq 0 -or $Top -ge ($(if ($null -ne $paginatedResult.TotalCount) { $paginatedResult.TotalCount } else { 0 }))) { $completeResponse = [PSCustomObject]@{ numOfResults = $(if ($null -ne $paginatedResult.TotalCount) { $paginatedResult.TotalCount } else { 0 }) results = $(if ($null -ne $paginatedResult.Results) { $paginatedResult.Results } else { @() }) } Set-XdrCache -CacheKey $config.CacheKey -Value $completeResponse -TTLMinutes 30 } # Return empty array if results are null if ($null -eq $paginatedResult.Results -or $paginatedResult.Results.Count -eq 0) { return } return $paginatedResult.Results } end { } } |