Bringing IIQ Maintenance Mode to ISC: Custom Workflow & Forms Solution

Introduction

  • Why: During a recent IIQ to ISC migration, a key requirement was to replicate IIQ’s Maintenance Mode functionality. Currently, ISC does not offer an OOTB equivalent, and there is no immediate roadmap for its implementation. If you need to ensure sources stop “pinging” or interacting with destination applications during a window, this solution is for you.

  • Problem: Currently there is no way to ensure that an ISC source doesn’t attempt to ping the end system in the UI because of the health check functionality. The only way possible is using a combination of API calls to change the configuration of the source.

  • Goal: A solution that matches IIQ’s Maintenance Mode checkbox as close as possible in ISC.

Solution Overview

  • Tech Stack: 1 Form + 1 Workflow + 1 PowerShell Reporting Script.
  • High-Level Flow: Through an interactive form and custom workflow approach, I’ve created a launcher where any source can be toggled from Maintenance Mode On/Off. This solution also includes a PowerShell script that reports on the status of all sources with Maintenance Mode currently enabled.
  • Behavior Note: For those familiar with SailPoint IIQ, Maintenance Mode marks an application as offline for a defined period, which causes scheduled activities (such as aggregations) to be skipped. This ISC workflow replicates that behavior by taking the source completely offline for a specified timeframe, pausing all automated tasks. If your operational requirements dictate that a source must remain continuously active, this Maintenance Mode workflow should not be applied.

User Interface

  • Input Fields

    • Enable / Disable - Dropdown Field
    • Source Selector - Predefined Sources Dropdown Field
    • Reason - Text Field
  • User Experience: Since there’s currently no option to add customized fields into the source UI, I thought the next best was an interactive form. Also, there are many options for customizing this form to your exact org’s needs if you need additional fields.

Form Definition Code:
Form-MaintenanceMode_final.json (3.7 KB)

Back End

  • Trigger: This solution utilizes an Interactive Trigger so we can use the interactive forms options in the next step of the workflow.
  • Interactive Form: You’ll select the form in the section above in this Action step so the user can see and fill out the form in the launcher.
  • Enable or Disable: In the 5th step of the workflow, it branches into two different options (Enable or Disable) and this is controlled by using a compare strings operator which checks the option they selected in the form. Based on that selection, the workflow will do different API calls (shown in the API Calls section below).
  • After API Calls: Once all the API calls are completed to update the selected source, an interactive message & email are sent to the user using the launcher as well as an email configured in the Send Email step. This will notify the user that it was completed successfully as well as provide an audit email that shows what was selected in the form. This is useful if you have a team distribution list that should know when a source is put into Maintenance Mode.

Workflow Code:
Workflow-MaintenanceMode_final.json (13.2 KB)

API Calls

  • Enable Maintenance Mode API Calls
    • GET Source by ID - /v2026/sources/{{$.interactiveForm.formData.sources}}
    • GET Group Aggregation Schedule - /v2026/sources/{{$.interactiveForm.formData.sources}}/schedules/GROUP_AGGREGATION
    • GET Account Aggregation Schedule - /v2026/sources/{{$.interactiveForm.formData.sources}}/schedules/ACCOUNT_AGGREGATION
    • DELETE Current Group Aggregation Schedule - /v2026/sources/{{$.interactiveForm.formData.sources}}/schedules/GROUP_AGGREGATION
    • DELETE Current Account Aggregation Schedule - /v2026/sources/{{$.interactiveForm.formData.sources}}/schedules/ACCOUNT_AGGREGATION
    • PATCH Source Connector Attributes - /v2026/sources/{{$.interactiveForm.formData.sources}}
      Body:
[
            {
              "op": "add",
              "path": "/connectorAttributes/enableProvisioningFeature",
              "value": false
            },
            {
              "op": "add",
              "path": "/connectorAttributes/disable_health_check",
              "value": true
            },
            {
              "op": "replace",
              "path": "/features",
              "value": []
            },
            {
              "op": "add",
              "path": "/connectorAttributes/groupaggregationschedule",
              "value": "{{$.hTTPRequest1.body.cronExpression}}"
            },
            {
              "op": "add",
              "path": "/connectorAttributes/accountaggregationschedule",
              "value": "{{$.hTTPRequest2.body.cronExpression}}"
            },
            {
              "op": "add",
              "path": "/connectorAttributes/maintenancemode",
              "value": true
            }
]
  • Disable Maintenance Mode API Calls
    • GET Source by ID - /v2026/sources/{{$.interactiveForm.formData.sources}}
    • PATCH Source by ID - /v2026/sources/{{$.interactiveForm.formData.sources}}
    • POST Group Aggregation Schedule - /v2026/sources/{{$.interactiveForm.formData.sources}}/schedules
    • POST Account Aggregation Schedule - /v2026/sources/{{$.interactiveForm.formData.sources}}/schedules
    • PATCH Source Connector Attributes - /v2026/sources/{{$.interactiveForm.formData.sources}}
      Body:
