Active Directory: User Principal Name

Introduction

When you synchronize on-premises Active Directory users with Azure, Office 365, or InTune, the User Principal Name (UPN) is often used to identify the users. This means that all users that will be synchronized should have the userPrincipalName attribute assigned, and the values should be unique in the Forest.

Unfortunately, Active Directory does not enforce this. The userPrincipalName attribute is not mandatory. And while the Active Directory Users and Computers MMC will not allow you to assign a duplicate value, you can assign duplicates in code, such as scripts.



The userPrincipalName Attribute in Active Directory

The PowerShell Get-ADUser and Get-ADComputer cmdlets expose the UserPrincipalName property. This property is the value of the userPrincipalName attribute of the Active Directory objects. The following features of the userPrincipalName attribute are relevant:

  • The userPrincipalName attribute is not mandatory in on-premises Active Directory (AD). Users are not required to have a value assigned.
  • The Active Directory Users and Computers MMC (ADUC) will enforce unique values, but you can assign duplicate values for userPrincipalName in code.
  • In order to synchronize Active Directory accounts with Azure, Office 365, or InTune, the userPrincipalName should be in the form of an email address, such as "LogonName@mydomain.com".
  • In AD the name before the final "@" character does not need to match the sAMAccountName (the pre-Windows 2000 logon name).
  • In AD the UPN suffix, after the final "@" character, does not need to match any real domain.
  • In fact, Active Directory allows you to assign any string value (up to 1024 characters by default) to the userPrincipalName attribute.
  • Even if the domain is restructured or renamed, or the user is moved, they can always logon to AD with their UPN.
  • If no value is assigned to the userPrincipalName attribute, the user can still logon to AD using their sAMAccountName, followed by the "@" character, followed by the DNS name of the domain. For example "jsmith@mydomain.com", where "jsmith" is the sAMAccountName. It's as if this is a default UPN. And this default can be used to logon even when the user has a different value assigned for their UPN.
  • Both user and computer objects have the userPrincipalName attribute.

A user cannot logon to Active Directory with just their sAMAccountName if it includes the "@" character. ADUC will not allow you to assign a sAMAccountName with this character, but it can be done in code. For example, assume the sAMAccountName is "r@cameron" in a domain with DNS name "mydomain.com". If this user attempts to logon using either "r@cameron" or "mydomain\r@cameron", the system will respond with the message "the user name or password is incorrect". However, if the userPrincipalName (UPN) for this user is "rcameron@mydomain.com", they can logon with that name. Even if the UPN is "r@johnson@mydomain.com", they can logon successfully with that name. And whether or not a value has been assigned to the userPrincipalName attribute of the user, they can always logon with their sAMAccountName, followed by the "@" character, following by the DNS name of the domain. This is a default UPN in Active Directory. So in this example, the user can always logon as "r@cameron@mydomain.com".

↑ Return to Top


Find Users with no User Principal Name

Any on-premises Active Directory users that will be synchronized with Office 365 or Azure should have a User Principal Name assigned. The following PowerShell script can be used to find all users with no value assigned to their userPrincipalName attribute in Active Directory:

Get-ADUser -LDAPFilter "(!(userPrincipalName=*))" | Select distinguishedName

The following dsquery command can be also used to find all users with no userPrincipalName assigned in Active Directory. It can be run at the command prompt of a domain controller, or any client with RSAT (Remote Server Administration Tools) installed:

dsquery * -Filter "(&(objectCategory=person)(objectClass=user)(!(userPrincipalName=*)))" -Limit 0

↑ Return to Top


Find Duplicate User Principal Names

If on-premises Active Directory users are to be successfully synchronized with Office 365 or Azure, they should have a unique User Principal Name. The following PowerShell script can be used to find all objects with duplicate userPrincipalName values in Active Directory:

# Script to find objects with duplicate userPrincipalName values.
# Version 1.0 - December 4, 2018


# Retrieve all objects with a UPN value assigned.
# Users and computers can have userPrincipalName.
$Objects = Get-ADObject -LDAPFilter "(userPrincipalName=*)" -Properties userPrincipalName


# Hash table of UPNs. The key is the UPN, the value is the DN of the object.
$UPNs = @{}


# Loop through the objects.
ForEach ($Object In $Objects)
{
    $UPN = $Object.userPrincipalName
    $DN = $Object.distinguishedName
    # Check if this UPN has been seen already.
    If ($UPNs.ContainsKey($UPN))
    {
        # Duplicate UPN.
        "Duplicate UPN: $UPN"
        $DN_Dupl = $UPNs[$UPN]
        "    DN: $DN_Dupl"
        "    DN: $DN"
    }
    Else
    {
        # Add this UPN to the hash table.
        $UPNs.Add($UPN, $DN)
    }
}

↑ Return to Top


Find Duplicates Among Several Attributes

Errors are raised during synchronization if duplicate email addresses are found among any of these attributes: userPrincipalName, mail, msRTCSIP-PrimaryUserAddress, and proxyAddresses. The duplicates can be among any class of object in AD. For example, if the mail attribute of a user has a value that matches one of the proxyAddresses values of a group object, then the user will not synchronize properly. The following PowerShell script can be used to find all objects with duplicates among any of these attributes:

# FindDuplIDs.ps1
# Script to find objects with duplicates among the following AD attributes:
# userPrincipalName, mail, msRTCSIP-PrimaryUserAddress, proxyAddresses
# Version 1.0 - December 8, 2018
# Version 1.1 - March 1, 2019.
#               Quote attribute with lDAPDisplayName msRTCSIP-PrimaryUserAddress.


# Retrieve all objects where any of the attributes are assigned values.
$Prop = @("userPrincipalName","mail","msRTCSIP-PrimaryUserAddress","proxyAddresses")
$Filter = "(|(userPrincipalName=*)(mail=*)(msRTCSIP-PrimaryUserAddress=*)(proxyAddresses=*))"
$Objects = Get-ADObject -LDAPFilter $Filter -Properties $Prop


# Hash table of IDs. The key is the ID (the value of one of the 4 attributes),
# the value is the DN of the objects with the value (and the attribute names).
# The DNs in the value are separated by the "@" character. It is assumed that
# no distinguished names have this character.
$IDs = @{}


