Public/History/Get-JIMConfigurationChangeHistory.ps1

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

function Get-JIMConfigurationChangeHistory {
    <#
    .SYNOPSIS
        Gets the Configuration Change History for a Synchronisation Rule, Connected System, Schedule, Service
        Setting, Metaverse Object Type, Metaverse Attribute, Trusted Certificate, API Key, Role, Predefined
        Search, Connector Definition, Example Data Template, or Example Data Set in JIM.
 
    .DESCRIPTION
        Retrieves the recorded configuration changes for a configuration object. Three modes are supported:
 
        - List (default): a paginated summary of changes, newest version first, each with who changed it,
          when, the optional reason, and a one-line summary of what changed. Use -All to page automatically.
        - Single version (-Version): the full change at that version, including the redacted snapshot and the
          structured diff against the previous version. Add -AsDiff to render a git-style coloured diff.
        - Compare (-CompareFrom / -CompareTo): the structured diff between any two versions. Add -AsDiff to
          render a git-style coloured diff.
 
        Secret values (for example encrypted Connected System settings) are never returned; a changed secret
        is reported only as "changed", never by value.
 
    .PARAMETER Type
        The kind of configuration object.
 
    .PARAMETER Id
        The ID of the configuration object: an integer for most kinds; a GUID for a Schedule, Trusted Certificate or
        API Key; or the setting's key for a Service Setting, such as History.RetentionPeriod.
 
    .PARAMETER All
        Returns every change. Stops after 1,000 pages with a warning unless -Force is given.
 
    .PARAMETER Force
        Fetches beyond the -All page ceiling.
 
    .PARAMETER Page
        The page of changes to return.
 
    .PARAMETER PageSize
        How many changes to return per page.
 
    .PARAMETER Version
        Returns the change that produced this version, with the object's redacted snapshot and the diff against the
        previous version.
 
    .PARAMETER CompareFrom
        The earlier of the two versions to compare.
 
    .PARAMETER CompareTo
        The later of the two versions to compare.
 
    .PARAMETER AsDiff
        Renders the change as a coloured, git-style diff instead of returning the structured object.
 
    .PARAMETER Raw
        Returns the structured object, which is the default; the switch only makes that explicit.
 
    .OUTPUTS
        The list returns one record per change, newest first. -Version returns that change in full, and -CompareFrom
        with -CompareTo the diff between two versions; with -AsDiff, either is rendered as lines of text instead. A
        secret is never returned: a changed secret is reported only as changed.
 
        JIM.ConfigurationChangeDetail: With -Version
 
        JIM.ConfigurationDiff: With -CompareFrom and -CompareTo
 
        System.String: With -AsDiff
 
    .EXAMPLE
        Get-JIMConfigurationChangeHistory -Type SynchronisationRule -Id 5
 
        Lists the most recent configuration changes for Synchronisation Rule 5.
 
    .EXAMPLE
        Get-JIMConfigurationChangeHistory -Type ConnectedSystem -Id 9 -All
 
        Returns every recorded configuration change for Connected System 9.
 
    .EXAMPLE
        Get-JIMConfigurationChangeHistory -Type ConnectedSystem -Id 9 -All -Force
 
        Returns every recorded configuration change, overriding the 1000-page safety cap.
 
    .EXAMPLE
        Get-JIMConfigurationChangeHistory -Type ConnectedSystem -Id 9 -Version 7 -AsDiff
 
        Shows version 7's change as a git-style coloured diff against version 6.
 
    .EXAMPLE
        Get-JIMSyncRule -Name "HR Inbound" | Get-JIMConfigurationChangeHistory -Type SynchronisationRule -Version 7 -AsDiff
 
        Pipes a Synchronisation Rule in (binding its id) and shows version 7 as a coloured diff.
 
    .EXAMPLE
        Get-JIMConfigurationChangeHistory -Type SynchronisationRule -Id 5 -CompareFrom 6 -CompareTo 8 -AsDiff
 
        Compares versions 6 and 8 of Synchronisation Rule 5 as a coloured diff.
 
    .EXAMPLE
        Get-JIMSchedule -Name "Nightly Sync" | Get-JIMConfigurationChangeHistory -Type Schedule
 
        Pipes a Schedule in (binding its GUID id) and lists its recorded configuration changes.
 
    .EXAMPLE
        Get-JIMConfigurationChangeHistory -Type ServiceSetting -Id 'History.RetentionPeriod'
 
        Lists the recorded configuration changes for the history retention period Service Setting.
 
    .EXAMPLE
        Get-JIMMetaverseAttribute -Name 'Email' | Get-JIMConfigurationChangeHistory -Type MetaverseAttribute
 
        Pipes a Metaverse Attribute in (binding its id) and lists its recorded configuration changes.
 
    .EXAMPLE
        Get-JIMCertificate | Get-JIMConfigurationChangeHistory -Type TrustedCertificate
 
        Pipes a Trusted Certificate in (binding its GUID id) and lists its recorded configuration changes.
 
    .EXAMPLE
        Get-JIMApiKey | Get-JIMConfigurationChangeHistory -Type ApiKey
 
        Pipes an API Key in (binding its GUID id) and lists its recorded configuration changes.
 
    .EXAMPLE
        Get-JIMRole -Name "Administrator" | Get-JIMConfigurationChangeHistory -Type Role
 
        Pipes a Role in (binding its integer id) and lists its recorded configuration changes, covering both
        its definition and every membership change.
 
    .EXAMPLE
        Get-JIMPredefinedSearch -Uri 'people' | Get-JIMConfigurationChangeHistory -Type PredefinedSearch
 
        Pipes a Predefined Search in (binding its integer id) and lists its recorded configuration changes,
        covering its own definition as well as every criteria-group and criterion change.
 
    .EXAMPLE
        Get-JIMConfigurationChangeHistory -Type ConnectorDefinition -Id 3
 
        Lists the most recent configuration changes for Connector Definition 3.
 
    .EXAMPLE
        Get-JIMConfigurationChangeHistory -Type ExampleDataSet -Id 5
 
        Lists the most recent configuration changes for Example Data Set 5.
 
    .EXAMPLE
        Get-JIMConfigurationChangeHistory -Type ExampleDataTemplate -Id 1
 
        Lists the most recent configuration changes for Example Data Template 1.
 
    .LINK
        Get-JIMSyncRule
 
    .LINK
        Get-JIMConnectedSystem
 
    .LINK
        Get-JIMActivity
    #>

    [CmdletBinding(DefaultParameterSetName = 'Page')]
    [OutputType('JIM.ConfigurationChangeHistoryItem', ParameterSetName = 'Page')]
    [OutputType('JIM.ConfigurationChangeHistoryItem', ParameterSetName = 'All')]
    [OutputType('JIM.ConfigurationChangeDetail', ParameterSetName = 'Version')]
    [OutputType('JIM.ConfigurationDiff', ParameterSetName = 'Compare')]
    [OutputType([string], ParameterSetName = 'Version')]
    [OutputType([string], ParameterSetName = 'Compare')]
    param(
        [Parameter(Mandatory)]
        [ValidateSet('SynchronisationRule', 'ConnectedSystem', 'Schedule', 'ServiceSetting', 'MetaverseObjectType', 'MetaverseAttribute', 'TrustedCertificate', 'ApiKey', 'Role', 'PredefinedSearch', 'ConnectorDefinition', 'ExampleDataTemplate', 'ExampleDataSet')]
        [string]$Type,

        # A string rather than [int] so it can carry an integer (Synchronisation Rule / Connected System), a GUID
        # (Schedule), or a setting key (Service Setting); validated per -Type in the process block.
        [Parameter(Mandatory, ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [string]$Id,

        [Parameter(Mandatory, ParameterSetName = 'All')]
        [switch]$All,

        [Parameter(ParameterSetName = 'All')]
        [switch]$Force,

        [Parameter(ParameterSetName = 'Page')]
        [ValidateRange(1, [int]::MaxValue)]
        [int]$Page = 1,

        [Parameter(ParameterSetName = 'Page')]
        [Parameter(ParameterSetName = 'All')]
        [ValidateRange(1, 100)]
        [int]$PageSize = 50,

        [Parameter(Mandatory, ParameterSetName = 'Version')]
        [ValidateRange(1, [int]::MaxValue)]
        [int]$Version,

        [Parameter(Mandatory, ParameterSetName = 'Compare')]
        [ValidateRange(1, [int]::MaxValue)]
        [int]$CompareFrom,

        [Parameter(Mandatory, ParameterSetName = 'Compare')]
        [ValidateRange(1, [int]::MaxValue)]
        [int]$CompareTo,

        [Parameter(ParameterSetName = 'Version')]
        [Parameter(ParameterSetName = 'Compare')]
        [switch]$AsDiff,

        [Parameter(ParameterSetName = 'Version')]
        [Parameter(ParameterSetName = 'Compare')]
        [switch]$Raw
    )

    process {
        # Validate the id shape per object type before anything else (so a bad id fails fast, even offline):
        # Synchronisation Rules, Connected Systems, Metaverse Object Types, Metaverse Attributes, Roles,
        # Predefined Searches, Connector Definitions, Example Data Templates and Example Data Sets are
        # integer-keyed, Schedules, Trusted Certificates and API Keys are GUID-keyed, and Service Settings are
        # keyed by their dot-notation setting key.
        if ($Type -in 'Schedule', 'TrustedCertificate', 'ApiKey') {
            $parsedGuid = [Guid]::Empty
            if (-not [Guid]::TryParse($Id, [ref]$parsedGuid)) {
                Write-Error "For -Type $Type, -Id must be a GUID ($($Type)s are GUID-keyed). Got: '$Id'."
                return
            }
            $base = switch ($Type) {
                'Schedule' { "/api/v1/schedules/$Id/change-history" }
                'TrustedCertificate' { "/api/v1/certificates/$Id/change-history" }
                'ApiKey' { "/api/v1/apikeys/$Id/change-history" }
            }
        }
        elseif ($Type -eq 'ServiceSetting') {
            # Any non-empty string is a candidate key; escape it for the URL path.
            $base = "/api/v1/service-settings/$([uri]::EscapeDataString($Id))/change-history"
        }
        else {
            $parsedInt = 0
            if (-not [int]::TryParse($Id, [ref]$parsedInt)) {
                Write-Error "For -Type $Type, -Id must be an integer. Got: '$Id'."
                return
            }
            $base = switch ($Type) {
                'SynchronisationRule' { "/api/v1/synchronisation/sync-rules/$Id/change-history" }
                'ConnectedSystem' { "/api/v1/synchronisation/connected-systems/$Id/change-history" }
                'MetaverseObjectType' { "/api/v1/metaverse/object-types/$Id/change-history" }
                'MetaverseAttribute' { "/api/v1/metaverse/attributes/$Id/change-history" }
                'Role' { "/api/v1/security/roles/$Id/change-history" }
                'PredefinedSearch' { "/api/v1/predefined-searches/$Id/change-history" }
                'ConnectorDefinition' { "/api/v1/synchronisation/connector-definitions/$Id/change-history" }
                'ExampleDataSet' { "/api/v1/example-data/example-data-sets/$Id/change-history" }
                'ExampleDataTemplate' { "/api/v1/example-data/templates/$Id/change-history" }
            }
        }

        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
        }

        switch ($PSCmdlet.ParameterSetName) {
            'Version' {
                Write-Verbose "Getting $Type $Id configuration change version $Version"
                $detail = Invoke-JIMApi -Endpoint "$base/$Version"
                if ($AsDiff) {
                    Format-JIMConfigurationDiff -Diff $detail.diff
                }
                else {
                    $detail
                }
            }
            'Compare' {
                Write-Verbose "Comparing $Type $Id configuration versions $CompareFrom and $CompareTo"
                $diff = Invoke-JIMApi -Endpoint "${base}/compare?fromVersion=$CompareFrom&toVersion=$CompareTo"
                if ($AsDiff) {
                    Format-JIMConfigurationDiff -Diff $diff
                }
                else {
                    $diff
                }
            }
            default {
                # 'Page' or 'All': the change-history list. The shared helper owns the -All loop, the page
                # cap and the warnings (issue #487); a single page is fetched directly.
                $pageRequest = {
                    param($p)
                    Write-Verbose "Getting $Type $Id Configuration Change History (Page: $p, PageSize: $PageSize)"
                    Invoke-JIMApi -Endpoint "${base}?page=$p&pageSize=$PageSize"
                }

                if ($All) {
                    Invoke-JIMPagedFetch -PageRequest $pageRequest -CmdletName 'Get-JIMConfigurationChangeHistory' -PageSize $PageSize -Force:$Force `
                        -ItemNoun 'change records'
                }
                else {
                    $response = & $pageRequest $Page
                    foreach ($item in $response.items) {
                        $item
                    }
                }
            }
        }
    }
}