AfterModify PowerShell rule capabilities

Hey Team.

More of a design/architectural question here.

We’re currently managing 1Password items via an afterCreate PS Rule to AD domains for customer. We need to, however, change this to only trigger on LCS changes: newValue:active

Is anyone familiar with how I can leverage the afterModify PS rule for AD conectors to trigger only when identities enter the “active” lifecycle state? I am familiar that this case is probably possible to solve using Privileged Task Automation, we do not have the “new” VA’s however and we will not be upgrading any time soon.

Current solution: afterCreate rule - triggers script on AD creation

Proposed solution: afterModify rule - triggers script on identity lifecyclestate - newValue: active

Best regards,

Seb

@Swegmann you can also think of leveraging the event triggers without workflows

how would I go about using Event Trigger to run a powershell script on the IQService host?

@Swegmann do you have an extensionattribute in AD that stores the ISC status of the user? If so, you could create a powershell script that triggers when it is modified to active.

Hello Austin!

Ah you mean skip “rule” in ISC completely and just have the trigger on the windows-side?

@SebastianWilmsen Get the operation type in the Aftermodify script like below,

# Parse the XML request string from the environment variable
$sReader = New-Object System.IO.StringReader([System.String]$env:Request)
$xmlReader = [System.xml.XmlTextReader]([sailpoint.utils.xml.XmlUtil]::getReader($sReader))
$requestObject = New-Object Sailpoint.Utils.objects.AccountRequest($xmlReader)


# Get the operation type (e.g., "Disable", "Modify", "Enable", etc.)
$operation = $requestObject.Operation.ToString()