[
            {
              "op": "add",
              "path": "/connectorAttributes/enableProvisioningFeature",
              "value": true
            },
            {
              "op": "add",
              "path": "/connectorAttributes/disable_health_check",
              "value": false
            },
            {
              "op": "add",
              "path": "/connectorAttributes/maintenancemode",
              "value": false
            }
]

Monitoring & Audit

  • Purpose: A script to quickly audit which sources are currently in Maintenance Mode.
  • Functionality: The script queries the /sources endpoint to grab all the sources in the tenant with the connectorAttribute maintenancemode == true. It exports a list of all the sources that contain that flag and puts them into a CSV file with the current date & time. In my environment, it currently runs as a scheduled task on a windows server, but you can modify it to fit your org’s needs.
  • Why it’s necessary: Preventing “zombie” sources that were put in Maintenance Mode and forgotten. Also, if Audit ever questions why a source didn’t do something automatically during a period of time, you can look at the Maintenance Mode reports to determine if the source was in Maintenance Mode during that time.

PowerShell Script:

$clientId     = "ENTERYOURCLIENTIDHERE"
$clientSecret = "ENTERYOURCLIENTSECRETHERE"
$tenantName   = "ENTERYOURTENANTNAMEHERE"
$baseUrl      = "https://$tenantName.api.identitynow.com"
$exportPath   = "ISC_MaintenanceMode_Sources_$(Get-Date -Format 'MM-dd-yyyy').csv"


$tokenBody = @{
    grant_type    = "client_credentials"
    client_id     = $clientId
    client_secret = $clientSecret
}

try {
    $tokenResponse = Invoke-RestMethod -Method Post -Uri "$baseUrl/oauth/token" -Body $tokenBody
    $accessToken = $tokenResponse.access_token
} catch {
    Write-Error "Failed to authenticate with SailPoint ISC: $($_.Exception.Message)"
    exit
}

$headers = @{
    "Authorization" = "Bearer $accessToken"
    "Content-Type"  = "application/json"
}

$allSources = @()
$limit = 250
$offset = 0
$totalCount = 0

do {
    $uri = "$baseUrl/v3/sources?limit=$limit&offset=$offset&count=true"
    $response = Invoke-WebRequest -Method Get -Uri $uri -Headers $headers
    
    # Get total count from headers on first run
    if ($offset -eq 0) {
        $totalCount = [int]$response.Headers["X-Total-Count"]
    }
    
    $sources = $response.Content | ConvertFrom-Json
    $allSources += $sources
    $offset += $limit
    
} while ($offset -lt $totalCount)

$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"

$results = $allSources | ForEach-Object {
    $mMode = $_.connectorAttributes.maintenancemode
  
    if ($mMode -eq "true" -or $mMode -eq $true) {
        [PSCustomObject]@{
            SourceName      = $_.name
            SourceId        = $_.id
            ConnectorType   = $_.type
            MaintenanceMode = $true
            ExportedAt      = $timestamp
        }
    }
}

if ($results) {
    $results | Export-Csv -Path $exportPath -NoTypeInformation
    Write-Host "Success! Found $($results.Count) sources in Maintenance Mode. Exported to: $exportPath"
} else {
    Write-Host "No sources found with maintenancemode set to true."
}

Considerations & Best Practices

  • Permissioning: The launcher should only be granted to admins in ISC as this will basically give you access to disable provisioning/aggregations/health checks completely for a source.
  • Error Handling: Many of the HTTP Request steps in the workflow will log as failures and thus show in your execution logs as a failure for you to investigate. If you’re seeing any weird behavior, the easiest way to check if it performed the execution successfully is by viewing the source configuration in VSCode and looking if the proper connectorAttributes were set.
  • PAT Needed to Run: When setting the PAT client ID and secret for all of the HTTP Requests, ensure your service account that holds this PAT has ORG_ADMIN. You should only need the scopes necessary to update source configurations on your PAT: idn:sources:manage & idn:sources:read

Conclusion

  • Summary: This is a good workaround if your org requires Maintenance Mode functionality in ISC and previously relied on it in IIQ or another system.
  • Call to Action: If you have any questions or concerns on the approach, feel free to post below. I’m happy to help extend the functionality of this workaround!

Great work, Tyler! As someone learning ISC, it’s really helpful to see practical solutions like this for handling maintenance mode scenarios. Thanks for sharing!

Thank you and happy to help!

If you have any questions on it feel free to DM me or message here :slight_smile:

Thanks for sharing this @trettkowski It really Great insight.