Public/Previews/New-JIMConfigurationChangePreview.ps1
|
# Copyright (c) Tetron Limited. All rights reserved. # Licensed under the Tetron Commercial License. See LICENSE file in the project root. function New-JIMConfigurationChangePreview { <# .SYNOPSIS Finds out what a proposed configuration change would do, without making it. .DESCRIPTION Starts a Configuration Change Preview: JIM evaluates a proposed change against the objects already in the metaverse and reports what would happen to them, changing nothing. Several surfaces can be previewed, selected by which identifier and proposal you pass: - -MetaverseObjectTypeId previews a change to that type's deletion settings. The field semantics match Set-JIMMetaverseObjectType exactly, so an omitted parameter previews the stored value. Pass the same parameters to this cmdlet and then to Set-JIMMetaverseObjectType and the preview describes precisely what the change will do. - -ConnectedSystemId previews a change to that system's partition and container selection. Pass the whole selection rather than one flag: what a deselection costs depends on the rest of the selection, because an object leaves import scope only when nothing else still covers it. Apply the previewed change with Set-JIMConnectedSystemPartition and Set-JIMConnectedSystemContainer. - -SyncRuleId previews a change to that Synchronisation Rule's two destructive toggles: the Outbound Deprovision Action (Disconnect or Delete on a scope exit) and the Inbound Out-of-Scope Action (RemainJoined or Disconnect). The field semantics match Set-JIMSyncRule exactly, so an omitted parameter previews the stored value. Apply the previewed change with Set-JIMSyncRule. - -SyncRuleId with -ScopingCriteriaGroup previews a change to that rule's Scoping Criteria: which objects it would manage at all, and what each movement in or out of scope would cost. - -SyncRuleId with -AttributeFlowMapping previews a change to that rule's Attribute Flow: what value every object it manages would end up with, per attribute. - -SyncRuleId with -RuleState previews a change to that rule's behaviour toggles: how many objects would stop having an identity or an account created for them, and how many would be left free to drift. -ProjectToMetaverse, -ProvisionToConnectedSystem and -EnforceState are optional and merge with the stored rule. Direction cannot be previewed: a saved rule's mappings are written for the direction it has, so a flip comes back with a blocking finding. - -ConnectedSystemId with -MatchingRule previews a change to that system's Object Matching Rules: which of its unjoined objects would join a different Metaverse Object, join instead of projecting a new identity, project instead of joining, or match ambiguously and fail. Add -ObjectMatchingRuleMode to preview the Simple/Advanced switch. Objects already joined are never re-matched, so no matching change can move them, and the preview says so. - -ConnectedSystemId with -SchemaObjectType previews a change to that system's schema selection: how many Connected System Objects would stop being imported, which attributes would stop being refreshed and on how many objects, and whose contributed Metaverse values would be withdrawn, or kept, when their obsolete objects are next synchronised. Deselecting an Object Type does not obsolete the objects already imported from it: they stay joined and keep contributing the values they last imported, which the preview states rather than leaving to be discovered. Evaluation is asynchronous. Without -Wait this returns as soon as the proposal itself has been validated, carrying the ActivityId to poll with Get-JIMConfigurationChangePreview. With -Wait it polls until the preview reaches a terminal state and returns the finished preview. A proposal that cannot be applied comes back with a blocking validation finding and is never evaluated: check IsBlocked and ValidationFindings before reading anything else. Pass the returned ActivityId to Set-JIMMetaverseObjectType -PreviewActivityId when you make the change, so the audit records which preview informed it. .PARAMETER MetaverseObjectTypeId The Metaverse Object Type whose deletion settings are being proposed. Selects the deletion settings surface; other surfaces gain their own parameters as their adapters ship. .PARAMETER ConnectedSystemId The Connected System whose partition and container selection is being proposed. Selects the scope selection surface. .PARAMETER SchemaObjectType The proposed schema selection, as one hashtable per Connected System Object Type being changed. Selects the schema selection surface. Each hashtable needs objectTypeId, and then only the keys being changed: selected (bool), removeContributedAttributesOnObsoletion (bool), and selectedAttributeIds (int array). Every omission means "leave this as it stands", at both levels: an Object Type not named here is left alone, and a key not set keeps that Type's stored value. Send the whole attribute set for a Type rather than the attributes that changed; External IDs are selected implicitly and need not be listed. .PARAMETER SelectedPartitionIds The partitions that would be managed. Omitted previews the partitions currently selected, so a proposal changing only containers need not restate them. .PARAMETER SelectedContainerIds The containers that would be managed. Omitted previews the containers currently selected. Selecting a container selects its whole subtree, so a descendant does not need listing to be in scope; pass the containers that would carry a tick. Read the current selection with Get-JIMConnectedSystemPartition. .PARAMETER ExcludedContainerIds The containers that would be carved out of the selection around them. Omitted previews the exclusions currently in force, so a proposal changing only the selection need not restate them; an empty array previews lifting every exclusion, which brings those branches back into scope. A container states one or the other, so an id passed here must not also appear in -SelectedContainerIds; a proposal naming both is refused rather than resolved. .PARAMETER DeletionRule The proposed deletion rule. Omitted previews the stored rule. - Manual: objects are never automatically deleted - WhenLastConnectorDisconnected: objects are deleted when all connectors are removed - WhenAuthoritativeSourceDisconnected: objects are deleted when an authoritative source disconnects .PARAMETER DeletionGracePeriod The proposed grace period, as a TimeSpan. Omitted previews the stored grace period; [TimeSpan]::Zero previews no grace period, matching how Set-JIMMetaverseObjectType stores it. .PARAMETER DeletionTriggerConnectedSystemIds The proposed authoritative sources. Omitted previews the stored list. Worth knowing before reading the result: this list is consulted at the moment a Connected System Object disconnects, not by the housekeeping pass that acts on objects already marked, so changing it alone moves no object's deletion date and the preview will honestly report no impact from it. What it can do is make the proposal invalid, which comes back as a blocking finding. .PARAMETER DeletionTriggerMode The proposed trigger mode. Omitted previews the stored mode. Read at the same moment as the trigger list, and so with the same standing impact: none. .PARAMETER SyncRuleId The Synchronisation Rule whose destructive toggles are being proposed. Selects the destructive toggles surface. .PARAMETER OutboundDeprovisionAction The proposed action for a joined target object whose Metaverse Object leaves this export rule's scope: Disconnect leaves the object in the target Connected System; Delete stages a Delete export that removes it. Omitted previews the stored action. Read only by export Synchronisation Rules, and the preview says so honestly when the rule is an import rule. .PARAMETER InboundOutOfScopeAction The proposed action for a joined Connected System Object that leaves this import rule's scope or is obsoleted: RemainJoined keeps the join ("once managed, always managed"); Disconnect breaks it, recalls what the object contributed and can trigger the Metaverse Object's deletion rules. Omitted previews the stored action. Read only by import Synchronisation Rules. .PARAMETER ScopingCriteriaGroup The proposed Scoping Criteria, as one hashtable per top-level criteria group. Groups are combined with OR, exactly as a synchronisation combines them. Each group takes a Type of 'All' or 'Any', a Criteria array, and an optional ChildGroups array of further groups nested inside it. Each criterion names one attribute by id (ConnectedSystemAttributeId on an import rule, MetaverseAttributeId on an export rule), a ComparisonType, and the value to compare against in the field matching the attribute's data type (StringValue, IntValue, LongValue, DecimalValue, DateTimeValue, BoolValue or GuidValue). Mandatory, and an empty array is a valid and deliberate value: it proposes removing every criterion, which hands the rule every object of its type. That is the widest change the Scope tab can make, so it has to be asked for rather than arrived at by omission. .PARAMETER AttributeFlowMapping The proposed Attribute Flow, as one hashtable per mapping. Each mapping names the attribute it writes (TargetMetaverseAttributeId on an import rule, TargetConnectedSystemAttributeId on an export rule) and a Sources array; a source takes an Order, and either an attribute id (ConnectedSystemAttributeId on an import rule, MetaverseAttributeId on an export rule) or an Expression with an optional MissingInputBehaviour. A mapping may also carry Priority, NullIsValue, InitialExportOnly, InboundValueProcessing and CaseNormalisation, which default to the same values the editor uses. Mandatory, and an empty array is a valid and deliberate value: it proposes removing every mapping, so the rule flows nothing. Priority is worth passing deliberately on an import mapping. It defaults to the lowest, so a mapping proposed for an attribute another rule already contributes to would be evaluated and then write nothing; the preview reports that as a validation finding rather than as values that would never be written. .PARAMETER FullDataSet Keep every object-level detail row rather than the per-group cap's worth. Summary counts are exact either way; this decides only how much of the detail behind them can be read back with Get-JIMConfigurationChangePreviewDelta. Use it when you intend to export the full list. .PARAMETER Wait Poll until the preview finishes, and return the finished preview rather than the start result. .PARAMETER TimeoutSeconds How long -Wait polls before giving up and writing an error. The preview itself keeps running; read it later with Get-JIMConfigurationChangePreview. Defaults to 300 seconds. .OUTPUTS Without -Wait: PSCustomObject with ActivityId, ValidationFindings, IsBlocked, Failed, EstimatedAffectedObjects and EstimatedDeltaRows. With -Wait: the preview, as returned by Get-JIMConfigurationChangePreview. .EXAMPLE New-JIMConfigurationChangePreview -MetaverseObjectTypeId 1 -DeletionRule WhenLastConnectorDisconnected -Wait Reports what switching the User type to automatic deletion would do to the Metaverse Objects whose connectors have already gone, and waits for the answer. .EXAMPLE $preview = New-JIMConfigurationChangePreview -MetaverseObjectTypeId 1 -DeletionGracePeriod ([TimeSpan]::FromDays(7)) -Wait $preview.ImpactCounts Shortens the grace period to seven days and reads how many objects each transition affects. .EXAMPLE $preview = New-JIMConfigurationChangePreview -MetaverseObjectTypeId 1 -DeletionRule WhenLastConnectorDisconnected -Wait if ($preview.ImpactCounts.Count -eq 0) { Set-JIMMetaverseObjectType -Id 1 -DeletionRule WhenLastConnectorDisconnected -PreviewActivityId $preview.ActivityId } Applies the change only when the preview found nothing would happen to existing objects, and records the preview against the change. .EXAMPLE $current = Get-JIMConnectedSystemPartition -ConnectedSystemId 2 $keep = $current.containers | Where-Object { $_.selected -and $_.name -ne 'Contractors' } New-JIMConfigurationChangePreview -ConnectedSystemId 2 -SelectedContainerIds $keep.id -Wait Reports what deselecting the Contractors container would do: how many Connected System Objects leave import scope, how many of those are joined and would disconnect from their Metaverse Object, and how many Metaverse Objects would then become eligible for automatic deletion. .EXAMPLE $current = Get-JIMConnectedSystemPartition -ConnectedSystemId 2 $carveOut = $current.containers | Where-Object name -eq 'Service Accounts' New-JIMConfigurationChangePreview -ConnectedSystemId 2 -ExcludedContainerIds $carveOut.id -Wait Reports what excluding the Service Accounts container would do. Its parent stays selected, so nothing else in the branch moves; the objects inside the carve-out are the ones that leave import scope, and the ones already joined would disconnect from their Metaverse Objects. .EXAMPLE $preview = New-JIMConfigurationChangePreview -ConnectedSystemId 2 -SelectedPartitionIds 5 -Wait $preview.ImpactCounts | Where-Object transitionType -eq 'WouldBecomeDeletionEligible' Narrows the managed partitions to one and reads how many Metaverse Objects the resulting disconnections would put on course for deletion. .EXAMPLE $preview = New-JIMConfigurationChangePreview -SyncRuleId 42 -OutboundDeprovisionAction Delete -Wait $preview.ImpactCounts Reports what flipping an export Synchronisation Rule's Deprovisioning Action to Delete would do: how many joined objects already out of scope would be removed from the target Connected System at the next synchronisation, and how many managed objects' fate on a future scope exit changes. .EXAMPLE $group = @{ Type = 'All' Criteria = @( @{ ConnectedSystemAttributeId = 101; ComparisonType = 'Equals'; StringValue = 'Sales' } ) } $preview = New-JIMConfigurationChangePreview -SyncRuleId 42 -ScopingCriteriaGroup $group -Wait $preview.ImpactCounts Reports what narrowing an import Synchronisation Rule to the Sales department would do: how many joined objects would leave scope and disconnect from their Metaverse Objects, how many unjoined ones simply stop matching, and how many objects would newly enter scope and be projected. .EXAMPLE $preview = New-JIMConfigurationChangePreview -SyncRuleId 42 -ScopingCriteriaGroup @() -Wait $preview.ImpactCounts | Where-Object transitionType -eq 'Projected' Reports what removing every Scoping Criterion would do, which puts every object of the rule's type in scope. The Projected count is how many Metaverse Objects that would create. .EXAMPLE $mapping = @{ TargetMetaverseAttributeId = 201 Priority = 1 Sources = @( @{ Order = 1; Expression = 'cs["givenName"] + "." + cs["sn"] + "@corp.local"'; MissingInputBehaviour = 'FailMapping' } ) } $preview = New-JIMConfigurationChangePreview -SyncRuleId 42 -AttributeFlowMapping $mapping -Wait $preview.ImpactCounts Reports what an email cutover would write: how many identities' addresses change, and how many objects the Expression could not be evaluated for at all because a required input is missing. .EXAMPLE $preview = New-JIMConfigurationChangePreview -SyncRuleId 42 -AttributeFlowMapping $mapping -FullDataSet -Wait Get-JIMConfigurationChangePreviewDelta -ActivityId $preview.ActivityId | Where-Object transitionType -eq 'WouldFailAttributeFlow' Keeps every detail row and lists the objects the proposed Expression would not evaluate for, which is the handful the cutover would otherwise leave without an address. .EXAMPLE $preview = New-JIMConfigurationChangePreview -SyncRuleId 42 -InboundOutOfScopeAction Disconnect -Wait if (($preview.ImpactCounts | Measure-Object objectCount -Sum).Sum -eq 0) { Set-JIMSyncRule -Id 42 -InboundOutOfScopeAction Disconnect -PreviewActivityId $preview.ActivityId } Applies the tightened Out-of-Scope Action only when the preview found no object would be disconnected by it today, and records the preview against the change. .EXAMPLE $rules = @( @{ order = 0 connectedSystemObjectTypeId = 9 metaverseObjectTypeId = 3 targetMetaverseAttributeId = 201 caseSensitive = $false sources = @(@{ order = 0; connectedSystemAttributeId = 102 }) } ) $preview = New-JIMConfigurationChangePreview -ConnectedSystemId 5 -MatchingRule $rules -Wait $preview.ImpactCounts | Where-Object transitionType -eq 'WouldJoinDifferentMetaverseObject' Previews matching on a different attribute and reports how many accounts would end up on a different identity, which is the count that decides whether the rule is safe to save. .EXAMPLE New-JIMConfigurationChangePreview -ConnectedSystemId 5 -MatchingRule @() -Wait Previews removing every Object Matching Rule: nothing would join, and every unjoined object would project a new identity instead. .EXAMPLE $types = @(@{ objectTypeId = 9; selected = $false }) $preview = New-JIMConfigurationChangePreview -ConnectedSystemId 5 -SchemaObjectType $types -Wait $preview.ImpactCounts | Where-Object transitionType -eq 'WouldStopBeingImported' Previews deselecting an Object Type and reports how many of its objects would stop being imported while staying joined to their Metaverse Objects, contributing values that never refresh again. .EXAMPLE $types = @(@{ objectTypeId = 9; removeContributedAttributesOnObsoletion = $false }) New-JIMConfigurationChangePreview -ConnectedSystemId 5 -SchemaObjectType $types -Wait Previews turning obsoletion recall off, over the objects already obsolete and still joined, which are the only ones whose fate the setting changes now. .EXAMPLE $preview = New-JIMConfigurationChangePreview -SyncRuleId 42 -RuleState Disabled -Wait $preview.ImpactCounts Previews disabling a Synchronisation Rule and reports what stops happening: the identities and accounts that would no longer be created, which is the count that says whether "just pausing it" is what it does. .EXAMPLE New-JIMConfigurationChangePreview -SyncRuleId 42 -RuleState Enabled -ProvisionToConnectedSystem $true -Wait Previews enabling a rule and turning provisioning on together, which is account creation at scale. .LINK Get-JIMConfigurationChangePreview Get-JIMConfigurationChangePreviewDelta Stop-JIMConfigurationChangePreview Set-JIMMetaverseObjectType Set-JIMConnectedSystemPartition Set-JIMConnectedSystemContainer Set-JIMSyncRule Set-JIMMatchingRule Set-JIMConnectedSystemObjectType #> [CmdletBinding(DefaultParameterSetName = 'MetaverseObjectTypeDeletionSettings')] [OutputType([PSCustomObject])] param( [Parameter(Mandatory, ParameterSetName = 'MetaverseObjectTypeDeletionSettings', ValueFromPipelineByPropertyName)] [int]$MetaverseObjectTypeId, [Parameter(ParameterSetName = 'MetaverseObjectTypeDeletionSettings')] [ValidateSet('Manual', 'WhenLastConnectorDisconnected', 'WhenAuthoritativeSourceDisconnected')] [string]$DeletionRule, [Parameter(ParameterSetName = 'MetaverseObjectTypeDeletionSettings')] [TimeSpan]$DeletionGracePeriod, [Parameter(ParameterSetName = 'MetaverseObjectTypeDeletionSettings')] [int[]]$DeletionTriggerConnectedSystemIds, [Parameter(ParameterSetName = 'MetaverseObjectTypeDeletionSettings')] [ValidateSet('AllSourcesDisconnect', 'SpecificSourcesDisconnect')] [string]$DeletionTriggerMode, [Parameter(Mandatory, ParameterSetName = 'ConnectedSystemScopeSelection', ValueFromPipelineByPropertyName)] [Parameter(Mandatory, ParameterSetName = 'ObjectMatching', ValueFromPipelineByPropertyName)] [Parameter(Mandatory, ParameterSetName = 'ConnectedSystemSchema', ValueFromPipelineByPropertyName)] [int]$ConnectedSystemId, [Parameter(ParameterSetName = 'ConnectedSystemScopeSelection')] [int[]]$SelectedPartitionIds, [Parameter(ParameterSetName = 'ConnectedSystemScopeSelection')] [int[]]$SelectedContainerIds, [Parameter(ParameterSetName = 'ConnectedSystemScopeSelection')] [AllowEmptyCollection()] [int[]]$ExcludedContainerIds, [Parameter(Mandatory, ParameterSetName = 'SyncRuleDestructiveToggles', ValueFromPipelineByPropertyName)] [Parameter(Mandatory, ParameterSetName = 'SyncRuleScopingCriteria', ValueFromPipelineByPropertyName)] [Parameter(Mandatory, ParameterSetName = 'SyncRuleAttributeFlow', ValueFromPipelineByPropertyName)] [Parameter(Mandatory, ParameterSetName = 'SyncRuleBehaviour', ValueFromPipelineByPropertyName)] [int]$SyncRuleId, [Parameter(ParameterSetName = 'SyncRuleDestructiveToggles')] [ValidateSet('Disconnect', 'Delete')] [string]$OutboundDeprovisionAction, [Parameter(ParameterSetName = 'SyncRuleDestructiveToggles')] [ValidateSet('RemainJoined', 'Disconnect')] [string]$InboundOutOfScopeAction, [Parameter(Mandatory, ParameterSetName = 'SyncRuleScopingCriteria')] [AllowEmptyCollection()] [hashtable[]]$ScopingCriteriaGroup, [Parameter(Mandatory, ParameterSetName = 'SyncRuleAttributeFlow')] [AllowEmptyCollection()] [hashtable[]]$AttributeFlowMapping, [Parameter(Mandatory, ParameterSetName = 'ObjectMatching')] [AllowEmptyCollection()] [hashtable[]]$MatchingRule, [Parameter(ParameterSetName = 'ObjectMatching')] [ValidateSet('ConnectedSystem', 'SyncRule')] [string]$ObjectMatchingRuleMode, [Parameter(Mandatory, ParameterSetName = 'SyncRuleBehaviour')] [ValidateSet('Enabled', 'Disabled')] [string]$RuleState, [Parameter(ParameterSetName = 'SyncRuleBehaviour')] [bool]$ProjectToMetaverse, [Parameter(ParameterSetName = 'SyncRuleBehaviour')] [bool]$ProvisionToConnectedSystem, [Parameter(ParameterSetName = 'SyncRuleBehaviour')] [bool]$EnforceState, [Parameter(Mandatory, ParameterSetName = 'ConnectedSystemSchema')] [hashtable[]]$SchemaObjectType, [Parameter()] [switch]$FullDataSet, [Parameter()] [switch]$Wait, [Parameter()] [ValidateRange(1, 86400)] [int]$TimeoutSeconds = 300 ) 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 } $body = @{} if ($PSCmdlet.ParameterSetName -eq 'ConnectedSystemScopeSelection') { if ($PSBoundParameters.ContainsKey('SelectedPartitionIds')) { # Wrapped in @() so a single id serialises as a JSON array rather than a bare number. $body.selectedPartitionIds = @($SelectedPartitionIds) } if ($PSBoundParameters.ContainsKey('SelectedContainerIds')) { $body.selectedContainerIds = @($SelectedContainerIds) } if ($PSBoundParameters.ContainsKey('ExcludedContainerIds')) { $body.excludedContainerIds = @($ExcludedContainerIds) } } if ($DeletionRule) { # Enum sent as its string name; the API rejects numeric ordinals # (JsonStringEnumConverter allowIntegerValues:false). $body.deletionRule = $DeletionRule } if ($PSBoundParameters.ContainsKey('DeletionGracePeriod')) { $body.deletionGracePeriod = $DeletionGracePeriod.ToString() } if ($PSBoundParameters.ContainsKey('DeletionTriggerConnectedSystemIds')) { $body.deletionTriggerConnectedSystemIds = $DeletionTriggerConnectedSystemIds } if ($PSBoundParameters.ContainsKey('DeletionTriggerMode')) { $body.deletionTriggerMode = $DeletionTriggerMode } if ($PSCmdlet.ParameterSetName -eq 'SyncRuleDestructiveToggles') { if ($OutboundDeprovisionAction) { # Enum sent as its string name; the API rejects numeric ordinals # (JsonStringEnumConverter allowIntegerValues:false). $body.outboundDeprovisionAction = $OutboundDeprovisionAction } if ($InboundOutOfScopeAction) { $body.inboundOutOfScopeAction = $InboundOutOfScopeAction } } if ($PSCmdlet.ParameterSetName -eq 'SyncRuleScopingCriteria') { # Always sent, including as an empty array: omitting the field previews the rule's stored criteria, # while an empty array proposes removing every one of them. The two are different questions, and the # parameter is mandatory so the caller has to say which they are asking. # Wrapped in @() so a single group serialises as a JSON array rather than a bare object. $body.criteriaGroups = @($ScopingCriteriaGroup) } if ($PSCmdlet.ParameterSetName -eq 'SyncRuleAttributeFlow') { # Always sent, including as an empty array, for the same reason as the criteria groups above: omitting # the field previews the rule's stored mappings, while an empty array proposes removing every one of # them. Wrapped in @() so a single mapping serialises as a JSON array rather than a bare object. $body.mappings = @($AttributeFlowMapping) } if ($PSCmdlet.ParameterSetName -eq 'SyncRuleBehaviour') { # -RuleState is mandatory because the toggle it sets is a boolean: were it optional, a caller who left # it out could not be told apart from one proposing to switch the rule off, and those are opposite # questions. The other three are optional and merge with the stored rule, so silence never proposes a # change nobody asked for. $body.enabled = ($RuleState -eq 'Enabled') foreach ($toggle in 'ProjectToMetaverse', 'ProvisionToConnectedSystem', 'EnforceState') { if ($PSBoundParameters.ContainsKey($toggle)) { $body[[System.Char]::ToLowerInvariant($toggle[0]) + $toggle.Substring(1)] = $PSBoundParameters[$toggle] } } } if ($PSCmdlet.ParameterSetName -eq 'ConnectedSystemSchema') { # Only the Object Types the caller names are sent, and within each only the keys they set. Every # omission means "leave this as it stands", because every type default here is the destructive answer: # an absent 'selected' read as false would propose taking a whole Object Type out of management on a # request that meant to change one attribute. Wrapped in @() so a single Object Type serialises as a # JSON array rather than a bare object. $body.objectTypes = @($SchemaObjectType) } if ($PSCmdlet.ParameterSetName -eq 'ObjectMatching') { # Always sent, including as an empty array, for the same reason as the mappings above: omitting the # field previews the Connected System's stored rules, while an empty array proposes removing every one # of them, leaving nothing able to join. Wrapped in @() so a single rule serialises as a JSON array # rather than a bare object. $body.rules = @($MatchingRule) # The mode is only sent when the caller is changing it; omitted, the preview keeps the stored mode, so # a caller editing rules alone does not have to restate which mode they are in. if ($ObjectMatchingRuleMode) { $body.mode = $ObjectMatchingRuleMode } } if ($FullDataSet) { $body.deltaPersistence = 'Full' } if ($PSCmdlet.ParameterSetName -eq 'ConnectedSystemScopeSelection') { $endpoint = "/api/v1/synchronisation/connected-systems/$ConnectedSystemId/scope-selection/preview" $subject = "Connected System $ConnectedSystemId" } elseif ($PSCmdlet.ParameterSetName -eq 'SyncRuleDestructiveToggles') { $endpoint = "/api/v1/synchronisation/sync-rules/$SyncRuleId/destructive-toggles/preview" $subject = "Synchronisation Rule $SyncRuleId" } elseif ($PSCmdlet.ParameterSetName -eq 'SyncRuleScopingCriteria') { $endpoint = "/api/v1/synchronisation/sync-rules/$SyncRuleId/scoping-criteria/preview" $subject = "Synchronisation Rule $SyncRuleId" } elseif ($PSCmdlet.ParameterSetName -eq 'SyncRuleAttributeFlow') { $endpoint = "/api/v1/synchronisation/sync-rules/$SyncRuleId/mappings/preview" $subject = "Synchronisation Rule $SyncRuleId" } elseif ($PSCmdlet.ParameterSetName -eq 'ObjectMatching') { $endpoint = "/api/v1/synchronisation/connected-systems/$ConnectedSystemId/matching-rules/preview" $subject = "Connected System $ConnectedSystemId" } elseif ($PSCmdlet.ParameterSetName -eq 'SyncRuleBehaviour') { $endpoint = "/api/v1/synchronisation/sync-rules/$SyncRuleId/behaviour/preview" $subject = "Synchronisation Rule $SyncRuleId" } elseif ($PSCmdlet.ParameterSetName -eq 'ConnectedSystemSchema') { $endpoint = "/api/v1/synchronisation/connected-systems/$ConnectedSystemId/schema-selection/preview" $subject = "Connected System $ConnectedSystemId" } else { $endpoint = "/api/v1/metaverse/object-types/$MetaverseObjectTypeId/deletion-settings/preview" $subject = "Metaverse Object Type $MetaverseObjectTypeId" } try { $start = Invoke-JIMApi -Endpoint $endpoint -Method 'POST' -Body $body } catch { Write-Error "Failed to start a preview for ${subject}: $_" return } if (-not $start) { return } foreach ($finding in $start.ValidationFindings) { # Surfaced as they are produced rather than left for the caller to find in the object: a # blocking finding means nothing was evaluated, and a script that read straight past it # would treat "this change is impossible" as "this change affects nothing". $message = "Preview validation ($($finding.Severity)): $($finding.Message)" if ($finding.Severity -eq 'Blocking') { Write-Warning $message } else { Write-Verbose $message } } if (-not $Wait -or $start.IsBlocked -or $start.Failed) { return $start } $deadline = (Get-Date).AddSeconds($TimeoutSeconds) while ($true) { $preview = Get-JIMConfigurationChangePreview -ActivityId $start.ActivityId if ($preview.IsComplete -or $preview.HasFailed -or $preview.ActivityStatus -eq 'Cancelled') { return $preview } if ((Get-Date) -ge $deadline) { Write-Error "The preview did not finish within $TimeoutSeconds seconds. It is still running; read it with Get-JIMConfigurationChangePreview -ActivityId $($start.ActivityId), or abandon it with Stop-JIMConfigurationChangePreview." return $preview } Start-Sleep -Seconds 2 } } } |