Create_SecureMFA_OTP_AD_attributes.ps1

#THIS ONLY REQUIRES IF YOU PLAN TO RUN SecureMFA OTP Provider in AD MODE.
#You need to execute this script on Read-Write domain controller with AD account which is member of Schema Admins group.
#SecureMFA OTP provider when operates in AD mode ("auth_mode": "AD") it requires custom Active Directory (AD) attributes to be created to store OTP data for the user.
#This action cannot be undone and needs to be tested in your TEST domain first before moving into PRODUCTION.
#New AD Schema Attributes will be added into custom SecureMFA Auxiliary Class and that Class will be added into Existing User Class as AD Schema best practices suggest.
#OID numbers for custom attributes are from SecureMFA Private Enterprise range assigned by iana.org . Which do not overlap with other vendors OIDs numbers used to create custom AD attributes.

function ADDADSchemaAttributetoClass($SchemaAttribute)
{
    [bool]$reset = $true
    $attempts=30    
    $sleepInSeconds=180
    do
    {
        try
        {
            $reset = $true
            Write-Host "Adding $SchemaAttribute into SecureMFA Auxiliary Class.($attempts)" -ForegroundColor Green

            #Get AD Schema details
            $dse =  Get-ADRootDSE
            $schemaPath = $dse.schemaNamingContext       
            $type = 'attributeSchema'  

            #ADD AD Schema Attribute To SecureMFA Class
            $Schema = Get-ADObject -SearchBase $schemaPath -Filter "name -eq `'SecureMFA`'"
            $Schema | Set-ADObject -Add @{mayContain = $SchemaAttribute}  -ErrorAction stop             
        }
        catch [Exception]
        {
            Write-Host "Waiting for AD schema attribute refresh ... Please note this action can take a few minutes to complete." -ForegroundColor Yellow
            $reset = $false
        }            
        $attempts--

        if ($attempts -gt 0 -and $reset -eq $false) { sleep $sleepInSeconds } 
    } until ($attempts -le 0 -or $reset -eq $true)
    
    return $reset    
}

function ADDADAttribute{
 
    Param( 
        [parameter(Mandatory)]
        [String]$Name,
        [Parameter(Mandatory)]
        [String]$Description,
        [parameter(Mandatory)]
        [String]$AttributeID,
        [Parameter(Mandatory)]
        [ValidateSet('String','Int','Time','Boolean')]
        [String]$AttributeType,
        [Parameter()]
        [Boolean]$IsSingleValued = $True 
        )
 
    #Attribute values by type https://technet.microsoft.com/en-us/library/cc961740.aspx
    switch ($AttributeType) {
                'String'            {$attributeSyntax = '2.5.5.4';  $omSyntax = 20}
                'Int'               {$attributeSyntax = '2.5.5.9';  $omSyntax = 2}
                'Time'              {$attributeSyntax = '2.5.5.11'; $omSyntax = 24}
                'Boolean'           {$attributeSyntax = '2.5.5.8';  $omSyntax = 1}
                Default {}
            }    
    
    #AD atribute params-------
    $attributes = @{
    lDAPDisplayName = $Name;
    attributeId = $AttributeID;
    oMSyntax = $omSyntax;
    attributeSyntax = $attributeSyntax;
    isSingleValued = $IsSingleValued;
    adminDescription = $Description;
    searchflags = 0
            }
    #If AD Attribute doesn't exist create one.
    if(!(Get-ADObject -SearchBase "$((Get-ADRootDSE).SchemaNamingContext)" -Filter {lDAPDisplayName -eq $Name})) {
    New-ADObject -Name $Name -Type $type -Path $schemapath -OtherAttributes $attributes
    Write-host "AD attribute $Name has been created." -ForegroundColor Green
    }
    Else {Write-host "AD attribute $Name allready exist in AD." -ForegroundColor yellow}
 
}

#START Script

#Get AD Schema details
$dse =  Get-ADRootDSE
$schemaPath = $dse.schemaNamingContext       
$type = 'attributeSchema'


#Create a New AD Schema Auxiliary Class for SecureMFA
$Name = 'SecureMFA'
$attributes = @{
            governsId = '1.3.6.1.4.1.54153.2.1000'
            adminDescription = 'SecureMFA Class to host OTP attributes'
            objectClass =  'classSchema'
            ldapDisplayName = $Name
            adminDisplayName =  $Name
            objectClassCategory = 3
            systemOnly =  $FALSE
            # subclassOf: top
            subclassOf = "2.5.6.0"
            # rdnAttId: cn
            rdnAttId = "2.5.4.3"
        }

