cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

Hybrid exchange provisioning via PSS script

Hybrid exchange provisioning via PSS script

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;
	}

 

  • The above method can be created in Rule Library or Utility class file 

 

 

 acctReq.setArguments(CORPUtil.getExchangeAttributes(context,"Joiner"));

 

  • Add this arguments map in your plan to Enable and Disable operations whichever suits your organization need

 

ConnectorAfterCreate

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>

 

  • The above script connects via Remote PSSession and executes the script for enabling the mailbox and it also sends an email notification upon failures while creating mailbox
  • The above snippet can be used with some modifications as AfterModifyScript - by changing the flow and powershell command for disabling and hiding the mail address.

 

 

 

Set-RemoteMailbox $sAMAccountName -HiddenFromAddressListsEnabled $true

 

  • This will make sure that the disabled user's mail id is hidden from the directory.
Comments

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?

  • Based on the above architecture you need to implement Exchange provisioning.
  • The major difference is that a RemoteUserMailboxis indeed a mailbox, which is hosted in the Exchange Online environment, but a mailuser is not a real mailbox, it's just a local AD account with a valid SMTP address.

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.

  • you can run Get-ADuser "SamAccountName" -properties * | fl in AD installed machine for more insights.
  • At this moment there might be no account in Azure existing - yes you're correct. If Azure is configured with Exchange provisioning i.e. Assigning an O365 group in AD which will synch to Azure followed by the exchange. probably you do not need to run the PSS Script.

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]

  • One more possible option your workflow might be in retrying the project check on that as well if you configured any provision retries
  • Please check whether you're able to take session via power shell

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

@amy_trisko 

Open application in debug, u can find a tag for Native Rules

 

Thanks

Krishna

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

Version history
Revision #:
3 of 3
Last update:
‎Mar 15, 2023 04:39 PM
Updated by: