Email Creation and Validation via PowerShell

Hi everyone,

We are currently implementing a process where, to create an email in the local Active Directory, it is necessary to validate — through a PowerShell script executed locally in the AD — whether the email already exists in Microsoft Entra ID (Office).

  • If the email does not exist, it will be created locally in the AD.

  • If it already exists, the script automatically generates a new variation according to the defined naming rule.

Normally, this type of process would be handled using an AttributeGenerator rule and a connected source to Microsoft Entra ID.
However, in this case, the validation will be performed via PowerShell, directly from the local AD environment.

:backhand_index_pointing_right: Questions:

  • Is this PowerShell-based validation approach supported by SailPoint?

  • Are there any recommended best practices for this type of hybrid integration?

Thank you in advance for your help and guidance.

@thiagogosantanasi -

Yes—you can do this with IdentityNow (ISC) using the AD connector’s IQService PowerShell hooks. It’s a common pattern when you need logic that can’t run in a cloud-executed rule (e.g., calling Microsoft Graph). Below are the do’s, don’ts, and a clean blueprint you can adopt.

Is it supported?

  • Supported approach: Use the Before Script
    PowerShell script on the AD source (IQService). The script runs locally on the IQService Windows host, can call Microsoft Graph/Entra ID, and can set/adjust attributes (like mail and proxyAddresses) before the create hits AD.

  • What it’s not: Cloud-executed rules in ISC can’t make outbound HTTP calls; so your Graph validation cannot live there. Doing it in IQService PowerShell is the right “supported” spot.

  • Supportability note: SailPoint Support will help with connector behavior; for issues inside your custom script they’ll expect you to own the script. That’s standard.

Recommended best practices (hybrid AD + Entra validation)

Where to put the logic

  1. Before Create PowerShell (AD Source)

    • Input: Provisioning payload (intended AD attributes).

    • Action:

      • Build candidate email from your naming policy.

      • Call Microsoft Graph to check collisions in both mail and proxyAddresses (including aliases).

      • If taken, iterate (john.doe, john.doe1, john.doe2, …) until free.

      • Write back the final mail and appropriate proxyAddresses (e.g., SMTP:primary@corp.com, smtp:alias@corp.com) into the outgoing payload before create.

    • Result: AD is created with a conflict-free email that’s also unique in Entra.

  2. (Optional) Before Modify PowerShell

    • Handle rename scenarios or policy changes the same way.

If you need ISC to know the final value immediately, also set the same value in the outgoing payload so it lands in AD and is visible to ISC on the provisioning response/next aggregation. Most teams rely on the next account aggregation to reflect it back to the identity authoritatively.

Microsoft Graph specifics

  • API: Prefer Graph over legacy MSOnline/ExchangeOnline modules.

    • Checks you likely need:

      • GET /users?$filter=mail eq 'john.doe@corp.com'

      • GET /users?$filter=userPrincipalName eq 'john.doe@corp.com' (if UPN aligns)

      • For aliases: GET /users?$filter=proxyAddresses/any(p:p eq 'SMTP:john.doe@corp.com' or p eq 'smtp:john.doe@corp.com')

  • Auth: Use an app registration with client credentials; store secrets securely:

    • Use Windows Credential Manager or a local encrypted file (DPAPI), not hard-coded strings.
  • Resilience: Implement retries with Retry-After handling for 429/503, short timeouts, and a max attempts loop on name variants.

Naming policy & collision handling

  • Centralize the pattern (first.last, length caps, diacritics folding) and variant rule (first.last1, first.last2, …).

  • Normalize (lowercase, ASCII) and trim to enforce Exchange/AD constraints (e.g., 64 local-part soft guard).

  • Validate against both directories:

    • Local AD (search by mail and proxyAddresses)

    • Entra ID via Graph (same checks)

  • Stop after a sane max (e.g., 50 variants) and fail gracefully with a clear error if no slot is free.

AD attributes to set

  • mail: the decided primary SMTP address

  • proxyAddresses: include SMTP:primary@corp.com and any policy-required aliases as smtp: entries

  • (If UPN follows email) set userPrincipalName consistently

