Hey Sailors,
Hope you are healthy and safe during these uncertain and unprecedented times.
Running Powershell directly via the IQService. This is a Native Rule to invoke PowerShell which creates RemoteUserMailbox for clients that are in Hybrid Environment after the Active Directory account is created.
We need to send Exchange Attributes [i.e. username, password in text] to Provisioning Plan as attributes map in Account Request that is used while establishing a Remote PSS connection to exchange server.
public static Attributes<String, String> getExchangeAttributes(SailPointContext context, String flow)
throws GeneralException {
Custom custom = context.getObjectByName(Custom.class, "Customobject where Exchange credentials are saved");
Attributes<String, String> attributes = new Attributes<String, String>();
attributes.put("flow", flow);
attributes.put("username", custom.get("SVC_UserName"));
attributes.put("pwdText", context.decrypt((String) custom.get("SVC_Password")));
return attributes;
}
acctReq.setArguments(CORPUtil.getExchangeAttributes(context,"Joiner"));
Here some example code for the ConnectorAfterCreate PowerShell script that can be invoked after AD account creation.
<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE Rule PUBLIC "sailpoint.dtd" "sailpoint.dtd">
<Rule language="beanshell" name="RemoteMailbox-Create-PSScript" 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="150"/>
</Map>
</Attributes>
<Description>
An IdentityIQ Server-Side rule that is executed AFTER the connector's provisioning method is called.
This rule is called after accounts have been created on the underlying AD domain.
</Description>
<Signature>
<Inputs>
<Argument name="log">
<Description>
The log object associated with the SailPointContext.
</Description>
</Argument>
<Argument name="context">
<Description>
A sailpoint.api.SailPointContext object that can be used to query the database if necessary.
</Description>
</Argument>
<Argument name="plan">
<Description>
The ProvisioningPlan object on its way to the Connector.
</Description>
</Argument>
<Argument name="application">
<Description>
The application object that references this before/after script.
</Description>
</Argument>
</Inputs>
</Signature>
<Source><![CDATA[
$logFile = "C:\ExchangeLogs\AfterCreateLogs\$(Get-Date -format "yyyy-MM-dd").ConnectorAfterCreate.log"
Function writeToLog() {
param([string]$message)
Add-Content ($(Get-Date -format "yyyy-MM-dd HH:mm:ss") + "::$message") -Path $logFile
}
Function sendEmail{
$emailMessage = New-Object System.Net.Mail.MailMessage
$emailMessage.From = "John Smith <john@CORP.com>"
$emailMessage.To.Add("akhilreddy@CORP.com" )
$emailMessage.Subject = "User mailbox creation failed for User -$sAMAccountName"
$emailMessage.IsBodyHtml = $true
$emailMessage.Body = @"
<p>Hello Team,</p>
<p>The usermailbox creation for user -$sAMAccountName has falied due to following erros.</p>
<p> $dn on server: $exchangeServer . Error:: $_.FullyQualifiedErrorId $_.Exception.Message </p>
<p>Please take neccessary actions to create an mailbox for the individual immediately .</p>
<p>Best Regards,</p>
<p>-Akhil Reddy</p>
"@
$SMTPClient = New-Object System.Net.Mail.SmtpClient( "yourSMTPServer -FQDN/IP" , "port" )
$SMTPClient.Credentials = New-Object System.Net.NetworkCredential( $username , $pwdText );
$SMTPClient.Send( $emailMessage)
}
#Notify that we are in afterscript
writeToLog("Running AfterCreate Script: 'CORP-Rule-AfterCreateScript'`r`n");
#Refer to SailPoint class library
Add-type -path "C:\IQService\utils.dll"
#Read the environment variables
$sReader = New-Object System.IO.StringReader([System.String]$env:Request);
$sResult = New-Object System.IO.StringReader([System.String]$env:Result);
##Form the xml reader object###
$xmlReader = [System.xml.XmlTextReader]([sailpoint.Utils.xml.XmlUtil]::getReader($sReader));
$xmlReader_Result = [System.xml.XmlTextReader]([sailpoint.utils.xml.XmlUtil]::getReader($sResult));
###Create SailPoint Request object
$requestObject = New-Object Sailpoint.Utils.objects.AccountRequest($xmlReader);
$resultObject = New-Object Sailpoint.Utils.objects.ServiceResult($xmlReader_Result);
##Done for inclusion in Log File
$requestXML = $requestObject.toxml();
$resultXML = $resultObject.toxml();
writeToLog("Request Object :: $requestXML");
writeToLog("Result Object :: $resultXML");
$objectType = ""
if($resultObject.Errors.count -eq 0){
foreach ($attribute in $requestObject.AttributeRequests){
if($attribute.Name -eq "ObjectType"){
$objectType = $attribute.value
}
if($attribute.Name -eq "op"){
$op = $attribute.value
}
if($attribute.Name -eq "sAMAccountName"){
$samAccountName = $attribute.value
}
if($attribute.Name -eq "userPrincipalName"){
$upn = $attribute.value
}
}
if($objectType -eq "User"){
foreach ($resultEntry in $resultObject.Attributes.GetEnumerator()){
if($resultEntry.Name -eq "createdOnServer"){
$createdOnServer = $resultEntry.Value
}
}
foreach ($requestEntry in $requestObject.Attributes.GetEnumerator()){
if($requestEntry.Name -eq "username"){
$username = $requestEntry.Value
}
if($requestEntry.Name -eq "pwdText"){
$pwdText = $requestEntry.Value
}
if($requestEntry.Name -eq "flow"){
$flow = $requestEntry.Value
}
}
$dn = $requestObject.NativeIdentity;
$op= $requestObject.Operation
writeToLog("//********************* Main Entry Point *******************************//");
writeToLog("Request attributes ---- dn: $dn, ObjectType: $objectType, op : $op, createdOnServer: $createdOnServer, sAMAccountName: $sAMAccountName, upn: $upn, flow:$flow ");
if( $flow -eq "Joiner" -And $op -eq"Create"){
$sApplication = New-Object System.IO.StringReader([System.String]$env:Application);
$xmlReader_Application = [System.xml.XmlTextReader]([sailpoint.utils.xml.XmlUtil]::getReader($sApplication));
[System.Xml.XmlDocument]$document = new-object System.Xml.XmlDocument
$document.load($xmlReader_Application)
$exchangeServers = $document.SelectNodes("//Map/entry[@key='ExchHost']/value/List/String")
$exchangeServersString = $exchangeServers.InnerText
writeToLog("Exchange servers is :::::::::::::::::::::::::$exchangeServersString")
foreach ($exchServerAddress in $exchangeServers) {
Try {
$exchServerAddressString = $exchServerAddress.InnerText
writeToLog("Trying exchange server: $exchServerAddressString")
$hostInfo = [System.Net.Dns]::GetHostByName($exchServerAddressString)
$exchangeServer = $hostInfo.HostName
$IsOnline = Test-Connection -ComputerName $hostInfo.HostName -BufferSize 16 -Count 1 -Quiet
writeToLog("is server online::::::::::::::::::::: $IsOnline ")
if ($IsOnline -eq $true){
Function Login {
$secpasswd = ConvertTo-SecureString $pwdText -AsPlainText -Force
$credObject = New-Object System.Management.Automation.PSCredential($username, $secpasswd)
$uri = "http://"+ $exchangeServersString + ":80/PowerShell/"
$session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri $uri -Credential $credObject -Authentication Kerberos #-ErrorAction Stop
Import-PSSession $session -AllowClobber -DisableNameChecking
$session
}
$session = Login
writeToLog("Session value: $session")
writeToLog("Invoking Enable-RemoteMailbox command for account: $samAccountName")
set-adserversettings -viewentireforest $true
$email = $samAccountName +"@CORP365.mail.onmicrosoft.com"
writeToLog("email:::::::::::::::::::::::$email")
writeToLog("Executing second cmd for account: $samAccountName")
Enable-RemoteMailbox $samAccountName -RemoteRoutingAddress $email
$logger = Get-RemoteMailbox -Identity $samAccountName
writeToLog("checking mailbox print the display name of the user :: $logger")
writeToLog("cmdlets execution completed")
$id =$session.ID
Remove-PSSession -ID $session.ID
writeToLog("After session remove: $Session")
$scriptExecuted=$true
}
else{
writeToLog("Cannot connect to server : $exchServerAddressString")
}
if($scriptExecuted){
writeToLog("Mailbox script execution completed for ::: $samAccountName on server ::: $exchServerAddressString")
}
} catch {
writeToLog("Cannot create mailbox features for user: $dn on server: $exchangeServer . Error: $_.FullyQualifiedErrorId $_.Exception.Message")
sendEmail
}
finally {
if($session){
writeToLog(":::::Finally:::")
Remove-PSSession -ID $session.ID
##Disconnect-PSSession -ID $session.ID
}
}
}
}
else{
writeToLog("::::--!! skipping the after create scripts execution as the plan would be from the identityRefreshExecutor !!--:::::")
}
} else {
writeToLog("Skipping mailbox creation. After Create script triggered for ObjectType: $objectType")
}
} else {
foreach ($errorMsg in $resultObject.Errors){
writeToLog("Errors from Service Result :::`n`r $errorMsg ");
}
}
writeToLog("Executing After Create Script completed for User: $samAccountName");
writeToLog("//*********************End of Script *******************************//");
]]></Source>
</Rule>
Set-RemoteMailbox $sAMAccountName -HiddenFromAddressListsEnabled $true
HI @Akhil
Currently, we have on perm exchange server for mailbox but we are moving completely to Azure AD. What will be the best option to create a mailbox in the Azure environment? Current architecture -> Create an account in On perm AD and then it gets synched to Azure AD.
Great article...!!!
I understand this article was posted long before. Please clarify my questions.
OUR ARCHITECTURE: We have an on-prem Active Directory integrated and an on-prem Exchange currently integrated with our IIQ 8.2. We also have an Exchange Online application, it is not connect to IIQ.
CURRENT BEHAVIOUR: For users who request Exchange accounts, we create an Active Directory account for them on the prem instance, set their mailnickname and in turn Exchange creates their mailboxes on on-prem Exchange. Post Exchange account creation, a backend script runs and migrates the account to Exchange Online
REQUIREMENT: We would like create (enable) remote mailboxes for those users who request Exchange accounts directly from IDM. We do not want to create accounts on on-prem Exchange application
We have stopped creating on-prem Exchange accounts. I am using the rule in this article to enable remote mailboxes post AD account creation but the rule execution fails with the below error
08/08/2022 04:50:14 : AbstractConnector [ Thread-5 ] DEBUG : "Started process"
08/08/2022 04:50:16 : AbstractConnector [ Thread-5 ] DEBUG : "Script return code : 1"
08/08/2022 04:50:16 : AbstractConnector [ Thread-5 ] DEBUG : "EXIT executeScript"
08/08/2022 04:50:16 : AbstractConnector [ Thread-5 ] DEBUG : "After script returned non zero exit code : 1 : "
08/08/2022 04:50:16 : AbstractConnector [ Thread-5 ] DEBUG : "EXIT executePostScript"
08/08/2022 04:50:16 : AbstractConnector [ Thread-5 ] WARN : "Create operation is successful but post script execution failed : After script returned non zero exit code : 1 : "
08/08/2022 04:50:16 : AbstractConnector [ Thread-5 ] DEBUG : "EXIT Provision"
Can someone please help me understand why the execution fails?
Hi, is there a version of this that can be used for IdentityNow?
What a nice article, ty for this information:)
But i have some questions...
Isn't it bad practice to enter the password in the acount request?
It will be recorded in the identityRequest in clear text...
Is there another way to pass credentials securely?
Hi
After adding the Account request with IQservice username and password, the joiner workflow fails at Identity Initialize step, at the buildingapproval step with null exception. Not sure what to do.