...
One way to achieve this is for example to use a Base36 number (alphanumeric) that is stored as a string, padding the string left with zeros for sorting. ePPN is incremented for every new user. If we use a length of 6 (36^6), that gives us 2176782336 ePPN's before overflow occurs. Either append the scope, a owned domain, before storing the ePPN or do it later e.g., in the IdP.
Example for AD
Below is a sample script for Active Directory. The script assigns ePPN to users where the attribute is empty. The script check the AD for the highest assigned ePPN, increments it, and then assign the new ePPN. This script can be run manually or as a scheduled job. Do not execute more than one instance of the script at a time for the same SearchBase to avoid a race condition
Code Block | ||
---|---|---|
| ||
param (
[Parameter(
Mandatory,
HelpMessage="The name of the attribute that holds the ePPN"
)]
[string]$Attribute,
[Parameter(
Mandatory,
HelpMessage="Specifies an Active Directory path to search under"
)]
[ValidateScript({[adsi]::Exists("LDAP://$_")})]
[string]$SearchBase,
[Parameter(
Mandatory,
HelpMessage="The length of the ePPN i.e, EppnLen 3, will generate ePPN's from 001 to zzz"
)]
[int]$EppnLen,
[Parameter(
HelpMessage='If set, the value "@$Scope" will be appended to the ePPN'
)]
[string]$Scope
)
$DebugPreference = "Continue"
function IncStr
{
[CmdletBinding()]
param ([string]$eppnPre="")
$alphabet = "0123456789abcdefghijklmnopqrstuvwxyz"
$alen = $alphabet.Length - 1
$incremented = ""
foreach ($i in $eppnPre.tolower()[$eppnPre.length..0])
{
$count++
$index = $alphabet.IndexOf($i)
if ($alphabet.IndexOf($i) -eq $alen)
{
$index = 0
} else {
$incremented = $eppnPre.Substring(0, $eppnPre.Length - $count) + $alphabet.Substring($index +1 ,1) + $incremented
break
}
$incremented = $alphabet.Substring($index,1)+$incremented
if ($count -eq $eppnPre.length)
{
$incremented = $alphabet.Substring(1,1)+$incremented
}
}
$incremented
}
$newUsers = Get-ADUser -Filter {-not($Attribute -like "*")} -Properties $Attribute -SearchBase $SearchBase -ErrorAction Stop
if ($newUsers) {
$users = Get-ADUser -Filter '$Attribute -like "*"' -Properties $Attribute | Select-Object $Attribute
$eppn = "0"
$users | ForEach-Object {
$prefix = ($_.$Attribute -split "@")[0]
if ($prefix -gt $eppn) {
$eppn = $prefix
}
}
$newUsers | ForEach-Object {
$eppn = (IncStr -eppnPre $eppn).PadLeft($EppnLen,'0')
if ($eppn.Length -gt $EppnLen){
throw "ePPN lenght overflows EppnLen: $EppnLen"
}
if ($Scope) {
$params= @{"Identity" = $_
$Attribute = "$eppn@$Scope"}
} else {
$params= @{"Identity" = $_
$Attribute = $eppn}
}
Set-ADUser @params
Write-Debug "Added $eppn"
}
Write-Debug "New Users added"
}
|
Parameters
Attribute <String>
REQUIRED. The name of the attribute that holds the ePPNSearchBase <String>
REQUIRED. Specifies an Active Directory path to search underEppnLen <Int>
REQUIRED. The length of the ePPN i.e, EppnLen 3, will generate ePPN's from 001 to zzzScope <String>
OPTIONAL. If set, the value "@$Scope" will be appended to the ePPN
Execution
Exampel of how to execute the script.
ad-eppn.ps1 -Attribute eduPersonPrincipalName -SearchBase "OU=Pupils,DC=example,DC=com" -EppnLen 6 -Scope exampel.com
Messurment
I measured the time it took to run scripts on a SearchBase with 100,000 users. The test was run on a computer with rather low performance and took 28 min.
Measure-Command {ad-eppn.ps1 -Attribute eduPersonPrincipalName -SearchBase "OU=Pupils,DC=example,DC=com" -EppnLen 6 -Scope exampel.com}
...
Exampel Scripts
The examples shows how you can add the attribute to users by utilizing scripts. The scripts are built so that it should be possible to run them with a scheduler.
ePPN for Google Workspace on Github
ePPN for Microsoft AD on Github