Operational hygiene

  • Logging: Write clear events to Windows Event Log (source: IQService-EmailPolicy), include the SailPoint requestId if available. Avoid logging secrets.

  • Idempotency: If a create is retried, the function should return the same final email decision for the same person (e.g., seed with a stable key like employee ID + timestamp floor).

  • Performance: Token cache (client-credentials token) and keep checks sub-second.

  • Security: App perms should be least-privilege (read-only users).

  • Testing: Dry-run mode and unit tests for your variant generator; test with “evil” names (accents, hyphens, long names).

  • Monitoring: Alert on repeated collisions >N or throttling spikes.

Minimal PowerShell blueprint (IQService “Before Script/After Script”)

Pseudocode outline—adapt to your IQService script skeleton and payload names.

# Input: $operation, $accountData (hashtable/dictionary of attributes destined to AD)
# Output: mutate $accountData to set mail/proxyAddresses before create

function Get-GraphToken {
  # Retrieve client credentials from Windows Credential Manager
  # Call token endpoint; cache in a global variable with expiry
}

function EmailExists-InEntra($email) {
  $token = Get-GraphToken
  $encoded = [System.Web.HttpUtility]::UrlEncode("mail eq '$email' or userPrincipalName eq '$email'")
  $uri = "https://graph.microsoft.com/v1.0/users`?\$filter=$encoded&\$select=id"
  # Invoke-RestMethod with retry for 429/503; return $true if any user
}

function AliasExists-InEntra($email) {
  $token = Get-GraphToken
  $probe1 = "proxyAddresses/any(p:p eq 'SMTP:$email') or proxyAddresses/any(p:p eq 'smtp:$email')"
  $encoded = [System.Web.HttpUtility]::UrlEncode($probe1)
  $uri = "https://graph.microsoft.com/v1.0/users`?\$filter=$encoded&\$select=id"
  # Invoke-RestMethod; return $true if any user
}

function EmailExists-InAD($email) {
  # Use ADSI/RSAT to query local AD for (mail=$email) OR (proxyAddresses=SMTP:$email) OR (proxyAddresses=smtp:$email)
}

function New-UniqueEmail($baseLocalPart, $domain) {
  for ($i=0; $i -le 50; $i++) {
    $local = ($i -eq 0) ? $baseLocalPart : "$baseLocalPart$i"
    $candidate = "$local@$domain"
    if (-not (EmailExists-InAD $candidate) -and -not (EmailExists-InEntra $candidate) -and -not (AliasExists-InEntra $candidate)) {
      return $candidate
    }
  }
  throw "Unable to find unique email after 50 attempts."
}

if ($operation -eq "Create") {
  # 1) Derive base from givenName/surname; normalize (lowercase, strip diacritics)
  $first = $accountData['givenName']
  $last  = $accountData['sn']
  $base  = (Normalize-ToAscii "$first.$last").ToLower()
  $domain = "corp.example.com"  # Or read from config

  # 2) Find unique
  $finalEmail = New-UniqueEmail -baseLocalPart $base -domain $domain

  # 3) Set attributes for AD create
  $accountData['mail'] = $finalEmail
  $proxy = @("SMTP:$finalEmail") # add policy aliases if needed
  $accountData['proxyAddresses'] = $proxy

  # 4) (Optional) keep UPN aligned
  if ($accountData.ContainsKey('userPrincipalName')) {
    $accountData['userPrincipalName'] = $finalEmail
  }
}

FAQ quick hits

  • Why not an AttributeGenerator rule + connected Entra source?
    That’s the cleanest “all-inside-SailPoint” route, but cloud rules can’t call Graph and you’ve chosen to validate from AD. The IQService PowerShell hook is the right supported escape hatch.

  • Will SailPoint “see” the final email immediately?
    The value is written to AD during create; ISC will reflect it either from the provisioning response or on the next aggregation. If you need it on the identity profile instantly, ensure the attribute is in the provisioning response or run a targeted account refresh.

  • Any sync gotchas?
    If you run AAD Connect (or cloud sync), make sure your authoritative system and flows don’t overwrite each other. Keep a single source of truth for the email policy and ensure mappings are consistent.

Cheers!!!

1 Like

Hi, @sukanta_biswas
Thank you very much for your reply. I will do all the tests as you recommend.

This topic was automatically closed 60 days after the last reply. New replies are no longer allowed.