# --- Your Custom Logic Here ---
# Only execute your custom logic for the 'Disable' operation
if ($operation -ieq "Enable") {

This is the best straightforward approach. I implemented custom logic for Disable operation. Check my post here - ISC triggers multiple account modify requests during identity termination - #3 by Jhm356

Hello,

You do the below.

  1. Create a new Identity attribute named as “LCS State“. Map the same transform as that attached to “CloudlifeCycleState“ identity attribute
  2. Map this new “LCS State“ identity attribute to one the extension attributes in AD. Lets say name of extension attribute is “extensionAttribute10“
  3. Enable the attribute sync for this
  4. Develop a PowerShell Script which is “After Modify AD PowerShell“.
  5. Use the logic in AfterModify PowerShell Script as below.

foreach ($attrib in $requestObject.AttributeRequests) {

LogToFile "Attr in request: Name=$($attrib.Name) Op=$($attrib.Operation) Value=$($attrib.Value)" "Debug"



if ($attrib.Name -eq "extensionAttribute10" -and $attrib.Value) {

    $newLCS= $attrib.Value

    LogToFile "New extensionAttribute10 from request: $newLCS"

}

}

  1. In this way, your script and respectively child logic of Password Creation will work only when the LCS state of the user ins changed to active.
  2. Also, using a dedicated LCS state identity attribute and mapping it to a dedicated account attribute in AD in the form of “extensionAttribute“ will not trigger this in infinite loop.

Kindly review and let me know whether it works.

Thank You,

Regards,

Rohit Wekhande.

1 Like

Updated if condition to trigger only on Active LCS

LogToFile "Attr in request: Name=$($attrib.Name) Op=$($attrib.Operation) Value=$($attrib.Value)" "Debug"



if ($attrib.Name -eq "extensionAttribute10" -and $attrib.Value -eq 'Active') {

    $newLCS= $attrib.Value

    LogToFile "New extensionAttribute10 from request: $newLCS"

}


Hello all and thank you for your answers.

all of your replies are valid solutions, except I don’t want to provision another attribute to AD.

What I have done now is a mix of AfterCreate and AfterModify scripts. The AfterModify script only runs on enable operations, where we run the password script on the server side:

  • if person has already received an OTP password in the past → don’t generate a new one.

The AfterCreate script only runs if IIQDisabled = false.

AfterModify script:

$command = "C:\SailPoint\Scripts\<censored>

function Get-AccountRequestOpFromXml([string] $xml) {
    if ([string]::IsNullOrWhiteSpace($xml)) { return $null }
    $m = [regex]::Match($xml, '(?is)<AccountRequest\b[^>]*\bop\s*=\s*"(Create|Modify|Enable|Disable|Delete|Unlock)"')
    if ($m.Success -and $m.Groups.Count -ge 2) {
        return $m.Groups[1].Value
    }
    return $null
}

try {
    # Parse request (no logging unless we actually trigger)
    Add-Type -Path "utils.dll"
    $sReader = New-Object System.IO.StringReader([System.String]$env:Request)
    $xmlReader = [System.Xml.XmlTextReader]([SailPoint.Utils.Xml.XmlUtil]::getReader($sReader))
    $requestObject = New-Object SailPoint.Utils.Objects.AccountRequest($xmlReader) | Out-Null

    $requestAsString = $env:Request

    # Gate: only Enable
    $rawOp = Get-AccountRequestOpFromXml $requestAsString
    if (-not $rawOp -or -not $rawOp.Equals("Enable", [System.StringComparison]::OrdinalIgnoreCase)) {
        return
    }

    # From here on, we ARE doing something → start logging
    $timestamp = Get-Date -UFormat "%Y%m%d_%H%M%S_%3N"
    $logFile = "C:\SailPoint\Scripts\Logs\AfterModify_Enable_$timestamp.log"

    function LogToFile([String] $info) {
        try { $info | Out-File $logFile -Append -Encoding utf8 } catch {}
    }

    $uniqueId = [guid]::NewGuid().ToString()
    $env:UNIQUE_ID = $uniqueId

    LogToFile("[$(Get-Date -Format 'u')] AfterModify trigger matched (Enable). UNIQUE_ID=$uniqueId")
    LogToFile("AccountRequest op (raw) = '$rawOp'")

    # If you *really* want, log request XML here — but be careful with secrets
    LogToFile("Request as XML: $requestAsString")

    # Escape single quotes to avoid malformed command strings
    $escapedRequest = $requestAsString.Replace("'", "''")
    $fullCommand = "$command -requestString '$escapedRequest'"

    LogToFile("Executing command: $fullCommand")
    Invoke-Expression $fullCommand
}
catch {
    # Optional: if you want *errors only* logging, create a separate error log here.
    # If you prefer absolute silence on non-trigger events, keep this empty.
}

AfterCreate script:

$timestamp = Get-Date -UFormat "%Y%m%d_%H%M%S_%3N"
$command = "C:\SailPoint\Scripts\<censored>"

function Get-IiqDisabledFromRequestXml([string] $xml) {
    if ([string]::IsNullOrWhiteSpace($xml)) { return $null }
    $pattern = '(?is)<AttributeRequest\b[^>]*\bname\s*=\s*"IIQDisabled"[^>]*>.*?<Boolean>\s*(true|false)\s*</Boolean>'
    $m = [regex]::Match($xml, $pattern)
    if ($m.Success -and $m.Groups.Count -ge 2) { return $m.Groups[1].Value.ToLowerInvariant() }
    return $null
}

function Get-NativeIdentityFromXml([string] $xml) {
    if ([string]::IsNullOrWhiteSpace($xml)) { return $null }
    $m = [regex]::Match($xml, '(?is)<AccountRequest\b[^>]*\bnativeIdentity\s*=\s*"([^"]+)"')
    if ($m.Success -and $m.Groups.Count -ge 2) { return $m.Groups[1].Value }
    return $null
}

try {
    $requestAsString = $env:Request

    # Gate: only run if IIQDisabled=false
    $iiqDisabled = Get-IiqDisabledFromRequestXml $requestAsString
    if ($iiqDisabled -ne "false") { return }

    # Only now do we log
    $logFile = "C:\SailPoint\Scripts\Logs\AfterCreate_Enabled_$timestamp.log"
    function LogToFile([String] $info) { try { $info | Out-File $logFile -Append -Encoding utf8 } catch {} }

    $uniqueId = [guid]::NewGuid().ToString()
    $env:UNIQUE_ID = $uniqueId

    $nativeIdentity = Get-NativeIdentityFromXml $requestAsString

    LogToFile("[$(Get-Date -Format 'u')] AfterCreate trigger matched (IIQDisabled=false). UNIQUE_ID=$uniqueId")
    LogToFile("nativeIdentity = $nativeIdentity")

    # Parse kept (optional) — but no XML logging
    Add-Type -Path "utils.dll" | Out-Null
    $sReader = New-Object System.IO.StringReader([System.String]$env:Request)
    $xmlReader = [System.Xml.XmlTextReader]([SailPoint.Utils.Xml.XmlUtil]::getReader($sReader))
    $null = New-Object SailPoint.Utils.Objects.AccountRequest($xmlReader)

    $escapedRequest = $requestAsString.Replace("'", "''")
    $fullCommand = "$command -requestString '$escapedRequest'"

    LogToFile("Executing command: $fullCommand")
    Invoke-Expression $fullCommand
}
catch {
    # Optional errors-only log
    $errLog = "C:\SailPoint\Scripts\Logs\AfterCreate_Errors.log"
    try { "[$(Get-Date -Format 'u')] ERROR: $($_.Exception.Message)" | Out-File $errLog -Append -Encoding utf8 } catch {}
}