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
Good Article, even we were looking to head start on provisioning Remote user mailbox [Hybrid Environment]
Helpful!
Hi everyone,
I am trying to provision the similar script and creating remote session. I see workflow is getting stuck at provision step even after running Perform Maintenance task.
This happens only when I have Remote session code in native rule.
Could you please help me what could be the issue ?
Hi! Thanks for this great article!
we are actually planning to do the same. But we are just wondering what is happening, if we create the "RemoteUserMailbox" after creating the AD Account on prem. At this moment there might be no account in Azure existing. (Not synced yet)
Does anybody run in this problem?
Volker
Hi @vlingens
What is the current architecture for your Exchange? is it Hybrid or is it in the cloud i.e. Exchange online?
Note: The provisioning is not how it looks when you execute PowerShell command to create mailbox it will do the Exchange provisioning first with valid mailNickname so, that targetAddress[attribute in AD which will hold the " SMTP:mailNickname @365.mail.onmicrosoft.com"] is populated by the Exchange application.
Hope I have answered your query.
Thank you,
Akhildeep Reddy
Hello @vijay_sharma
There are two ways to execute Powershell. Could please give more insights on why you're running PSScript? are you trying to do provision for Exchange box or any special requirements to provide attributes for AD?
@vijay_sharma This happens only when I have a Remote session code in the native rule.
@Akhil - IMO, The workflow is getting struck on the Provision step and Our Remote session in the native rule are two different things Moreover Native script would be triggered if provisioning is successful [ object is successfully created in my case then I'm taking the Remote session by Kerberos Authentication]
Hope this gives some clarity for you.
Thank you,
Akhil
Hey @Akhil - Thanks for this great article!
Hi - where do I add this rule so it runs after a specific application? I see an after provisioning rule configuration but not a ConectorAfterCreate rule configuration spot on my active directory application.
Thanks!
Amy
Hi All,
I was able to create mailbox using PSScript used in native rule for AD Application. my exchange is hybrid environment cloud and on-prime.
@vlingens Yes, we need to check if azure account is synced or not post ad creation, this can be done through azure single account aggregation task. In case if account is not yet synced, create azure account with "immutableId" same as active directory profile. This takes care of updating azure profile during AAD sync. I was able to provision mailbox using remoteMailBox command
@akhi
I noticed workflow gets hanged when there is wait time inside PSScript.
Regards,
Vijay