The code snippet can trigger the PowerShell Rule within workflow in IIQ for various business use cases and we can also validate the Powershell code.
- Rule Runner task for launching the Workflow with the config parameters from the task.
import sailpoint.object.WorkflowLaunch;
import sailpoint.object.Workflow;
import sailpoint.api.Workflower;
//config.get() parameters can be passed in the task definition
HashMap launchArgsMap = new HashMap();
launchArgsMap.put("identityName",config.get("identity"));
launchArgsMap.put("launcher", "spadmin");
launchArgsMap.put("powerShellRuleName", "PowerShell-Rule-UAT");
launchArgsMap.put("adApplicationName", "Active Directory");
launchArgsMap.put("identityDisplayName",config.get("identity"));
WorkflowLaunch wflaunch = new WorkflowLaunch();
Workflow wf = null;
try {
wf = (Workflow) context.getObjectByName(Workflow.class, config.get("workflowName"));
}
catch (GeneralException e) {
log.error(e);
}
if (wf != null) {
Workflower workflower = new Workflower(context);
try {
WorkflowLaunch launch = workflower.launchSafely(wf, wf.getName()+" for Identity :"+config.get("identity"), launchArgsMap);
} catch (GeneralException eGeneralException) {
log.error(eGeneralException);
}
}
- The workflow will execute and return the RPC response to the root workflow context.
import sailpoint.object.RpcRequest;
import sailpoint.object.RpcResponse;
import sailpoint.connector.RPCService;
import sailpoint.object.Identity;
import sailpoint.object.Link;
import sailpoint.object.Rule;
import sailpoint.object.Custom;
import sailpoint.object.Application;
import sailpoint.object.ProvisioningPlan.AccountRequest;
import sailpoint.object.ProvisioningPlan.AccountRequest.Operation;
import sailpoint.object.ProvisioningPlan.AttributeRequest;
import sailpoint.object.ProvisioningPlan.Operation;
import sailpoint.object.ProvisioningPlan;
import sailpoint.object.ProvisioningProject;
import sailpoint.object.Attributes;
import sailpoint.tools.Util;
import sailpoint.tools.Message;
import sailpoint.tools.GeneralException;
import sailpoint.api.IdentityService;
import org.apache.log4j.Logger;
//Map of data to be passed to the RPCRequest
Map data = new HashMap();
Identity identity = context.getObjectByName(Identity.class, identityName);
if (identity == null)
throw new GeneralException("Could not find identity for name [" + identityName + "] in worklfow Workflow-InvokePowerShell");
Rule powershellRule = context.getObjectByName(Rule.class, powerShellRuleName);
if (powershellRule == null)
throw new GeneralException("Could not find PowerShell rule for name: " + powerShellRuleName);
Application application = context.getObjectByName(Application.class, adApplicationName);
if (application == null)
throw new GeneralException("Could not find application for name [" + adApplicationName + "] in worklfow Workflow-InvokePowerShell");
if (application != null && powershellRule != null) {
IdentityService idService = new IdentityService(context);
data.put("Application", application.getAttributes());
//Fake or real account request
AccountRequest accountRequest = new AccountRequest();
Link adLink;
//Attempt to get the Active Directory AccountRequest for the project if it exists
if (project != null) {
logger.debug("Project passed in is non-null, getting AD request from it:\n");
for (ProvisioningPlan plan : Util.safeIterable(project.getIntegrationPlans())) {
for (AccountRequest acctReq : Util.safeIterable(plan.getAccountRequests(adApplicationName))) {
accountRequest = acctReq;
logger.debug("Found AccountRequest for AD:\n" + acctReq.toXml());
break;
}
}
if (accountRequest != null) {
//If we already have an AccountRequest, let's throw in some additional attributes from and existing link
List adLinks = idService.getLinks(identity, application);
if (!Util.isEmpty(adLinks)) {
adLink = adLinks.get(0);
}
}
} else {
//If a project was not passed in, get the current AD account from the identity
logger.debug("Creating a fake account request and adding some reference attributes to it");
List adLinks = idService.getLinks(identity, application);
if (!Util.isEmpty(adLinks)) {
adLink = adLinks.get(0);
accountRequest.setApplication(adApplicationName);
accountRequest.setNativeIdentity(adLink.getNativeIdentity());
accountRequest.setOperation(AccountRequest.Operation.Modify);
}
}
//Add some helpful attributes purely for PowerShell reference purposes
if (adLink != null) {
if (Util.isNotNullOrEmpty(adLink.getAttribute("userPrincipalName"))) {
accountRequest.add(new AttributeRequest("userPrincipalName", ProvisioningPlan.Operation.Set, adLink.getAttribute("userPrincipalName")));
}
if (Util.isNotNullOrEmpty(adLink.getAttribute("sAMAccountName"))) {
accountRequest.add(new AttributeRequest("sAMAccountName", ProvisioningPlan.Operation.Set, adLink.getAttribute("sAMAccountName")));
}
if (Util.isNotNullOrEmpty(adLink.getAttribute("givenName"))) {
accountRequest.add(new AttributeRequest("givenName", ProvisioningPlan.Operation.Set, adLink.getAttribute("givenName")));
}
if (Util.isNotNullOrEmpty(adLink.getAttribute("sn"))) {
accountRequest.add(new AttributeRequest("sn", ProvisioningPlan.Operation.Set, adLink.getAttribute("sn")));
}
if (powerShellRuleName.equals("Rule-PowerShell-LitigationHoldMailbox-UAT")) {
Attributes attr = application.getAttributes();
Map map = attr.getMap();
String completeUserName = map.get("onlineExchangeUser");
String password = context.decrypt(map.get("onlineExchangePassword"));
accountRequest.add(new AttributeRequest("user", ProvisioningPlan.Operation.Set, completeUserName));
accountRequest.add(new AttributeRequest("password", ProvisioningPlan.Operation.Set, password));
accountRequest.add(new AttributeRequest("email", ProvisioningPlan.Operation.Set, identity.getEmail()));
}
}
logger.debug("Final AccountRequest to be passed to the RPCRequest:\n" + accountRequest.toXml());
// Add to the IQService params
logger.debug("Adding IQService params");
data.put("postScript", powershellRule);
data.put("Request", accountRequest);
logger.debug("Added script and request objects to data map successfully");
RpcResponse rpcResponse;
try {
List iqServiceConfig = application.getAttributeValue("IQServiceConfiguration");
if (iqServiceConfig != null && !iqServiceConfig.isEmpty()) {
Map iqServiceDetailsMap = iqServiceConfig.get(0);
if (iqServiceDetailsMap != null && !iqServiceDetailsMap.isEmpty()) {
String iqServiceHost = iqServiceDetailsMap.get("IQServiceHost");
int iqServicePort = Integer.parseInt(iqServiceDetailsMap.get("IQServicePort"));
RPCService service = new RPCService(iqServiceHost, iqServicePort, false, false);
service.setConnectorServices(new sailpoint.connector.DefaultConnectorServices());
RpcRequest request = new RpcRequest("ScriptExecutor", "runAfterScript", data);
logger.debug("Executing RPC Request...");
rpcResponse = service.execute(request);
logger.debug("RPC Request completed");
if (rpcResponse != null) {
logger.debug("RPCResponse:\n" + rpcResponse.toXml());
if (!Util.isEmpty(rpcResponse.getErrors())) {
logger.debug("Errors from the RPC Response:\n" + rpcResponse.getErrors().toString());
//wfcontext.getRootWorkflowCase().addMessage(Message.error("IQService Error: " + rpcResponse.getErrors().toString()), null);
}
} else {
logger.debug("RPCResponse is null!");
}
}
}
} catch(Exception e) {
/*
* If there are errors in the RPCResponse object, an exception will be throw so we need to add them
* to the root workflowcase here so the TaskResult shows them
*/
logger.error("RPC Request compelted with error: " + e.getMessage());
//wfcontext.getRootWorkflowCase().addMessage(Message.error("IQService Error: " + e.getMessage(), null));
if (rpcResponse != null) {
logger.debug("RPCResponse returned from script:\n" + rpcResponse.toXml());
if (!Util.isEmpty(rpcResponse.getErrors())) {
logger.debug("Errors from the RPC Response:\n" + rpcResponse.getErrors().toString());
//You can handle custom errors for creating Icnident or Email to Support Teams
Custom errorMsg = context.getObjectByName(Custom.class,"Custom-PowerShell-Errors");
Map customErrors = errorMsg.getAttributes();
wfcontext.getRootWorkflowCase().addMessage(Message.error(customErrors.get(powerShellRuleName) + rpcResponse.getErrors().toString(), null));
}
} else {
logger.debug("RPCResponse is null!");
}
}
}
- The Reference PowerShell script
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE Rule PUBLIC "sailpoint.dtd" "sailpoint.dtd">
<Rule created="" id="" language="beanshell" modified="" name="Example-PowerShell-LitigationHold-Script" type="ConnectorAfterCreate">
<Attributes>
<Map>
<entry key="ObjectOrientedScript" value="true" />
<entry key="disabled" value="false" />
<entry key="extension" value=".ps1" />
<entry key="program" value="powershell.exe" />
<entry key="timeout" value="1200" />
</Map>
</Attributes>
<Source>
#Refer to SailPoint class library. Requires V3 EXO PowerShell installed on the system.
#ErrorActionPreference is important to add in the ps scripts to "stop" the execution on exceptions or failures
Add-type -Path D:\IQService\Utils.dll
$ErrorActionPreference = "Stop";
$LoggingEnabled = $true;
#Read our application for tokenized attributes
#parseObject will return application object as a Hashtable
#get xmlFactory object
$xmlFactory = [sailpoint.Utils.xml.XmlFactory]::Instance;
#Read the environment variables
$sReader1 = [System.String]$env:Application;
#Remove any line containing '<Date>' because IQService was expecting date in milliseconds
#but application contains date in the format MM/DD/YY HH:MM:SS AM
$escaped = $sReader1 -split "`n" | Select-String -Pattern "Date" -NotMatch
#Convert String array to String
$content = $escaped | Out-String
#Create stringReader object from app xml string
$stringReader = New-Object -TypeName System.IO.StringReader -ArgumentList $content
#New xml reader object
$xmlreaderApp = [System.Xml.XmlTextReader] [sailpoint.Utils.xml.XmlUtil]::getReader([System.IO.TextReader]$stringReader);
#This step is missing in existing IQservice
$xmlreaderApp.MoveToContent()
#parsObject will return application object as a Hashtable
$appObject = $xmlFactory.parseObject($xmlreaderApp)
#Get variables from app object for mailbox creation
$environmentUpn = $appObject.upnDomainName
$exchangeServer = $appObject.exchangeServer
$exchangeTargetAddress = $appObject.exchangeTargetAddress
#Build some variables
$IQServiceUtilsPath = "D:\IQService\Utils.dll"
$IQServiceLogPath = "D:\IQService\IQOutput.txt"
#Read the environment variables
$stringRequest = New-Object System.IO.StringReader([System.String]$env:Request);
$stringResult = New-Object System.IO.StringReader([System.String]$env:Result);
# Form the xml reader objects
$xmlReader = [System.xml.XmlTextReader]([sailpoint.utils.xml.XmlUtil]::getReader($stringRequest));
$xmlReader_Result = [System.xml.XmlTextReader]([sailpoint.utils.xml.XmlUtil]::getReader($stringResult));
# Create SailPoint objects
$requestObject = New-Object Sailpoint.Utils.objects.AccountRequest($xmlReader);
$resultObject = New-Object Sailpoint.Utils.objects.ServiceResult($xmlReader_Result);
$objectType = "AccountRequest";
$sAMAccountName = "";
$userPrincipalName = "";
$givenName = "";
$sn = "";
$username = "";
$password = "";
$email = "";
#This error list is important to catch the errors in a list and return them #to root context
$Error_List = New-Object -TypeName 'System.Collections.ArrayList';
# Check if the request was processed successfully
foreach ($attribute in $requestObject.AttributeRequests) {
if ($LoggingEnabled) {
Write-Output ("Attribute Name: " + $attribute.Name + "; Attribute Value: " + $attribute.Value) | Out-File -Append $IQServiceLogPath;
}
if ($attribute.Name -eq "userPrincipalName") {
$userPrincipalName = $attribute.Value;
Write-Output ("userPrincipalName: " + $userPrincipalName) | Out-File -Append $IQServiceLogPath;
}
if ($attribute.Name -eq "sAMAccountName") {
$sAMAccountName = $attribute.Value;
Write-Output ("sAMAccountName: " + $sAMAccountName) | Out-File -Append $IQServiceLogPath;
}
if ($attribute.Name -eq "user") {
$username = $attribute.Value;
}
if ($attribute.Name -eq "password") {
$password = $attribute.Value;
}
if ($attribute.Name -eq "email") {
$email = $attribute.Value;
Write-Output ("email: " + $email) | Out-File -Append $IQServiceLogPath;
}
}
# The log file should be created dynamically rather than appending to the the same file,if same file is used the log may lead to dead lock situations # in concurrent operations.
# Start-transcript is a Microsoft module for logging the powershell code.
# This creates a record of all or part of a PowerShell session to a text file.
if ($resultObject.Errors.count -eq 0) {
Start-transcript -path "D:\ExchangeLogs\$sAMAccountName.txt" -Append
if ($objectType -eq "AccountRequest") {
$server = $resultObject.Attributes["createdOnServer"];
Try {
if ($email) {
#The authentioncation method is changed from Basic to Certificate thumbprint as per guidelines
Connect-ExchangeOnline -AppId XXXXXXXXXXXXXXXXXXXX -CertificateThumbprint XXXXXXXXXXXXXXXXXXXX -Organization ACME.onmicrosoft.com
try {
Set-Mailbox $email -LitigationHoldEnabled $true -ErrorAction Stop
if ($LoggingEnabled) {
Write-Output "Remote mailbox LitigationHoldEnabled successfully"
}
} Catch {
$Error_List.Add($_.Exception.Message)
Write-Output ("An error occurred while enabling litigation hold: $_.Exception.Message")
}
try {
Set-CalendarProcessing -Identity $email -ResourceDelegates $null -Confirm:$false -ErrorAction Stop
if ($LoggingEnabled) {
Write-Output "CalendarProcessing ResourceDelegates successfully"
}
} Catch {
Write-Output $_.Exception
}
$calendar = $email + ":\Calendar"
try {
Remove-MailboxFolderPermission -Identity $calendar -ResetDelegateUserCollection -Confirm:$false -ErrorAction Stop
if ($LoggingEnabled) {
Write-Output "Remove MailboxFolderPermission successfully"
}
} Catch { Write-Output $_.Exception}
if ($LoggingEnabled) {
try {
Set-CASMailbox -Identity $email -OWAforDevicesEnabled $false -ErrorAction stop
Write-Output "CASMailbox OWAforDevicesEnabled disabled successfully"
}
catch {Write-Output $_.Exception}
try {
Set-CASMailbox -Identity $email -OWAEnabled $false -ErrorAction stop
Write-Output "CASMailbox OWAEnabled disabled successfully"
}
catch {Write-Output $_.Exception}
try {
Set-CASMailbox -Identity $email -ActiveSyncEnabled $false -ErrorAction stop
Write-Output "CASMailbox ActiveSyncEnabled disabled successfully"
}
catch {Write-Output $_.Exception}
try {
Set-CASMailbox -Identity $email -MAPIEnabled $false -ErrorAction stop
Write-Output "CASMailbox MAPIEnabled disabled successfully"
}
catch {Write-Output $_.Exception}
try {
Set-CASMailbox -Identity $email -ImapEnabled $false -ErrorAction stop
Write-Output "CASMailbox ImapEnabled disabled successfully"
}
catch {Write-Output $_.Exception}
try {
Set-CASMailbox -Identity $email -PopEnabled $false -ErrorAction stop
Write-Output "CASMailbox PopEnabled disabled successfully"
}
catch {Write-Output $_.Exception} $CASMailboxFeaturesStatus=Get-CASMailbox -Identity $email|Select @{N="Email";E={$_.PrimarySmtpAddress}},OWAforDevicesEnabled,OWAEnabled,ActiveSyncEnabled,MAPIEnabled,ImapEnabled,PopEnabled
Write-Output $CASMailboxFeaturesStatus
}
}
$groupToAdd = 'AP_Applicaion-Leaver-Group'
Try {
Write-Output ("Adding "+$email+" to "+ $groupToAdd)
Add-DistributionGroupMember -Identity $groupToAdd -Member $email -BypassSecurityGroupManagerCheck -confirm:$false -ErrorAction stop
if ($LoggingEnabled) {
Write-Output ("DistributionGroupMember " + $groupToAdd + " added successfully")
}
} Catch {
# The powershell exceptions are hard to interpret in some situations. so, it is always better to have the custom message returned to workflow.
Write-Output $_.Exception
}
Write-Output ("************END of LitigationHold Script***************")
}
Catch {
Try {
Disconnect-ExchangeOnline -Confirm:$false
} Catch {
Write-Output "PowerShell-LitigationHoldMailbox script was not successfuly completed"
}
$ErrorMessage = $_.Exception.Message
$FailedItem = $_.Exception.ItemName
if ($LoggingEnabled) {
Write-Output $ErrorMessage
Write-Output $resultObject.toxml()
}
$resultObject.Errors.Add($ErrorMessage);
} Finally {
#This is must to Disconnect-ExchangeOnline session in finally block and catch the erros in this block
Disconnect-ExchangeOnline -Confirm:$false
if($Error_List -ne $null){
$resultObject.Errors.Add($Error_List);
}
# Dump the result object back to the out-file passed in as arg 0 so any updates are fed back to IIQ
$resultObject.toxml() | out-file $args[0];
}
} else {
if ($LoggingEnabled) {
Write-Output ("Request type was not an AccountRequest, skipping Remove-RemoteMailbox command")
}
}
Stop-transcript
} else {
# Add an error to let IIQ know nothing was done since there were errors as the connector level
$resultObject.Errors.Add("IQService: Errors occured during provisioning, AfterScript took no action");
# Dump the result object back to the out-file passed in as arg 0 so any updates are fed back to IIQ
$resultObject.toxml() | out-file $args[0];
}
</Source>
</Rule>