# Loop through the objects.
ForEach ($Object In $Objects)
{
    # Retrieve attribute values.
    $DN = $Object.distinguishedName
    $UPN = $Object.userPrincipalName
    $Mail = $Object.mail
    $PrimAddr = $Object."msRTCSIP-PrimaryUserAddress"
    $ProxyAddrs = $Object.proxyAddresses


    # Check userPrincipalName.
    If ($UPN)
    {
        # Check if this ID has been seen already.
        If ($IDs.ContainsKey($UPN))
        {
            # Duplicate ID, append to value.
            $IDs[$UPN] = $IDs[$UPN] + "@$DN `(UPN)"
        }
        Else
        {
            # Add this ID to the hash table.
            $IDs.Add($UPN, "$DN `(UPN)")
        }
    }
    # Check mail.
    If ($Mail)
    {
        # Only consider value after any colon character.
        $Mail = ($Mail.Split(":"))[-1]
        # Check if this ID has been seen already.
        If ($IDs.ContainsKey($Mail))
        {
            # Check for the current DN.
            If ($IDs[$Mail] -Like "*$DN `(*")
            {
                # Add mail to the list of attributes for this DN.
                $IDs[$Mail] = $IDs[$Mail].Replace("$DN `(","$DN `(mail,")
            }
            Else
            {
                # Duplicate ID, append to value.
                $IDs[$Mail] = $IDs[$Mail] + "@$DN `(Mail)"
            }
        }
        Else
        {
            # Add this ID to the hash table.
            $IDs.Add($Mail, "$DN `(mail)")
        }
    }
    # Check msRTCSIP-PrimaryUserAddress.
    If ($PrimAddr)
    {
        # Only consider value after any colon character.
        $PrimAddr = ($PrimAddr.Split(":"))[-1]
        # Check if this ID has been seen already.
        If ($IDs.ContainsKey($PrimAddr))
        {
            # Check for the current DN.
            If ($IDs[$PrimAddr] -Like "*$DN `(*")
            {
                # Add msRTCSIP-PrimaryUserAddress to the list of attributes for this DN.
                $IDs[$PrimAddr] = $IDs[$PrimAddr].Replace("$DN `(","$DN `(msRTCSIP-PrimaryUserAddress,")
            }
            Else
            {
                # Duplicate ID, append to value.
                $IDs[$PrimAddr] = $IDs[$PrimAddr] + "@$DN `(msRTCSIP-PrimaryUserAddress)"
            }
        }
        Else
        {
            # Add this ID to the hash table.
            $IDs.Add($PrimAddr, "$DN `(msRTCSIP-PrimaryUserAddress)")
        }
    }
    # Check proxyAddresses.
    If ($ProxyAddrs)
    {
        # Check each address in proxyAddresses.
        ForEach ($Addr In $ProxyAddrs)
        {
            # Only consider value after any colon character.
            $Addr = ($Addr.Split(":"))[-1]
            # Check if this ID has been seen already.
            If ($IDs.ContainsKey($Addr))
            {
                # Check for the current DN.
                If ($IDs[$Addr] -Like "*$DN `(*")
                {
                    # Add proxyAddresses to the list of attributes for this DN.
                    $IDs[$Addr] = $IDs[$Addr].Replace("$DN `(","$DN `(proxyAddresses,")
                }
                Else
                {
                    # Duplicate ID, append to value.
                    $IDs[$Addr] = $IDs[$Addr] + "@$DN `(proxyAddresses)"
                }
            }
            Else
            {
                # Add this ID to the hash table.
                $IDs.Add($Addr, "$DN `(proxyAddresses)")
            }
        }
    }
}


# Enumerate all IDs.
ForEach ($ID In $IDs.Keys)
{
    $Values = $IDs[$ID].Split("@")
    If ($Values.Count -gt 1)
    {
        "Duplicate ID: $ID"
        ForEach ($Entry In $Values)
        {
            "    $Entry"
        }
    }
}

The script can also be found here.

Find Duplicate Email Addresses among Several Attributes of any AD Object

It is also linked in the "Other Resources" section below.

↑ Return to Top


Valid Names in Office 365

Office 365 requires that the UserName portion of the User Principal Name (the string before the "@" character in the UPN) meet the following conditions:

  • All alphanumeric characters are allowed.
  • The following characters are allowed, subject to the exceptions noted below: ` _ ' . -
  • The following characters are not allowed: ~ ! @ # $ % ^ & * ( ) + = [ ] { } \ / | ; : " < > ? ,
  • The UserName must not begin or end with the period character, ".". The period character is allowed anywhere else in the UserName.
  • However, two or more periods in a row, "..", are not allowed.
  • The UserName must not begin or end with the hyphen character, "-". The hyphen character is allowed anywhere else in the UserName.
  • The userPrincipalName attribute value must have one "@" character, which separates the UserName from the DNS domain name. The UserName itself cannot have the "@" character.

↑ Return to Top


Function to Validate UserName Portion of UPN

The following PowerShell function returns $True if the UserName meets the conditions above.

Function ValidateUserName ($Name)
{
    # Function to validate the UserName portion of a UserPrincipalName, the string before the "@" character.
    # Version 1.0 - December 4, 2018.
    If ($Name.StartsWith(".") -Or $Name.StartsWith("-") -Or $Name.EndsWith(".") -Or $Name.EndsWith("-"))
    {
        Return $False
    }
    ElseIf ($Name.Contains(".."))
    {
        Return $False
    }
    Else
    {
        # Invalid characters: ~ ! # $ % ^ & * ( ) + = [ ] { } \ / | ; : " < > ? ,
        # Reserved characters that must be escaped with the backslash in regular expressions:
        # [ ] ( ) . \ ^ $ | ? * + { }
        [regex]$Reg = "(\~|\!|\#|\$|\%|\^|\&|\*|\(|\)|\+|\=|\[|\]|\{|\}|\\|\/|\||\;|\:|`"|\<|\>|\?|\,)
        If ($Reg.Matches($Name).Count -eq 0)
        {
            Return $True
        }
        Else
        {
            Return $False
        }
    }
}

