private/Get-MsecAdminWriteResult.ps1
|
function Get-MsecAdminWriteResult { <# .SYNOPSIS Re-reads an object after a write and reports which requested fields have not settled. .DESCRIPTION Defender XDR is eventually consistent. A GET issued immediately after a successful PATCH can return the PRE-WRITE values, and reporting that as "the change did not hold" is a false alarm - observed live: an alert PATCHed at 16:37:00 still read as unchanged moments later and was correct when read again. A verification step that cries wolf is worse than none, because people learn to ignore it. So this polls, briefly and with a bounded budget, and stops the moment every requested field matches. Mismatch is only returned when the values are STILL wrong after the last attempt, which makes it worth acting on. It never retries the write. Only the read is repeated - if the PATCH itself failed the caller has already handled that. .PARAMETER Path Path to re-read, e.g. /v1.0/security/alerts_v2/{id}. .PARAMETER Expected Key/value pairs that were sent. Compared against the re-read object by the same keys. .PARAMETER DelaySeconds Waits between attempts. The first read happens immediately, before any of these, so the common case costs nothing. Defaults to a ~10s total budget. .OUTPUTS Hashtable: Object (the last successful read, or $null), Mismatch (array of key names still wrong), Attempts, and Error (the last read failure, if every attempt failed). #> [CmdletBinding()] param( [Parameter(Mandatory)] [string] $Path, [Parameter(Mandatory)] $Expected, [Parameter()] [int[]] $DelaySeconds = @(2, 3, 5) ) # Collections compare as a joined string so customTags behaves like any other field. $normalize = { param($value) if ($null -eq $value) { return '' } if ($value -is [string]) { return $value } if ($value -is [System.Collections.IEnumerable]) { return ((@($value) | ForEach-Object { [string] $_ }) -join '|') } [string] $value } $object = $null $mismatch = @() $lastErr = $null $attempt = 0 # One immediate read, then one per delay. foreach ($wait in @(0) + @($DelaySeconds)) { if ($wait -gt 0) { Start-Sleep -Seconds $wait } $attempt++ try { $object = Invoke-MsecAdminGraphRequest -Path $Path } catch { $lastErr = $_ continue } $mismatch = @() foreach ($key in $Expected.Keys) { if ((& $normalize $object.$key) -ne (& $normalize $Expected[$key])) { $mismatch += $key } } if (-not $mismatch.Count) { break } Write-Verbose "Re-read $attempt of $($DelaySeconds.Count + 1): $($mismatch -join ', ') not settled yet." } @{ Object = $object Mismatch = $mismatch Attempts = $attempt Error = $lastErr } } |