Last active
April 22, 2026 00:22
-
-
Save JustinGrote/29852aebc19dd235168130aaf23bc681 to your computer and use it in GitHub Desktop.
Find the changes in an Azure Conditional Access Named Location when so many records exist it uses CompressedCidrIPRanges instead of CidrIPRanges
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // The complete data we need gets split over multiple logs, so we have to recombine them. Export to CSV afterwards. | |
| AuditLogs | |
| | where OperationName == "Update policy" | |
| | where isnotempty(CorrelationId) and isnotempty(AdditionalDetails) | |
| | project TimeGenerated, CorrelationId, Identity, AdditionalDetails | |
| | mv-apply d = AdditionalDetails on ( | |
| summarize | |
| seq = tolong(take_anyif(tostring(d.value), tostring(coalesce(d.key, d.name)) == "seq")), | |
| b = take_anyif(tostring(d.value), tostring(coalesce(d.key, d.name)) == "b") | |
| ) | |
| | where isnotnull(seq) and isnotempty(b) | |
| | project TimeGenerated, CorrelationId, Identity, seq, b | |
| | order by CorrelationId asc, seq asc | |
| | serialize | |
| | summarize | |
| combined = strcat_array(make_list(b), ""), | |
| TimeGenerated = min(TimeGenerated), | |
| Identity = take_any(Identity) | |
| by CorrelationId | |
| | extend combinedJson = parse_json(combined) | |
| | extend properties = combinedJson.targetUpdatedProperties | |
| | project-away combined,combinedJson |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| param( | |
| $Path = '~\downloads\query_data.csv' | |
| ) | |
| $logItems = Import-Csv $Path | |
| function ConvertFrom-Base64DeflateText { | |
| [CmdletBinding()] | |
| param( | |
| [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)] | |
| [Alias('InputObject')] | |
| [string]$Base64Text, | |
| [System.Text.Encoding]$Encoding = [System.Text.Encoding]::UTF8 | |
| ) | |
| process { | |
| $bytes = [Convert]::FromBase64String($Base64Text) | |
| $inputStream = [System.IO.MemoryStream]::new($bytes) | |
| $deflateStream = [System.IO.Compression.DeflateStream]::new( | |
| $inputStream, | |
| [System.IO.Compression.CompressionMode]::Decompress | |
| ) | |
| $reader = [System.IO.StreamReader]::new($deflateStream, $Encoding) | |
| try { | |
| $reader.ReadToEnd() | |
| } finally { | |
| $reader.Dispose() | |
| $deflateStream.Dispose() | |
| $inputStream.Dispose() | |
| } | |
| } | |
| } | |
| $logItems | |
| | Foreach-Object { | |
| ConvertFrom-Json $_.properties | |
| | Where-Object Name -EQ 'PolicyDetail' | |
| | Add-Member -NotePropertyName 'Identity' -NotePropertyValue $_.Identity -PassThru | |
| | Add-Member -NotePropertyName 'TimeGenerated' -NotePropertyValue $_.'TimeGenerated [Local Time]' -PassThru | |
| } | |
| | Foreach-Object { | |
| $oldValue = $_.OldValue | ConvertFrom-Json |% CompressedCidrIPRanges | ConvertFrom-Base64DeflateText | |
| $oldValues = $oldValue.split(',') | |
| $newValue = $_.NewValue | ConvertFrom-Json |% CompressedCidrIPRanges | ConvertFrom-Base64DeflateText | |
| $newValues = $newValue.split(',') | |
| $diff = Compare-Object -ReferenceObject $oldValues -DifferenceObject $newValues | |
| [PSCustomObject]@{ | |
| TimeGenerated = [DateTimeOffset]::Parse($_.TimeGenerated) | |
| Identity = $_.Identity | |
| Added = ($diff | Where-Object SideIndicator -EQ '=>').InputObject | |
| Removed = ($diff | Where-Object SideIndicator -EQ '<=').InputObject | |
| } | |
| } | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment