Public/Restart-ECMA2ConnectorSyncJob.ps1

function Restart-ECMA2ConnectorSyncJob {
    <#
    .SYNOPSIS
        Restarts a Microsoft Entra cloud provisioning synchronization job, optionally resetting escrows and other state.

    .DESCRIPTION
        Calls the Microsoft Graph synchronizationJob restart action to clear stuck
        provisioning state - most commonly to reset escrows (retry provisioning
        failures), but supports every documented resetScope value: Escrows, Watermark,
        QuarantineState, ForceDeletes, Full, None, and ConnectorDataStore.

        This is a destructive, high-impact operation with two safety guards:
        - 'None' and 'ConnectorDataStore' are flagged by Microsoft as "do not use
          without guidance" (None: use Start-ECMA2ConnectorSyncJob instead;
          ConnectorDataStore: contact Microsoft Support) and are blocked unless
          -Force is specified.
        - If the job's last run is still in progress, only a 'Full' reset (or -Force)
          is allowed - a narrower reset such as Escrows should wait for the run to
          finish, while a Full reset is the deliberate abort-and-recover path.

        Requires a prior Connect-ECMA2Graph with Synchronization.ReadWrite.All.

    .PARAMETER ServicePrincipalId
        The object ID of the service principal (enterprise application) the job belongs to.

    .PARAMETER JobId
        The synchronization job identifier to restart.

    .PARAMETER ResetScope
        One or more reset scopes to apply. Defaults to 'Escrows'. See
        https://learn.microsoft.com/en-us/graph/api/resources/synchronization-synchronizationjobrestartcriteria
        for details of each value.

    .PARAMETER ApiVersion
        The Microsoft Graph API version to call. Defaults to v1.0.

    .PARAMETER Force
        Required to select 'None' or 'ConnectorDataStore', and to bypass the
        currently-running-job guard for a non-Full reset.

    .PARAMETER PassThru
        Return the job's status after the restart completes.

    .EXAMPLE
        Restart-ECMA2ConnectorSyncJob -ServicePrincipalId $spId -JobId $jobId
        Resets escrows (the default) - retries previously failed provisioning operations.

    .EXAMPLE
        Restart-ECMA2ConnectorSyncJob -ServicePrincipalId $spId -JobId $jobId -ResetScope Full
        Full restart - can be used to abort a stuck/in-progress run and recover.

    .EXAMPLE
        Restart-ECMA2ConnectorSyncJob -ServicePrincipalId $spId -JobId $jobId -WhatIf
        Preview the action without calling Microsoft Graph.

    .NOTES
        Confirmation is required by default (ConfirmImpact = High). Use -WhatIf to preview.
    #>

    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')]
    param(
        [Parameter(Mandatory, ValueFromPipelineByPropertyName)]
        [string]$ServicePrincipalId,

        [Parameter(Mandatory, ValueFromPipelineByPropertyName)]
        [string]$JobId,

        [Parameter()]
        [ValidateSet('Escrows', 'Watermark', 'QuarantineState', 'ForceDeletes', 'Full', 'None', 'ConnectorDataStore')]
        [string[]]$ResetScope = @('Escrows'),

        [Parameter()]
        [ValidateSet('v1.0', 'beta')]
        [string]$ApiVersion = 'v1.0',

        [Parameter()]
        [switch]$Force,

        [Parameter()]
        [switch]$PassThru
    )

    process {
        try {
            $currentJob = Get-ECMA2ConnectorSyncJob -ServicePrincipalId $ServicePrincipalId -JobId $JobId -ApiVersion $ApiVersion -WarningAction SilentlyContinue

            if (-not $currentJob) {
                Write-Error "Synchronization job '$JobId' not found for service principal '$ServicePrincipalId'. Confirm the ids with Get-ECMA2ConnectorSyncJob first."
                return
            }

            $dangerousScopes = @('None', 'ConnectorDataStore')
            $requestedDangerous = $ResetScope | Where-Object { $_ -in $dangerousScopes }
            if ($requestedDangerous -and -not $Force) {
                Write-Error "ResetScope '$($requestedDangerous -join ', ')' is flagged by Microsoft as 'do not use without guidance' (None: use Start-ECMA2ConnectorSyncJob instead; ConnectorDataStore: contact Microsoft Support). Pass -Force to proceed anyway."
                return
            }

            $isFullReset = 'Full' -in $ResetScope
            if ($currentJob.IsRunning -and -not $isFullReset -and -not $Force) {
                Write-Error "Synchronization job '$JobId' is currently running (LastExecutionState: $($currentJob.LastExecutionState)). A narrow reset ('$($ResetScope -join ', ')') should wait for the run to finish - wait for completion, pass -ResetScope Full to abort and recover, or pass -Force to override."
                return
            }
            if ($currentJob.IsRunning -and $isFullReset) {
                Write-Warning "Synchronization job '$JobId' is currently running - this Full reset will abort the in-progress synchronization cycle."
            }

            $resetScopeValue = $ResetScope -join ','
            $uri = if ($ApiVersion -eq 'beta') {
                "https://graph.microsoft.com/beta/servicePrincipals/$ServicePrincipalId/synchronization/jobs/$JobId/microsoft.graph.restart"
            }
            else {
                "https://graph.microsoft.com/v1.0/servicePrincipals/$ServicePrincipalId/synchronization/jobs/$JobId/restart"
            }

            $target = "$JobId (ServicePrincipal $ServicePrincipalId)"
            $action = "Restart synchronization job with ResetScope '$resetScopeValue' (current status: $($currentJob.StatusCode), LastExecutionState: $($currentJob.LastExecutionState))"

            if ($PSCmdlet.ShouldProcess($target, $action)) {
                Invoke-ECMA2GraphRequest -Method POST -Uri $uri -Body @{ criteria = @{ resetScope = $resetScopeValue } } | Out-Null

                $result = [PSCustomObject]@{
                    PSTypeName         = 'ECMA2Host.SyncJobRestart'
                    ServicePrincipalId = $ServicePrincipalId
                    JobId              = $JobId
                    ResetScope         = $resetScopeValue
                    ApiVersion         = $ApiVersion
                    RestartedAt        = Get-Date
                }

                if ($PassThru) {
                    $result | Add-Member -MemberType NoteProperty -Name 'CurrentJob' -Value (Get-ECMA2ConnectorSyncJob -ServicePrincipalId $ServicePrincipalId -JobId $JobId -ApiVersion $ApiVersion)
                }

                Write-Verbose "Restarted synchronization job '$JobId' with ResetScope '$resetScopeValue'"
                return $result
            }
        }
        catch {
            Write-Error "Failed to restart synchronization job '$JobId': $_"
        }
    }
}