Greetings! New to SailPoint, so I feel like this is going to be a question with either a very simple fix or expose my lack of deep understanding of the platform. I’m fine with either, any knowledge helps!
I am trying to create BeforeCreate rule/script for an on-prem Active Directory source that generates a sAMAccountName value (and by extension a new nativeIdentity value) based on some custom logic. I’ve gathered from extensive reading of the discussions here that this might not be the preferred or optimal method of accomplishing this, but it’s unlikely that my org will be exposing all of the attributes that we look at when determining a sAMAccountName to ISC, so we’re left with IQService, PowerShell, and a BeforeCreate rule.
To start, I’m keeping it simple: I want to be able to provision an account with a static username. I’ll worry about our more complicated logic once I’ve proven the simple example works.
The documentation on this process is not as clear as I’d like it to be. Reading through a combination of that and other discussions here, I think I’ve almost got it: rule was created and attached to the connector, rule successfully fires and executes a local script on the IQService host, local script successfully generates the new XML structure to feed back to IQservice. Where this falls apart is getting the modified AccountRequest back to IQservice.
SailPoint documentation (and other discussions here) states that this is done with a statement like this:
$requestObject.toxml()|out-file $args[0];
This statement produces an error when executed by IQService: “Cannot index on a null array” and IQService proceeds to provision the account with the original attributes passed to it by the rule. I’m at a loss to understand why $args[0] would be empty, or if there’s some other mechanism I should be using to pass the modified request back to IQService.
Here’s the existing rule. Most of this is taken directly from SailPoint documentation for creating rules:
$logDate = Get-Date -UFormat "%Y%m%d"
$logFile = "D:\IQServiceLogs\BeforeCreateRule\BeforeCreate_$logDate.log"
$command = "D:\IQservice\Scripts\New-sAMAccountName.ps1"
$enableDebug = $true
#====================-------Helper functions-------====================
function LogToFile([String] $info) {
$info | Out-File $logFile -Append
}
#====================-------Get the request object-------====================
Try{
if($enableDebug) {
LogToFile("Entering SailPoint rule")
}
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);
$requestAsString = $env:Request
if($enableDebug) {
LogToFile("Request as XML object is: $requestAsString")
}
#Call the client script
$command = -join ($command, " -requestString '$requestAsString'")
Invoke-Expression $command
}Catch{
$ErrorMessage = $_.Exception.Message
$ErrorItem = $_.Exception.ItemName
LogToFile("Error: Item = $ErrorItem -> Message = $ErrorMessage")
}
if($enableDebug) {
LogToFile("Exiting SailPoint rule")
}
And here’s the PowerShell script that executes on the IQService host:
##############################################################################################################################
# SETUP
# Instructions (for each IQService host that could run the script):
# - Update the path to Utils.dll (can be an unqualified path like "Utils.dll" since script is copied to IQService folder for execution)
# - Make sure Utils.dll is in the specified folder on each IQService host
# - Be sure the account that runs IQService has appropriate permissions to create directories and set permissions on them
# - Be sure to set the "run as" account for the IQService in Windows Service to the above-specified account instead of just the "logged on" user
# - Set a proper location for the $logFile variable
# - Set the $enableDebug flag to $true or $false to toggle debug mode
###############################################################################################################################
param (
[Parameter(Mandatory=$true)][System.String]$requestString
)
#include SailPoint library
Add-type -path "D:\IQService\utils.dll";
#import AD cmdlets
Import-Module activeDirectory;
#log file info
$logDate = Get-Date -UFormat "%Y%m%d";
$logFile = "D:\IQServiceLogs\BeforeCreate\AD-BeforeCreate_$logDate.log";
$enableDebug = $true;
#save logging files to a separate txt file
function LogToFile([String] $info) {
$info | Out-File $logFile -Append;
}
try{
LogToFile("Starting processing now")
$sReader = New-Object System.IO.StringReader([System.String]$requestString);
$xmlReader = [System.xml.XmlTextReader]([sailpoint.utils.xml.XmlUtil]::getReader($sReader));
$requestObject = New-Object Sailpoint.Utils.objects.AccountRequest($xmlReader);
$sAMAccountName = "newusername";
$originalUsernameAttribute = $null;
LogToFile("Finding the sAMAccountName attribute request")
foreach ($attribute in $requestObject.AttributeRequests) {
if ($attribute.Name -eq "sAMAccountName")
{
LogToFile("Found sAMAccountName request of $($attribute.Value)")
$originalUsernameAttribute = $attribute;
}
}
## Create new username attribute
LogToFile("Creating new sAMAccountName object")
$newAttributeValue = New-Object SailPoint.Utils.objects.AttributeRequest;
$newAttributeValue.Operation = "Add";
$newAttributeValue.Name = "sAMAccountName";
$newAttributeValue.Value = $sAMAccountName;
## Remove the old attribute from the account request
LogToFile("Removing old sAMAccountName object")
$requestObject.AttributeRequests.Remove($originalUsernameAttribute);
## Add the new attribute to the account request
LogToFile("Adding new sAMAccountName object")
$requestObject.AttributeRequests.Add($newAttributeValue);
## Update the CN for the object with the new username.
$requestObject.nativeIdentity = "CN=$sAMAccountName,OU=AdminAccounts,OU=Users,OU=_LAB,DC=uslab,DC=lab,DC=local";
## Pass the new account request to SailPoint
LogToFile("Passing updated attributes to Sailpoint")
LogToFile($requestObject.toxml())
$requestObject.toxml()|out-file $args[0];
LogToFile("End of processing")
} catch {
$ErrorMessage = $_.Exception.Message
$ErrorItem = $_.Exception.ItemName
LogToFile("Error: Item = $ErrorItem -> Message = $ErrorMessage")
}
Hoping the community can help a newbie out! Thanks!