#If Schema Auxiliary Class for SecureMFA doesn't exist create one.
if(!(Get-ADObject -SearchBase (Get-ADRootDSE).SchemaNamingContext -Filter {name -like "SecureMFA"})) {
New-ADObject -Name $Name -Type 'classSchema' -Path $schemapath -OtherAttributes $attributes
Write-host "A New AD Schema Auxiliary Class SecureMFA has been created." -ForegroundColor Green
}
Else {Write-host "Auxiliary Class SecureMFA allready exist in AD." -ForegroundColor Yellow}

#Add AD Schema SecureMFA Auxiliary Class To default User Class
$auxClass = Get-ADObject -SearchBase $schemaPath -Filter "name -eq `'$Name`'" -Properties governsID
$classToAddTo  = Get-ADObject -SearchBase $schemaPath -Filter "name -eq `'user`'"
$classToAddTo | Set-ADObject -Add @{auxiliaryClass = $($auxClass.governsID)}

#START Creating AD attributes

ADDADAttribute -Name 'sMFA-OTP-secret' -Description 'User Secret attribute' -AttributeID '1.3.6.1.4.1.54153.2.1000.00001' -AttributeType String -IsSingleValued $true
ADDADAttribute -Name 'sMFA-OTP-logon' -Description 'User logon status attribute' -AttributeID '1.3.6.1.4.1.54153.2.1000.00002' -AttributeType Int -IsSingleValued $true
ADDADAttribute -Name 'sMFA-OTP-lastlogon' -Description 'User laslogon attribute' -AttributeID '1.3.6.1.4.1.54153.2.1000.00003' -AttributeType Time -IsSingleValued $true
ADDADAttribute -Name 'sMFA-OTP-logoncount' -Description 'User logoncount attribute' -AttributeID '1.3.6.1.4.1.54153.2.1000.00004' -AttributeType Int -IsSingleValued $true
ADDADAttribute -Name 'sMFA-OTP-failedlogoncount' -Description 'User failedlogoncount attribute' -AttributeID '1.3.6.1.4.1.54153.2.1000.00005' -AttributeType Int -IsSingleValued $true
ADDADAttribute -Name 'sMFA-OTP-failedlastlogon' -Description 'User failedlastlogon attribute' -AttributeID '1.3.6.1.4.1.54153.2.1000.00006' -AttributeType Time -IsSingleValued $true
ADDADAttribute -Name 'sMFA-OTP-failedcode' -Description 'User failedcode attribute' -AttributeID '1.3.6.1.4.1.54153.2.1000.00007' -AttributeType String -IsSingleValued $true
ADDADAttribute -Name 'sMFA-OTP-logonip' -Description 'User logonip attribute' -AttributeID '1.3.6.1.4.1.54153.2.1000.00008' -AttributeType String -IsSingleValued $true
ADDADAttribute -Name 'sMFA-OTP-useragent' -Description 'User useragent attribute' -AttributeID '1.3.6.1.4.1.54153.2.1000.00009' -AttributeType String -IsSingleValued $true
ADDADAttribute -Name 'sMFA-OTP-interval' -Description 'User interval attribute' -AttributeID '1.3.6.1.4.1.54153.2.1000.00010' -AttributeType Int -IsSingleValued $false

#Refresh AD schema
$dse.schemaUpdateNow = $true
Write-host "Refreshing AD Schema." -ForegroundColor Green

[bool]$reset = $false
#START Adding AD Schema Attributes to User Class:

@("sMFA-OTP-secret","sMFA-OTP-logon","sMFA-OTP-lastlogon","sMFA-OTP-logoncount","sMFA-OTP-failedlogoncount","sMFA-OTP-failedlastlogon","sMFA-OTP-failedcode","sMFA-OTP-logonip","sMFA-OTP-useragent","sMFA-OTP-interval") | foreach {    
    $SchemaAttribute = $_.trim()
    $reset = ADDADSchemaAttributetoClass $SchemaAttribute
    if($reset) {Write-host "AD attribute $SchemaAttribute have been added into SecureMFA Auxiliary Class." -ForegroundColor Green}
    else {Write-host "AD attribute $SchemaAttribute has failed to add into SecureMFA Auxiliary Class. Please try again later when AD replication for attributes is completed across your environment." -ForegroundColor red}    
    }

#Refresh AD schema
$dse.schemaUpdateNow = $true

write-host "Custom sMFA attributes have been created. Please note that depending on AD replication policies it may take a while to show a new custom attribute for user object in AD when using GUI tools." -ForegroundColor Cyan
pause