Public/ConnectedSystems/Remove-JIMConnectedSystem.ps1

# Copyright (c) Tetron Limited. All rights reserved.
# Licensed under the Tetron Commercial License. See LICENSE file in the project root.

function Remove-JIMConnectedSystem {
    <#
    .SYNOPSIS
        Removes a Connected System from JIM.
 
    .DESCRIPTION
        Deletes a Connected System and all its related data from JIM.
 
        By default the deletion runs as "Deprovision through synchronisation" (recommended): the system is
        fenced and a background run processes every Connected System Object through the synchronisation
        engine's obsoletion semantics; attribute recall with surviving-contributor re-election, Metaverse
        Object Deletion Rule evaluation, and Pending Export staging all happen exactly as a normal
        synchronisation disconnect would, before the system itself is deleted. The cmdlet then returns a
        tracking object carrying the Activity id; monitor progress with Get-JIMActivity.
 
        Use -DeleteImmediately for "Delete immediately and keep contributed data" (today's fast path):
        - Small systems (< 1000 objects): deleted immediately
        - Large systems: queued as a background job (a tracking object is returned)
        - Systems with a running sync: queued to run after the sync completes
        WARNING: the attribute values this system contributed are kept but lose their provenance, so
        nothing can ever recall them; surviving contributors are not re-elected and downstream systems are
        not corrected. Deletion Rules are still evaluated in bulk for orphaned Metaverse Objects, but no
        per-object synchronisation processing occurs.
 
        If a deprovisioning run fails partway, the system stays fenced (its Status remains Deleting) and
        never returns to service. Re-running this cmdlet retries the run: it resumes from its checkpoint
        rather than starting again. Running it with -DeleteImmediately instead finishes the deletion
        immediately, abandoning the remaining deprovisioning work (the Activity records the abandonment).
 
        Unless -Force is used, the cmdlet retrieves the deletion preview first so the confirmation states
        the impact of the chosen mode. Use Get-JIMConnectedSystem -Id <id> -DeletionPreview to review the
        impact yourself before deleting.
 
    .PARAMETER Id
        The unique identifier of the Connected System to delete.
 
    .PARAMETER InputObject
        A Connected System object to delete. Accepts pipeline input.
 
    .PARAMETER DeleteImmediately
        Deletes the Connected System immediately instead of deprovisioning through synchronisation.
        WARNING: contributed attribute values are KEPT with no provenance, so nothing can ever recall
        them; surviving contributors are not re-elected and downstream systems are not corrected. On a
        system whose deprovisioning run failed partway, this finishes the deletion immediately,
        abandoning the remaining deprovisioning work.
 
    .PARAMETER PassThru
        If specified, returns the deletion result object for deletions that complete immediately.
        (Queued deletions always return a tracking object, with or without -PassThru.)
 
    .PARAMETER Force
        Suppresses confirmation prompts (and skips the deletion-preview lookup the confirmation uses).
 
    .PARAMETER ChangeReason
        Optional reason for the deletion, recorded on the audit Activity and the configuration change history tombstone.
 
    .OUTPUTS
        When the deletion queues (always the case for the default deprovisioning mode), a PSCustomObject
        tracking the queued work:
        - ActivityId: the deletion Activity's id (a GUID); monitor it with Get-JIMActivity
        - WorkerTaskId: the queued Worker Task's id (a GUID)
        - Outcome: QueuedAsBackgroundJob, or QueuedAfterSync when a running synchronisation delays it
        - ConnectedSystemObjectCount, ContributedValueCount, ContributedValueObjectCount: headline counts
          from the deletion preview, or null when it was not retrieved (-Force skips the lookup)
 
        When an immediate deletion completes synchronously, nothing is returned unless -PassThru is
        specified, in which case the deletion result (Outcome, ActivityId) is returned.
 
    .EXAMPLE
        Remove-JIMConnectedSystem -Id 1
 
        Deprovisions the Connected System with ID 1 through synchronisation (prompts for confirmation,
        stating the impact from the deletion preview) and returns a tracking object for the queued run.
 
    .EXAMPLE
        $tracking = Remove-JIMConnectedSystem -Id 1 -Force
        Get-JIMActivity -Id $tracking.ActivityId
 
        Deprovisions the Connected System without confirmation, capturing the tracking object, then
        retrieves the deletion Activity to monitor the run's progress.
 
    .EXAMPLE
        Remove-JIMConnectedSystem -Id 1 -DeleteImmediately -Force
 
        Deletes the Connected System immediately, KEEPING the attribute values it contributed. The kept
        values lose their provenance, so nothing can ever recall them, and downstream systems are not
        corrected. Only choose this when the data should outlive the system, or for disposable test data.
 
    .EXAMPLE
        Remove-JIMConnectedSystem -Id 1 -Force
 
        Run again after a failed deprovisioning run, this RETRIES the run: the system is still fenced and
        the run resumes from its checkpoint. Use -DeleteImmediately instead to finish the deletion
        immediately, abandoning the remaining deprovisioning work.
 
    .EXAMPLE
        Get-JIMConnectedSystem -Name "Test*" | Remove-JIMConnectedSystem -DeleteImmediately -Force
 
        Force-deletes every Connected System whose name starts with "Test", immediately and keeping
        contributed data; the usual choice for disposable test systems. Review the matches first by
        running the Get-JIMConnectedSystem filter on its own, or run the pipeline without -Force to
        confirm each deletion individually.
 
    .EXAMPLE
        Remove-JIMConnectedSystem -Id 1 -Force -ChangeReason "Decommissioned (CHG0123)"
 
        Deprovisions the Connected System, recording the reason on the deletion's change history tombstone.
 
    .LINK
        Get-JIMConnectedSystem
        New-JIMConnectedSystem
        Get-JIMActivity
    #>

    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High', DefaultParameterSetName = 'ById')]
    [OutputType([PSCustomObject])]
    param(
        [Parameter(Mandatory, ParameterSetName = 'ById', ValueFromPipelineByPropertyName)]
        [int]$Id,

        [Parameter(Mandatory, ParameterSetName = 'ByInputObject', ValueFromPipeline)]
        [PSCustomObject]$InputObject,

        [switch]$DeleteImmediately,

        [switch]$PassThru,

        [switch]$Force,

        [string]$ChangeReason
    )

    process {
        # Check connection first
        if (-not $script:JIMConnection) {
            Write-Error "You are not connected to JIM. Run Connect-JIM -Url <your JIM URL> to authenticate, then try again."
            return
        }

        # Get the ID from InputObject if provided
        $systemId = if ($PSCmdlet.ParameterSetName -eq 'ByInputObject') {
            $InputObject.id
        } else {
            $Id
        }

        # Get system name for confirmation message
        $systemName = if ($PSCmdlet.ParameterSetName -eq 'ByInputObject') {
            $InputObject.name
        } else {
            try {
                $system = Invoke-JIMApi -Endpoint "/api/v1/synchronisation/connected-systems/$systemId"
                $system.name
            } catch {
                "ID $systemId"
            }
        }

        # Retrieve the deletion preview so the confirmation states the impact of the chosen mode
        # (the #1537 precedent). -Force suppresses the confirmation, so the lookup would be wasted there
        # (the documented bulk-pipeline path); the tracking object's counts are then null.
        $preview = $null
        $confirmAction = if ($DeleteImmediately) {
            'Delete Connected System immediately, keeping contributed data'
        } else {
            'Deprovision Connected System through synchronisation'
        }
        if (-not $Force) {
            try {
                $preview = Invoke-JIMApi -Endpoint "/api/v1/synchronisation/connected-systems/$systemId/deletion-preview"
            }
            catch {
                # An unavailable preview must not block the deletion; the server still applies the chosen
                # mode regardless of what the confirmation could state.
                Write-Verbose "Could not retrieve the deletion preview for Connected System ${systemId}: $_"
            }

            $impactText = Get-JIMConnectedSystemDeletionImpactText -Preview $preview -DeleteImmediately:$DeleteImmediately
            if ($impactText) {
                $confirmAction = "$confirmAction ($impactText)"
            }
        }

        if ($Force -or $PSCmdlet.ShouldProcess($systemName, $confirmAction)) {
            Write-Verbose "Deleting Connected System: $systemName (ID: $systemId)"

            try {
                # The mode and the reason are supplied as query parameters because HTTP DELETE bodies are
                # awkward for clients. The deprovisioning default is the server's own, so it is only the
                # immediate mode that needs saying.
                $deleteEndpoint = "/api/v1/synchronisation/connected-systems/$systemId"
                $queryParts = @()
                if ($DeleteImmediately) {
                    $queryParts += 'synchronisedDeprovisioning=false'
                }
                if ($PSBoundParameters.ContainsKey('ChangeReason')) {
                    $queryParts += "changeReason=$([System.Uri]::EscapeDataString($ChangeReason))"
                }
                if ($queryParts.Count -gt 0) {
                    $deleteEndpoint += '?' + ($queryParts -join '&')
                }

                $result = Invoke-JIMApi -Endpoint $deleteEndpoint -Method 'DELETE'

                Write-Verbose "Deletion result: $($result.outcome)"

                if ($result -and $result.WorkerTaskId) {
                    # 202 Accepted: the deletion queued (always the case for deprovisioning). Surface the
                    # tracking object so scripts can monitor the Activity; headline counts come from the
                    # preview when it was retrieved.
                    [PSCustomObject]@{
                        ActivityId                  = $result.ActivityId
                        WorkerTaskId                = $result.WorkerTaskId
                        Outcome                     = $result.Outcome
                        ConnectedSystemObjectCount  = if ($preview) { $preview.ConnectedSystemObjectCount } else { $null }
                        ContributedValueCount       = if ($preview) { $preview.ContributedValueCount } else { $null }
                        ContributedValueObjectCount = if ($preview) { $preview.ContributedValueObjectCount } else { $null }
                    }
                }
                elseif ($PassThru) {
                    $result
                }
            }
            catch {
                Write-Error "Failed to delete Connected System '$systemName': $_"
            }
        }
    }
}