Public/Get-ConnectorSyncSessionOperation.ps1

<#
.SYNOPSIS
    Generates Create, Update, and Delete operations by comparing the sync session objects to existing API data.

.DESCRIPTION
    Fetches the current connector objects from the API and compares them against the objects
    accumulated in the active sync session (via Add-ConnectorSyncSessionObject). Emits
    Build-ConnectorOperation hashtables for each required change:
    - Create: objects present in the session but not in the API.
    - Update: objects present in both but with differing Data payloads.
    - Delete: objects present in the API but not in the session.

    The resulting operations can be piped directly to Complete-ConnectorSyncSessionOperation.

.OUTPUTS
    System.Collections.Hashtable

.EXAMPLE
    Get-ConnectorSyncSessionOperation | Complete-ConnectorSyncSessionOperation

.EXAMPLE
    $ops = Get-ConnectorSyncSessionOperation
    $ops | Where-Object { $_.OperationType -eq "Delete" } | Complete-ConnectorSyncSessionOperation -WhatIf
#>

function Get-ConnectorSyncSessionOperation {
    [CmdletBinding()]
    param (
        [Switch] $AllowEmptySession
    )

    process {
        if ([String]::IsNullOrEmpty($Script:APIRoot)) {
            throw "Please connect first"
        }
        
        $GetExistingConnectorObjectsWebResult = Invoke-WebRequest -Uri "$($Script:APIRoot)data" -Headers (Get-EntraIDAccessTokenHeader -Profile $Script:AccessTokenProfile) -Method Get -SkipHttpErrorCheck
        if($GetExistingConnectorObjectsWebResult.StatusCode -ge 400) {
            throw "Unable to get connector objects. HTTP Status: $($GetExistingConnectorObjectsWebResult.StatusCode). Response: $($GetExistingConnectorObjectsWebResult.Content)"
        }

        $GetExistingConnectorObjectsResult = $GetExistingConnectorObjectsWebResult.Content | ConvertFrom-Json -AsHashtable -Depth 20
    
        if (!$GetExistingConnectorObjectsResult.isSuccess) {
            throw "Unable to get connector objects"
        }

        $ExistingConnectorObjects = @{}
        if ($GetExistingConnectorObjectsResult.data) {
            $GetExistingConnectorObjectsResult.data | ForEach-Object {
                $ExistingConnectorObjects[$_.externalId] = $_
            }
        }
        Write-Verbose "Found $($ExistingConnectorObjects.Count) existing objects"

        if ($Script:SyncSessionObjects.Count -gt 0) {
            $Script:SyncSessionObjects.GetEnumerator() | ForEach-Object {
                if ($ExistingConnectorObjects.ContainsKey($_.Key)) {
                    Write-Debug "Object $($_.Key) already exists, checking for diff"
                    $ExistingConnectorObject = $ExistingConnectorObjects[$_.Key]

                    $ExistingJSON = ($ExistingConnectorObject.data | ConvertTo-Json -Compress -Depth 20).ToCharArray()
                    $SyncJSON = ($_.Value.data | ConvertTo-Json -Depth 20 -Compress).ToCharArray()

                    $IsDifferent = $ExistingJSON.Length -ne $SyncJSON.Length
                    if (!$IsDifferent) {
                        $IsDifferent = (($SyncJSON | Sort-Object) -join "") -cne (($ExistingJSON | Sort-Object) -join "")
                    }

                    if ($IsDifferent) {
                        Build-ConnectorOperation -OperationType Update -ConnectorObject $_.Value -Id $ExistingConnectorObject.id
                    }
                }
                else {
                    Build-ConnectorOperation -OperationType Create -ConnectorObject $_.Value
                }
            }
        } else {
            if (-not $AllowEmptySession) {
                throw "No objects in sync session. Use -AllowEmptySession to emit delete operations for all existing objects."
            }
        }
        

        $ExistingConnectorObjects.GetEnumerator() | ForEach-Object {
            if (-not $Script:SyncSessionObjects.ContainsKey($_.Key)) {
                Build-ConnectorOperation -OperationType Delete -ConnectorObject $_.Value -Id $_.Value.id
            }
        }
    }
}