Powershell Script – AD Name Change

This script modifies AD attributes in case user wants a name changes or email address modified. Script is intended to update UPN, NAME, E-MAIL.

Scenarios where name change may occur:
Marriage or Divorce/Gender Transition/Cultural or Religious Reasons/Adoption/legal reasons/etc...

*Notes:
Firstname, Initials, surname are optional fields (fill only if changes to name)
Old UPN and new UPN is required
This script assumes that the old email is to be used as an email alias and proxy address.

Impact of modification:
Systems like AD, Exchange, etc. aren't joined together by the attributes like userPrincipalName (deliberately setting aside AD-AAD soft matching), but rather "primary key" identifiers like objectGUID or objectSid (on-premise Active Directory), or objectID/Id (Azure AD/Graph).
These are generally referred to as "immutable identifiers" as they cannot change over the course of their life.
Using these immutable identifiers allows actions like renames to take place and be reflected across all those integrated systems without breaking anything.

So modification to SAM, UPN, email should have little to no impact to a user unless an attribute like UPN/email/SAM is used to integrate with third party apps.

Platform with no impact:
Onedrive
sharepoint
Office apps
Folder ACL

Possible impact:

Third-party apps that key off UPN/email/SAM instead of an immutable ID (HR systems like Workday/ADP, DUO/2FA sync providers, custom department apps). Evaluation of products that utilize AD directory synchronization will be impacted.


Source code:

<#
.SYNOPSIS
    Name change for hybrid (on-prem AD + AAD) environment.
    Updates GivenName/Initials/Surname, CN, DisplayName, UPN, SamAccountName,
    primary email, mailNickname, and proxyAddresses for a single user.

    # The user MUST have exchange mailbox setup first or script will error out. The custom mail attributes are REQUIRED!!
    # Script tested and works in Powershell 7 as well #

    ##########################
    # Modify the domain under parameter:
        
    [string]$FundsDomain   = "@red929.com",
    [string]$DefaultDomain = "@red929.mail.onmicrosoft.com"
    ##########################
    
.NOTES

    Scenarios: Marriage/Divorce, Gender Transition, Cultural/Religious reasons, Adoption, legal name change, etc.

    Immutable identifiers (objectGUID/objectSid on-prem, objectId in AAD) are what keep systems
    linked together — NOT UPN/SamAccountName/email. So this rename is safe for AD, AAD, Exchange,
    OneDrive, SharePoint, Office apps, and folder ACLs.

    Possible impact: third-party apps that key off UPN/email/SAM instead of an immutable ID
    (HR systems like Workday/ADP, DUO/2FA sync providers, custom department apps).

    The script uses [$PSCmdlet.ShouldProcess] for -whatif parameter - this triggers a prompt for user to accept before proceeding with changes..
    if ($PSCmdlet.ShouldProcess($OldUPN, "Update EmailAddress, mailNickname, proxyAddresses"))

    Example:
    Performing the operation "Update EmailAddress, mailNickname, proxyAddresses" on target "pli".
    [Y] Yes  [A] Yes to All  [N] No  [L] No to All  [S] Suspend  [?] Help (default is "Y"): y


.EXAMPLE
    .\AD_NameChangeV2.ps1 -OldUPN "sli" -NewUPN "slee" -Lastname_New "Lee" -Firstname_New "Stephan" -Initial_New "M"

.EXAMPLE
    # Dry run first - shows what would happen, changes nothing
    .\AD_NameChangeV2.ps1 -OldUPN "sli" -NewUPN "slee" -Lastname_New "Lee" -WhatIf
#>

[CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High')]
param(
    [Parameter(Mandatory)]
    [ValidatePattern('^\S+$')]
    [string]$OldUPN,

    [Parameter(Mandatory)]
    [ValidatePattern('^\S+$')]
    [string]$NewUPN,

    # Blank string is ok for firstname/initial/Lastname, but not for UPN/SAM. If blank, the existing value is kept.
    [Parameter(Mandatory=$false)]
    [string]$Firstname_New =  (Read-Host "Enter [NEW] First Name (Optional)"),

    [Parameter(Mandatory=$false)]
    [string]$Initial_New = (Read-Host "Enter [NEW] Initial (Optional)" ),

    [Parameter(Mandatory=$false)]
    [string]$Lastname_New =(Read-Host "Enter [NEW] Last Name (Optional)"),

    [string]$FundsDomain   = "@red929.com",
    [string]$DefaultDomain = "@red929.mail.onmicrosoft.com"

)

Import-Module ActiveDirectory -ErrorAction Stop

# ###############
# Logging setup
# ###############

# Create the log folder if it doesn't exist, or clean it up if it does.
$LogPath = "C:\Temp\AD_NameChange\"
$TestPath = Test-Path -Path $LogPath
if($TestPath -eq $false ){
    
    Write-Output "##### Creating Log folder #####"
    New-Item -Path $LogPath -ItemType "Directory"
}

if($TestPath -eq $true ){
    Write-Output "##### Cleaning up Log folder #####"
    Remove-Item "$($LogPath)\*" -Recurse -Force -ErrorAction SilentlyContinue
}

# start logging
$LogFile = "$LogPath\AD_NameChange_logs-$(Get-Date -Format 'MMddyyyy-HHmmss').log"
Start-Transcript -Path $LogFile -Force


try {
  
# ###################################################################
# Start Pre-flight validation- validate if there is existing new UPN #
# ###################################################################

    # The user MUST have exchange mailbox setup first or script will error out. The custom mail attributes are REQUIRED!!
    $ADUser = Get-ADUser -Identity $OldUPN -Properties GivenName, Initials, Surname, Name, `
    UserPrincipalName, SamAccountName, EmailAddress, mailNickname, proxyAddresses -ErrorAction Stop

    # If OldUPN not equal to NewUPN, check if NewUPN already exists in AD.
    if ($OldUPN -ne $NewUPN) {

        # Grab details on new UPN
        $conflict = Get-ADUser -Filter "SamAccountName -eq '$NewUPN' -or UserPrincipalName -eq '$NewUPN$FundsDomain'" -ErrorAction SilentlyContinue

        if ($conflict) {
            # If AD already has new UPN, abort before making any changes. This prevents a half-done rename that would leave the user in a broken state.
            throw "NewUPN '$NewUPN' already exists in AD (conflicts with $($conflict.DistinguishedName)). Aborting before any changes were made."
        }
    }
# ###################################################################
# End Pre-flight validation- validate if there is existing new UPN #
# ###################################################################

# ##########################
# Take Snapshot of Old UPN #
# ##########################

# Snapshot "before" state for the report. Grab details on old UPN before any changes are made, so we can report on what changed at the end.
    $OldUPNInfo = $ADUser | Select-Object GivenName, Initials, Surname, Name, UserPrincipalName, `
    SamAccountName, EmailAddress, mailNickname, proxyAddresses

# ############################
# Declare variable for GUID #
# ############################

    # Use the immutable ObjectGUID as -Identity for every subsequent write.
    # This keeps working even after SamAccountName/UPN change mid-script, and avoids the
    # PS7 pipeline-binding issue from piping Get-ADUser into Rename-ADObject/Set-ADUser. Extremely important to use for rename as it avoid dependency on SamAccountName/UPN, which change mid-script.

    # GUID for OLD UPN
    $Identity = $ADUser.ObjectGUID

# ##################################################################################
# Name attributes (GivenName / Initials / Lastname) - Get full name from new values.
# ##################################################################################

    # Grab the new string for each name attribute, or keep the existing value if no new value was provided.
    # This method does not erase any attributes if no values are provided. The existing values are PRESERVED!!
    
    $NameChanged = $Firstname_New -or $Initial_New -or $Lastname_New

    $EffectiveFirst   = if ($Firstname_New) { $Firstname_New } else { $ADUser.GivenName }
    $EffectiveInitial = if ($Initial_New) { $Initial_New }   else { $ADUser.Initials }
    $EffectiveLastname = if ($Lastname_New) { $Lastname_New }  else { $ADUser.Surname }

    # IF ANY NEW VALUES are provided, update the name attributes. Otherwise, keep the existing values.
    if ($NameChanged) {
            
        Write-Output @"
        #############################
        Updating name attribute(s)...
        #############################
"@
        # Assign variables and pass it to hashtable [NameParams]. Cmdlet [Set-ADUser] will use these values to update identity
        $nameParams = @{}
        if ($Firstname_New){ $nameParams['GivenName'] = $Firstname_New }
        if ($Initial_New)  { $nameParams['Initials']  = $Initial_New }
        if ($Lastname_New) { $nameParams['surname']  = $Lastname_New }

        # Update AD attributes with new values
        # Prompt user to confirm changes with $PSCmdlet.ShouldProcess
        if ($PSCmdlet.ShouldProcess($OldUPN, "Execute [Set-ADUser -identity] to update Firstname, initial, Lastname to $(($nameParams.Values))")){

            Set-ADUser -Identity $Identity @nameParams -ErrorAction Stop
        }

    } else {

    Write-Output @"
        ############################################################
        No name changes requested; keeping existing name attributes.
        ############################################################
"@
    }
    
# ######################################################
# Start Update Full Name (for attributes CN and DisplayName)
# ######################################################

    # If initials are not provided (Firstname + Lastname only)
    $FullName = if ([string]::IsNullOrEmpty($EffectiveInitial)) {

        "$EffectiveFirst $EffectiveLastname"

    } else {
    # Combine all the name attributes (Firstname + Initial + Lastname)
        "$EffectiveFirst $EffectiveInitial $EffectiveLastname"

    } 
    #clean up any double space problem
    $Fullnamev2 = $fullname -replace '\s+', ' ' 

 Write-Output @"

    ##########################################################
    Updating [NEW] string for First name, Initial or Last name
    
    Users updated full name is: $Fullnamev2
    ##########################################################
"@
    # Update CN and DisplayName if any of (First, inital, lastname) is provided.
    if ($PSCmdlet.ShouldProcess($OldUPN, "Execute [Rename-ADObject -Identity and] [Set-ADUser -DisplayName] to '$FullNamev2'")) {

    Write-Output @"

    ################################################
    Updating [Fullname] field for user............ "
    ################################################

"@
        Rename-ADObject -Identity $Identity -NewName $FullNamev2 -ErrorAction Stop

    Write-Output @"

    ################################################
    Updating [DisplayName] for user............ 
    ################################################

"@
        Set-ADUser -Identity $Identity -DisplayName $FullNamev2 -ErrorAction Stop
    }

# ######################################################
# End Update Full Name (for attributes CN and DisplayName)
# ######################################################

# ######################################################
# Start Update Email / proxyAddresses
# ######################################################
<# Update proxy addresses. The proxyAddresses attribute in Active Directory is a multi-valued attribute that stores a list of email addresses associated with a user, group, contact, or other mail-enabled object.
    
Normal setup for proxy address:
    #SMTP:sli@32bjfunds.com
    #smtp:sli@32bjfunds.mail.onmicrosoft.com

    SMTP: (uppercase) denotes the primary SMTP address.
    smtp: (lowercase) denotes secondary SMTP addresses.
    X400: denotes X400 addresses.
    Other address types may also be present.
#>
$NewPrimarySmtp = "$NewUPN$FundsDomain"
$NewSecondarySmtp = "$NewUPN$DefaultDomain"
$OldPrimarySmtp = "$OldUPN$FundsDomain"

# The [$PSCmdlet.ShouldProcess] prompts user to accept Yes or NO to proceed with setting the attributes

    if ($PSCmdlet.ShouldProcess($OldUPN, "Execute [Set-ADUser -identity -EmailAddress] to update EmailAddress, mailNickname and proxyAddresses")) {

    Write-Output @"

    ##########################################################################
    Updating EmailAddress, mailNickname and proxyAddresses for user............ 

    New Primary SMTP proxy address = $NewPrimarySmtp 
    New secondary SMTP proxy address = "$NewUPN$DefaultDomain"
    ##########################################################################

"@
        Set-ADUser -Identity $Identity -EmailAddress $NewPrimarySmtp -Replace @{mailNickname = $NewUPN} -ErrorAction Stop

        # Add the new primary SMTP primary email (@Company Domain) and secondary smtp routing address(@Microsoft Default Domain)
        Set-aduser -identity $Identity -Add @{proxyaddresses="SMTP:$NewPrimarySmtp"} -ErrorAction Stop #Required
        Set-aduser -identity $Identity -Add @{proxyaddresses="smtp:$NewSecondarySmtp"}-ErrorAction Stop #Required

        # Remove old SMTP primary email address(@Company Domain) and re-add the old SMTP address as secondary "smtp" addresses
        Set-aduser -identity $Identity -Remove @{proxyaddresses="SMTP:$OldPrimarySmtp"}-ErrorAction Stop #Required
        Set-aduser -identity $Identity -add @{proxyaddresses="smtp:$OldPrimarySmtp"}-ErrorAction Stop # optional
                    
    }

# ######################################################
# End Update Email / proxyAddresses
# ######################################################

# #############################################################################
# Start Update UPN and SamAccountName - do this LAST, exactly like the original
# #############################################################################

    if ($PSCmdlet.ShouldProcess($OldUPN, "Set UserPrincipalName and SamAccountName to '$NewUPN'")) {

        
    Write-Output @"

    ##########################################################################
    Updating UPN (UserPrincipalName) and SAM (SamAccountName) for user............ 

    New UserPrincipalName = $NewPrimarySmtp 
    New SamAccountName = $NewUPN
    ##########################################################################

"@
        Set-ADUser -Identity $Identity -UserPrincipalName $NewPrimarySmtp -ErrorAction Stop

        Set-ADUser -Identity $Identity -SamAccountName $NewUPN -ErrorAction Stop
    }

# #############################################################################
# End Update UPN and SamAccountName - do this LAST, exactly like the original
# #############################################################################

# ################################################
# Start Detail summary of changes (Report before/after)
# ################################################

# If not in whatif mode
    if (-not $WhatIfPreference) {

        $NewUPNInfo = Get-ADUser -Identity $Identity -Properties GivenName, Initials, Surname, Name, `
            UserPrincipalName, SamAccountName, EmailAddress, mailNickname, proxyAddresses |
            Select-Object GivenName, Initials, Surname, Name, UserPrincipalName, SamAccountName, `
                EmailAddress, mailNickname, proxyAddresses

        Write-Output "`nOn-prem name change complete.`n--- BEFORE ---"
        $OldUPNInfo | Format-List | Out-String | Write-Output

        Write-Output "--- AFTER ---"
        $NewUPNInfo | Format-List | Out-String | Write-Output
    }
# ################################################
# End Detail summary of changes (Report before/after)
# ################################################

} #end try for entire script

catch {
    # Terminate script if any errors encountered
    Write-Error "AD name change FAILED and was stopped: $($_.Exception.Message)"
    throw
}
finally {
    # Stop logging
    Stop-Transcript 
}


# ################
# End script
# ################

Write-Warning "`n################ Script complete ################"

Leave a comment