↑ Return to Top


Valid Domain Names

The DNS domain name portion of the UPN, the string after the "@" character, should meet the following conditions:

  • If the first character is an opening bracket character, "[", the domain name can be an IPv4 address followed by a closing bracket, "]". For example, the domain name can be "[129.126.118.1]".
  • If the first character is not an opening bracket character, the domain name must be a period-delimited sequence of strings representing domain components.
  • Each of the domain component strings (delimited by periods) must be alphanumeric characters or the hyphen character, "-", as long as the hyphen is not the first or last character in the component. The domain name "East7.My-Domain.com" is allowed, but "East5.-MyDomain.com" and "West3.MyDomain-.com" are not.
  • The top-level domain name (the string after the final period character) must begin and end with an alphanumeric character.
  • The remaining characters of the top level domain name must be from zero to 22 characters that are either alphanumeric or the hyphen character, "-". For example, "MyDomain.US-ER3" is allowed.
  • Two periods in a row are not allowed in the DNS domain name.

↑ Return to Top


Function to Validate DNS Domain Name Portion of UPN

The following PowerShell function returns $True if the DNS domain name meets the conditions above.

Function ValidateDomain ($DNSDomain)
{
    # Function to validate the DNS Domain Name portion of a UserPrincipalName, the string after the "@" character.
    # Reserved characters that must be escaped with the backslash in regular expressions:
    # [ ] ( ) . \ ^ $ | ? * + { }
    # Version 1.0 - December 4, 2018.
    Try {
        [regex]$Reg = "(?(\[)(\[(\d{1,3}\.){3}\d{1,3}\])|(([0-9a-zA-Z][-0-9a-zA-Z]*[0-9a-zA-Z]*\.)+[0-9a-zA-Z][-0-9a-zA-Z]{0,22}[0-9a-zA-Z]))$"
        If ($Reg.Matches($DNSDomain).Count -eq 1)
        {
            Return $True
        }
        Else
        {
            Return $False
        }
    }
    Catch {
        Return $False
    }
}

↑ Return to Top


Find Invalid User Principal Names

The two PowerShell functions described above can be combined in a script to validate User Principal Names. The following script also ensures that the UPN includes one and only one "@" character.

# ValidateUPN.ps1
# PowerShell script to validate userPrincipalName attribute of on-premises Active Directory users
# that will be synchronized with Office 365 or Azure Active Directory.
# Version 1.0 - December 4, 2018.


$UPNs = "David.Jones@proseware.com","d.j@server1.proseware.com","jones@ms1.proseware.com","j.@server1.proseware.com","j@server1.proseware.com9","js#internal@server1.proseware.com","j_9@[129.126.118.1]","j..s@proseware.com","js*@proseware.com","js@proseware..com","js@proseware.com9","j.s@Server1.Proseware.com","j\""s\""@proseware.com"


ForEach ($UPN In $UPNs)
{
    "--UPN: $UPN"
    $Parts = $UPN.Split("@")
    Switch ($Parts.Count)
    {
        2 {
            $UserName = $Parts[0]
            $Domain = $Parts[1]
            If (ValidateUserName $UserName)
            {
                "    UserName OK"
            }
            Else
            {
                "    UserName Bad"
            }
            If (ValidateDomain $Domain)
            {
                "    Domain OK"
            }
            Else
            {
                "    Domain Bad"
            }
        }
        Default {
            "    UPN Bad"
        }
    }
}

The function definitions shown earlier (for ValidateUser and ValidateDomain) must appear before the ForEach statement in the code above.

↑ Return to Top


See Also

↑ Return to Top


Other Resources

↑ Return to Top