Private/Compare-CustomDetection.ps1
|
function Compare-CustomDetection { <# .SYNOPSIS Compares two detection rule objects for meaningful differences. .DESCRIPTION Compares the key properties of a local detection rule against the remote version and returns $true when they differ. #> [CmdletBinding()] [OutputType([bool])] param( [Parameter(Mandatory)] [hashtable]$Local, [Parameter(Mandatory)] [PSObject]$Remote ) # Properties to compare – order matches the JSON schema structure $propertiesToCompare = @( @{ Local = 'displayName'; Remote = 'displayName' } @{ Local = 'isEnabled'; Remote = 'isEnabled' } @{ Local = 'queryText'; Remote = 'queryCondition.queryText' } @{ Local = 'period'; Remote = 'schedule.period' } @{ Local = 'title'; Remote = 'detectionAction.alertTemplate.title' } @{ Local = 'description'; Remote = 'detectionAction.alertTemplate.description' } @{ Local = 'severity'; Remote = 'detectionAction.alertTemplate.severity' } @{ Local = 'category'; Remote = 'detectionAction.alertTemplate.category' } ) # Helper to resolve dot-separated property paths on the remote object function Get-NestedValue { param([PSObject]$Object, [string]$Path) $current = $Object foreach ($segment in $Path.Split('.')) { if ($null -eq $current) { return $null } $current = $current.$segment } return $current } foreach ($prop in $propertiesToCompare) { $localVal = $Local[$prop.Local] $remoteVal = Get-NestedValue -Object $Remote -Path $prop.Remote # Normalise nulls / empty strings for fair comparison if ([string]::IsNullOrEmpty($localVal)) { $localVal = '' } if ([string]::IsNullOrEmpty($remoteVal)) { $remoteVal = '' } if ([string]$localVal -ne [string]$remoteVal) { Write-Verbose "Difference found on '$($prop.Local)': local='$localVal' remote='$remoteVal'" return $true } } return $false } |